diff --git a/.gitignore b/.gitignore index 9630f11..9ec683f 100644 --- a/.gitignore +++ b/.gitignore @@ -126,5 +126,6 @@ CLOUD_TRAIL_ENCRYPTION_ENABLED/ API_GW_NOT_EDGE_OPTIMISED/ manageTest/ +myguardrule/ football/ diff --git a/README.md b/README.md index 1add22b..acd9196 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,7 @@ rdk init --generate-lambda-layer --custom-layer-name ## Create Rules In your working directory, use the `create` command to start creating a -new custom rule. You must specify the runtime for the lambda function +new custom rule. You must specify the runtime for the lambda function (or CfnGuard rule) that will back the Rule, and you can also specify a resource type (or comma-separated list of types) that the Rule will evaluate or a maximum frequency for a periodic rule. This will add a new directory for the @@ -144,6 +144,10 @@ maximum-frequency, but not both. We have found that rules that try to be both event-triggered as well as periodic wind up being very complicated and so we do not recommend it as a best practice. +As of RDK v0.18.0, you can also specify a runtime of `guard-2.x.x` to generate the files for deploying a CfnGuard Custom Policy Config rule. + +CfnGuard rules are only event-triggered; they **cannot** be run periodically. + ### Edit Rules Locally Once you have created the rule, edit the python file in your rule diff --git a/pyproject.toml b/pyproject.toml index 95221fb..743f9ed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,8 @@ # or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. [tool.poetry] name = "rdk" -version = "0.17.14" +version = "0.18.0" + description = "Rule Development Kit CLI for AWS Config" authors = [ "AWS RDK Maintainers ", diff --git a/rdk/__init__.py b/rdk/__init__.py index 422e4dc..16ca799 100644 --- a/rdk/__init__.py +++ b/rdk/__init__.py @@ -6,4 +6,5 @@ # # or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -MY_VERSION = "0.17.14" +MY_VERSION = "0.18.0" + diff --git a/rdk/rdk.py b/rdk/rdk.py index 35c0675..66cfd16 100644 --- a/rdk/rdk.py +++ b/rdk/rdk.py @@ -35,6 +35,9 @@ import botocore from botocore.exceptions import ClientError, EndpointConnectionError +# logging +logging.basicConfig(level=logging.INFO) + # sphinx-argparse is a delight. try: from rdk import MY_VERSION @@ -262,6 +265,7 @@ def get_rule_parser(is_required, command): required=False, help="Runtime for lambda function", choices=[ + "guard-2.x.x", "java8", "python3.7", "python3.7-lib", @@ -1238,6 +1242,7 @@ def create(self): "python3.11-lib": ".py", "python3.12": ".py", "python3.12-lib": ".py", + "guard-2.x.x": ".guard", } if self.args.runtime not in extension_mapping: print("rdk does not support that runtime yet.") @@ -1259,6 +1264,11 @@ def create(self): # copy rule template into rule directory if self.args.runtime == "java8": self.__create_java_rule() + if self.args.runtime == "guard-2.x.x": + response = self.__create_guard_rule() + if response != 0: + logging.info("Error encountered creating CfnGuard Rule.") + return 1 else: src = os.path.join( path.dirname(__file__), @@ -1371,6 +1381,10 @@ def modify(self): # Get existing parameters old_params, tags = self.__get_rule_parameters(self.args.rulename) + if re.match(r"guard", old_params["SourceRuntime"]): + print("CfnGuard rules are not yet supported for 'modify' command.") + return + if not self.args.custom_lambda_name and "CustomLambdaName" in old_params: self.args.custom_lambda_name = old_params["CustomLambdaName"] @@ -1768,6 +1782,7 @@ def deploy(self): "ParameterKey": "EvaluationMode", "ParameterValue": rule_params.get("EvaluationMode", "DETECTIVE"), }, + # TODO - Add any additional CfnGuard-specific keys ] my_cfn = my_session.client("cloudformation") if "Remediation" in rule_params: @@ -1947,185 +1962,220 @@ def deploy(self): continue - print(f"[{my_session.region_name}]: Found Custom Rule.") - - s3_src = "" - s3_dst = self.__upload_function_code(rule_name, rule_params, account_id, my_session, code_bucket_name) - - # create CFN Parameters for Custom Rules - lambdaRoleArn = "" - if self.args.lambda_role_arn: - print(f"[{my_session.region_name}]: Existing IAM Role provided: " + self.args.lambda_role_arn) - lambdaRoleArn = self.args.lambda_role_arn - elif self.args.lambda_role_name: - print(f"[{my_session.region_name}]: Building IAM Role ARN from Name: " + self.args.lambda_role_name) - arn = f"arn:{partition}:iam::{account_id}:role/{self.args.lambda_role_name}" - lambdaRoleArn = arn + # CfnGuard rule logic + elif re.match(r"guard", rule_params["SourceRuntime"]): + logging.info(f"[{my_session.region_name}]: Found CfnGuard Rule: " + rule_name) + try: + rule_description = rule_params["Description"] + except KeyError: + rule_description = rule_name - if self.args.boundary_policy_arn: - print(f"[{my_session.region_name}]: Boundary Policy provided: " + self.args.boundary_policy_arn) - boundaryPolicyArn = self.args.boundary_policy_arn + my_params = [ + { + "ParameterKey": "RuleName", + "ParameterValue": rule_name, + }, + { + "ParameterKey": "Description", + "ParameterValue": rule_description, + }, + { + "ParameterKey": "SourceEvents", + "ParameterValue": source_events, + }, + # { + # "ParameterKey": "SourceInputParameters", + # "ParameterValue": json.dumps(combined_input_parameters), + # }, + { + "ParameterKey": "EvaluationMode", + "ParameterValue": rule_params.get("EvaluationMode", "DETECTIVE"), + }, + { + "ParameterKey": "PolicyText", + "ParameterValue": open(os.path.join(os.getcwd(), rule_name, f"{rule_name}.guard"), "r").read(), + }, + ] + cfn_body = os.path.join(path.dirname(__file__), "template", "configCfnGuardRule.yaml") + template_body = open(cfn_body, "r").read() + yaml_body = yaml.safe_load(template_body) else: - boundaryPolicyArn = "" - - try: - rule_description = rule_params["Description"] - except KeyError: - rule_description = rule_name - - my_params = [ - { - "ParameterKey": "RuleName", - "ParameterValue": rule_name, - }, - { - "ParameterKey": "RuleLambdaName", - "ParameterValue": self.__get_lambda_name(rule_name, rule_params), - }, - { - "ParameterKey": "Description", - "ParameterValue": rule_description, - }, - { - "ParameterKey": "LambdaRoleArn", - "ParameterValue": lambdaRoleArn, - }, - { - "ParameterKey": "BoundaryPolicyArn", - "ParameterValue": boundaryPolicyArn, - }, - { - "ParameterKey": "SourceBucket", - "ParameterValue": code_bucket_name, - }, - # { - # "ParameterKey": "SourcePath", - # "ParameterValue": s3_dst, - # }, - { - "ParameterKey": "SourceRuntime", - "ParameterValue": self.__get_runtime_string(rule_params), - }, - { - "ParameterKey": "SourceEvents", - "ParameterValue": source_events, - }, - { - "ParameterKey": "SourcePeriodic", - "ParameterValue": source_periodic, - }, - { - "ParameterKey": "SourceInputParameters", - "ParameterValue": json.dumps(combined_input_parameters), - }, - { - "ParameterKey": "SourceHandler", - "ParameterValue": self.__get_handler(rule_name, rule_params), - }, - { - "ParameterKey": "Timeout", - "ParameterValue": str(self.args.lambda_timeout), - }, - { - "ParameterKey": "EvaluationMode", - "ParameterValue": rule_params.get("EvaluationMode", "DETECTIVE"), - }, - ] - layers = self.__get_lambda_layers(my_session, self.args, rule_params) - - if self.args.lambda_layers: - additional_layers = self.args.lambda_layers.split(",") - layers.extend(additional_layers) + print(f"[{my_session.region_name}]: Found Custom Rule.") + + s3_src = "" + s3_dst = self.__upload_function_code(rule_name, rule_params, account_id, my_session, code_bucket_name) + + # create CFN Parameters for Custom Rules + lambdaRoleArn = "" + if self.args.lambda_role_arn: + print(f"[{my_session.region_name}]: Existing IAM Role provided: " + self.args.lambda_role_arn) + lambdaRoleArn = self.args.lambda_role_arn + elif self.args.lambda_role_name: + print(f"[{my_session.region_name}]: Building IAM Role ARN from Name: " + self.args.lambda_role_name) + arn = f"arn:{partition}:iam::{account_id}:role/{self.args.lambda_role_name}" + lambdaRoleArn = arn + + if self.args.boundary_policy_arn: + print(f"[{my_session.region_name}]: Boundary Policy provided: " + self.args.boundary_policy_arn) + boundaryPolicyArn = self.args.boundary_policy_arn + else: + boundaryPolicyArn = "" - if layers: - my_params.append({"ParameterKey": "Layers", "ParameterValue": ",".join(layers)}) + try: + rule_description = rule_params["Description"] + except KeyError: + rule_description = rule_name - if self.args.lambda_security_groups and self.args.lambda_subnets: - my_params.append( + my_params = [ { - "ParameterKey": "SecurityGroupIds", - "ParameterValue": self.args.lambda_security_groups, - } - ) - my_params.append( + "ParameterKey": "RuleName", + "ParameterValue": rule_name, + }, { - "ParameterKey": "SubnetIds", - "ParameterValue": self.args.lambda_subnets, - } - ) - - # create json of CFN template - cfn_body = os.path.join(path.dirname(__file__), "template", "configRule.yaml") - template_body = open(cfn_body, "r").read() - yaml_body = yaml.safe_load(template_body) + "ParameterKey": "RuleLambdaName", + "ParameterValue": self.__get_lambda_name(rule_name, rule_params), + }, + { + "ParameterKey": "Description", + "ParameterValue": rule_description, + }, + { + "ParameterKey": "LambdaRoleArn", + "ParameterValue": lambdaRoleArn, + }, + { + "ParameterKey": "BoundaryPolicyArn", + "ParameterValue": boundaryPolicyArn, + }, + { + "ParameterKey": "SourceBucket", + "ParameterValue": code_bucket_name, + }, + # { + # "ParameterKey": "SourcePath", + # "ParameterValue": s3_dst, + # }, + { + "ParameterKey": "SourceRuntime", + "ParameterValue": self.__get_runtime_string(rule_params), + }, + { + "ParameterKey": "SourceEvents", + "ParameterValue": source_events, + }, + { + "ParameterKey": "SourcePeriodic", + "ParameterValue": source_periodic, + }, + { + "ParameterKey": "SourceInputParameters", + "ParameterValue": json.dumps(combined_input_parameters), + }, + { + "ParameterKey": "SourceHandler", + "ParameterValue": self.__get_handler(rule_name, rule_params), + }, + { + "ParameterKey": "Timeout", + "ParameterValue": str(self.args.lambda_timeout), + }, + { + "ParameterKey": "EvaluationMode", + "ParameterValue": rule_params.get("EvaluationMode", "DETECTIVE"), + }, + ] + layers = self.__get_lambda_layers(my_session, self.args, rule_params) - remediation = "" - if "Remediation" in rule_params: - remediation = self.__create_remediation_cloudformation_block(rule_params["Remediation"]) - yaml_body["Resources"]["Remediation"] = remediation + if self.args.lambda_layers: + additional_layers = self.args.lambda_layers.split(",") + layers.extend(additional_layers) - if "SSMAutomation" in rule_params: - ##AWS needs to build the SSM before the Config Rule - resource_depends_on = [ - "rdkConfigRule", - self.__get_alphanumeric_rule_name(rule_name + "RemediationAction"), - ] - remediation["DependsOn"] = resource_depends_on - # Add JSON Reference to SSM Document { "Ref" : "MyEC2Instance" } - remediation["Properties"]["TargetId"] = { - "Ref": self.__get_alphanumeric_rule_name(rule_name + "RemediationAction") - } + if layers: + my_params.append({"ParameterKey": "Layers", "ParameterValue": ",".join(layers)}) - if "SSMAutomation" in rule_params: - print(f"[{my_session.region_name}]: Building SSM Automation Section") - - ssm_automation = self.__create_automation_cloudformation_block(rule_params["SSMAutomation"], rule_name) - yaml_body["Resources"][ - self.__get_alphanumeric_rule_name(rule_name + "RemediationAction") - ] = ssm_automation - if "IAM" in rule_params["SSMAutomation"]: - print("Lets Build IAM Role and Policy") - # TODO Check For IAM Settings - yaml_body["Resources"]["Remediation"]["Properties"]["Parameters"]["AutomationAssumeRole"][ - "StaticValue" - ]["Values"] = [ + if self.args.lambda_security_groups and self.args.lambda_subnets: + my_params.append( { - "Fn::GetAtt": [ - self.__get_alphanumeric_rule_name(rule_name + "Role"), - "Arn", - ] + "ParameterKey": "SecurityGroupIds", + "ParameterValue": self.args.lambda_security_groups, + } + ) + my_params.append( + { + "ParameterKey": "SubnetIds", + "ParameterValue": self.args.lambda_subnets, } - ] + ) - ( - ssm_iam_role, - ssm_iam_policy, - ) = self.__create_automation_iam_cloudformation_block(rule_params["SSMAutomation"], rule_name) - yaml_body["Resources"][self.__get_alphanumeric_rule_name(rule_name + "Role")] = ssm_iam_role - yaml_body["Resources"][self.__get_alphanumeric_rule_name(rule_name + "Policy")] = ssm_iam_policy + # create json of CFN template + cfn_body = os.path.join(path.dirname(__file__), "template", "configRule.yaml") + template_body = open(cfn_body, "r").read() + yaml_body = yaml.safe_load(template_body) - # debugging - # print(json.dumps(json_body, indent=2)) + remediation = "" + if "Remediation" in rule_params: + remediation = self.__create_remediation_cloudformation_block(rule_params["Remediation"]) + yaml_body["Resources"]["Remediation"] = remediation + + if "SSMAutomation" in rule_params: + ##AWS needs to build the SSM before the Config Rule + resource_depends_on = [ + "rdkConfigRule", + self.__get_alphanumeric_rule_name(rule_name + "RemediationAction"), + ] + remediation["DependsOn"] = resource_depends_on + # Add JSON Reference to SSM Document { "Ref" : "MyEC2Instance" } + remediation["Properties"]["TargetId"] = { + "Ref": self.__get_alphanumeric_rule_name(rule_name + "RemediationAction") + } + + if "SSMAutomation" in rule_params: + print(f"[{my_session.region_name}]: Building SSM Automation Section") + + ssm_automation = self.__create_automation_cloudformation_block( + rule_params["SSMAutomation"], rule_name + ) + yaml_body["Resources"][ + self.__get_alphanumeric_rule_name(rule_name + "RemediationAction") + ] = ssm_automation + if "IAM" in rule_params["SSMAutomation"]: + print("Lets Build IAM Role and Policy") + # TODO Check For IAM Settings + yaml_body["Resources"]["Remediation"]["Properties"]["Parameters"]["AutomationAssumeRole"][ + "StaticValue" + ]["Values"] = [ + { + "Fn::GetAtt": [ + self.__get_alphanumeric_rule_name(rule_name + "Role"), + "Arn", + ] + } + ] + + ( + ssm_iam_role, + ssm_iam_policy, + ) = self.__create_automation_iam_cloudformation_block(rule_params["SSMAutomation"], rule_name) + yaml_body["Resources"][self.__get_alphanumeric_rule_name(rule_name + "Role")] = ssm_iam_role + yaml_body["Resources"][self.__get_alphanumeric_rule_name(rule_name + "Policy")] = ssm_iam_policy # deploy config rule my_cfn = my_session.client("cloudformation") + my_stack_name = self.__get_stack_name_from_rule_name(rule_name) + cfn_args = { + "StackName": my_stack_name, + "TemplateBody": json.dumps(yaml_body, indent=2), + "Parameters": my_params, + "Capabilities": ["CAPABILITY_IAM", "CAPABILITY_NAMED_IAM"], + } + # If no tags key is specified, or if the tags dict is empty + if cfn_tags is not None: + cfn_args["Tags"] = cfn_tags try: - my_stack_name = self.__get_stack_name_from_rule_name(rule_name) my_stack = my_cfn.describe_stacks(StackName=my_stack_name) # If we've gotten here, stack exists and we should update it. print(f"[{my_session.region_name}]: Updating CloudFormation Stack for " + rule_name) try: - cfn_args = { - "StackName": my_stack_name, - "TemplateBody": json.dumps(yaml_body, indent=2), - "Parameters": my_params, - "Capabilities": ["CAPABILITY_IAM", "CAPABILITY_NAMED_IAM"], - } - - # If no tags key is specified, or if the tags dict is empty - if cfn_tags is not None: - cfn_args["Tags"] = cfn_tags - response = my_cfn.update_stack(**cfn_args) except ClientError as e: if e.response["Error"]["Code"] == "ValidationError": @@ -2155,16 +2205,7 @@ def deploy(self): except ClientError as e: # If we're in the exception, the stack does not exist and we should create it. print(f"[{my_session.region_name}]: Creating CloudFormation Stack for " + rule_name) - cfn_args = { - "StackName": my_stack_name, - "TemplateBody": json.dumps(yaml_body, indent=2), - "Parameters": my_params, - "Capabilities": ["CAPABILITY_IAM", "CAPABILITY_NAMED_IAM"], - } - - if cfn_tags is not None: - cfn_args["Tags"] = cfn_tags - + logging.info(f"Creating stack with parameters {my_params}") response = my_cfn.create_stack(**cfn_args) # wait for changes to propagate. @@ -2292,6 +2333,7 @@ def deploy_organization(self): "ParameterKey": "ExcludedAccounts", "ParameterValue": combined_excluded_accounts_str, }, + # TODO - Add CfnGuard properties ] my_cfn = my_session.client("cloudformation") @@ -2299,7 +2341,7 @@ def deploy_organization(self): cfn_body = os.path.join( path.dirname(__file__), "template", - "configManagedRuleOrganization.yaml", + "configManagedRuleOrganization.yaml", # TODO - option for CfnGuard ) try: @@ -2662,20 +2704,7 @@ def test_local(self): for rule_name in rule_names: rule_params, rule_tags = self.__get_rule_parameters(rule_name) - if rule_params["SourceRuntime"] not in ( - "python3.7", - "python3.7-lib", - "python3.8", - "python3.8-lib", - "python3.9", - "python3.9-lib", - "python3.10", - "python3.10-lib", - "python3.11", - "python3.11-lib", - "python3.12", - "python3.12-lib", - ): + if not re.match(r"python", rule_params["SourceRuntime"]): print("Skipping " + rule_name + " - Runtime not supported for local testing.") continue @@ -3261,6 +3290,18 @@ def __create_java_rule(self): dst = os.path.join(os.getcwd(), rules_dir, self.args.rulename, "build.gradle") shutil.copyfile(src, dst) + def __create_guard_rule(self): + runtime = "guard-2.x.x" # TODO - make dynamic + file_name = "rule_code.guard" + src = os.path.join(path.dirname(__file__), "template", "runtime", runtime, file_name) + dst = os.path.join(os.getcwd(), rules_dir, self.args.rulename, f"{self.args.rulename}.guard") + try: + shutil.copy(src, dst) + return 0 + except FileExistsError: + print("Rule " + self.args.rulename + " already exists. Exiting.") + return 1 + def __print_log_event(self, event): time_string = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(event["timestamp"] / 1000)) @@ -3459,6 +3500,7 @@ def __get_rule_parameters(self, rule_name): return my_json["Parameters"], my_tags def __parse_rule_args(self, is_required): + # TODO - Validate CfnGuard args self.args = get_rule_parser(is_required, self.args.command).parse_args(self.args.command_args, self.args) max_resource_types = 100 @@ -3532,9 +3574,11 @@ def __parse_test_args(self): return self.args def __parse_deploy_args(self, ForceArgument=False): + self.args = get_deployment_parser(ForceArgument).parse_args(self.args.command_args, self.args) # Validate inputs # + # TODO - Validate that CfnGuard rule inputs are sane if bool(self.args.lambda_security_groups) != bool(self.args.lambda_subnets): print("You must specify both lambda-security-groups and lambda-subnets, or neither.") sys.exit(1) @@ -3584,6 +3628,7 @@ def __parse_deploy_organization_args(self, ForceArgument=False): self.args = get_deployment_organization_parser(ForceArgument).parse_args(self.args.command_args, self.args) # Validate inputs # + # TODO - Validate sane inputs for CfnGuard rules if bool(self.args.lambda_security_groups) != bool(self.args.lambda_subnets): print("You must specify both lambda-security-groups and lambda-subnets, or neither.") sys.exit(1) @@ -3690,6 +3735,9 @@ def __package_function_code(self, rule_name, params): return s3_dst def __populate_params(self): + + # TODO - Ensure that parameters.json is populated appropriately for CfnGuard rules + # create custom session based on whatever credentials are available to us my_session = self.__get_boto_session() @@ -3896,33 +3944,15 @@ def __wait_for_cfn_stack(self, cfn_client, stackname): def __get_handler(self, rule_name, params): if "SourceHandler" in params: return params["SourceHandler"] - if params["SourceRuntime"] in [ - "python3.7", - "python3.7-lib", - "python3.8", - "python3.8-lib", - "python3.9", - "python3.9-lib", - "python3.10", - "python3.10-lib", - "python3.11", - "python3.11-lib", - "python3.12", - "python3.12-lib", - ]: + if re.match(r"python", params["SourceRuntime"]): return rule_name + ".lambda_handler" + elif re.match(r"guard", params["SourceRuntime"]): + return "N/A" elif params["SourceRuntime"] in ["java8"]: return "com.rdk.RuleUtil::handler" def __get_runtime_string(self, params): - if params["SourceRuntime"] in [ - "python3.7-lib", - "python3.8-lib", - "python3.9-lib", - "python3.10-lib", - "python3.11-lib", - "python3.12-lib", - ]: + if re.match(r"python", params["SourceRuntime"]): runtime = params["SourceRuntime"].split("-") return runtime[0] @@ -4309,14 +4339,7 @@ def __tag_config_rule(self, rule_name, cfn_tags, my_session): def __get_lambda_layers(self, my_session, args, params): layers = [] if "SourceRuntime" in params: - if params["SourceRuntime"] in [ - "python3.7-lib", - "python3.8-lib", - "python3.9-lib", - "python3.10-lib", - "python3.11-lib", - "python3.12-lib", - ]: + if re.match(r"python", params["SourceRuntime"]): if hasattr(args, "generated_lambda_layer") and args.generated_lambda_layer: lambda_layer_version = self.__get_existing_lambda_layer( my_session, layer_name=args.custom_layer_name diff --git a/rdk/template/configCfnGuardRule.yaml b/rdk/template/configCfnGuardRule.yaml new file mode 100644 index 0000000..fcb6cec --- /dev/null +++ b/rdk/template/configCfnGuardRule.yaml @@ -0,0 +1,73 @@ +AWSTemplateFormatVersion: "2010-09-09" +Description: AWS CloudFormation template to create custom AWS Config Custom Policy rules (CfnGuard Rules). +Parameters: + RuleName: + Description: Name of the Rule + Type: String + MinLength: "1" + MaxLength: "128" + Description: + Description: Description of the Rule + Type: String + MinLength: "1" + MaxLength: "255" + PolicyText: + Description: The policy definition, written as a CfnGuard rule + Type: String + MinLength: "1" + SourceEvents: + Description: Event Type + Type: CommaDelimitedList + DebugLogging: + Description: Whether to enable Debug Logging + Type: String + Default: "false" + AllowedValues: + - "true" + - "false" + EvaluationMode: + Description: The evaluation mode to use, either DETECTIVE, PROACTIVE, or BOTH. + Type: String + Default: DETECTIVE + AllowedValues: + - DETECTIVE + - PROACTIVE + - BOTH +Conditions: + UseBothEvaluationModes: + Fn::Equals: + - Ref: EvaluationMode + - "BOTH" +Resources: + rdkConfigRule: + Type: AWS::Config::ConfigRule + Properties: + ConfigRuleName: + Ref: RuleName + Description: + Ref: Description + Scope: + ComplianceResourceTypes: + Ref: SourceEvents # TODO - Confirm this expands to a list + EvaluationModes: + Fn::If: + - UseBothEvaluationModes + - + - Mode: DETECTIVE + - Mode: PROACTIVE + - + - Mode: + Ref: EvaluationMode + Source: + Owner: CUSTOM_POLICY + CustomPolicyDetails: + EnableDebugLogDelivery: + Ref: DebugLogging + PolicyRuntime: "guard-2.x.x" + PolicyText: + Ref: PolicyText + SourceDetails: + - EventSource: aws.config + MessageType: ConfigurationItemChangeNotification + - EventSource: aws.config + MessageType: OversizedConfigurationItemChangeNotification \ No newline at end of file diff --git a/rdk/template/configCfnGuardRuleOrganization.yaml b/rdk/template/configCfnGuardRuleOrganization.yaml new file mode 100644 index 0000000..6751365 --- /dev/null +++ b/rdk/template/configCfnGuardRuleOrganization.yaml @@ -0,0 +1,56 @@ +AWSTemplateFormatVersion: "2010-09-09" +Description: AWS CloudFormation template to create custom AWS Config Custom Policy rules (CfnGuard Rules). +Parameters: + RuleName: + Description: Name of the Rule + Type: String + MinLength: "1" + MaxLength: "128" + Description: + Description: Description of the Rule + Type: String + MinLength: "1" + MaxLength: "255" + PolicyText: + Description: The policy definition, written as a CfnGuard rule + Type: String + MinLength: "1" + SourceEvents: + Description: Event Type + Type: CommaDelimitedList + ExcludedAccounts: + Description: A comma-separated list of account IDs to exclude from the rule + Type: CommaDelimitedList + Default: "" +Conditions: + ExcludedAccountsPresent: + Fn::Not: + - Fn::Equals: + - Fn::Join: + - "," + - Ref: ExcludedAccounts + - "" +Resources: + rdkConfigRule: + Type: AWS::Config::OrganizationConfigRule + Properties: + OrganizationConfigRuleName: + Ref: RuleName + ExcludedAccounts: + Fn::If: + - ExcludedAccountsPresent + - Ref: ExcludedAccounts + - Ref: AWS::NoValue + OrganizationCustomPolicyRuleMetadata: + Description: + Ref: Description + PolicyText: + Ref: PolicyText + ResourceTypesScope: + Ref: SourceEvents # TODO - Confirm this expands to a list + OrganizationConfigRuleTriggerTypes: + - ConfigurationItemChangeNotification + - OversizedConfigurationItemChangeNotification + Runtime: "guard-2.x.x" + # DebugLogDeliveryAccounts: + # - TODO \ No newline at end of file diff --git a/rdk/template/runtime/guard-2.x.x/rule_code.guard b/rdk/template/runtime/guard-2.x.x/rule_code.guard new file mode 100644 index 0000000..585380d --- /dev/null +++ b/rdk/template/runtime/guard-2.x.x/rule_code.guard @@ -0,0 +1,16 @@ +# # This is an example rule -- for full documentation, see https://docs.aws.amazon.com/cfn-guard/latest/ug/query-and-filtering.html + +# # Set resources variable +# let resources = Resources.*[ Type == 'AWS::S3::Bucket' ] + +# # Check S3 Bucket has required tags +# rule check_tags when %resources !empty { +# %resources.Properties.Tags exists +# #For each resource in resources +# %resources { +# #Check a tag has a key of "billingcode" +# some Properties.Tags[*].key == "billingcode" +# #Check a tag has a key of "env" and value of "dev" +# some Properties.Tags[*] { Key == "env" Value == "dev"} +# } +# }