Split AWS Config modules (#40111)
* Adding module for AWS Config service * adding integration tests * Split resource types into their own modules * Properly use resource_prefix and retry on IAM "eventual consistency" * Add config aggregator module * AWS config aggregator integration test fixes * AWS config recorder module * Config aggregation auth rule * Use resource_prefix in IAM role name * Disable config tests
This commit is contained in:
parent
a90342ac33
commit
046561bbb0
10 changed files with 1511 additions and 0 deletions
|
@ -0,0 +1,159 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
# Copyright: (c) 2018, Aaron Smith <ajsmith10381@gmail.com>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: aws_config_aggregation_authorization
|
||||
short_description: Manage cross-account AWS Config authorizations
|
||||
description:
|
||||
- Module manages AWS Config resources
|
||||
version_added: "2.6"
|
||||
requirements: [ 'botocore', 'boto3' ]
|
||||
author:
|
||||
- "Aaron Smith (@slapula)"
|
||||
options:
|
||||
state:
|
||||
description:
|
||||
- Whether the Config rule should be present or absent.
|
||||
default: present
|
||||
choices: ['present', 'absent']
|
||||
authorized_account_id:
|
||||
description:
|
||||
- The 12-digit account ID of the account authorized to aggregate data.
|
||||
authorized_aws_region:
|
||||
description:
|
||||
- The region authorized to collect aggregated data.
|
||||
extends_documentation_fragment:
|
||||
- aws
|
||||
- ec2
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Get current account ID
|
||||
aws_caller_facts:
|
||||
register: whoami
|
||||
- aws_config_aggregation_authorization:
|
||||
state: present
|
||||
authorized_account_id: '{{ whoami.account }}'
|
||||
authorzed_aws_region: us-east-1
|
||||
'''
|
||||
|
||||
RETURN = r'''#'''
|
||||
|
||||
|
||||
try:
|
||||
import botocore
|
||||
from botocore.exceptions import BotoCoreError, ClientError
|
||||
except ImportError:
|
||||
pass # handled by AnsibleAWSModule
|
||||
|
||||
from ansible.module_utils.aws.core import AnsibleAWSModule
|
||||
from ansible.module_utils.ec2 import boto3_conn, get_aws_connection_info, AWSRetry
|
||||
from ansible.module_utils.ec2 import camel_dict_to_snake_dict, boto3_tag_list_to_ansible_dict
|
||||
|
||||
|
||||
def resource_exists(client, module, params):
|
||||
try:
|
||||
current_authorizations = client.describe_aggregation_authorizations()['AggregationAuthorizations']
|
||||
authorization_exists = next(
|
||||
(item for item in current_authorizations if item["AuthorizedAccountId"] == params['AuthorizedAccountId']),
|
||||
None
|
||||
)
|
||||
if authorization_exists:
|
||||
return True
|
||||
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError):
|
||||
return False
|
||||
|
||||
|
||||
def create_resource(client, module, params, result):
|
||||
try:
|
||||
response = client.put_aggregation_authorization(
|
||||
AuthorizedAccountId=params['AuthorizedAccountId'],
|
||||
AuthorizedAwsRegion=params['AuthorizedAwsRegion']
|
||||
)
|
||||
result['changed'] = True
|
||||
return result
|
||||
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
||||
module.fail_json_aws(e, msg="Couldn't create AWS Aggregation authorization")
|
||||
|
||||
|
||||
def update_resource(client, module, params, result):
|
||||
current_authorizations = client.describe_aggregation_authorizations()['AggregationAuthorizations']
|
||||
current_params = next(
|
||||
(item for item in current_authorizations if item["AuthorizedAccountId"] == params['AuthorizedAccountId']),
|
||||
None
|
||||
)
|
||||
|
||||
del current_params['AggregationAuthorizationArn']
|
||||
del current_params['CreationTime']
|
||||
|
||||
if params != current_params:
|
||||
try:
|
||||
response = client.put_aggregation_authorization(
|
||||
AuthorizedAccountId=params['AuthorizedAccountId'],
|
||||
AuthorizedAwsRegion=params['AuthorizedAwsRegion']
|
||||
)
|
||||
result['changed'] = True
|
||||
return result
|
||||
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
||||
module.fail_json_aws(e, msg="Couldn't create AWS Aggregation authorization")
|
||||
|
||||
|
||||
def delete_resource(client, module, params, result):
|
||||
try:
|
||||
response = client.delete_aggregation_authorization(
|
||||
AuthorizedAccountId=params['AuthorizedAccountId'],
|
||||
AuthorizedAwsRegion=params['AuthorizedAwsRegion']
|
||||
)
|
||||
result['changed'] = True
|
||||
return result
|
||||
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
||||
module.fail_json_aws(e, msg="Couldn't delete AWS Aggregation authorization")
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleAWSModule(
|
||||
argument_spec={
|
||||
'state': dict(type='str', choices=['present', 'absent'], default='present'),
|
||||
'authorized_account_id': dict(type='str', required=True),
|
||||
'authorized_aws_region': dict(type='str', required=True),
|
||||
},
|
||||
supports_check_mode=False,
|
||||
)
|
||||
|
||||
result = {'changed': False}
|
||||
|
||||
params = {
|
||||
'AuthorizedAccountId': module.params.get('authorized_account_id'),
|
||||
'AuthorizedAwsRegion': module.params.get('authorized_aws_region'),
|
||||
}
|
||||
|
||||
client = module.client('config', retry_decorator=AWSRetry.jittered_backoff())
|
||||
resource_status = resource_exists(client, module, params)
|
||||
|
||||
if module.params.get('state') == 'present':
|
||||
if not resource_status:
|
||||
create_resource(client, module, params, result)
|
||||
else:
|
||||
update_resource(client, module, params, result)
|
||||
|
||||
if module.params.get('state') == 'absent':
|
||||
if resource_status:
|
||||
delete_resource(client, module, params, result)
|
||||
|
||||
module.exit_json(changed=result['changed'])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
218
lib/ansible/modules/cloud/amazon/aws_config_aggregator.py
Normal file
218
lib/ansible/modules/cloud/amazon/aws_config_aggregator.py
Normal file
|
@ -0,0 +1,218 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
# Copyright: (c) 2018, Aaron Smith <ajsmith10381@gmail.com>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: aws_config_aggregator
|
||||
short_description: Manage AWS Config aggregations across multiple accounts
|
||||
description:
|
||||
- Module manages AWS Config resources
|
||||
version_added: "2.6"
|
||||
requirements: [ 'botocore', 'boto3' ]
|
||||
author:
|
||||
- "Aaron Smith (@slapula)"
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- The name of the AWS Config resource.
|
||||
required: true
|
||||
state:
|
||||
description:
|
||||
- Whether the Config rule should be present or absent.
|
||||
default: present
|
||||
choices: ['present', 'absent']
|
||||
account_sources:
|
||||
description:
|
||||
- Provides a list of source accounts and regions to be aggregated.
|
||||
suboptions:
|
||||
account_ids:
|
||||
description:
|
||||
- A list of 12-digit account IDs of accounts being aggregated.
|
||||
aws_regions:
|
||||
description:
|
||||
- A list of source regions being aggregated.
|
||||
all_aws_regions:
|
||||
description:
|
||||
- If true, aggreagate existing AWS Config regions and future regions.
|
||||
organization_source:
|
||||
description:
|
||||
- The region authorized to collect aggregated data.
|
||||
suboptions:
|
||||
role_arn:
|
||||
description:
|
||||
- ARN of the IAM role used to retreive AWS Organization details associated with the aggregator account.
|
||||
aws_regions:
|
||||
description:
|
||||
- The source regions being aggregated.
|
||||
all_aws_regions:
|
||||
description:
|
||||
- If true, aggreagate existing AWS Config regions and future regions.
|
||||
extends_documentation_fragment:
|
||||
- aws
|
||||
- ec2
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Create cross-account aggregator
|
||||
aws_config_aggregator:
|
||||
name: test_config_rule
|
||||
state: present
|
||||
account_sources:
|
||||
account_ids:
|
||||
- 1234567890
|
||||
- 0123456789
|
||||
- 9012345678
|
||||
all_aws_regions: yes
|
||||
'''
|
||||
|
||||
RETURN = r'''#'''
|
||||
|
||||
|
||||
try:
|
||||
import botocore
|
||||
from botocore.exceptions import BotoCoreError, ClientError
|
||||
except ImportError:
|
||||
pass # handled by AnsibleAWSModule
|
||||
|
||||
from ansible.module_utils.aws.core import AnsibleAWSModule
|
||||
from ansible.module_utils.ec2 import boto3_conn, get_aws_connection_info, AWSRetry
|
||||
from ansible.module_utils.ec2 import camel_dict_to_snake_dict, boto3_tag_list_to_ansible_dict
|
||||
|
||||
|
||||
def resource_exists(client, module, resource_type, params):
|
||||
try:
|
||||
aggregator = client.describe_configuration_aggregators(
|
||||
ConfigurationAggregatorNames=[params['name']]
|
||||
)
|
||||
return aggregator['ConfigurationAggregators'][0]
|
||||
except client.exceptions.from_code('NoSuchConfigurationAggregatorException'):
|
||||
return
|
||||
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
||||
module.fail_json_aws(e)
|
||||
|
||||
|
||||
def create_resource(client, module, params, result):
|
||||
try:
|
||||
response = client.put_configuration_aggregator(
|
||||
ConfigurationAggregatorName=params['ConfigurationAggregatorName'],
|
||||
AccountAggregationSources=params['AccountAggregationSources'],
|
||||
OrganizationAggregationSource=params['OrganizationAggregationSource']
|
||||
)
|
||||
result['changed'] = True
|
||||
result['aggregator'] = camel_dict_to_snake_dict(resource_exists(client, module, params))
|
||||
return result
|
||||
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
||||
module.fail_json_aws(e, msg="Couldn't create AWS Config configuration aggregator")
|
||||
|
||||
|
||||
def update_resource(client, module, resource_type, params, result):
|
||||
current_params = client.describe_configuration_aggregators(
|
||||
ConfigurationAggregatorNames=[params['name']]
|
||||
)
|
||||
|
||||
del current_params['ConfigurationAggregatorArn']
|
||||
del current_params['CreationTime']
|
||||
del current_params['LastUpdatedTime']
|
||||
|
||||
if params != current_params['ConfigurationAggregators'][0]:
|
||||
try:
|
||||
client.put_configuration_aggregator(
|
||||
ConfigurationAggregatorName=params['ConfigurationAggregatorName'],
|
||||
AccountAggregationSources=params['AccountAggregationSources'],
|
||||
OrganizationAggregationSource=params['OrganizationAggregationSource']
|
||||
)
|
||||
result['changed'] = True
|
||||
result['aggregator'] = camel_dict_to_snake_dict(resource_exists(client, module, params))
|
||||
return result
|
||||
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
||||
module.fail_json_aws(e, msg="Couldn't create AWS Config configuration aggregator")
|
||||
|
||||
|
||||
def delete_resource(client, module, resource_type, params, result):
|
||||
try:
|
||||
client.delete_configuration_aggregator(
|
||||
ConfigurationAggregatorName=params['ConfigurationAggregatorName']
|
||||
)
|
||||
result['changed'] = True
|
||||
return result
|
||||
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
||||
module.fail_json_aws(e, msg="Couldn't delete AWS Config configuration aggregator")
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleAWSModule(
|
||||
argument_spec={
|
||||
'name': dict(type='str', required=True),
|
||||
'state': dict(type='str', choices=['present', 'absent'], default='present'),
|
||||
'account_sources': dict(type='list', required=True),
|
||||
'organization_source': dict(type='dict', required=True)
|
||||
},
|
||||
supports_check_mode=False,
|
||||
)
|
||||
|
||||
result = {
|
||||
'changed': False
|
||||
}
|
||||
|
||||
name = module.params.get('name')
|
||||
state = module.params.get('state')
|
||||
|
||||
params = {}
|
||||
if name:
|
||||
params['ConfigurationAggregatorName'] = name
|
||||
if module.params.get('account_sources'):
|
||||
params['AccountAggregationSources'] = []
|
||||
for i in module.params.get('account_sources'):
|
||||
tmp_dict = {}
|
||||
if i.get('account_ids'):
|
||||
tmp_dict['AccountIds'] = i.get('account_ids')
|
||||
if i.get('aws_regions'):
|
||||
tmp_dict['AwsRegions'] = i.get('aws_regions')
|
||||
if i.get('all_aws_regions') is not None:
|
||||
tmp_dict['AllAwsRegions'] = i.get('all_aws_regions')
|
||||
params['AccountAggregationSources'].append(tmp_dict)
|
||||
if module.params.get('organization_source'):
|
||||
params['OrganizationAggregationSource'] = {}
|
||||
if module.params.get('organization_source').get('role_arn'):
|
||||
params['OrganizationAggregationSource'].update({
|
||||
'RoleArn': module.params.get('organization_source').get('role_arn')
|
||||
})
|
||||
if module.params.get('organization_source').get('aws_regions'):
|
||||
params['OrganizationAggregationSource'].update({
|
||||
'AwsRegions': module.params.get('organization_source').get('aws_regions')
|
||||
})
|
||||
if module.params.get('organization_source').get('all_aws_regions') is not None:
|
||||
params['OrganizationAggregationSourcep'].update({
|
||||
'AllAwsRegions': module.params.get('organization_source').get('all_aws_regions')
|
||||
})
|
||||
|
||||
client = module.client('config', retry_decorator=AWSRetry.jittered_backoff())
|
||||
|
||||
resource_status = resource_exists(client, module, params)
|
||||
|
||||
if state == 'present':
|
||||
if not resource_status:
|
||||
create_resource(client, module, params, result)
|
||||
else:
|
||||
update_resource(client, module, params, result)
|
||||
|
||||
if state == 'absent':
|
||||
if resource_status:
|
||||
delete_resource(client, module, params, result)
|
||||
|
||||
module.exit_json(changed=result['changed'])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
213
lib/ansible/modules/cloud/amazon/aws_config_delivery_channel.py
Normal file
213
lib/ansible/modules/cloud/amazon/aws_config_delivery_channel.py
Normal file
|
@ -0,0 +1,213 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
# Copyright: (c) 2018, Aaron Smith <ajsmith10381@gmail.com>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: aws_config_delivery_channel
|
||||
short_description: Manage AWS Config delivery channels
|
||||
description:
|
||||
- This module manages AWS Config delivery locations for rule checks and configuration info
|
||||
version_added: "2.6"
|
||||
requirements: [ 'botocore', 'boto3' ]
|
||||
author:
|
||||
- "Aaron Smith (@slapula)"
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- The name of the AWS Config resource.
|
||||
required: true
|
||||
state:
|
||||
description:
|
||||
- Whether the Config rule should be present or absent.
|
||||
default: present
|
||||
choices: ['present', 'absent']
|
||||
s3_bucket:
|
||||
description:
|
||||
- The name of the Amazon S3 bucket to which AWS Config delivers configuration snapshots and configuration history files.
|
||||
s3_prefix:
|
||||
description:
|
||||
- The prefix for the specified Amazon S3 bucket.
|
||||
sns_topic_arn:
|
||||
description:
|
||||
- The Amazon Resource Name (ARN) of the Amazon SNS topic to which AWS Config sends notifications about configuration changes.
|
||||
delivery_frequency:
|
||||
description:
|
||||
- The frequency with which AWS Config delivers configuration snapshots.
|
||||
choices: ['One_Hour', 'Three_Hours', 'Six_Hours', 'Twelve_Hours', 'TwentyFour_Hours']
|
||||
extends_documentation_fragment:
|
||||
- aws
|
||||
- ec2
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Create Delivery Channel for AWS Config
|
||||
aws_config_delivery_channel:
|
||||
name: test_delivery_channel
|
||||
state: present
|
||||
s3_bucket: 'test_aws_config_bucket'
|
||||
sns_topic_arn: 'arn:aws:sns:us-east-1:123456789012:aws_config_topic:1234ab56-cdef-7g89-01hi-2jk34l5m67no'
|
||||
delivery_frequency: 'Twelve_Hours'
|
||||
'''
|
||||
|
||||
RETURN = r'''#'''
|
||||
|
||||
|
||||
try:
|
||||
import botocore
|
||||
from botocore.exceptions import BotoCoreError, ClientError
|
||||
except ImportError:
|
||||
pass # handled by AnsibleAWSModule
|
||||
|
||||
from ansible.module_utils.aws.core import AnsibleAWSModule
|
||||
from ansible.module_utils.ec2 import boto3_conn, get_aws_connection_info, AWSRetry
|
||||
from ansible.module_utils.ec2 import camel_dict_to_snake_dict, boto3_tag_list_to_ansible_dict
|
||||
|
||||
|
||||
# this waits for an IAM role to become fully available, at the cost of
|
||||
# taking a long time to fail when the IAM role/policy really is invalid
|
||||
retry_unavailable_iam_on_put_delivery = AWSRetry.backoff(
|
||||
catch_extra_error_codes=['InsufficientDeliveryPolicyException'],
|
||||
)
|
||||
|
||||
|
||||
def resource_exists(client, module, params):
|
||||
try:
|
||||
channel = client.describe_delivery_channels(
|
||||
DeliveryChannelNames=[params['name']],
|
||||
aws_retry=True,
|
||||
)
|
||||
return channel['DeliveryChannels'][0]
|
||||
except client.exceptions.from_code('NoSuchDeliveryChannelException'):
|
||||
return
|
||||
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
||||
module.fail_json_aws(e)
|
||||
|
||||
|
||||
def create_resource(client, module, params, result):
|
||||
try:
|
||||
retry_unavailable_iam_on_put_delivery(
|
||||
client.put_delivery_channel,
|
||||
)(
|
||||
DeliveryChannel=params,
|
||||
)
|
||||
result['changed'] = True
|
||||
result['channel'] = camel_dict_to_snake_dict(resource_exists(client, module, params))
|
||||
return result
|
||||
except client.exceptions.from_code('InvalidS3KeyPrefixException') as e:
|
||||
module.fail_json_aws(e, msg="The `s3_prefix` parameter was invalid. Try '/' for no prefix")
|
||||
except client.exceptions.from_code('InsufficientDeliveryPolicyException') as e:
|
||||
module.fail_json_aws(e, msg="The `s3_prefix` or `s3_bucket` parameter is invalid. "
|
||||
"Make sure the bucket exists and is available")
|
||||
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
||||
module.fail_json_aws(e, msg="Couldn't create AWS Config delivery channel")
|
||||
|
||||
|
||||
def update_resource(client, module, params, result):
|
||||
current_params = client.describe_delivery_channels(
|
||||
DeliveryChannelNames=[params['name']],
|
||||
aws_retry=True,
|
||||
)
|
||||
|
||||
if params != current_params['DeliveryChannels'][0]:
|
||||
try:
|
||||
retry_unavailable_iam_on_put_delivery(
|
||||
client.put_delivery_channel,
|
||||
)(
|
||||
DeliveryChannel=params,
|
||||
)
|
||||
result['changed'] = True
|
||||
result['channel'] = camel_dict_to_snake_dict(resource_exists(client, module, params))
|
||||
return result
|
||||
except client.exceptions.from_code('InvalidS3KeyPrefixException') as e:
|
||||
module.fail_json_aws(e, msg="The `s3_prefix` parameter was invalid. Try '/' for no prefix")
|
||||
except client.exceptions.from_code('InsufficientDeliveryPolicyException') as e:
|
||||
module.fail_json_aws(e, msg="The `s3_prefix` or `s3_bucket` parameter is invalid. "
|
||||
"Make sure the bucket exists and is available")
|
||||
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
||||
module.fail_json_aws(e, msg="Couldn't create AWS Config delivery channel")
|
||||
|
||||
|
||||
def delete_resource(client, module, params, result):
|
||||
try:
|
||||
response = client.delete_delivery_channel(
|
||||
DeliveryChannelName=params['name']
|
||||
)
|
||||
result['changed'] = True
|
||||
return result
|
||||
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
||||
module.fail_json_aws(e, msg="Couldn't delete AWS Config delivery channel")
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleAWSModule(
|
||||
argument_spec={
|
||||
'name': dict(type='str', required=True),
|
||||
'state': dict(type='str', choices=['present', 'absent'], default='present'),
|
||||
's3_bucket': dict(type='str', required=True),
|
||||
's3_prefix': dict(type='str'),
|
||||
'sns_topic_arn': dict(type='str'),
|
||||
'delivery_frequency': dict(
|
||||
type='str',
|
||||
choices=[
|
||||
'One_Hour',
|
||||
'Three_Hours',
|
||||
'Six_Hours',
|
||||
'Twelve_Hours',
|
||||
'TwentyFour_Hours'
|
||||
]
|
||||
),
|
||||
},
|
||||
supports_check_mode=False,
|
||||
)
|
||||
|
||||
result = {
|
||||
'changed': False
|
||||
}
|
||||
|
||||
name = module.params.get('name')
|
||||
state = module.params.get('state')
|
||||
|
||||
params = {}
|
||||
if name:
|
||||
params['name'] = name
|
||||
if module.params.get('s3_bucket'):
|
||||
params['s3BucketName'] = module.params.get('s3_bucket')
|
||||
if module.params.get('s3_prefix'):
|
||||
params['s3KeyPrefix'] = module.params.get('s3_prefix')
|
||||
if module.params.get('sns_topic_arn'):
|
||||
params['snsTopicARN'] = module.params.get('sns_topic_arn')
|
||||
if module.params.get('delivery_frequency'):
|
||||
params['configSnapshotDeliveryProperties'] = {
|
||||
'deliveryFrequency': module.params.get('delivery_frequency')
|
||||
}
|
||||
|
||||
client = module.client('config', retry_decorator=AWSRetry.jittered_backoff())
|
||||
|
||||
resource_status = resource_exists(client, module, params)
|
||||
|
||||
if state == 'present':
|
||||
if not resource_status:
|
||||
create_resource(client, module, params, result)
|
||||
if resource_status:
|
||||
update_resource(client, module, params, result)
|
||||
|
||||
if state == 'absent':
|
||||
if resource_status:
|
||||
delete_resource(client, module, params, result)
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
206
lib/ansible/modules/cloud/amazon/aws_config_recorder.py
Normal file
206
lib/ansible/modules/cloud/amazon/aws_config_recorder.py
Normal file
|
@ -0,0 +1,206 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
# Copyright: (c) 2018, Aaron Smith <ajsmith10381@gmail.com>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: aws_config_recorder
|
||||
short_description: Manage AWS Config Recorders
|
||||
description:
|
||||
- Module manages AWS Config configuration recorder settings
|
||||
version_added: "2.6"
|
||||
requirements: [ 'botocore', 'boto3' ]
|
||||
author:
|
||||
- "Aaron Smith (@slapula)"
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- The name of the AWS Config resource.
|
||||
required: true
|
||||
state:
|
||||
description:
|
||||
- Whether the Config rule should be present or absent.
|
||||
default: present
|
||||
choices: ['present', 'absent']
|
||||
role_arn:
|
||||
description:
|
||||
- Amazon Resource Name (ARN) of the IAM role used to describe the AWS resources associated with the account.
|
||||
- Required when state=present
|
||||
recording_group:
|
||||
description:
|
||||
- Specifies the types of AWS resources for which AWS Config records configuration changes.
|
||||
- Required when state=present
|
||||
suboptions:
|
||||
all_supported:
|
||||
description:
|
||||
- Specifies whether AWS Config records configuration changes for every supported type of regional resource.
|
||||
- If you set this option to `true`, when AWS Config adds support for a new type of regional resource, it starts
|
||||
recording resources of that type automatically.
|
||||
- If you set this option to `true`, you cannot enumerate a list of `resource_types`.
|
||||
include_global_types:
|
||||
description:
|
||||
- Specifies whether AWS Config includes all supported types of global resources (for example, IAM resources)
|
||||
with the resources that it records.
|
||||
- Before you can set this option to `true`, you must set the allSupported option to `true`.
|
||||
- If you set this option to `true`, when AWS Config adds support for a new type of global resource, it starts recording
|
||||
resources of that type automatically.
|
||||
- The configuration details for any global resource are the same in all regions. To prevent duplicate configuration items,
|
||||
you should consider customizing AWS Config in only one region to record global resources.
|
||||
resource_types:
|
||||
description:
|
||||
- A list that specifies the types of AWS resources for which AWS Config records configuration changes (for example,
|
||||
`AWS::EC2::Instance` or `AWS::CloudTrail::Trail`).
|
||||
- Before you can set this option to `true`, you must set the `all_supported` option to `false`.
|
||||
extends_documentation_fragment:
|
||||
- aws
|
||||
- ec2
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Create Configuration Recorder for AWS Config
|
||||
aws_config_recorder:
|
||||
name: test_configuration_recorder
|
||||
state: present
|
||||
role_arn: 'arn:aws:iam::123456789012:role/AwsConfigRecorder'
|
||||
recording_group:
|
||||
all_supported: true
|
||||
include_global_types: true
|
||||
'''
|
||||
|
||||
RETURN = r'''#'''
|
||||
|
||||
|
||||
try:
|
||||
import botocore
|
||||
from botocore.exceptions import BotoCoreError, ClientError
|
||||
except ImportError:
|
||||
pass # handled by AnsibleAWSModule
|
||||
|
||||
from ansible.module_utils.aws.core import AnsibleAWSModule
|
||||
from ansible.module_utils.ec2 import boto3_conn, get_aws_connection_info, AWSRetry
|
||||
from ansible.module_utils.ec2 import camel_dict_to_snake_dict, boto3_tag_list_to_ansible_dict
|
||||
|
||||
|
||||
def resource_exists(client, module, params):
|
||||
try:
|
||||
recorder = client.describe_configuration_recorders(
|
||||
ConfigurationRecorderNames=[params['name']]
|
||||
)
|
||||
return recorder['ConfigurationRecorders'][0]
|
||||
except client.exceptions.from_code('NoSuchConfigurationRecorderException'):
|
||||
return
|
||||
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
||||
module.fail_json_aws(e)
|
||||
|
||||
|
||||
def create_resource(client, module, params, result):
|
||||
try:
|
||||
response = client.put_configuration_recorder(
|
||||
ConfigurationRecorder=params
|
||||
)
|
||||
result['changed'] = True
|
||||
result['recorder'] = camel_dict_to_snake_dict(resource_exists(client, module, params))
|
||||
return result
|
||||
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
||||
module.fail_json_aws(e, msg="Couldn't create AWS Config configuration recorder")
|
||||
|
||||
|
||||
def update_resource(client, module, params, result):
|
||||
current_params = client.describe_configuration_recorders(
|
||||
ConfigurationRecorderNames=[params['name']]
|
||||
)
|
||||
|
||||
if params != current_params['ConfigurationRecorders'][0]:
|
||||
try:
|
||||
response = client.put_configuration_recorder(
|
||||
ConfigurationRecorder=params
|
||||
)
|
||||
result['changed'] = True
|
||||
result['recorder'] = camel_dict_to_snake_dict(resource_exists(client, module, params))
|
||||
return result
|
||||
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
||||
module.fail_json_aws(e, msg="Couldn't update AWS Config configuration recorder")
|
||||
|
||||
|
||||
def delete_resource(client, module, params, result):
|
||||
try:
|
||||
response = client.delete_configuration_recorder(
|
||||
ConfigurationRecorderName=params['name']
|
||||
)
|
||||
result['changed'] = True
|
||||
return result
|
||||
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
||||
module.fail_json_aws(e, msg="Couldn't delete AWS Config configuration recorder")
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
module = AnsibleAWSModule(
|
||||
argument_spec={
|
||||
'name': dict(type='str', required=True),
|
||||
'state': dict(type='str', choices=['present', 'absent'], default='present'),
|
||||
'role_arn': dict(type='str'),
|
||||
'recording_group': dict(type='dict'),
|
||||
},
|
||||
supports_check_mode=False,
|
||||
required_if=[
|
||||
('state', 'present', ['role_arn', 'recording_group']),
|
||||
],
|
||||
)
|
||||
|
||||
result = {
|
||||
'changed': False
|
||||
}
|
||||
|
||||
name = module.params.get('name')
|
||||
state = module.params.get('state')
|
||||
|
||||
params = {}
|
||||
if name:
|
||||
params['name'] = name
|
||||
if module.params.get('role_arn'):
|
||||
params['roleARN'] = module.params.get('role_arn')
|
||||
if module.params.get('recording_group'):
|
||||
params['recordingGroup'] = {}
|
||||
if module.params.get('recording_group').get('all_supported') is not None:
|
||||
params['recordingGroup'].update({
|
||||
'allSupported': module.params.get('recording_group').get('all_supported')
|
||||
})
|
||||
if module.params.get('recording_group').get('include_global_types') is not None:
|
||||
params['recordingGroup'].update({
|
||||
'includeGlobalResourceTypes': module.params.get('recording_group').get('include_global_types')
|
||||
})
|
||||
if module.params.get('recording_group').get('resource_types'):
|
||||
params['recordingGroup'].update({
|
||||
'resourceTypes': module.params.get('recording_group').get('resource_types')
|
||||
})
|
||||
|
||||
client = module.client('config', retry_decorator=AWSRetry.jittered_backoff())
|
||||
|
||||
resource_status = resource_exists(client, module, params)
|
||||
|
||||
if state == 'present':
|
||||
if not resource_status:
|
||||
create_resource(client, module, params, result)
|
||||
if resource_status:
|
||||
update_resource(client, module, params, result)
|
||||
|
||||
if state == 'absent':
|
||||
if resource_status:
|
||||
delete_resource(client, module, params, result)
|
||||
|
||||
module.exit_json(changed=result['changed'])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
267
lib/ansible/modules/cloud/amazon/aws_config_rule.py
Normal file
267
lib/ansible/modules/cloud/amazon/aws_config_rule.py
Normal file
|
@ -0,0 +1,267 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
# Copyright: (c) 2018, Aaron Smith <ajsmith10381@gmail.com>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: aws_config_rule
|
||||
short_description: Manage AWS Config resources
|
||||
description:
|
||||
- Module manages AWS Config rules
|
||||
version_added: "2.6"
|
||||
requirements: [ 'botocore', 'boto3' ]
|
||||
author:
|
||||
- "Aaron Smith (@slapula)"
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- The name of the AWS Config resource.
|
||||
required: true
|
||||
state:
|
||||
description:
|
||||
- Whether the Config rule should be present or absent.
|
||||
default: present
|
||||
choices: ['present', 'absent']
|
||||
description:
|
||||
description:
|
||||
- The description that you provide for the AWS Config rule.
|
||||
scope:
|
||||
description:
|
||||
- Defines which resources can trigger an evaluation for the rule.
|
||||
suboptions:
|
||||
compliance_types:
|
||||
description:
|
||||
- The resource types of only those AWS resources that you want to trigger an evaluation for the rule.
|
||||
You can only specify one type if you also specify a resource ID for `compliance_id`.
|
||||
compliance_id:
|
||||
description:
|
||||
- The ID of the only AWS resource that you want to trigger an evaluation for the rule. If you specify a resource ID,
|
||||
you must specify one resource type for `compliance_types`.
|
||||
tag_key:
|
||||
description:
|
||||
- The tag key that is applied to only those AWS resources that you want to trigger an evaluation for the rule.
|
||||
tag_value:
|
||||
description:
|
||||
- The tag value applied to only those AWS resources that you want to trigger an evaluation for the rule.
|
||||
If you specify a value for `tag_value`, you must also specify a value for `tag_key`.
|
||||
source:
|
||||
description:
|
||||
- Provides the rule owner (AWS or customer), the rule identifier, and the notifications that cause the function to
|
||||
evaluate your AWS resources.
|
||||
suboptions:
|
||||
owner:
|
||||
description:
|
||||
- The resource types of only those AWS resources that you want to trigger an evaluation for the rule.
|
||||
You can only specify one type if you also specify a resource ID for `compliance_id`.
|
||||
identifier:
|
||||
description:
|
||||
- The ID of the only AWS resource that you want to trigger an evaluation for the rule.
|
||||
If you specify a resource ID, you must specify one resource type for `compliance_types`.
|
||||
details:
|
||||
description:
|
||||
- Provides the source and type of the event that causes AWS Config to evaluate your AWS resources.
|
||||
- This parameter expects a list of dictionaries. Each dictionary expects the following key/value pairs.
|
||||
- Key `EventSource` The source of the event, such as an AWS service, that triggers AWS Config to evaluate your AWS resources.
|
||||
- Key `MessageType` The type of notification that triggers AWS Config to run an evaluation for a rule.
|
||||
- Key `MaximumExecutionFrequency` The frequency at which you want AWS Config to run evaluations for a custom rule with a periodic trigger.
|
||||
input_parameters:
|
||||
description:
|
||||
- A string, in JSON format, that is passed to the AWS Config rule Lambda function.
|
||||
execution_frequency:
|
||||
description:
|
||||
- The maximum frequency with which AWS Config runs evaluations for a rule.
|
||||
choices: ['One_Hour', 'Three_Hours', 'Six_Hours', 'Twelve_Hours', 'TwentyFour_Hours']
|
||||
extends_documentation_fragment:
|
||||
- aws
|
||||
- ec2
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Create Config Rule for AWS Config
|
||||
aws_config_rule:
|
||||
name: test_config_rule
|
||||
state: present
|
||||
description: 'This AWS Config rule checks for public write access on S3 buckets'
|
||||
scope:
|
||||
compliance_types:
|
||||
- 'AWS::S3::Bucket'
|
||||
source:
|
||||
owner: AWS
|
||||
identifier: 'S3_BUCKET_PUBLIC_WRITE_PROHIBITED'
|
||||
|
||||
'''
|
||||
|
||||
RETURN = r'''#'''
|
||||
|
||||
|
||||
try:
|
||||
import botocore
|
||||
from botocore.exceptions import BotoCoreError, ClientError
|
||||
except ImportError:
|
||||
pass # handled by AnsibleAWSModule
|
||||
|
||||
from ansible.module_utils.aws.core import AnsibleAWSModule
|
||||
from ansible.module_utils.ec2 import AWSRetry, camel_dict_to_snake_dict
|
||||
|
||||
|
||||
def rule_exists(client, module, params):
|
||||
try:
|
||||
rule = client.describe_config_rules(
|
||||
ConfigRuleNames=[params['ConfigRuleName']],
|
||||
aws_retry=True,
|
||||
)
|
||||
return rule['ConfigRules'][0]
|
||||
except client.exceptions.from_code('NoSuchConfigRuleException'):
|
||||
return
|
||||
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
||||
module.fail_json_aws(e)
|
||||
|
||||
|
||||
def create_resource(client, module, params, result):
|
||||
try:
|
||||
client.put_config_rule(
|
||||
ConfigRule=params
|
||||
)
|
||||
result['changed'] = True
|
||||
return result
|
||||
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
||||
module.fail_json_aws(e, msg="Couldn't create AWS Config rule")
|
||||
|
||||
|
||||
def update_resource(client, module, params, result):
|
||||
current_params = client.describe_config_rules(
|
||||
ConfigRuleNames=[params['ConfigRuleName']],
|
||||
aws_retry=True,
|
||||
)
|
||||
|
||||
del current_params['ConfigRules'][0]['ConfigRuleArn']
|
||||
del current_params['ConfigRules'][0]['ConfigRuleId']
|
||||
|
||||
if params != current_params['ConfigRules'][0]:
|
||||
try:
|
||||
client.put_config_rule(
|
||||
ConfigRule=params
|
||||
)
|
||||
result['changed'] = True
|
||||
result['rule'] = camel_dict_to_snake_dict(rule_exists(client, module, params))
|
||||
return result
|
||||
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
||||
module.fail_json_aws(e, msg="Couldn't create AWS Config rule")
|
||||
|
||||
|
||||
def delete_resource(client, module, params, result):
|
||||
try:
|
||||
response = client.delete_config_rule(
|
||||
ConfigRuleName=params['ConfigRuleName'],
|
||||
aws_retry=True,
|
||||
)
|
||||
result['changed'] = True
|
||||
result['rule'] = {}
|
||||
return result
|
||||
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
||||
module.fail_json_aws(e, msg="Couldn't delete AWS Config rule")
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleAWSModule(
|
||||
argument_spec={
|
||||
'name': dict(type='str', required=True),
|
||||
'state': dict(type='str', choices=['present', 'absent'], default='present'),
|
||||
'description': dict(type='str'),
|
||||
'scope': dict(type='dict'),
|
||||
'source': dict(type='dict', required=True),
|
||||
'input_parameters': dict(type='str'),
|
||||
'execution_frequency': dict(
|
||||
type='str',
|
||||
choices=[
|
||||
'One_Hour',
|
||||
'Three_Hours',
|
||||
'Six_Hours',
|
||||
'Twelve_Hours',
|
||||
'TwentyFour_Hours'
|
||||
]
|
||||
),
|
||||
},
|
||||
supports_check_mode=False,
|
||||
)
|
||||
|
||||
result = {
|
||||
'changed': False
|
||||
}
|
||||
|
||||
name = module.params.get('name')
|
||||
resource_type = module.params.get('resource_type')
|
||||
state = module.params.get('state')
|
||||
|
||||
params = {}
|
||||
if name:
|
||||
params['ConfigRuleName'] = name
|
||||
if module.params.get('description'):
|
||||
params['Description'] = module.params.get('description')
|
||||
if module.params.get('scope'):
|
||||
params['Scope'] = {}
|
||||
if module.params.get('scope').get('compliance_types'):
|
||||
params['Scope'].update({
|
||||
'ComplianceResourceTypes': module.params.get('scope').get('compliance_types')
|
||||
})
|
||||
if module.params.get('scope').get('tag_key'):
|
||||
params['Scope'].update({
|
||||
'TagKey': module.params.get('scope').get('tag_key')
|
||||
})
|
||||
if module.params.get('scope').get('tag_value'):
|
||||
params['Scope'].update({
|
||||
'TagValue': module.params.get('scope').get('tag_value')
|
||||
})
|
||||
if module.params.get('scope').get('compliance_id'):
|
||||
params['Scope'].update({
|
||||
'ComplianceResourceId': module.params.get('scope').get('compliance_id')
|
||||
})
|
||||
if module.params.get('source'):
|
||||
params['Source'] = {}
|
||||
if module.params.get('source').get('owner'):
|
||||
params['Source'].update({
|
||||
'Owner': module.params.get('source').get('owner')
|
||||
})
|
||||
if module.params.get('source').get('identifier'):
|
||||
params['Source'].update({
|
||||
'SourceIdentifier': module.params.get('source').get('identifier')
|
||||
})
|
||||
if module.params.get('source').get('details'):
|
||||
params['Source'].update({
|
||||
'SourceDetails': module.params.get('source').get('details')
|
||||
})
|
||||
if module.params.get('input_parameters'):
|
||||
params['InputParameters'] = module.params.get('input_parameters')
|
||||
if module.params.get('execution_frequency'):
|
||||
params['MaximumExecutionFrequency'] = module.params.get('execution_frequency')
|
||||
params['ConfigRuleState'] = 'ACTIVE'
|
||||
|
||||
client = module.client('config', retry_decorator=AWSRetry.jittered_backoff())
|
||||
|
||||
existing_rule = rule_exists(client, module, params)
|
||||
|
||||
if state == 'present':
|
||||
if not existing_rule:
|
||||
create_resource(client, module, params, result)
|
||||
else:
|
||||
update_resource(client, module, params, result)
|
||||
|
||||
if state == 'absent':
|
||||
if existing_rule:
|
||||
delete_resource(client, module, params, result)
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
3
test/integration/targets/aws_config/aliases
Normal file
3
test/integration/targets/aws_config/aliases
Normal file
|
@ -0,0 +1,3 @@
|
|||
cloud/aws
|
||||
disabled
|
||||
posix/ci/cloud/group4/aws
|
4
test/integration/targets/aws_config/defaults/main.yaml
Normal file
4
test/integration/targets/aws_config/defaults/main.yaml
Normal file
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
config_s3_bucket: '{{ resource_prefix }}-config-records'
|
||||
config_sns_name: '{{ resource_prefix }}-delivery-channel-test-topic'
|
||||
config_role_name: 'config-recorder-test-{{ resource_prefix }}'
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Sid": "",
|
||||
"Effect": "Allow",
|
||||
"Principal": {
|
||||
"Service": "config.amazonaws.com"
|
||||
},
|
||||
"Action": "sts:AssumeRole"
|
||||
}
|
||||
]
|
||||
}
|
405
test/integration/targets/aws_config/tasks/main.yaml
Normal file
405
test/integration/targets/aws_config/tasks/main.yaml
Normal file
|
@ -0,0 +1,405 @@
|
|||
---
|
||||
- block:
|
||||
|
||||
# ============================================================
|
||||
# Prerequisites
|
||||
# ============================================================
|
||||
- name: set connection information for all tasks
|
||||
set_fact:
|
||||
aws_connection_info: &aws_connection_info
|
||||
aws_access_key: "{{ aws_access_key }}"
|
||||
aws_secret_key: "{{ aws_secret_key }}"
|
||||
security_token: "{{ security_token }}"
|
||||
region: "{{ aws_region }}"
|
||||
no_log: true
|
||||
|
||||
- name: ensure IAM role exists
|
||||
iam_role:
|
||||
<<: *aws_connection_info
|
||||
name: '{{ config_role_name }}'
|
||||
assume_role_policy_document: "{{ lookup('file','config-trust-policy.json') }}"
|
||||
state: present
|
||||
create_instance_profile: no
|
||||
managed_policy:
|
||||
- 'arn:aws:iam::aws:policy/service-role/AWSConfigRole'
|
||||
register: config_iam_role
|
||||
|
||||
- name: ensure SNS topic exists
|
||||
sns_topic:
|
||||
<<: *aws_connection_info
|
||||
name: '{{ config_sns_name }}'
|
||||
state: present
|
||||
subscriptions:
|
||||
- endpoint: "rando_email_address@rando.com"
|
||||
protocol: "email"
|
||||
register: config_sns_topic
|
||||
|
||||
- name: ensure S3 bucket exists
|
||||
s3_bucket:
|
||||
<<: *aws_connection_info
|
||||
name: "{{ config_s3_bucket }}"
|
||||
|
||||
- name: ensure S3 access for IAM role
|
||||
iam_policy:
|
||||
<<: *aws_connection_info
|
||||
iam_type: role
|
||||
iam_name: '{{ config_role_name }}'
|
||||
policy_name: AwsConfigRecorderTestRoleS3Policy
|
||||
state: present
|
||||
policy_json: "{{ lookup( 'template', 'config-s3-policy.json.j2') }}"
|
||||
|
||||
# ============================================================
|
||||
# Module requirement testing
|
||||
# ============================================================
|
||||
- name: test rule with no source parameter
|
||||
aws_config_rule:
|
||||
<<: *aws_connection_info
|
||||
name: random_name
|
||||
state: present
|
||||
register: output
|
||||
ignore_errors: true
|
||||
|
||||
- name: assert failure when called with no source parameter
|
||||
assert:
|
||||
that:
|
||||
- output.failed
|
||||
- 'output.msg.startswith("missing required arguments:")'
|
||||
|
||||
- name: test resource_type delivery_channel with no s3_bucket parameter
|
||||
aws_config_delivery_channel:
|
||||
<<: *aws_connection_info
|
||||
name: random_name
|
||||
state: present
|
||||
register: output
|
||||
ignore_errors: true
|
||||
|
||||
- name: assert failure when called with no s3_bucket parameter
|
||||
assert:
|
||||
that:
|
||||
- output.failed
|
||||
- 'output.msg.startswith("missing required arguments:")'
|
||||
|
||||
- name: test resource_type configuration_recorder with no role_arn parameter
|
||||
aws_config_recorder:
|
||||
<<: *aws_connection_info
|
||||
name: random_name
|
||||
state: present
|
||||
register: output
|
||||
ignore_errors: true
|
||||
|
||||
- name: assert failure when called with no role_arn parameter
|
||||
assert:
|
||||
that:
|
||||
- output.failed
|
||||
- 'output.msg.startswith("state is present but all of the following are missing")'
|
||||
|
||||
- name: test resource_type configuration_recorder with no recording_group parameter
|
||||
aws_config_recorder:
|
||||
<<: *aws_connection_info
|
||||
name: random_name
|
||||
state: present
|
||||
role_arn: 'arn:aws:iam::123456789012:role/AwsConfigRecorder'
|
||||
register: output
|
||||
ignore_errors: true
|
||||
|
||||
- name: assert failure when called with no recording_group parameter
|
||||
assert:
|
||||
that:
|
||||
- output.failed
|
||||
- 'output.msg.startswith("state is present but all of the following are missing")'
|
||||
|
||||
- name: test resource_type aggregation_authorization with no authorized_account_id parameter
|
||||
aws_config_aggregation_authorization:
|
||||
state: present
|
||||
<<: *aws_connection_info
|
||||
register: output
|
||||
ignore_errors: true
|
||||
|
||||
- name: assert failure when called with no authorized_account_id parameter
|
||||
assert:
|
||||
that:
|
||||
- output.failed
|
||||
- 'output.msg.startswith("missing required arguments:")'
|
||||
|
||||
- name: test resource_type aggregation_authorization with no authorized_aws_region parameter
|
||||
aws_config_aggregation_authorization:
|
||||
<<: *aws_connection_info
|
||||
state: present
|
||||
authorized_account_id: '123456789012'
|
||||
register: output
|
||||
ignore_errors: true
|
||||
|
||||
- name: assert failure when called with no authorized_aws_region parameter
|
||||
assert:
|
||||
that:
|
||||
- output.failed
|
||||
- 'output.msg.startswith("missing required arguments:")'
|
||||
|
||||
- name: test resource_type configuration_aggregator with no account_sources parameter
|
||||
aws_config_aggregator:
|
||||
<<: *aws_connection_info
|
||||
name: random_name
|
||||
state: present
|
||||
register: output
|
||||
ignore_errors: true
|
||||
|
||||
- name: assert failure when called with no account_sources parameter
|
||||
assert:
|
||||
that:
|
||||
- output.failed
|
||||
- 'output.msg.startswith("missing required arguments: account_sources")'
|
||||
|
||||
- name: test resource_type configuration_aggregator with no organization_source parameter
|
||||
aws_config_aggregator:
|
||||
<<: *aws_connection_info
|
||||
name: random_name
|
||||
state: present
|
||||
account_sources: []
|
||||
register: output
|
||||
ignore_errors: true
|
||||
|
||||
- name: assert failure when called with no organization_source parameter
|
||||
assert:
|
||||
that:
|
||||
- output.failed
|
||||
- 'output.msg.startswith("missing required arguments: organization_source")'
|
||||
|
||||
# ============================================================
|
||||
# Creation testing
|
||||
# ============================================================
|
||||
- name: Create Configuration Recorder for AWS Config
|
||||
aws_config_recorder:
|
||||
<<: *aws_connection_info
|
||||
name: test_configuration_recorder
|
||||
state: present
|
||||
role_arn: "{{ config_iam_role.arn }}"
|
||||
recording_group:
|
||||
all_supported: true
|
||||
include_global_types: true
|
||||
register: output
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- output.changed
|
||||
|
||||
- name: Create Delivery Channel for AWS Config
|
||||
aws_config_delivery_channel:
|
||||
<<: *aws_connection_info
|
||||
name: test_delivery_channel
|
||||
state: present
|
||||
s3_bucket: "{{ config_s3_bucket }}"
|
||||
s3_prefix: "foo/bar"
|
||||
sns_topic_arn: "{{ config_sns_topic.sns_arn }}"
|
||||
delivery_frequency: 'Twelve_Hours'
|
||||
register: output
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- output.changed
|
||||
|
||||
- name: Create Config Rule for AWS Config
|
||||
aws_config_rule:
|
||||
<<: *aws_connection_info
|
||||
name: test_config_rule
|
||||
state: present
|
||||
description: 'This AWS Config rule checks for public write access on S3 buckets'
|
||||
scope:
|
||||
compliance_types:
|
||||
- 'AWS::S3::Bucket'
|
||||
source:
|
||||
owner: AWS
|
||||
identifier: 'S3_BUCKET_PUBLIC_WRITE_PROHIBITED'
|
||||
register: output
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- output.changed
|
||||
|
||||
# ============================================================
|
||||
# Update testing
|
||||
# ============================================================
|
||||
- name: Update Configuration Recorder
|
||||
aws_config_recorder:
|
||||
<<: *aws_connection_info
|
||||
name: test_configuration_recorder
|
||||
state: present
|
||||
role_arn: "{{ config_iam_role.arn }}"
|
||||
recording_group:
|
||||
all_supported: false
|
||||
include_global_types: false
|
||||
resource_types:
|
||||
- 'AWS::S3::Bucket'
|
||||
register: output
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- output.changed
|
||||
|
||||
- name: Update Delivery Channel
|
||||
aws_config_delivery_channel:
|
||||
<<: *aws_connection_info
|
||||
name: test_delivery_channel
|
||||
state: present
|
||||
s3_bucket: "{{ config_s3_bucket }}"
|
||||
sns_topic_arn: "{{ config_sns_topic.sns_arn }}"
|
||||
delivery_frequency: 'TwentyFour_Hours'
|
||||
register: output
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- output.changed
|
||||
|
||||
- name: Update Config Rule
|
||||
aws_config_rule:
|
||||
<<: *aws_connection_info
|
||||
name: test_config_rule
|
||||
state: present
|
||||
description: 'This AWS Config rule checks for public write access on S3 buckets'
|
||||
scope:
|
||||
compliance_types:
|
||||
- 'AWS::S3::Bucket'
|
||||
source:
|
||||
owner: AWS
|
||||
identifier: 'S3_BUCKET_PUBLIC_READ_PROHIBITED'
|
||||
register: output
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- output.changed
|
||||
|
||||
# ============================================================
|
||||
# Read testing
|
||||
# ============================================================
|
||||
- name: Don't update Configuration Recorder
|
||||
aws_config_recorder:
|
||||
<<: *aws_connection_info
|
||||
name: test_configuration_recorder
|
||||
state: present
|
||||
role_arn: "{{ config_iam_role.arn }}"
|
||||
recording_group:
|
||||
all_supported: false
|
||||
include_global_types: false
|
||||
resource_types:
|
||||
- 'AWS::S3::Bucket'
|
||||
register: output
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- not output.changed
|
||||
|
||||
- name: Don't update Delivery Channel
|
||||
aws_config_delivery_channel:
|
||||
<<: *aws_connection_info
|
||||
name: test_delivery_channel
|
||||
state: present
|
||||
s3_bucket: "{{ config_s3_bucket }}"
|
||||
sns_topic_arn: "{{ config_sns_topic.sns_arn }}"
|
||||
delivery_frequency: 'TwentyFour_Hours'
|
||||
register: output
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- not output.changed
|
||||
|
||||
- name: Don't update Config Rule
|
||||
aws_config_rule:
|
||||
<<: *aws_connection_info
|
||||
name: test_config_rule
|
||||
state: present
|
||||
description: 'This AWS Config rule checks for public write access on S3 buckets'
|
||||
scope:
|
||||
compliance_types:
|
||||
- 'AWS::S3::Bucket'
|
||||
source:
|
||||
owner: AWS
|
||||
identifier: 'S3_BUCKET_PUBLIC_READ_PROHIBITED'
|
||||
register: output
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- not output.changed
|
||||
|
||||
always:
|
||||
# ============================================================
|
||||
# Destroy testing
|
||||
# ============================================================
|
||||
- name: Destroy Configuration Recorder
|
||||
aws_config_recorder:
|
||||
<<: *aws_connection_info
|
||||
name: test_configuration_recorder
|
||||
state: absent
|
||||
register: output
|
||||
ignore_errors: yes
|
||||
|
||||
# - assert:
|
||||
# that:
|
||||
# - output.changed
|
||||
|
||||
- name: Destroy Delivery Channel
|
||||
aws_config_delivery_channel:
|
||||
<<: *aws_connection_info
|
||||
name: test_delivery_channel
|
||||
state: absent
|
||||
s3_bucket: "{{ config_s3_bucket }}"
|
||||
sns_topic_arn: "{{ config_sns_topic.sns_arn }}"
|
||||
delivery_frequency: 'TwentyFour_Hours'
|
||||
register: output
|
||||
ignore_errors: yes
|
||||
|
||||
# - assert:
|
||||
# that:
|
||||
# - output.changed
|
||||
|
||||
- name: Destroy Config Rule
|
||||
aws_config_rule:
|
||||
<<: *aws_connection_info
|
||||
name: test_config_rule
|
||||
state: absent
|
||||
description: 'This AWS Config rule checks for public write access on S3 buckets'
|
||||
scope:
|
||||
compliance_types:
|
||||
- 'AWS::S3::Bucket'
|
||||
source:
|
||||
owner: AWS
|
||||
identifier: 'S3_BUCKET_PUBLIC_READ_PROHIBITED'
|
||||
register: output
|
||||
ignore_errors: yes
|
||||
|
||||
# - assert:
|
||||
# that:
|
||||
# - output.changed
|
||||
|
||||
# ============================================================
|
||||
# Clean up prerequisites
|
||||
# ============================================================
|
||||
- name: remove S3 access from IAM role
|
||||
iam_policy:
|
||||
<<: *aws_connection_info
|
||||
iam_type: role
|
||||
iam_name: '{{ config_role_name }}'
|
||||
policy_name: AwsConfigRecorderTestRoleS3Policy
|
||||
state: absent
|
||||
policy_json: "{{ lookup( 'template', 'config-s3-policy.json.j2') }}"
|
||||
ignore_errors: yes
|
||||
|
||||
- name: remove IAM role
|
||||
iam_role:
|
||||
<<: *aws_connection_info
|
||||
name: '{{ config_role_name }}'
|
||||
state: absent
|
||||
ignore_errors: yes
|
||||
|
||||
- name: remove SNS topic
|
||||
sns_topic:
|
||||
<<: *aws_connection_info
|
||||
name: '{{ config_sns_name }}'
|
||||
state: absent
|
||||
ignore_errors: yes
|
||||
|
||||
- name: remove S3 bucket
|
||||
s3_bucket:
|
||||
<<: *aws_connection_info
|
||||
name: "{{ config_s3_bucket }}"
|
||||
state: absent
|
||||
force: yes
|
||||
ignore_errors: yes
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Action": "sns:Publish",
|
||||
"Resource": "{{ config_sns_topic.sns_arn }}",
|
||||
"Effect": "Allow",
|
||||
"Sid": "PublishToSNS"
|
||||
},
|
||||
{
|
||||
"Action": "s3:PutObject",
|
||||
"Resource": "arn:aws:s3:::{{ config_s3_bucket }}/*",
|
||||
"Effect": "Allow",
|
||||
"Sid": "AllowPutS3Object"
|
||||
},
|
||||
{
|
||||
"Action": "s3:GetBucketAcl",
|
||||
"Resource": "arn:aws:s3:::{{ config_s3_bucket }}",
|
||||
"Effect": "Allow",
|
||||
"Sid": "AllowGetS3Acl"
|
||||
}
|
||||
]
|
||||
}
|
Loading…
Reference in a new issue