Skip to content

Commit 09a5cc4

Browse files
authored
feat(config): add custom policy rule constructs (#21794)
feat(config) #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*
1 parent 10974d9 commit 09a5cc4

23 files changed

+819
-28
lines changed

Diff for: packages/@aws-cdk/aws-config/README.md

+54-2
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,60 @@ new config.CloudFormationStackNotificationCheck(this, 'NotificationCheck', {
116116
### Custom rules
117117

118118
You can develop custom rules and add them to AWS Config. You associate each custom rule with an
119-
AWS Lambda function, which contains the logic that evaluates whether your AWS resources comply
120-
with the rule.
119+
AWS Lambda function and Guard.
120+
121+
#### Custom Lambda Rules
122+
123+
Lambda function which contains the logic that evaluates whether your AWS resources comply with the rule.
124+
125+
```ts
126+
// Lambda function containing logic that evaluates compliance with the rule.
127+
const evalComplianceFn = new lambda.Function(this, "CustomFunction", {
128+
code: lambda.AssetCode.fromInline(
129+
"exports.handler = (event) => console.log(event);"
130+
),
131+
handler: "index.handler",
132+
runtime: lambda.Runtime.NODEJS_14_X,
133+
});
134+
135+
// A custom rule that runs on configuration changes of EC2 instances
136+
const customRule = new config.CustomRule(this, "Custom", {
137+
configurationChanges: true,
138+
lambdaFunction: evalComplianceFn,
139+
ruleScope: config.RuleScope.fromResource(config.ResourceType.EC2_INSTANCE),
140+
});
141+
```
142+
143+
#### Custom Policy Rules
144+
145+
Guard which contains the logic that evaluates whether your AWS resources comply with the rule.
146+
147+
```ts
148+
const samplePolicyText = `
149+
# This rule checks if point in time recovery (PITR) is enabled on active Amazon DynamoDB tables
150+
let status = ['ACTIVE']
151+
152+
rule tableisactive when
153+
resourceType == "AWS::DynamoDB::Table" {
154+
configuration.tableStatus == %status
155+
}
156+
157+
rule checkcompliance when
158+
resourceType == "AWS::DynamoDB::Table"
159+
tableisactive {
160+
let pitr = supplementaryConfiguration.ContinuousBackupsDescription.pointInTimeRecoveryDescription.pointInTimeRecoveryStatus
161+
%pitr == "ENABLED"
162+
}
163+
`;
164+
165+
new config.CustomPolicy(stack, "Custom", {
166+
policyText: samplePolicyText,
167+
enableDebugLog: true,
168+
ruleScope: config.RuleScope.fromResources([
169+
config.ResourceType.DYNAMODB_TABLE,
170+
]),
171+
});
172+
```
121173

122174
### Triggers
123175

Diff for: packages/@aws-cdk/aws-config/lib/rule.ts

+146-8
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,63 @@ export class ManagedRule extends RuleNew {
281281
}
282282
}
283283

284+
/**
285+
* The source of the event, such as an AWS service,
286+
* that triggers AWS Config to evaluate your AWS resources.
287+
*/
288+
enum EventSource {
289+
290+
/* from aws.config */
291+
AWS_CONFIG = 'aws.config',
292+
293+
}
294+
295+
/**
296+
* The type of notification that triggers AWS Config to run an evaluation for a rule.
297+
*/
298+
enum MessageType {
299+
300+
/**
301+
* Triggers an evaluation when AWS Config delivers a configuration item as a result of a resource change.
302+
*/
303+
CONFIGURATION_ITEM_CHANGE_NOTIFICATION = 'ConfigurationItemChangeNotification',
304+
305+
/**
306+
* Triggers an evaluation when AWS Config delivers an oversized configuration item.
307+
*/
308+
OVERSIZED_CONFIGURATION_ITEM_CHANGE_NOTIFICATION = 'OversizedConfigurationItemChangeNotification',
309+
310+
/**
311+
* Triggers a periodic evaluation at the frequency specified for MaximumExecutionFrequency.
312+
*/
313+
SCHEDULED_NOTIFICATION = 'ScheduledNotification',
314+
315+
/**
316+
* Triggers a periodic evaluation when AWS Config delivers a configuration snapshot.
317+
*/
318+
CONFIGURATION_SNAPSHOT_DELIVERY_COMPLETED = 'ConfigurationSnapshotDeliveryCompleted',
319+
}
320+
321+
/**
322+
* Construction properties for a CustomRule.
323+
*/
324+
interface SourceDetail {
325+
/**
326+
* The source of the event, such as an AWS service,
327+
* that triggers AWS Config to evaluate your AWS resources.
328+
*
329+
*/
330+
readonly eventSource: EventSource;
331+
/**
332+
* The frequency at which you want AWS Config to run evaluations for a custom rule with a periodic trigger.
333+
*/
334+
readonly maximumExecutionFrequency?: MaximumExecutionFrequency;
335+
/**
336+
* The type of notification that triggers AWS Config to run an evaluation for a rule.
337+
*/
338+
readonly messageType: MessageType;
339+
}
340+
284341
/**
285342
* Construction properties for a CustomRule.
286343
*/
@@ -331,25 +388,24 @@ export class CustomRule extends RuleNew {
331388
throw new Error('At least one of `configurationChanges` or `periodic` must be set to true.');
332389
}
333390

334-
const sourceDetails: any[] = [];
391+
const sourceDetails: SourceDetail[] = [];
335392
this.ruleScope = props.ruleScope;
336-
337393
if (props.configurationChanges) {
338394
sourceDetails.push({
339-
eventSource: 'aws.config',
340-
messageType: 'ConfigurationItemChangeNotification',
395+
eventSource: EventSource.AWS_CONFIG,
396+
messageType: MessageType.CONFIGURATION_ITEM_CHANGE_NOTIFICATION,
341397
});
342398
sourceDetails.push({
343-
eventSource: 'aws.config',
344-
messageType: 'OversizedConfigurationItemChangeNotification',
399+
eventSource: EventSource.AWS_CONFIG,
400+
messageType: MessageType.OVERSIZED_CONFIGURATION_ITEM_CHANGE_NOTIFICATION,
345401
});
346402
}
347403

348404
if (props.periodic) {
349405
sourceDetails.push({
350-
eventSource: 'aws.config',
406+
eventSource: EventSource.AWS_CONFIG,
351407
maximumExecutionFrequency: props.maximumExecutionFrequency,
352-
messageType: 'ScheduledNotification',
408+
messageType: MessageType.SCHEDULED_NOTIFICATION,
353409
});
354410
}
355411

@@ -391,6 +447,88 @@ export class CustomRule extends RuleNew {
391447
}
392448
}
393449

450+
/**
451+
* Construction properties for a CustomPolicy.
452+
*/
453+
export interface CustomPolicyProps extends RuleProps {
454+
/**
455+
* The policy definition containing the logic for your AWS Config Custom Policy rule.
456+
*/
457+
readonly policyText: string;
458+
459+
/**
460+
* The boolean expression for enabling debug logging for your AWS Config Custom Policy rule.
461+
*
462+
* @default false
463+
*/
464+
readonly enableDebugLog?: boolean;
465+
}
466+
467+
/**
468+
* A new custom policy.
469+
*
470+
* @resource AWS::Config::ConfigRule
471+
*/
472+
export class CustomPolicy extends RuleNew {
473+
/** @attribute */
474+
public readonly configRuleName: string;
475+
476+
/** @attribute */
477+
public readonly configRuleArn: string;
478+
479+
/** @attribute */
480+
public readonly configRuleId: string;
481+
482+
/** @attribute */
483+
public readonly configRuleComplianceType: string;
484+
485+
constructor(scope: Construct, id: string, props: CustomPolicyProps) {
486+
super(scope, id, {
487+
physicalName: props.configRuleName,
488+
});
489+
490+
if (!props.policyText || [...props.policyText].length === 0) {
491+
throw new Error('Policy Text cannot be empty.');
492+
}
493+
if ( [...props.policyText].length > 10000 ) {
494+
throw new Error('Policy Text is limited to 10,000 characters or less.');
495+
}
496+
497+
const sourceDetails: SourceDetail[] = [];
498+
this.ruleScope = props.ruleScope;
499+
500+
sourceDetails.push({
501+
eventSource: EventSource.AWS_CONFIG,
502+
messageType: MessageType.CONFIGURATION_ITEM_CHANGE_NOTIFICATION,
503+
});
504+
sourceDetails.push({
505+
eventSource: EventSource.AWS_CONFIG,
506+
messageType: MessageType.OVERSIZED_CONFIGURATION_ITEM_CHANGE_NOTIFICATION,
507+
});
508+
const rule = new CfnConfigRule(this, 'Resource', {
509+
configRuleName: this.physicalName,
510+
description: props.description,
511+
inputParameters: props.inputParameters,
512+
scope: Lazy.any({ produce: () => renderScope(this.ruleScope) }), // scope can use values such as stack id (see CloudFormationStackDriftDetectionCheck)
513+
source: {
514+
owner: 'CUSTOM_POLICY',
515+
sourceDetails,
516+
customPolicyDetails: {
517+
enableDebugLogDelivery: props.enableDebugLog,
518+
policyRuntime: 'guard-2.x.x',
519+
policyText: props.policyText,
520+
},
521+
},
522+
});
523+
524+
this.configRuleName = rule.ref;
525+
this.configRuleArn = rule.attrArn;
526+
this.configRuleId = rule.attrConfigRuleId;
527+
this.configRuleComplianceType = rule.attrComplianceType;
528+
this.isCustomWithChanges = true;
529+
}
530+
}
531+
394532
/**
395533
* Managed rules that are supported by AWS Config.
396534
* @see https://docs.aws.amazon.com/config/latest/developerguide/managed-rules-by-aws-config.html

Diff for: packages/@aws-cdk/aws-config/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
"@aws-cdk/aws-events-targets": "0.0.0",
8585
"@aws-cdk/cdk-build-tools": "0.0.0",
8686
"@aws-cdk/integ-runner": "0.0.0",
87+
"@aws-cdk/integ-tests": "0.0.0",
8788
"@aws-cdk/cfn2ts": "0.0.0",
8889
"@aws-cdk/pkglint": "0.0.0",
8990
"@types/jest": "^27.5.2",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"version": "21.0.0",
3+
"files": {
4+
"51dce3b1479f4a685a2f5a815b141fdf3e07e49181ce9da06750e820f5b92859": {
5+
"source": {
6+
"path": "aws-cdk-config-custompolicy.template.json",
7+
"packaging": "file"
8+
},
9+
"destinations": {
10+
"current_account-current_region": {
11+
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
12+
"objectKey": "51dce3b1479f4a685a2f5a815b141fdf3e07e49181ce9da06750e820f5b92859.json",
13+
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
14+
}
15+
}
16+
}
17+
},
18+
"dockerImages": {}
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
{
2+
"Resources": {
3+
"Custom8166710A": {
4+
"Type": "AWS::Config::ConfigRule",
5+
"Properties": {
6+
"Source": {
7+
"CustomPolicyDetails": {
8+
"EnableDebugLogDelivery": true,
9+
"PolicyRuntime": "guard-2.x.x",
10+
"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"
11+
},
12+
"Owner": "CUSTOM_POLICY",
13+
"SourceDetails": [
14+
{
15+
"EventSource": "aws.config",
16+
"MessageType": "ConfigurationItemChangeNotification"
17+
},
18+
{
19+
"EventSource": "aws.config",
20+
"MessageType": "OversizedConfigurationItemChangeNotification"
21+
}
22+
]
23+
},
24+
"Scope": {
25+
"ComplianceResourceTypes": [
26+
"AWS::DynamoDB::Table"
27+
]
28+
}
29+
}
30+
},
31+
"sampleuser2D3A0B43": {
32+
"Type": "AWS::IAM::User"
33+
},
34+
"Customlazy5E6C8AE4": {
35+
"Type": "AWS::Config::ConfigRule",
36+
"Properties": {
37+
"Source": {
38+
"CustomPolicyDetails": {
39+
"EnableDebugLogDelivery": true,
40+
"PolicyRuntime": "guard-2.x.x",
41+
"PolicyText": "lazy-create-test"
42+
},
43+
"Owner": "CUSTOM_POLICY",
44+
"SourceDetails": [
45+
{
46+
"EventSource": "aws.config",
47+
"MessageType": "ConfigurationItemChangeNotification"
48+
},
49+
{
50+
"EventSource": "aws.config",
51+
"MessageType": "OversizedConfigurationItemChangeNotification"
52+
}
53+
]
54+
},
55+
"Scope": {
56+
"ComplianceResourceId": {
57+
"Ref": "sampleuser2D3A0B43"
58+
},
59+
"ComplianceResourceTypes": [
60+
"AWS::IAM::User"
61+
]
62+
}
63+
}
64+
}
65+
},
66+
"Parameters": {
67+
"BootstrapVersion": {
68+
"Type": "AWS::SSM::Parameter::Value<String>",
69+
"Default": "/cdk-bootstrap/hnb659fds/version",
70+
"Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]"
71+
}
72+
},
73+
"Rules": {
74+
"CheckBootstrapVersion": {
75+
"Assertions": [
76+
{
77+
"Assert": {
78+
"Fn::Not": [
79+
{
80+
"Fn::Contains": [
81+
[
82+
"1",
83+
"2",
84+
"3",
85+
"4",
86+
"5"
87+
],
88+
{
89+
"Ref": "BootstrapVersion"
90+
}
91+
]
92+
}
93+
]
94+
},
95+
"AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI."
96+
}
97+
]
98+
}
99+
}
100+
}

0 commit comments

Comments
 (0)