295 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			295 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
|  | from .test_config_rules import managed_config_rule, TEST_REGION | ||
|  | from botocore.exceptions import ClientError | ||
|  | from moto import mock_config, mock_iam, mock_lambda | ||
|  | from moto.core import ACCOUNT_ID | ||
|  | from io import BytesIO | ||
|  | from uuid import uuid4 | ||
|  | from zipfile import ZipFile, ZIP_DEFLATED | ||
|  | 
 | ||
|  | import boto3 | ||
|  | import json | ||
|  | import pytest | ||
|  | 
 | ||
|  | 
 | ||
|  | def custom_config_rule(func_name="test_config_rule"): | ||
|  |     """Return a valid custom AWS Config Rule.""" | ||
|  |     return { | ||
|  |         "ConfigRuleName": f"custom_rule_{uuid4()}[:6]", | ||
|  |         "Description": "Custom S3 Public Read Prohibited Bucket Rule", | ||
|  |         "Scope": {"ComplianceResourceTypes": ["AWS::S3::Bucket", "AWS::IAM::Group"]}, | ||
|  |         "Source": { | ||
|  |             "Owner": "CUSTOM_LAMBDA", | ||
|  |             "SourceIdentifier": f"arn:aws:lambda:{TEST_REGION}:{ACCOUNT_ID}:function:{func_name}", | ||
|  |             "SourceDetails": [ | ||
|  |                 { | ||
|  |                     "EventSource": "aws.config", | ||
|  |                     "MessageType": "ScheduledNotification", | ||
|  |                     "MaximumExecutionFrequency": "Three_Hours", | ||
|  |                 }, | ||
|  |             ], | ||
|  |         }, | ||
|  |         "MaximumExecutionFrequency": "One_Hour", | ||
|  |     } | ||
|  | 
 | ||
|  | 
 | ||
|  | def zipped_lambda_function(): | ||
|  |     """Return a simple test lambda function, zipped.""" | ||
|  |     func_str = """
 | ||
|  | def lambda_handler(event, context): | ||
|  |     print("testing") | ||
|  |     return event | ||
|  | """
 | ||
|  |     zip_output = BytesIO() | ||
|  |     with ZipFile(zip_output, "w", ZIP_DEFLATED) as zip_file: | ||
|  |         zip_file.writestr("lambda_function.py", func_str) | ||
|  |         zip_file.close() | ||
|  |         zip_output.seek(0) | ||
|  |         return zip_output.read() | ||
|  | 
 | ||
|  | 
 | ||
|  | @mock_lambda | ||
|  | def create_lambda_for_config_rule(): | ||
|  |     """Return the ARN of a lambda that can be used by a custom rule.""" | ||
|  |     role_name = "test-role" | ||
|  |     lambda_role = None | ||
|  |     with mock_iam(): | ||
|  |         iam_client = boto3.client("iam", region_name=TEST_REGION) | ||
|  |         try: | ||
|  |             lambda_role = iam_client.get_role(RoleName=role_name)["Role"]["Arn"] | ||
|  |         except ClientError: | ||
|  |             lambda_role = iam_client.create_role( | ||
|  |                 RoleName=role_name, AssumeRolePolicyDocument="test policy", Path="/", | ||
|  |             )["Role"]["Arn"] | ||
|  | 
 | ||
|  |     # Create the lambda function and identify its location. | ||
|  |     lambda_client = boto3.client("lambda", region_name=TEST_REGION) | ||
|  |     lambda_client.create_function( | ||
|  |         FunctionName="test_config_rule", | ||
|  |         Runtime="python3.8", | ||
|  |         Role=lambda_role, | ||
|  |         Handler="lambda_function.lambda_handler", | ||
|  |         Code={"ZipFile": zipped_lambda_function()}, | ||
|  |         Description="Lambda test function for config rule", | ||
|  |         Timeout=3, | ||
|  |         MemorySize=128, | ||
|  |         Publish=True, | ||
|  |     ) | ||
|  | 
 | ||
|  | 
 | ||
|  | @mock_config | ||
|  | def test_config_rules_source_details_errors(): | ||
|  |     """Test error conditions with ConfigRule.Source_Details instantiation.""" | ||
|  |     client = boto3.client("config", region_name=TEST_REGION) | ||
|  | 
 | ||
|  |     create_lambda_for_config_rule() | ||
|  | 
 | ||
|  |     custom_rule = custom_config_rule() | ||
|  |     custom_rule["Source"]["SourceDetails"][0] = {"MessageType": "ScheduledNotification"} | ||
|  |     with pytest.raises(ClientError) as exc: | ||
|  |         client.put_config_rule(ConfigRule=custom_rule) | ||
|  |     err = exc.value.response["Error"] | ||
|  |     assert err["Code"] == "ParamValidationError" | ||
|  |     assert ( | ||
|  |         "Missing required parameter in ConfigRule.SourceDetails: 'EventSource'" | ||
|  |         in err["Message"] | ||
|  |     ) | ||
|  | 
 | ||
|  |     custom_rule = custom_config_rule() | ||
|  |     custom_rule["Source"]["SourceDetails"][0]["EventSource"] = "foo" | ||
|  |     with pytest.raises(ClientError) as exc: | ||
|  |         client.put_config_rule(ConfigRule=custom_rule) | ||
|  |     err = exc.value.response["Error"] | ||
|  |     assert err["Code"] == "ValidationException" | ||
|  |     assert "Member must satisfy enum value set: {aws.config}" in err["Message"] | ||
|  | 
 | ||
|  |     custom_rule = custom_config_rule() | ||
|  |     custom_rule["Source"]["SourceDetails"][0] = {"EventSource": "aws.config"} | ||
|  |     with pytest.raises(ClientError) as exc: | ||
|  |         client.put_config_rule(ConfigRule=custom_rule) | ||
|  |     err = exc.value.response["Error"] | ||
|  |     assert err["Code"] == "ParamValidationError" | ||
|  |     assert ( | ||
|  |         "Missing required parameter in ConfigRule.SourceDetails: 'MessageType'" | ||
|  |         in err["Message"] | ||
|  |     ) | ||
|  | 
 | ||
|  |     custom_rule = custom_config_rule() | ||
|  |     custom_rule["Source"]["SourceDetails"][0] = { | ||
|  |         "MessageType": "foo", | ||
|  |         "EventSource": "aws.config", | ||
|  |     } | ||
|  |     with pytest.raises(ClientError) as exc: | ||
|  |         client.put_config_rule(ConfigRule=custom_rule) | ||
|  |     err = exc.value.response["Error"] | ||
|  |     assert err["Code"] == "ValidationException" | ||
|  |     assert ( | ||
|  |         "Member must satisfy enum value set: " | ||
|  |         "{ConfigurationItemChangeNotification, " | ||
|  |         "ConfigurationSnapshotDeliveryCompleted, " | ||
|  |         "OversizedConfigurationItemChangeNotification, ScheduledNotification}" | ||
|  |         in err["Message"] | ||
|  |     ) | ||
|  | 
 | ||
|  |     custom_rule = custom_config_rule() | ||
|  |     custom_rule["Source"]["SourceDetails"][0]["MaximumExecutionFrequency"] = "foo" | ||
|  |     with pytest.raises(ClientError) as exc: | ||
|  |         client.put_config_rule(ConfigRule=custom_rule) | ||
|  |     err = exc.value.response["Error"] | ||
|  |     assert err["Code"] == "ValidationException" | ||
|  |     assert ( | ||
|  |         "Member must satisfy enum value set: " | ||
|  |         "{One_Hour, Six_Hours, Three_Hours, Twelve_Hours, TwentyFour_Hours}" | ||
|  |         in err["Message"] | ||
|  |     ) | ||
|  | 
 | ||
|  |     custom_rule = custom_config_rule() | ||
|  |     custom_rule["Source"]["SourceDetails"][0][ | ||
|  |         "MessageType" | ||
|  |     ] = "ConfigurationItemChangeNotification" | ||
|  |     with pytest.raises(ClientError) as exc: | ||
|  |         client.put_config_rule(ConfigRule=custom_rule) | ||
|  |     err = exc.value.response["Error"] | ||
|  |     assert err["Code"] == "InvalidParameterValueException" | ||
|  |     assert ( | ||
|  |         "A maximum execution frequency is not allowed if MessageType " | ||
|  |         "is ConfigurationItemChangeNotification or " | ||
|  |         "OversizedConfigurationItemChangeNotification" in err["Message"] | ||
|  |     ) | ||
|  | 
 | ||
|  | 
 | ||
|  | @mock_config | ||
|  | def test_valid_put_config_custom_rule(): | ||
|  |     """Test valid put_config_rule API calls for custom rules.""" | ||
|  |     client = boto3.client("config", region_name=TEST_REGION) | ||
|  |     # Create custom rule and compare input against describe_config_rule | ||
|  |     # output. | ||
|  |     create_lambda_for_config_rule() | ||
|  |     custom_rule = custom_config_rule() | ||
|  |     custom_rule["Scope"]["ComplianceResourceTypes"] = ["AWS::IAM::Group"] | ||
|  |     custom_rule["Scope"]["ComplianceResourceId"] = "basic_custom_test" | ||
|  |     custom_rule["InputParameters"] = '{"TestName":"true"}' | ||
|  |     custom_rule["ConfigRuleState"] = "ACTIVE" | ||
|  |     client.put_config_rule(ConfigRule=custom_rule) | ||
|  | 
 | ||
|  |     rsp = client.describe_config_rules(ConfigRuleNames=[custom_rule["ConfigRuleName"]]) | ||
|  |     custom_rule_json = json.dumps(custom_rule, sort_keys=True) | ||
|  |     new_config_rule = rsp["ConfigRules"][0] | ||
|  |     rule_arn = new_config_rule.pop("ConfigRuleArn") | ||
|  |     rule_id = new_config_rule.pop("ConfigRuleId") | ||
|  |     rsp_json = json.dumps(new_config_rule, sort_keys=True) | ||
|  |     assert custom_rule_json == rsp_json | ||
|  | 
 | ||
|  |     # Update custom rule and compare again. | ||
|  |     custom_rule["ConfigRuleArn"] = rule_arn | ||
|  |     custom_rule["ConfigRuleId"] = rule_id | ||
|  |     custom_rule["Description"] = "Updated Managed S3 Public Read Rule" | ||
|  |     custom_rule["Scope"]["ComplianceResourceTypes"] = ["AWS::S3::Bucket"] | ||
|  |     custom_rule["Scope"]["ComplianceResourceId"] = "S3-BUCKET_VERSIONING_ENABLED" | ||
|  |     custom_rule["Source"]["SourceDetails"][0] = { | ||
|  |         "EventSource": "aws.config", | ||
|  |         "MessageType": "ConfigurationItemChangeNotification", | ||
|  |     } | ||
|  |     custom_rule["InputParameters"] = "{}" | ||
|  |     client.put_config_rule(ConfigRule=custom_rule) | ||
|  | 
 | ||
|  |     rsp = client.describe_config_rules(ConfigRuleNames=[custom_rule["ConfigRuleName"]]) | ||
|  |     custom_rule_json = json.dumps(custom_rule, sort_keys=True) | ||
|  |     rsp_json = json.dumps(rsp["ConfigRules"][0], sort_keys=True) | ||
|  |     assert custom_rule_json == rsp_json | ||
|  | 
 | ||
|  |     # Update a custom rule specifying just the rule Id.  Test the default | ||
|  |     # value for MaximumExecutionFrequency while we're at it. | ||
|  |     del custom_rule["ConfigRuleArn"] | ||
|  |     rule_name = custom_rule.pop("ConfigRuleName") | ||
|  |     custom_rule["Source"]["SourceDetails"][0] = { | ||
|  |         "EventSource": "aws.config", | ||
|  |         "MessageType": "ConfigurationSnapshotDeliveryCompleted", | ||
|  |     } | ||
|  |     client.put_config_rule(ConfigRule=custom_rule) | ||
|  |     rsp = client.describe_config_rules(ConfigRuleNames=[rule_name]) | ||
|  |     updated_rule = rsp["ConfigRules"][0] | ||
|  |     assert updated_rule["ConfigRuleName"] == rule_name | ||
|  |     assert ( | ||
|  |         updated_rule["Source"]["SourceDetails"][0]["MaximumExecutionFrequency"] | ||
|  |         == "TwentyFour_Hours" | ||
|  |     ) | ||
|  | 
 | ||
|  |     # Update a custom rule specifying just the rule ARN. | ||
|  |     custom_rule["ConfigRuleArn"] = rule_arn | ||
|  |     del custom_rule["ConfigRuleId"] | ||
|  |     custom_rule["MaximumExecutionFrequency"] = "Six_Hours" | ||
|  |     client.put_config_rule(ConfigRule=custom_rule) | ||
|  |     rsp = client.describe_config_rules(ConfigRuleNames=[rule_name]) | ||
|  |     updated_rule = rsp["ConfigRules"][0] | ||
|  |     assert updated_rule["ConfigRuleName"] == rule_name | ||
|  |     assert updated_rule["MaximumExecutionFrequency"] == "Six_Hours" | ||
|  | 
 | ||
|  | 
 | ||
|  | @mock_config | ||
|  | def test_config_rules_source_errors():  # pylint: disable=too-many-statements | ||
|  |     """Test various error conditions in ConfigRule.Source instantiation.""" | ||
|  |     client = boto3.client("config", region_name=TEST_REGION) | ||
|  | 
 | ||
|  |     # Missing fields (ParamValidationError) caught by botocore and not | ||
|  |     # tested here:  ConfigRule.Source.SourceIdentifier | ||
|  | 
 | ||
|  |     managed_rule = managed_config_rule() | ||
|  |     managed_rule["Source"]["Owner"] = "test" | ||
|  |     with pytest.raises(ClientError) as exc: | ||
|  |         client.put_config_rule(ConfigRule=managed_rule) | ||
|  |     err = exc.value.response["Error"] | ||
|  |     assert err["Code"] == "ValidationException" | ||
|  |     assert "Member must satisfy enum value set: {AWS, CUSTOM_LAMBDA}" in err["Message"] | ||
|  | 
 | ||
|  |     managed_rule = managed_config_rule() | ||
|  |     managed_rule["Source"]["SourceIdentifier"] = "test" | ||
|  |     with pytest.raises(ClientError) as exc: | ||
|  |         client.put_config_rule(ConfigRule=managed_rule) | ||
|  |     err = exc.value.response["Error"] | ||
|  |     assert err["Code"] == "InvalidParameterValueException" | ||
|  |     assert ( | ||
|  |         "The sourceIdentifier test is invalid.  Please refer to the " | ||
|  |         "documentation for a list of valid sourceIdentifiers that can be used " | ||
|  |         "when AWS is the Owner" in err["Message"] | ||
|  |     ) | ||
|  | 
 | ||
|  |     managed_rule = managed_config_rule() | ||
|  |     managed_rule["Source"]["SourceDetails"] = [ | ||
|  |         {"EventSource": "aws.config", "MessageType": "ScheduledNotification"} | ||
|  |     ] | ||
|  |     with pytest.raises(ClientError) as exc: | ||
|  |         client.put_config_rule(ConfigRule=managed_rule) | ||
|  |     err = exc.value.response["Error"] | ||
|  |     assert err["Code"] == "InvalidParameterValueException" | ||
|  |     assert ( | ||
|  |         "SourceDetails should be null/empty if the owner is AWS. " | ||
|  |         "SourceDetails should be provided if the owner is CUSTOM_LAMBDA" | ||
|  |         in err["Message"] | ||
|  |     ) | ||
|  | 
 | ||
|  |     custom_rule = custom_config_rule() | ||
|  |     custom_rule["Source"] = { | ||
|  |         "Owner": "CUSTOM_LAMBDA", | ||
|  |         "SourceIdentifier": "test", | ||
|  |     } | ||
|  |     with pytest.raises(ClientError) as exc: | ||
|  |         client.put_config_rule(ConfigRule=custom_rule) | ||
|  |     err = exc.value.response["Error"] | ||
|  |     assert err["Code"] == "InvalidParameterValueException" | ||
|  |     assert ( | ||
|  |         "SourceDetails should be null/empty if the owner is AWS. " | ||
|  |         "SourceDetails should be provided if the owner is CUSTOM_LAMBDA" | ||
|  |         in err["Message"] | ||
|  |     ) | ||
|  | 
 | ||
|  |     custom_rule = custom_config_rule(func_name="unknown_func") | ||
|  |     with pytest.raises(ClientError) as exc: | ||
|  |         client.put_config_rule(ConfigRule=custom_rule) | ||
|  |     err = exc.value.response["Error"] | ||
|  |     assert err["Code"] == "InsufficientPermissionsException" | ||
|  |     assert ( | ||
|  |         f'The AWS Lambda function {custom_rule["Source"]["SourceIdentifier"]} ' | ||
|  |         f"cannot be invoked. Check the specified function ARN, and check the " | ||
|  |         f"function's permissions" in err["Message"] | ||
|  |     ) |