def test_assert_successful_evaluation(self): response = [ Evaluation(ComplianceType.COMPLIANT, 'some-resource-id', 'some-resource-type', 'some-annotation') ] response_two = [ Evaluation(ComplianceType.NON_COMPLIANT, 'some-resource-id', 'some-resource-type', 'some-annotation') ] response_combine = response + response_two # AssertionError due to 2 different lenghts of response and resp_expected with self.assertRaises(AssertionError) as context: CODE.assert_successful_evaluation(self, response, response_combine) self.assertTrue("1 != 2" in str(context.exception)) # AssertionError due to evaluation_count default with self.assertRaises(AssertionError) as context: CODE.assert_successful_evaluation(self, response_combine, response_combine) self.assertTrue("2 != 1" in str(context.exception)) # AssertionError due to evaluation_count incorrect with self.assertRaises(AssertionError) as context: CODE.assert_successful_evaluation(self, response, response, 2) self.assertTrue("1 != 2" in str(context.exception)) # Passing test for several evaluations CODE.assert_successful_evaluation(self, response, response) CODE.assert_successful_evaluation(self, response_combine, response_combine, 2)
def evaluate_periodic(self, event, client_factory, valid_rule_parameters): evaluations = [] iam_client = client_factory.build_client(service='iam') config_client = client_factory.build_client(service='config') for role_name in describe_roles(config_client): role_data = iam_client.get_role(RoleName=role_name) role = role_data.get('Role') last_used = role.get('RoleLastUsed') if last_used: diff = (CURRENT_TIME - last_used.get('LastUsedDate')).days else: created_on = role.get('CreateDate') diff = (CURRENT_TIME - created_on).days days_before_unused = valid_rule_parameters.get('DaysBeforeUnused') if diff <= days_before_unused: evaluations.append( Evaluation(ComplianceType.COMPLIANT, role_name, RESOURCE_TYPE)) else: evaluations.append( Evaluation( ComplianceType.NON_COMPLIANT, role_name, RESOURCE_TYPE, annotation= "This AWS IAM Role has not been used within the last {} day(s)" .format(days_before_unused))) return evaluations
def evaluate_change(self, event, client_factory, configuration_item, valid_rule_parameters): ec2_client = client_factory.build_client("ec2") resource_type = configuration_item.get("resourceType") if resource_type == "AWS::EC2::Volume": volume_id = configuration_item.get("resourceId") if configuration_item.get("configuration").get("attachments"): instance_id = configuration_item.get("configuration").get( "attachments")[0].get("instanceId") # Scenario:3 EBS Volume not attached to an EC2 instance. if not configuration_item.get("configuration").get("attachments"): return [Evaluation(ComplianceType.NOT_APPLICABLE)] volume_tags = configuration_item.get("configuration").get("tags") instance_tags = get_instance_tags(ec2_client, instance_id) tags_to_apply = compare_instance_tags_to_volume_tags( instance_tags, volume_tags) # Scenario:1 EBS Volume Tags includes same tags as EC2 Instance its attached to. if not tags_to_apply: return [Evaluation(ComplianceType.COMPLIANT)] # Scenario:2 EBS Volume Tags does not include the same tags as EC2 Instance its attached to. annotation = "EBS Volume needs to have EC2 Instance Tags applied." return [ Evaluation(ComplianceType.NON_COMPLIANT, annotation=annotation) ] if resource_type == "AWS::EC2::Instance": instance_id = configuration_item.get("resourceId") instance_tags = configuration_item.get("configuration").get("tags") block_device_mappings = configuration_item.get( "configuration").get("blockDeviceMappings") all_volumes = get_volumes_attached_to_instance( block_device_mappings) evaluations = list() for volume in all_volumes: volume_id = volume volume_tags = get_volume_tags(ec2_client, volume_id) tags_to_apply = compare_instance_tags_to_volume_tags( instance_tags, volume_tags) # Scenario:1 EBS Volume Tags includes same tags as EC2 Instance its attached to. if not tags_to_apply: evaluations.append( Evaluation(ComplianceType.COMPLIANT, volume_id, DEFAULT_RESOURCE_TYPE)) # Scenario:2 EBS Volume Tags does not include the same tags as EC2 Instance its attached to. if tags_to_apply: annotation = "EBS Volume needs to have EC2 Instance Tags applied." evaluations.append( Evaluation(ComplianceType.NON_COMPLIANT, volume_id, DEFAULT_RESOURCE_TYPE, annotation)) return evaluations
def create_compliance_evaluation(is_compliant, key_state, key_id, annotation): compliance_state = "COMPLIANT" if is_compliant else "NON_COMPLIANT" if key_state == 'Enabled': return Evaluation(compliance_state, key_id, DEFAULT_RESOURCE_TYPE, annotation=f"{annotation}") else: return Evaluation( compliance_state, key_id, DEFAULT_RESOURCE_TYPE, annotation=f"{annotation} The KMS Key is in state {key_state}.")
def evaluate_periodic(self, event, client_factory, valid_rule_parameters): kms_client = client_factory.build_client('kms') iam_client = client_factory.build_client('iam') evaluations = [] account_id = event['accountId'] whitelisted_aws_principles = valid_rule_parameters[ "WhitelistedAWSPrincipalArns"] logger.info(f"Whitelisted AWS Principals {whitelisted_aws_principles}") all_kms_key_list = get_all_kms_keys(kms_client) if not all_kms_key_list: return [ Evaluation(ComplianceType.NOT_APPLICABLE, resourceId=account_id, resourceType=DEFAULT_RESOURCE_TYPE) ] for key_id in all_kms_key_list: kms_key_details = kms_client.describe_key(KeyId=key_id) key_state = kms_key_details['KeyMetadata']['KeyState'] get_key_policy = kms_client.get_key_policy(KeyId=key_id, PolicyName='default') key_policy = json.loads(get_key_policy['Policy'])['Statement'] evaluation = evaluate_key(iam_client, key_state, key_policy, key_id, whitelisted_aws_principles) evaluations.append(evaluation) return evaluations
def test_2_evaluatechange_sgnotassociated_noncompliant(self): CONFIG_CLIENT_MOCK.select_resource_config.return_value = { 'Results': ['{"COUNT(*)": 0}'] } config_item = { "configuration": { "groupName": "security-group-1" }, "relationships": [{ "resourceId": "vpc-01234567", "resourceType": "AWS::EC2::VPC" }], "resourceType": "AWS::EC2::SecurityGroup", "configurationItemCaptureTime": "2019-04-28T07:49:40.797Z", "resourceId": "sg-0123456789abcdefg" } response = RULE.evaluate_change({}, CLIENT_FACTORY, config_item, {}) resp_expected = [ Evaluation( ComplianceType.NON_COMPLIANT, annotation= 'This Amazon EC2 security group is not associated with an EC2 instance or an ENI.' ) ] rdklibtest.assert_successful_evaluation(self, response, resp_expected)
def test_scenario3_oneormoreiamrolesunused_returnsnoncompliant(self): rule_parameters = {"DaysBeforeUnused": "80"} rule_parameters = RULE.evaluate_parameters(rule_parameters) input_event = rdklibtest.create_test_scheduled_event( rule_parameters_json=rule_parameters) CONFIG_CLIENT.select_resource_config.return_value = { "Results": ['{"resourceName":"AWS-CodePipeline-Service"}'] } IAM_CLIENT_MOCK.get_role.return_value = { "Role": { "RoleLastUsed": { "LastUsedDate": datetime.now(timezone.utc) - timedelta(days=100) } } } response = RULE.evaluate_periodic(input_event, CLIENT_FACTORY, rule_parameters) resp_expected = [ Evaluation( ComplianceType.NON_COMPLIANT, 'AWS-CodePipeline-Service', RESOURCE_TYPE, annotation= 'This AWS IAM Role has not been used within the last 80 day(s)' ) ] rdklibtest.assert_successful_evaluation(self, response, resp_expected)
def test_scenario1_evaluatechange_ec2_instance_no_ebs_volumes_attached_returnsnotapplicable( self): response = RULE.evaluate_change( {}, CLIENT_FACTORY, self.ci_ec2_instance_no_ebs_volumes_attached, {}) response_expected = [Evaluation(ComplianceType.NOT_APPLICABLE)] assert_successful_evaluation(self, response, response_expected)
def test_scenario2_allAlbListenersAreSsl_returnsCompliant(self): mock_albs_in_config(['arn1']) ELBV2_CLIENT_MOCK.describe_listeners = MagicMock( return_value={'Listeners': [{'ListenerArn': 'arn1', 'SslPolicy': 'Some_policy_1'}, {'ListenerArn': 'arn2', 'SslPolicy': 'Some_policy_2'}]} ) response = RULE.evaluate_periodic(self.event, CLIENT_FACTORY, {}) rdklibtest.assert_successful_evaluation(self, response, [Evaluation(ComplianceType.COMPLIANT, 'arn1', ELB_RESOURCE_TYPE)], 1)
def test_scenario4_describeRulesPagination_returnsCompliant(self): self.mock_albs_in_config(['arn1']) ELBV2_CLIENT_MOCK.describe_listeners = MagicMock( return_value={ 'Listeners': [{ 'ListenerArn': 'listenerArn1', 'DefaultActions': [{ 'RedirectConfig': { 'Protocol': 'HTTPS' }, 'Type': 'redirect' }] }] }) ELBV2_CLIENT_MOCK.describe_rules = MagicMock(side_effect=[{ 'Rules': [ { 'RuleArn': 'ruleArn1', 'Actions': [{ 'RedirectConfig': { 'Protocol': 'HTTPS' }, 'Type': 'redirect' }] }, ], 'NextMarker': 'ghi' }, { 'Rules': [ { 'RuleArn': 'ruleArn2', 'Actions': [{ 'RedirectConfig': { 'Protocol': 'HTTPS' }, 'Type': 'redirect' }] }, ] }]) response = RULE.evaluate_periodic(self.event, CLIENT_FACTORY, {}) resp_expected = [ Evaluation(ComplianceType.COMPLIANT, 'arn1', ELB_RESOURCE_TYPE) ] rdklibtest.assert_successful_evaluation(self, response, resp_expected, 1) self.assertEqual(ELBV2_CLIENT_MOCK.mock_calls, [ call.describe_listeners(LoadBalancerArn='arn1', PageSize=400), call.describe_rules(ListenerArn='listenerArn1', PageSize=400), call.describe_rules( ListenerArn='listenerArn1', PageSize=400, Marker='ghi') ])
def test_evaluate_periodic_1_compliant(self): SECURITYHUB_CLIENT_MOCK.describe_hub.return_value = MOCK_SECURITYHUB_ENABLED response = RULE.evaluate_periodic(MOCK_EVENT, CLIENT_FACTORY, {}) resp_expected = [ Evaluation(ComplianceType.COMPLIANT, '632747342146', RESOURCE_TYPE) ] rdklibtest.assert_successful_evaluation(self, response, resp_expected, 1)
def test_evaluate_periodic_2_non_compliant(self): SECURITYHUB_CLIENT_MOCK.describe_hub.side_effect = my_side_effect response = RULE.evaluate_periodic(MOCK_EVENT, CLIENT_FACTORY, {}) resp_expected = [ Evaluation(ComplianceType.NON_COMPLIANT, '632747342146', RESOURCE_TYPE, 'AWS SecurityHub is not enabled.') ] rdklibtest.assert_successful_evaluation(self, response, resp_expected, 1)
def test_scenario6_evaluatechange_ebs_volume_attached_missing_tags_from_ec2_instance_returnsnoncompliant( self): CONFIG_CLIENT_MOCK.select_resource_config = MagicMock( return_value=self.ec2_instance_results_tags2) response = RULE.evaluate_change({}, CLIENT_FACTORY, self.ci_ebs_volume_attached_with_tags1, {}) response_expected = [Evaluation(ComplianceType.NON_COMPLIANT)] assert_successful_evaluation(self, response, response_expected)
def test_scenario6_evaluatechange_ebs_volume_attached_missing_tags_from_ec2_instance_returnsnoncompliant( self): EC2_CLIENT_MOCK.get_paginator.return_value = EC2_PAGINATOR_MOCK EC2_PAGINATOR_MOCK.paginate.return_value = self.ec2_instance_tags2 response = RULE.evaluate_change({}, CLIENT_FACTORY, self.ci_ebs_volume_attached_with_tags1, {}) response_expected = [Evaluation(ComplianceType.NON_COMPLIANT)] assert_successful_evaluation(self, response, response_expected)
def test_scenario3_evaluatechange_specifiedenginenotpresent_returnsnotapplicable( self): response = RULE.evaluate_change(None, CLIENT_FACTORY, self.engine_not_applicable, None) resp_expected = [ Evaluation(ComplianceType.NOT_APPLICABLE, annotation="Engine is not defined") ] rdklibtest.assert_successful_evaluation(self, response, resp_expected)
def test_scenario3b_evaluatechange_ec2_instance_volumes_has_tags_from_ec2_instance_with_token_returnscompliant( self): CONFIG_CLIENT_MOCK.select_resource_config = MagicMock( return_value=self.ebs_volume_results_tags2_with_token) response = RULE.evaluate_change({}, CLIENT_FACTORY, self.ci_ec2_instance_with_tags2, {}) response_expected = [ Evaluation(ComplianceType.COMPLIANT, "vol-123abc", RESOURCE_TYPE) ] assert_successful_evaluation(self, response, response_expected)
def test_scenario3_evaluatechange_ec2_instance_volumes_has_tags_from_ec2_instance_returnscompliant( self): EC2_CLIENT_MOCK.get_paginator.return_value = EC2_PAGINATOR_MOCK EC2_PAGINATOR_MOCK.paginate.return_value = self.ebs_volume_tags2 response = RULE.evaluate_change({}, CLIENT_FACTORY, self.ci_ec2_instance_with_tags2, {}) response_expected = [ Evaluation(ComplianceType.COMPLIANT, "vol-123abc", RESOURCE_TYPE) ] assert_successful_evaluation(self, response, response_expected)
def test_scenario3_AlbHasHttpListenerWithNoSSLRedirect_returnsNonCompliant(self): mock_albs_in_config(['arn1', 'arn2']) ELBV2_CLIENT_MOCK.describe_listeners = MagicMock( return_value={'Listeners': [ {'ListenerArn': 'arn1', 'DefaultActions': [{'RedirectConfig': {'Protocol': 'HTTP'}, 'Type': 'other'}]}, {'ListenerArn': 'arn2', 'DefaultActions': [{'RedirectConfig': {'Protocol': 'HTTP'}, 'Type': 'other'}]}, ]} ) ELBV2_CLIENT_MOCK.describe_rules = MagicMock( return_value={'Rules': [ {'RuleArn': 'arn1', 'Actions': [{'RedirectConfig': {'Protocol': 'HTTPS'}, 'Type': 'other'}]}, {'RuleArn': 'arn2', 'Actions': [{'RedirectConfig': {'Protocol': 'HTTPS'}, 'Type': 'other'}]}, ]} ) response = RULE.evaluate_periodic(self.event, CLIENT_FACTORY, {}) resp_expected = [] resp_expected.append(Evaluation(ComplianceType.NON_COMPLIANT, 'arn1', ELB_RESOURCE_TYPE, "HTTP listener rule must have HTTP to HTTPS redirection action configured")) resp_expected.append(Evaluation(ComplianceType.NON_COMPLIANT, 'arn2', ELB_RESOURCE_TYPE, "HTTP listener rule must have HTTP to HTTPS redirection action configured")) rdklibtest.assert_successful_evaluation(self, response, resp_expected, 2)
def test_scenario2_evaluatechange_onetypeoflogisenabledonrds_returnsnoncompliant( self): response = RULE.evaluate_change(None, CLIENT_FACTORY, self.one_log_enabled_non_compliant, None) resp_expected = [ Evaluation(ComplianceType.NON_COMPLIANT, annotation="One or more logs are not enabled") ] rdklibtest.assert_successful_evaluation(self, response, resp_expected)
def evaluate_periodic(self, event, client_factory, valid_rule_parameters): client = client_factory.build_client('securityhub') evaluations = [] try: security_hub_enabled = client.describe_hub() # Scenario:1 SecurityHub is enabled for an AWS Account. if security_hub_enabled: evaluations.append( Evaluation(ComplianceType.COMPLIANT, event['accountId'], APPLICABLE_RESOURCES[0])) except botocore.exceptions.ClientError as error: # Scenario:2 SecurityHub is not enabled for an AWS Account. if error.response['Error']['Code'] == 'InvalidAccessException': evaluations.append( Evaluation(ComplianceType.NON_COMPLIANT, event['accountId'], APPLICABLE_RESOURCES[0])) else: raise error return evaluations
def evaluate_change(self, event, client_factory, configuration_item, valid_rule_parameters): configuration = configuration_item.get('configuration') engine = configuration.get('engine') logs_list = self.engine_logs.get(engine) if logs_list: logs_enabled_list = configuration.get( 'enabledCloudwatchLogsExports', []) logs_not_enabled_list = list( set(logs_list) - set(logs_enabled_list)) if logs_not_enabled_list: return [ Evaluation(ComplianceType.NON_COMPLIANT, annotation='One or more logs are not enabled') ] return [Evaluation(ComplianceType.COMPLIANT)] return [ Evaluation(ComplianceType.NOT_APPLICABLE, annotation="Engine is not defined") ]
def evaluate_periodic(self, event, client_factory, valid_rule_parameters): client = client_factory.build_client('securityhub') is_securityhub_enabled = True try: response = client.describe_hub() except: is_securityhub_enabled = False if is_securityhub_enabled: print('HubArn:' + response['HubArn']) print('SecurityHub Enabled.') return [ Evaluation(ComplianceType.COMPLIANT, event['accountId'], RESOURCE_TYPE) ] print('HubArn: None') print('SecurityHub NOT Enabled.') return [ Evaluation(ComplianceType.NON_COMPLIANT, event['accountId'], RESOURCE_TYPE, 'AWS SecurityHub is not enabled.') ]
def test_1_evaluatechange_sgisdefault_notapplicable(self): config_item = { "configuration": { "groupName": "default" }, "resourceType": "AWS::EC2::SecurityGroup", "configurationItemCaptureTime": "2019-04-28T07:49:40.797Z", "resourceId": "sg-0123456789abcdefg" } response = RULE.evaluate_change({}, {}, config_item, {}) resp_expected = [Evaluation(ComplianceType.NOT_APPLICABLE)] rdklibtest.assert_successful_evaluation(self, response, resp_expected)
def test_evaluate_noncompliant_instance_deprecated_ami(self): EC2_CLIENT_MOCK.describe_instances.return_value = self.instance_response EC2_CLIENT_MOCK.describe_images.return_value = self.deprecated_ami_response response = RULE.evaluate_periodic({}, CLIENT_FACTORY, {'mode': 'EC2'}) instance = self.instance_response['Reservations'][0]['Instances'][0] response_expected = [ Evaluation(complianceType=ComplianceType.NON_COMPLIANT, resourceId=instance['InstanceId'], resourceType='AWS::EC2::Instance', annotation=f'Image {instance["ImageId"]} is deprecated') ] assert_successful_evaluation(self, response, response_expected)
def evaluate_change(self, event, client_factory, configuration_item, valid_rule_parameters): ec2_client = client_factory.build_client("ec2") resource_type = configuration_item.get("resourceType") if resource_type == "AWS::EC2::Volume": # Scenario 2: Volume Resource Type - EBS Volume not attached to an EC2 instance. if not configuration_item.get("configuration", {}).get("attachments"): return [Evaluation(ComplianceType.NOT_APPLICABLE)] volume_id = configuration_item.get("resourceId") instance_id = configuration_item.get("configuration").get( "attachments")[0].get("instanceId") volume_tags = configuration_item.get("configuration", {}).get("tags") instance_tags = get_resource_tags(ec2_client, instance_id) tags_to_apply = compare_instance_tags_to_volume_tags( instance_tags, volume_tags) # Scenario 5: Volume Resource Type - EBS Volume Tags includes same tags as EC2 Instance its attached to. if not tags_to_apply: return [Evaluation(ComplianceType.COMPLIANT)] # Scenario 6: Volume Resource Type - EBS Volume Tags does not include the same tags as EC2 Instance its attached to. return [Evaluation(ComplianceType.NON_COMPLIANT)] if resource_type == "AWS::EC2::Instance": instance_id = configuration_item.get("resourceId") instance_tags = configuration_item.get("configuration", {}).get("tags") block_device_mappings = configuration_item.get( "configuration", {}).get("blockDeviceMappings") # Scenario 1: EC2 Resource Type - EC2 Instance has no EBS volumes attached. if not block_device_mappings: return [Evaluation(ComplianceType.NOT_APPLICABLE)] all_volumes = get_volumes_attached_to_instance( block_device_mappings) evaluations = [] for volume_id in all_volumes: volume_tags = get_resource_tags(ec2_client, volume_id) tags_to_apply = compare_instance_tags_to_volume_tags( instance_tags, volume_tags) # Scenario 3: EC2 Resource Type - EBS Volumes attached, includes same tags as EC2 Instance. if not tags_to_apply: evaluations.append( Evaluation(ComplianceType.COMPLIANT, volume_id, DEFAULT_RESOURCE_TYPE)) # Scenario 4: EC2 Resource Type - EBS Volumes attached, does not include same tags as EC2 Instance. if tags_to_apply: evaluations.append( Evaluation(ComplianceType.NON_COMPLIANT, volume_id, DEFAULT_RESOURCE_TYPE)) return evaluations return []
def evaluate_periodic(self, event, client_factory, valid_rule_parameters): evaluations = [] alb_client = client_factory.build_client("elbv2") config_client = client_factory.build_client("config") all_elbv2 = get_all_albs(config_client) for elb in all_elbv2: alb_all_listeners = get_all_listeners(alb_client, elb) is_alb_compliant = all( is_listener_compliant(listener, alb_client) for listener in alb_all_listeners) if is_alb_compliant: evaluations.append( Evaluation(ComplianceType.COMPLIANT, elb, DEFAULT_RESOURCE_TYPE)) else: evaluations.append( Evaluation( ComplianceType.NON_COMPLIANT, elb, DEFAULT_RESOURCE_TYPE, "HTTP listener rule must have HTTP to HTTPS redirection action configured" )) return evaluations
def evaluate_change(self, event, client_factory, configuration_item, valid_rule_parameters): if configuration_item['resourceType'] == 'AWS::CodeBuild::Project': if configuration_item['configuration'].get('vpcConfig'): eval_list_sg = [] for sg_id in configuration_item['configuration']['vpcConfig'][ 'securityGroupIds']: eval_list_sg.append( Evaluation(ComplianceType.COMPLIANT, sg_id, 'AWS::EC2::SecurityGroup')) return eval_list_sg # CI is 'AWS::EC2::SecurityGroup': if configuration_item['configuration']['groupName'] == 'default': return [Evaluation(ComplianceType.NOT_APPLICABLE)] for relation in configuration_item['relationships']: #resourceId for eni: 'eni-123456abcdefghi12' if relation['resourceId'][0:3] == 'eni': return [Evaluation(ComplianceType.COMPLIANT)] if is_security_group_attached_codebuild( client_factory, configuration_item['resourceId']): return [ Evaluation( ComplianceType.COMPLIANT, annotation= 'This Amazon EC2 security group is associated with at least one AWS CodeBuild project.' ) ] return [ Evaluation( ComplianceType.NON_COMPLIANT, annotation= 'This Amazon EC2 security group is not associated with an EC2 instance or an ENI.' ) ]
def test_evaluate_noncompliant_asg_launch_config_deprecated_ami(self): ASG_CLIENT_MOCK.describe_auto_scaling_groups.return_value = self.asg_launch_config ASG_CLIENT_MOCK.describe_launch_configurations.return_value = self.launch_config EC2_CLIENT_MOCK.describe_images.return_value = self.deprecated_ami_response response = RULE.evaluate_periodic({}, CLIENT_FACTORY, {'mode': 'ASG'}) asg = self.asg_launch_config['AutoScalingGroups'][0] launch_config = self.launch_config['LaunchConfigurations'][0] response_expected = [ Evaluation( complianceType=ComplianceType.NON_COMPLIANT, resourceId=asg['AutoScalingGroupName'], resourceType='AWS::AutoScaling::AutoScalingGroup', annotation=f'Image {launch_config["ImageId"]} is deprecated') ] assert_successful_evaluation(self, response, response_expected)
def evaluate_instances(self, ec2_client): evaluations = [] instances = get_all_instances(ec2_client) for instance in instances: ami_id = instance['ImageId'] compliance_type, annotation = self.evaluate_ami(ec2_client, ami_id) evaluation = Evaluation( resourceType='AWS::EC2::Instance', resourceId=instance['InstanceId'], complianceType=compliance_type, annotation=annotation, ) evaluations.append(evaluation) return evaluations
def evaluate_asgs(self, ec2_client, asg_client): evaluations = [] asgs = get_all_asgs(asg_client) for asg in asgs: ami_id = get_ami_from_asg(asg_client, ec2_client, asg) compliance_type, annotation = self.evaluate_ami(ec2_client, ami_id) evaluation = Evaluation( resourceType='AWS::AutoScaling::AutoScalingGroup', resourceId=asg['AutoScalingGroupName'], complianceType=compliance_type, annotation=annotation, ) evaluations.append(evaluation) return evaluations