-
Notifications
You must be signed in to change notification settings - Fork 4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(config): add custom policy rule constructs #21794
Changes from all commits
d05b24b
4defbc2
8c187ed
12ce62a
0a798e9
cab0e77
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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. | ||
*/ | ||
|
@@ -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, | ||
}); | ||
} | ||
|
||
|
@@ -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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's add an additional integration test that uses something like stackId where it needs to be produced late. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @TheRealAmazonKendra I am reading the documentation that might be relevant, but there doesn't appear to be an i/f where I can enter a stackid. As a suggestion, can we remove this misleading comment from CustomRule and CustomPolicy. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The comment looks accurately written. Render scope does the following
So, if the resource that this scope is being assigned to doesn't yet have its resource id created at the time of this policy rule being declared, this needs to produce that information after that resource has been created. Basically, if this is declared. This test can be written by creating a resource of some sort and then making the rule scope specific to that resource by adding the id as the second input for |
||
source: { | ||
owner: 'CUSTOM_POLICY', | ||
sourceDetails, | ||
customPolicyDetails: { | ||
enableDebugLogDelivery: props.enableDebugLog, | ||
policyRuntime: 'guard-2.x.x', | ||
watany-dev marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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 | ||
|
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." | ||
} | ||
] | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we model these rules in a way that they can be implemented via code instead of just being blocks of code? I get that they're custom rules so there may be a lot of complexity there, so if it doesn't make sense, let me know.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry if my understanding is wrong. You need to attempt to parse the GuardDSL in the link below into a grammar in CDK.
https://github.com/aws-cloudformation/cloudformation-guard/blob/main/docs/CLAUSES.md
I am aware that this is a complex effort at this stage, and while it is great as a loadmap, would it be acceptable at this stage to work on it without a detailed parsing of the string as we do with other CustomRules?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The only problem with that approach is that, if we're changing the contract in the future, that's a breaking change. I suppose we can leave the string option and/or deprecate it so that it's not technically breaking. Considering that this is how we implement else where, though, I suppose I'm fine with this.