0

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?

user20358
  • 14,182
  • 36
  • 114
  • 186

2 Answers2

1

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.

fedonev
  • 20,327
  • 2
  • 25
  • 34
  • Thanks. I am new to python, so this might be a basic question. How does the main file which holds the aws_config_stack know of the individual files which contain the rules enclosed in a function? Is there a file include involved somewhere? Would that mean I would have to have 100s of includes for each function enclosed in its own file? – user20358 Oct 29 '22 at 18:20
  • 1
    @user20358 Yes, you need to [import](https://docs.python.org/3/reference/import.html) the rules to give the stack access to them. There are lots of approaches, as a SO search for [python import all](https://stackoverflow.com/search?q=python+import+all) reveals. – fedonev Oct 30 '22 at 12:17
  • Thanks. In the `make_rule` method once I return a particular set of rules in `rule_dict`. How do I execute a rule in the dict? Not looking at iterating thru a loop, but the part how the code within the dict is executed to create a rule in config – user20358 Oct 31 '22 at 20:55
  • 1
    @user20358 The `make_rule` method adds a single rule (by name) to the stack. It is calling the `make_x_rule` functions that are in the dict (see the `return` line). The next block's [list comprehension](https://stackoverflow.com/questions/34835951/what-does-list-comprehension-and-similar-mean-how-does-it-work-and-how-can-i) syntax calls `make_rule` once for each rule name in a list, adding rules to the stack. Note: I edited the answer to add explanations and to fix an error in `make_rule`'s signature. – fedonev Nov 01 '22 at 11:10
0

You can use a config file strategy instead if you would prefer. Similar to the answer by @fedonev, you can create a json file that holds the information for a particular set of rules. Then you can add conditionals before and a loop around loading the json file. Then you can use a trick of python and the fact that every one of these properties is a kwarg, and kwargs are dictionary.

Something like:

json_file:

{   
    "AccessKeysRotated": {
           "identifier": "ACCESS_KEYS_ROTATED",
           "input_parameters": {
                "max_access_key_age": 60
           },
           "maximum_execution_frequency": "TWELVE_HOURS"
     }
}

and then you load this into a python dict using json.loads.

you'll need to update the Enum values to the same classes, luckily the names can be used for that:

if "identifier" in your_config.keys():
    your_config["identifier"] = config.ManagedRuleIdentifiers(your_config["identifier"])

(that can be done a lot more elegantly, but you mentioned in a comment your new to python so that is fine for now)

and finally, the trick: Because every property on a CDK object is a kwarg (keyword argument) and kwargs are stored as a dictionary behind the scenes, and have default values (thats what defines them as kwargs) you can - even with different keys - use:

## load json files that you need into a dict of dict objects
## make sure the enum values are updated from strings to enums

for key, config_setting in my_configs.items():
    config.ManagedRule(self, key, **config_setting)

the key being the logical id of the object (so the "AccessKeysRotated" in your json file) and the ** syntax merging the dictionary of kwargs with the defaults. Because it merges your input second (its basically kwargs = {**kwargs, **config_setting} your values override any defaults in the kwarg dict and boom. done.

Some tweaking will be needed undoubtedly, but this is the basic idea.

lynkfox
  • 2,003
  • 1
  • 8
  • 16
  • Thanks. Where can I find the json files for each of the managed rules? – user20358 Oct 31 '22 at 23:18
  • also, when calling `config.ManagedRule(,,)` The parameter you are suggesting is a json object of the rule created from the json file right? That is not accepted. – user20358 Nov 01 '22 at 00:19
  • They would have to be something you create yourself. They do not exist normally, but would be a solution you could craft on your own if you so wished. – lynkfox Jan 04 '23 at 14:42