Skip to content

Commit

Permalink
updated documentations
Browse files Browse the repository at this point in the history
Signed-off-by: Nima Fotouhi <[email protected]>
  • Loading branch information
nimaft committed Nov 17, 2023
1 parent 2b509f2 commit d115d68
Show file tree
Hide file tree
Showing 12 changed files with 579 additions and 1 deletion.
79 changes: 79 additions & 0 deletions docs/Creating and editing rules/Creating rules.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Creating RDK rules

Below, we are going to explain metadata fields and how you can set them using RDK.
With RDK installed, all you need to do to create a new custom rule is to run the rdk create command with the required parameters. Answering the following questions will help you to provide the right arguments to rdk create:

- What is the resource type(s) you want to evaluate?
- What should trigger the rule execution (e.g., configuration changes, periodic, hybrid)?
- What are the parameters that should be passed to the Lambda function?

## Creating rules with rdk create command

`rdk create` has many possible arguments, which include rule metadata arguments and remediation arguments (remediation arguments are covered in a later section in this guide).
You can see the full list of commands by running the command `rdk create --help`.
The following table includes AWS Config rule’s metadata fields and its corresponding `rdk create` options:

| Config Metadata Field | `rdk create` option |
| --------------------- | ------------------- |
|_identifier_: ID for an AWS managed rule (written in all caps with underscores)| the first argument after `rdk create` is the rule name of your choice: `rdk create <RULE_NAME>` |
| _defaultName_: the name that instances of a rule will get by default. | rdk uses rule identifier as the defaultName. |
| _description_: provides context for what the rule evaluates. | `rdk create` does not accept an option for rule description and uses rule name/identifier. This can be modified manually by updating the parameters.json file. |
| _scope_: resource types targeted by the rule. | use `-r, --resource-types` with `rdk create` to specify resource types that trigger the rule (single resource or a comma delimited list of resources). |
| _sourceDetails_: rule's trigger type when detective evaluation occurs: `ConfigurationItemChangeNotification`, `OversizedConfigurationItemChangeNotification`, and `ScheduleNotification`. | When creating a rule with rdk, you don’t explicitly select rule trigger type, rather it is set when you use `-r, --resource-types` and/or `-m, --maximum-frequency` options. More on this in the following sections. |
| _compulsoryInputParameterDetails_: rule’s required parameters. | use `-i, --input-parameters` with `rdk create` to specify required parameters. Accepts JSON format inputs, for example `"{\"desiredInstanceType\":\"t2.micro\"}"` |
| _optionalInputParameterDetails_: parameters that are optional for a rule to do its evaluation. | use `--optional-parameters` with `rdk create` to specify optional parameters. Accepts JSON format inputs. |
| _labels_: used to optionally tag rules. | use `--tags` with `rdk create` to specify rule tags. Accepts JSON format input. |
| _supportedEvaluationModes_: could be `DETECTIVE` or `PROACTIVE`. We only cover detective rules in this guide. | rdk doesn’t support setting evaluation mode and will default to `DETECTIVE`. |

The only argument required by `rdk create` is `<rulename>` which is a positional argument. In addition, one of either `-r , --resource-types` or `-m , --maximum-frequency` is required to indicate the type of rule to create. So, in its simplest form, you can create your first rule by running:
`rdk create <rule_name> -r <resource_type>`
By running the `rdk create` command, RDK creates a new directory with several files, including a skeleton of your Lambda code. RDK creates three files in a directory named after your rule:

- _<rule_name>.py_: skeleton of your Lambda code
- _<rule_name>_test.py_: unit test framework for your rule
- _parameters.json_: holds rule metadata

Table below lists parameters (rule metadata) included in parameters.json file for different type of rules and how they map to `rdk create` arguments.

| Parameter | Description | Rule type | rdk create argument |
| --------- | ----------- | --------- | ------------------- |
| RuleName | Rule name | All | positional argument |
| Description | Rule description | All | Same as rule name by default |
| SourceRuntime | Lambda function runtime | All | `--runtime` |
| CodeKey | Name of zip file uploaded by RDK | All | N/A |
| InputParameters | Rule input parameters (JSON format) | Optional for all | `--input-parameters` |
| OptionalParameters | Rule optional parameters (JSON format) | Optional for all | `--optional-parameters` |
| SourceEvents | Resource type(s) | Configuration change/Hybrid | `--resource-types` |
| SourcePeriodic | Evaluation frequency | Periodic/Hybrid | `--maximum-frequency` |

Once you have your rule created, you should write your Lambda code and incorporate your compliance evaluation logic. We will cover the Lambda function in the next section.

## Examples

### Creating a configuration change triggered rule to assess IAM roles’ compliance

Run `rdk create IAM_ROLE --runtime python3.11 --resource-types AWS::IAM::Role`. This command creates a folder named IAM_ROLE in your working directory containing rule files.

When you use `--resource-types or -r` options, you are implicitly setting your rule’s trigger type to configuration changes, so when you deploy this rule, you will see Oversized configuration changes and Configuration changes under Detective evaluation trigger type in your rule’s detail page on AWS Config console:

![configuration change triggered rule](../../images/config_change_triggered.jpeg)

### Creating a periodically triggered rule to assess IAM roles’ compliance

Run `rdk create IAM_ROLE --runtime python3.11 --maximum-frequency Six_Hours`. Using the `-m, or --maximum-frequency` option, implicitly sets your rule’s trigger type to periodic, so when you deploy this rule, you will see Periodic: 6 hours under Detective evaluation trigger type in your rule’s detail page on AWS Config console:

![periodically triggered rule](../../images/config_periodic.jpeg)

Note that _Scope of Changes_ is empty because this is a periodically triggered role.

### Creating a hybrid rule with input parameters to assess multiple resource types’ compliance

You can create rules that use EITHER `resource-types` OR `maximum-frequency` options, 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.

However, it is possible to create rules with hybrid trigger type, you just need to make sure that hybrid trigger type rule is absolutely required to meet your compliance criteria. [Here](https://github.com/awslabs/aws-config-rules/tree/master/python/IAM_USER_USED_LAST_90_DAYS) is an example of hybrid trigger type rule.

Run `rdk create IAM_ROLE --runtime python3.11 --maximum-frequency Six_Hours --resource-types AWS::IAM::Role,AWS::IAM::Policy --input-parameters "{\"WhitelistedRoleList\": \"\", \"NotUsedTimeOutInDays\": \"90\", \"NewRoleCooldownInDays\": \"7\"}"`. This command creates a role which is triggered by both configuration changes and periodically. It also takes three input parameters, where `NotUsedTimeOutInDays` and `NewRoleCooldownInDays` have default values of 90 and 7. You can specify `--optional-parameters` using the same format used here for `--input-parameters`. On Windows it is necessary to escape the double-quotes when specifying input parameters, otherwise you don’t need to escape them.

This rule is triggered every six hours, and every time there is a change in _AWS::IAM::Role_ or _AWS::IAM::Policy_ resource types. When you deploy this rule you will see _Oversized configuration changes, Periodic: 6 hours_ and _Configuration changes_ under _Detective evaluation trigger type_ in your rule’s detail page on AWS Config console, you should also see two different resource types under _Resource types_:

![Hybrid rule with input parameters](../../images/config_hybrid.jpeg)
3 changes: 3 additions & 0 deletions docs/Creating and editing rules/Modifying rules.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Modifying rules

You can modify your rules by either editing _parameters.json_ or by running `rdk modify` command which takes the same arguments and options as `rdk create` command. Note that modifying your rule locally does not modify the rule in your AWS Account and you need to redeploy the rule to apply the changes.
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Introduction

When an AWS Config rule is triggered, AWS Config invokes the rule’s Lambda function, passing the triggering event as an argument to the Lambda function. An AWS Lambda event is a JSON-formatted document containing data for the Lambda function to operate. Visit [Example Events for AWS Config Rules](https://docs.aws.amazon.com/config/latest/developerguide/evaluate-config_develop-rules_example-events.html) for more information.

## Lambda function’s logic

An AWS Config rule’s Lambda function includes a lambda_handler function that takes the event published by the AWS Config service, and assesses the associated resource’s compliance. You might notice _context_ as the second argument for the lambda_handler function, in newer versions of Lambda, this argument is no longer needed.

The lambda_handler function, performs two major tasks:

- Validating published event by AWS Config and evaluating resource(s) compliance
- Validating compliance results, and reporting back to AWS Config

### Validating published event by AWS Config and evaluate resource(s) compliance

![lambda_handler function algorithm](../../images/lambda_logic1.png)

Here are the highlights of what the lambda_handler function does:

- Invokes check_defined function to make sure the event is passed to lambda_handler function is not empty.
- Runs `invoking_event = json.loads(event["invokingEvent"])` to extract invoking event element of the event and stores is invoking_event variable.
- Checks to see if there are any rule parameters in the event, and stores them in rule_parameters variable.
- Invokes evaluate_parameters function to check input parameters are valid. If you have input parameters, you need to modify evaluate_parameters to do input sanitization and validation.
- If the input parameters are valid, it checks rule’s trigger type by extracting `invoking_event["messageType"]` and comparing it against one of the possible trigger types:
- `"ConfigurationItemChangeNotification"`
- `"ScheduledNotification"`
- `"OversizedConfigurationItemChangeNotification"`
- If the rule trigger type is valid, it runs the get_configuration_item function:
- A configuration item represents a point-in-time view of the various attributes of a supported AWS resource that exists in your account. learn more here.
- For change-triggered rules it extracts the configuration item from invoking event
- For periodic rules it returns `None`
- For invalid message types (trigger types) it returns an _"Unexpected message type"_ error.
- Finally, it invokes the is_applicable function to discard events triggered by a deleted resource, for all other events, it calls the evaluate_compliance function to do compliance evaluation

### Validating compliance results, and reporting back to AWS Config

When you create your AWS Config rule files using `rdk create` command, your Lambda function file has an empty evaluate_compliance function which you need to populate with your compliance evaluation logic and return the compliance result (see the [Writing an evaluate_compliance function](Writing%20an%20evaluate_compliance%20function.md) for guidance on updating this function). Compliance results is expected to be a string, a dictionary or a list of dictionaries containing the following keys:

- `ComplianceResourceType`
- `ComplianceResourceId`
- `ComplianceType`
- `OrderingTimestamp`

The Lambda skeleton file includes following helper functions to create compliance result dictionaries:

- build_evaluation function, generally used for periodically triggered rules, returns a compliance result dictionary and accepts the following arguments:
- `resource_id`
- `compliance_type`: can be `COMPLIANT`, `NON_COMPLIANT`, or `NOT_APPLICABLE`
- event
- `resource_type=DEFAULT_RESOURCE_TYPE`, you can set `DEFAULT_RESOURCE_TYPE` variable (defaults to `'AWS::::Account'`) in the skeleton file and avoid passing it every time if your rule is scoped only to one type of resource
- `annotation=None` You can pass an annotation string providing more information about compliance status of the resource being assessed. Annotations are shown in the AWS Config console or response of AWS CLI or AWS SDK calls.
- build_evaluation_from_config_item function, generally used for configuration change triggered rules, returns a compliance result dictionary and accepts the following arguments:
- `configuration_item`: for configuration-change-based rules, the configuration item describing the resource that changed state.
- `compliance_type`: a string with the evaluated compliance status, one of `COMPLIANT`, `NON_COMPLIANT`, or `NOT_APPLICABLE`
- `annotation=None`: an optional string providing more information about compliance status of the resource being assessed. Annotations are shown on AWS Config console or response of AWS CLI or AWS SDK calls. These are particularly recommended for `NON_COMPLIANT` resources to explain why a resource is non-compliant.

The returned value from the evaluate_compliance function is stored in `compliance_result` variable.
![compliance evaluation function logic](../../images/compliance_evaluation.png)

Once the compliance results are returned, lambda_handler follows the below logic:

- If `compliance_result` variable is empty, the results are recorded as NOT_APPLICABLE.
- If `compliance_result` variable is list type with dictionary elements, lambda_handler checks to see if all the required keys are i- luded with each member, and upon successful verification it reports the results of evaluation.
- If `compliance_result` variable is dictionary type, lambda_handler checks to see if all the required keys are included, and upon successful verification it reports the results of evaluation.
- If `compliance_result` variable is string type, lambda_handler calls:
- build_evaluation_from_config_item function, if configuration_item exists and converts `compliance_result` to a dictionary and reports the results of evaluation.
- build_evaluation function if configuration_item does not exist and converts `compliance_result` to a dictionary and reports the results of evaluation.

The rest of the Lambda function, takes care of error handling, and should not be modified.
Loading

0 comments on commit d115d68

Please sign in to comment.