Search code examples
pythonaws-cdkaws-config

Conditionally deploying AWS Config rules using cdk python


I have a requirement to deploy config rules conditionally based on certain parameters. Here below is one

config.ManagedRule(self, "AccessKeysRotated",
    identifier=config.ManagedRuleIdentifiers.ACCESS_KEYS_ROTATED,
    input_parameters={
        "max_access_key_age": 60
    },
    maximum_execution_frequency=config.MaximumExecutionFrequency.TWELVE_HOURS
)

Here below is another one

config.ManagedRule(self, "S3BucketLogging",
    identifier=config.ManagedRuleIdentifiers.S3_BUCKET_LOGGING_ENABLED,
    config_rule_name="S3BucketLogging"
) 

The managed rule identifiers are in the hundreds. I don't want to have all of these in one big file but each rule stored in a separate file. I can then read off a dynamodb where I store the account name and a csv list of rules that pertain to that account. Each item in the csv can be a single file which has a single rule. Is there a way to do this?


Solution

  • Sure. Create a aws_config_stack, which you deploy once per account/region pair.

    In the constructor, get the environment's rule names from DynamoDB with a get_item SDK call. The boto3 command gets called at synth-time, which is keeping with CDK best practices.

    Define the rules in whatever files you want. Wrap each rule in a function that accepts a scope and returns the rule:

    # make_access_keys_rotated_rule.py
    # One function per ManagedRule.  Add to the rule dictionary.  Called by `make_rule`
    def make_access_keys_rotated_rule(scope: Construct) -> config.ManagedRule:
        return config.ManagedRule(scope, "AccessKeysRotated",
            identifier=config.ManagedRuleIdentifiers.ACCESS_KEYS_ROTATED,
            input_parameters={
                "max_access_key_age": 60
            },
            maximum_execution_frequency=config.MaximumExecutionFrequency.TWELVE_HOURS
        )
    

    Add each rule function to a dictionary, where the keys are the rule name. Perhaps add the dictionary lookup logic to a method in your aws_config_stack subclass. A make_rule method looks up the rule-making function by name and executes it, adding a single rule to the stack.

    # aws_config_stack.py method
    # Look up and execute a ManagedRule function by rule name.  Called in the constructor.
    def make_rule(self: Construct, rule_name: str) -> config.ManagedRule:
        rule_dict = {
            "AccessKeysRotated": make_access_keys_rotated_rule
        }
    
        return rule_dict[rule_name](self)
    

    Finally, in the stack constructor, call make_rule for every name in the rule-name list from DynamoDB.

    # aws_config_stack.py constructor
    rules: list[config.ManagedRule] = [self.make_rule(r) for r in rule_names_from_dynamo]
    

    After synth, a cdk diff should reveal rules being added and deleted from the stack to match the list from DynamoDB.

    P.S. Optionally add the Delivery Channel (CfnDeliveryChannel + Bucket) resources and Configuration Recorder (CfnConfigurationRecorder + Role) resources to the same stack to have the CDK fully manage the AWS Config resources.