def template(self): dir_path = os.path.dirname(os.path.realpath(__file__)) cf_script = open( "{}/test_templates/s3_bucket_cross_account.json".format(dir_path)) cf_template = S3Adapter().convert_json_or_yaml_to_dict( cf_script.read()) return pycfmodel.parse(cf_template)
def template(self): dir_path = os.path.dirname(os.path.realpath(__file__)) cf_script = open( '{}/test_templates/sqs_queue_with_wildcards.json'.format(dir_path)) cf_template = S3Adapter().convert_json_or_yaml_to_dict( cf_script.read()) return pycfmodel.parse(cf_template)
def test_invalid_url(self): url = "someweirdurl/wheretheresnoprotocol/justcoz" adapter = S3Adapter() with pytest.raises(InvalidURLException): result = adapter.download_template_to_dictionary(url)
def test_valid_yaml_with_cf_shorthand_join(self): yaml_content = """ myprop: !Join ['-', ['hello', 'world']]\n\r """ bucket = 'cf-templates-1234' filename = 'myexamplestack.yml' self.set_s3_file(bucket, filename, yaml_content) url = "https://s3-eu-west-1.amazonaws.com/{}/{}".format( bucket, filename) adapter = S3Adapter() result = adapter.download_template_to_dictionary(url) assert result['myprop'] == {'Fn::Join': ['-', ['hello', 'world']]}
def test_valid_yaml(self): yaml_content = """ hello: this is valid\n\r """ bucket = 'cf-templates-1234' filename = 'myexamplestack.yml' self.set_s3_file(bucket, filename, yaml_content) url = "https://s3-eu-west-1.amazonaws.com/{}/{}".format( bucket, filename) adapter = S3Adapter() result = adapter.download_template_to_dictionary(url) assert result['hello'] == "this is valid"
def test_valid_yaml_with_cf_getatt_dotsyntax(self): yaml_content = """ myprop: !GetAtt hello.world\n\r """ bucket = 'cf-templates-1234' filename = 'myexamplestack.yml' self.set_s3_file(bucket, filename, yaml_content) url = "https://s3-eu-west-1.amazonaws.com/{}/{}".format( bucket, filename) adapter = S3Adapter() result = adapter.download_template_to_dictionary(url) assert result['myprop'] == {'Fn::GetAtt': ['hello', 'world']}
def test_urlencoded_url(self): json_content = """ { "hello": "this is valid json" } """ bucket = 'cf-templates-1234' filename = '2017284ltK-adfs%20iam%20role.json' self.set_s3_file(bucket, '2017284ltK-adfs iam role.json', json_content) url = "https://s3-eu-west-1.amazonaws.com/{}/{}".format( bucket, filename) adapter = S3Adapter() result = adapter.download_template_to_dictionary(url) assert result['hello'] == "this is valid json"
def test_url_with_path_prefix(self): json_content = """ { "hello": "this is valid json" } """ bucket = 'cf-templates-1234' filename = 'myprefixes/aaaaaa/myexamplestack.json' self.set_s3_file(bucket, filename, json_content) url = "https://s3-eu-west-1.amazonaws.com/{}/{}".format( bucket, filename) adapter = S3Adapter() result = adapter.download_template_to_dictionary(url) assert result['hello'] == "this is valid json"
def test_invalid_format(self): invalid_content = """ { 'asdf': 'asdf' 'asd': 'asd' """ bucket = 'cf-templates-1234' filename = 'myexamplestack.yml' self.set_s3_file(bucket, filename, invalid_content) url = "https://s3-eu-west-1.amazonaws.com/{}/{}".format( bucket, filename) adapter = S3Adapter() assert adapter.download_template_to_dictionary(url) is None
def test_script(script_name, service_name, project_name): event = { 'stack_template_url': 'https://fake/bucket/key', 'project': project_name, 'serviceName': service_name, } cf_script = open('{}/test_cf_scripts/{}'.format(dir_path, script_name)) mock_created_s3_adapter_object = Mock() mock_created_s3_adapter_object.download_template_to_dictionary.return_value = S3Adapter( ).convert_json_or_yaml_to_dict(cf_script.read()) mock_s3_adapter = Mock(return_value=mock_created_s3_adapter_object) cf_script.close() with patch('cfripper.main.S3Adapter', new=mock_s3_adapter): from cfripper.main import handler event_result = handler(event, None) print('{} -- valid: {}\n {}'.format(script_name, event_result['valid'], event_result['reason']))
def test_with_templates(self): dir_path = os.path.dirname(os.path.realpath(__file__)) test_templates = glob.glob('{}/test_templates/*.*'.format(dir_path)) for template in test_templates: cf_script = open(template) cf_template = S3Adapter().convert_json_or_yaml_to_dict( cf_script.read()) config = Config( project_name=template, service_name=template, stack_name=template, rules=ALL_RULES.keys(), ) # Scan result result = Result() rules = [ ALL_RULES.get(rule)(config, result) for rule in config.RULES ] processor = RuleProcessor(*rules) processor.process_cf_template(cf_template, config, result) # Use this to print the stack if there's an error if len(result.exceptions): print(template) traceback.print_tb(result.exceptions[0].__traceback__) no_resource_templates = [ 'vulgar_bad_syntax.yml', 'rubbish.json', ] if template.split('/')[-1] in no_resource_templates: assert len(result.exceptions) == 1 else: assert len(result.exceptions) == 0
def handler(event, context): """ Main entry point of the Lambda function. :param event: Request JSON format for proxy integration { "resource": "Resource path", "path": "Path parameter", "httpMethod": "Incoming request's method name" "headers": {Incoming request headers} "queryStringParameters": {"stack_template_url": String } "pathParameters": {path parameters} "stageVariables": {Applicable stage variables} "requestContext": {Request context, including authorizer-returned key-value pairs} "body": "A JSON string of the request payload." "isBase64Encoded": "A boolean flag to indicate if the applicable request payload is Base64-encode" } :param context: :return: Response JSON format { "isBase64Encoded": true|false, "statusCode": httpStatusCode, "headers": { "headerName": "headerValue", ... }, "body": "..." } """ print(event) qp = event.get("queryStringParameters") if not qp.get("stack_template_url"): raise ValueError( "Invalid event type: no parameter 'stack_template_url' in request." ) result = Result() s3 = S3Adapter() template = s3.download_template_to_dictionary(qp["stack_template_url"]) if not template: # In case of an ivalid script log a warning and return early result.add_exception( TypeError("Malformated CF script: {}".format( qp["stack_template_url"]))) return { "isBase64Encoded": False, "statusCode": 400, "headers": {}, "body": str({ "valid": "false", "reason": '', "failed_rules": [], "exceptions": [x.args[0] for x in result.exceptions], }) } # Process Rules config = Config( project_name=event.get("project"), service_name=event.get("serviceName"), stack_name=event.get("stack", {}).get("name"), rules=ALL_RULES.keys(), event=event.get("event"), template_url=event.get("stack_template_url"), ) logger.info("Scan started for: {}; {}; {};".format( config.project_name, config.service_name, config.stack_name, )) rules = [ALL_RULES.get(rule)(config, result) for rule in config.RULES] processor = RuleProcessor(*rules) processor.process_cf_template(template, config, result) if not result.valid: log_results( "Failed rules", config.project_name, config.service_name, config.stack_name, result.failed_rules, result.warnings, qp["stack_template_url"], ) logger.info("FAIL: {}; {}; {}".format( config.project_name, config.service_name, config.stack_name, )) else: logger.info("PASS: {}; {}; {}".format( config.project_name, config.service_name, config.stack_name, )) if len(result.failed_monitored_rules) > 0 or len(result.warnings) > 0: log_results( "Failed monitored rules", config.project_name, config.service_name, config.stack_name, result.failed_monitored_rules, result.warnings, qp["stack_template_url"], ) # TODO base64 encode and implement more error code responses return { "isBase64Encoded": False, "statusCode": 200, "headers": {}, "body": str({ "valid": str(result.valid).lower(), "reason": ",".join([ "{}-{}".format(r["rule"], r["reason"]) for r in result.failed_rules ]), "failed_rules": result.failed_rules, "exceptions": [x.args[0] for x in result.exceptions], "warnings": result.failed_monitored_rules, }) }
def handler(event, context): """ Main entry point of the Lambda function. :param event: { "stack_template_url": String } :param context: :return: """ if not event.get("stack_template_url"): raise ValueError( "Invalid event type: no parameter 'stack_template_url' in request." ) result = Result() s3 = S3Adapter() template = s3.download_template_to_dictionary(event["stack_template_url"]) if not template: # In case of an ivalid script log a warning and return early result.add_exception( TypeError("Malformated CF script: {}".format( event["stack_template_url"]))) return { "valid": "true", "reason": '', "failed_rules": [], "exceptions": [x.args[0] for x in result.exceptions], } # Process Rules config = Config( project_name=event.get("project"), service_name=event.get("serviceName"), stack_name=event.get("stack", {}).get("name"), rules=ALL_RULES.keys(), ) logger.info("Scan started for: {}; {}; {};".format( config.project_name, config.service_name, config.stack_name, )) rules = [ALL_RULES.get(rule)(config, result) for rule in config.RULES] processor = RuleProcessor(*rules) processor.process_cf_template(template, config, result) if not result.valid: log_results( "Failed rules", config.project_name, config.service_name, config.stack_name, result.failed_rules, result.warnings, event["stack_template_url"], ) logger.info("FAIL: {}; {}; {}".format( config.project_name, config.service_name, config.stack_name, )) else: logger.info("PASS: {}; {}; {}".format( config.project_name, config.service_name, config.stack_name, )) if len(result.failed_monitored_rules) > 0 or len(result.warnings) > 0: log_results( "Failed monitored rules", config.project_name, config.service_name, config.stack_name, result.failed_monitored_rules, result.warnings, event["stack_template_url"], ) return { "valid": str(result.valid).lower(), "reason": ",".join([ "{}-{}".format(r["rule"], r["reason"]) for r in result.failed_rules ]), "failed_rules": result.failed_rules, "exceptions": [x.args[0] for x in result.exceptions], "warnings": result.failed_monitored_rules, }