Skip to content

Commit

Permalink
feat(config): add custom policy rule constructs (aws#21794)
Browse files Browse the repository at this point in the history
feat(config) aws#21441

I have created a `new config.CustomPolicy` so that this functionality is available in L2 Constructs.

The resources that can currently be created with `AWS::Config::ConfigRule` can be created with `config.CustomRule` and `config.ManagedRule` in the CDK. This is because the restrictions on the various properties are different.
CustomPolicy has different constraints compared to CustomRule as follows.

- There is a restriction on the format that can be selected in `SourceDetails`.
    - [docs](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-config-configrule-source.html)
- Properties that refer to Lambda are unnecessary.
- `CustomPolicyDetails` must be specified.
    - [docs](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-config-configrule-source-sourcedetails.html)

To avoid this limitation and complexity, `CustomPolicy` can be separated, making it more convenient for users. It also reduces the dependence on each rule type for updates during maintenance.



----

### All Submissions:

* [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md)

### Adding new Unconventional Dependencies:

* [x] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/#adding-new-unconventional-dependencies)

### New Features

* [x] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)?
	* [x] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)?

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
watany-dev authored and mrgrain committed Oct 24, 2022
1 parent 86fe002 commit 4c65ce6
Show file tree
Hide file tree
Showing 23 changed files with 819 additions and 28 deletions.
56 changes: 54 additions & 2 deletions packages/@aws-cdk/aws-config/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,60 @@ new config.CloudFormationStackNotificationCheck(this, 'NotificationCheck', {
### Custom rules

You can develop custom rules and add them to AWS Config. You associate each custom rule with an
AWS Lambda function, which contains the logic that evaluates whether your AWS resources comply
with the rule.
AWS Lambda function and Guard.

#### Custom Lambda Rules

Lambda function which contains the logic that evaluates whether your AWS resources comply with the rule.

```ts
// Lambda function containing logic that evaluates compliance with the rule.
const evalComplianceFn = new lambda.Function(this, "CustomFunction", {
code: lambda.AssetCode.fromInline(
"exports.handler = (event) => console.log(event);"
),
handler: "index.handler",
runtime: lambda.Runtime.NODEJS_14_X,
});

// A custom rule that runs on configuration changes of EC2 instances
const customRule = new config.CustomRule(this, "Custom", {
configurationChanges: true,
lambdaFunction: evalComplianceFn,
ruleScope: config.RuleScope.fromResource(config.ResourceType.EC2_INSTANCE),
});
```

#### Custom Policy Rules

Guard which contains the logic that evaluates whether your AWS resources comply with the rule.

```ts
const samplePolicyText = `
# This rule checks if point in time recovery (PITR) is enabled on active Amazon DynamoDB tables
let status = ['ACTIVE']
rule tableisactive when
resourceType == "AWS::DynamoDB::Table" {
configuration.tableStatus == %status
}
rule checkcompliance when
resourceType == "AWS::DynamoDB::Table"
tableisactive {
let pitr = supplementaryConfiguration.ContinuousBackupsDescription.pointInTimeRecoveryDescription.pointInTimeRecoveryStatus
%pitr == "ENABLED"
}
`;

new config.CustomPolicy(stack, "Custom", {
policyText: samplePolicyText,
enableDebugLog: true,
ruleScope: config.RuleScope.fromResources([
config.ResourceType.DYNAMODB_TABLE,
]),
});
```

### Triggers

Expand Down
154 changes: 146 additions & 8 deletions packages/@aws-cdk/aws-config/lib/rule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,63 @@ export class ManagedRule extends RuleNew {
}
}

/**
* The source of the event, such as an AWS service,
* that triggers AWS Config to evaluate your AWS resources.
*/
enum EventSource {

/* from aws.config */
AWS_CONFIG = 'aws.config',

}

/**
* The type of notification that triggers AWS Config to run an evaluation for a rule.
*/
enum MessageType {

/**
* Triggers an evaluation when AWS Config delivers a configuration item as a result of a resource change.
*/
CONFIGURATION_ITEM_CHANGE_NOTIFICATION = 'ConfigurationItemChangeNotification',

/**
* Triggers an evaluation when AWS Config delivers an oversized configuration item.
*/
OVERSIZED_CONFIGURATION_ITEM_CHANGE_NOTIFICATION = 'OversizedConfigurationItemChangeNotification',

/**
* Triggers a periodic evaluation at the frequency specified for MaximumExecutionFrequency.
*/
SCHEDULED_NOTIFICATION = 'ScheduledNotification',

/**
* Triggers a periodic evaluation when AWS Config delivers a configuration snapshot.
*/
CONFIGURATION_SNAPSHOT_DELIVERY_COMPLETED = 'ConfigurationSnapshotDeliveryCompleted',
}

/**
* Construction properties for a CustomRule.
*/
interface SourceDetail {
/**
* The source of the event, such as an AWS service,
* that triggers AWS Config to evaluate your AWS resources.
*
*/
readonly eventSource: EventSource;
/**
* The frequency at which you want AWS Config to run evaluations for a custom rule with a periodic trigger.
*/
readonly maximumExecutionFrequency?: MaximumExecutionFrequency;
/**
* The type of notification that triggers AWS Config to run an evaluation for a rule.
*/
readonly messageType: MessageType;
}

/**
* Construction properties for a CustomRule.
*/
Expand Down Expand Up @@ -331,25 +388,24 @@ export class CustomRule extends RuleNew {
throw new Error('At least one of `configurationChanges` or `periodic` must be set to true.');
}

const sourceDetails: any[] = [];
const sourceDetails: SourceDetail[] = [];
this.ruleScope = props.ruleScope;

if (props.configurationChanges) {
sourceDetails.push({
eventSource: 'aws.config',
messageType: 'ConfigurationItemChangeNotification',
eventSource: EventSource.AWS_CONFIG,
messageType: MessageType.CONFIGURATION_ITEM_CHANGE_NOTIFICATION,
});
sourceDetails.push({
eventSource: 'aws.config',
messageType: 'OversizedConfigurationItemChangeNotification',
eventSource: EventSource.AWS_CONFIG,
messageType: MessageType.OVERSIZED_CONFIGURATION_ITEM_CHANGE_NOTIFICATION,
});
}

if (props.periodic) {
sourceDetails.push({
eventSource: 'aws.config',
eventSource: EventSource.AWS_CONFIG,
maximumExecutionFrequency: props.maximumExecutionFrequency,
messageType: 'ScheduledNotification',
messageType: MessageType.SCHEDULED_NOTIFICATION,
});
}

Expand Down Expand Up @@ -391,6 +447,88 @@ export class CustomRule extends RuleNew {
}
}

/**
* Construction properties for a CustomPolicy.
*/
export interface CustomPolicyProps extends RuleProps {
/**
* The policy definition containing the logic for your AWS Config Custom Policy rule.
*/
readonly policyText: string;

/**
* The boolean expression for enabling debug logging for your AWS Config Custom Policy rule.
*
* @default false
*/
readonly enableDebugLog?: boolean;
}

/**
* A new custom policy.
*
* @resource AWS::Config::ConfigRule
*/
export class CustomPolicy extends RuleNew {
/** @attribute */
public readonly configRuleName: string;

/** @attribute */
public readonly configRuleArn: string;

/** @attribute */
public readonly configRuleId: string;

/** @attribute */
public readonly configRuleComplianceType: string;

constructor(scope: Construct, id: string, props: CustomPolicyProps) {
super(scope, id, {
physicalName: props.configRuleName,
});

if (!props.policyText || [...props.policyText].length === 0) {
throw new Error('Policy Text cannot be empty.');
}
if ( [...props.policyText].length > 10000 ) {
throw new Error('Policy Text is limited to 10,000 characters or less.');
}

const sourceDetails: SourceDetail[] = [];
this.ruleScope = props.ruleScope;

sourceDetails.push({
eventSource: EventSource.AWS_CONFIG,
messageType: MessageType.CONFIGURATION_ITEM_CHANGE_NOTIFICATION,
});
sourceDetails.push({
eventSource: EventSource.AWS_CONFIG,
messageType: MessageType.OVERSIZED_CONFIGURATION_ITEM_CHANGE_NOTIFICATION,
});
const rule = new CfnConfigRule(this, 'Resource', {
configRuleName: this.physicalName,
description: props.description,
inputParameters: props.inputParameters,
scope: Lazy.any({ produce: () => renderScope(this.ruleScope) }), // scope can use values such as stack id (see CloudFormationStackDriftDetectionCheck)
source: {
owner: 'CUSTOM_POLICY',
sourceDetails,
customPolicyDetails: {
enableDebugLogDelivery: props.enableDebugLog,
policyRuntime: 'guard-2.x.x',
policyText: props.policyText,
},
},
});

this.configRuleName = rule.ref;
this.configRuleArn = rule.attrArn;
this.configRuleId = rule.attrConfigRuleId;
this.configRuleComplianceType = rule.attrComplianceType;
this.isCustomWithChanges = true;
}
}

/**
* Managed rules that are supported by AWS Config.
* @see https://docs.aws.amazon.com/config/latest/developerguide/managed-rules-by-aws-config.html
Expand Down
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-config/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
"@aws-cdk/aws-events-targets": "0.0.0",
"@aws-cdk/cdk-build-tools": "0.0.0",
"@aws-cdk/integ-runner": "0.0.0",
"@aws-cdk/integ-tests": "0.0.0",
"@aws-cdk/cfn2ts": "0.0.0",
"@aws-cdk/pkglint": "0.0.0",
"@types/jest": "^27.5.2",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"version": "21.0.0",
"files": {
"51dce3b1479f4a685a2f5a815b141fdf3e07e49181ce9da06750e820f5b92859": {
"source": {
"path": "aws-cdk-config-custompolicy.template.json",
"packaging": "file"
},
"destinations": {
"current_account-current_region": {
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
"objectKey": "51dce3b1479f4a685a2f5a815b141fdf3e07e49181ce9da06750e820f5b92859.json",
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
}
}
}
},
"dockerImages": {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
{
"Resources": {
"Custom8166710A": {
"Type": "AWS::Config::ConfigRule",
"Properties": {
"Source": {
"CustomPolicyDetails": {
"EnableDebugLogDelivery": true,
"PolicyRuntime": "guard-2.x.x",
"PolicyText": "\n# This rule checks if point in time recovery (PITR) is enabled on active Amazon DynamoDB tables\nlet status = ['ACTIVE']\n\nrule tableisactive when\n resourceType == \"AWS::DynamoDB::Table\" {\n configuration.tableStatus == %status\n}\n\nrule checkcompliance when\n resourceType == \"AWS::DynamoDB::Table\"\n tableisactive {\n let pitr = supplementaryConfiguration.ContinuousBackupsDescription.pointInTimeRecoveryDescription.pointInTimeRecoveryStatus\n %pitr == \"ENABLED\"\n}\n"
},
"Owner": "CUSTOM_POLICY",
"SourceDetails": [
{
"EventSource": "aws.config",
"MessageType": "ConfigurationItemChangeNotification"
},
{
"EventSource": "aws.config",
"MessageType": "OversizedConfigurationItemChangeNotification"
}
]
},
"Scope": {
"ComplianceResourceTypes": [
"AWS::DynamoDB::Table"
]
}
}
},
"sampleuser2D3A0B43": {
"Type": "AWS::IAM::User"
},
"Customlazy5E6C8AE4": {
"Type": "AWS::Config::ConfigRule",
"Properties": {
"Source": {
"CustomPolicyDetails": {
"EnableDebugLogDelivery": true,
"PolicyRuntime": "guard-2.x.x",
"PolicyText": "lazy-create-test"
},
"Owner": "CUSTOM_POLICY",
"SourceDetails": [
{
"EventSource": "aws.config",
"MessageType": "ConfigurationItemChangeNotification"
},
{
"EventSource": "aws.config",
"MessageType": "OversizedConfigurationItemChangeNotification"
}
]
},
"Scope": {
"ComplianceResourceId": {
"Ref": "sampleuser2D3A0B43"
},
"ComplianceResourceTypes": [
"AWS::IAM::User"
]
}
}
}
},
"Parameters": {
"BootstrapVersion": {
"Type": "AWS::SSM::Parameter::Value<String>",
"Default": "/cdk-bootstrap/hnb659fds/version",
"Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]"
}
},
"Rules": {
"CheckBootstrapVersion": {
"Assertions": [
{
"Assert": {
"Fn::Not": [
{
"Fn::Contains": [
[
"1",
"2",
"3",
"4",
"5"
],
{
"Ref": "BootstrapVersion"
}
]
}
]
},
"AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI."
}
]
}
}
}
Loading

0 comments on commit 4c65ce6

Please sign in to comment.