From b50ae1bdd839f6a8eb23653688b754d4a5b9599f Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Thu, 18 Apr 2019 15:24:07 +0200 Subject: [PATCH 01/16] feat(config): add constructs for managed and custom rules Specific classes are exposed for an AWS managed rule (`ManagedRule`) and a custom rule (`CustomRule`) with support for compliance and re-evaluation events. Higher level constructs for configured AWS managed rules are also exposed (starting with Access Keys Rotated and CloudFormation rules). This defines a pattern for future configured managed rules. --- packages/@aws-cdk/aws-config/README.md | 70 ++++ packages/@aws-cdk/aws-config/lib/index.ts | 3 + .../@aws-cdk/aws-config/lib/managed-rules.ts | 118 ++++++ packages/@aws-cdk/aws-config/lib/rule.ts | 385 ++++++++++++++++++ packages/@aws-cdk/aws-config/package.json | 9 + .../test/integ.rule.lit.expected.json | 279 +++++++++++++ .../aws-config/test/integ.rule.lit.ts | 40 ++ .../@aws-cdk/aws-config/test/test.config.ts | 8 - .../aws-config/test/test.managed-rules.ts | 168 ++++++++ .../@aws-cdk/aws-config/test/test.rule.ts | 303 ++++++++++++++ 10 files changed, 1375 insertions(+), 8 deletions(-) create mode 100644 packages/@aws-cdk/aws-config/lib/managed-rules.ts create mode 100644 packages/@aws-cdk/aws-config/lib/rule.ts create mode 100644 packages/@aws-cdk/aws-config/test/integ.rule.lit.expected.json create mode 100644 packages/@aws-cdk/aws-config/test/integ.rule.lit.ts delete mode 100644 packages/@aws-cdk/aws-config/test/test.config.ts create mode 100644 packages/@aws-cdk/aws-config/test/test.managed-rules.ts create mode 100644 packages/@aws-cdk/aws-config/test/test.rule.ts diff --git a/packages/@aws-cdk/aws-config/README.md b/packages/@aws-cdk/aws-config/README.md index 57d24dbfd9e16..e9006492d01e9 100644 --- a/packages/@aws-cdk/aws-config/README.md +++ b/packages/@aws-cdk/aws-config/README.md @@ -1,2 +1,72 @@ ## The CDK Construct Library for AWS Config This module is part of the [AWS Cloud Development Kit](https://github.com/awslabs/aws-cdk) project. + +Supported: +* Config rules + +Not supported +* Configuration recoder +* Delivery channel +* Aggregation + +### Rules + +#### AWS managed rules +To set up a managed rule, define a `ManagedRule` and specify its identifier: + +```ts +new ManagedRule(this, 'AccessKeysRotated', { + identifier: 'ACCESS_KEYS_ROTATED' +}); +``` + +Available identifiers and parameters are listed in the [List of AWS Config Managed Rules](https://docs.aws.amazon.com/config/latest/developerguide/managed-rules-by-aws-config.html). + + +Higher level constructs for managed rules are available, see [Managed Rules](lib/managed-rules.ts). Prefer to use those constructs when available (PRs welcome to add more of those). + +#### Custom rules +To set up a custom rule, define a `CustomRule` and specify the Lambda Function to run and the trigger types: + +```ts +new CustomRule(this, 'CustomRule', { + lambdaFunction: myFn, + configurationChanges: true, + periodic: true +}); +``` + +#### Restricting the scope +By default rules are triggered by changes to all [resources](https://docs.aws.amazon.com/config/latest/developerguide/resource-config-reference.html#supported-resources). Use the `addResourceScope()`, `addResourcesScope()` or `addTagScope()` methods to restrict the scope of both managed and custom rules: + +```ts +const sshRule = new ManagedRule(this, 'SSH', { + identifier: 'INCOMING_SSH_DISABLED' +}); + +// Restrict to a specific security group +rule.addResourceScope('AWS::EC2::SecurityGroup', 'sg-1234567890abcdefgh'); + +const customRule = new CustomRule(this, 'CustomRule', { + lambdaFunction: myFn, + configurationChanges: true +}); + +// Restrict to a specific tag +customRule.addTagScope('Cost Center', 'MyApp'); +``` + +Only one type of scope restriction can be added to a rule (the last call to `addXxx()` sets the scope). + +#### Events +To define Amazon CloudWatch event rules, use the `onComplianceChange()` or `onReEvaluationStatus()` methods: + +```ts +const rule = new CloudFormationStackDriftDetectionCheck(this, 'Drift'); +rule.onComplianceChange(topic); +``` + +#### Example +Creating custom and managed rules with scope restriction and events: + +[example of setting up rules](test/integ.rule.lit.ts) diff --git a/packages/@aws-cdk/aws-config/lib/index.ts b/packages/@aws-cdk/aws-config/lib/index.ts index de950dfdb59af..73e3044e50c42 100644 --- a/packages/@aws-cdk/aws-config/lib/index.ts +++ b/packages/@aws-cdk/aws-config/lib/index.ts @@ -1,2 +1,5 @@ +export * from './rule'; +export * from './managed-rules'; + // AWS::Config CloudFormation Resources: export * from './config.generated'; diff --git a/packages/@aws-cdk/aws-config/lib/managed-rules.ts b/packages/@aws-cdk/aws-config/lib/managed-rules.ts new file mode 100644 index 0000000000000..d6ceafa25ead0 --- /dev/null +++ b/packages/@aws-cdk/aws-config/lib/managed-rules.ts @@ -0,0 +1,118 @@ +import iam = require('@aws-cdk/aws-iam'); +import sns = require('@aws-cdk/aws-sns'); +import { Construct, Token } from '@aws-cdk/cdk'; +import { ManagedRule, RuleProps } from './rule'; + +/** + * Construction properties for a AccessKeysRotated + */ +export interface AccessKeysRotatedProps extends RuleProps { + /** + * The maximum number of days within which the access keys must be rotated. + * + * @default 90 days + */ + readonly maxDays?: number; +} + +/** + * Checks whether the active access keys are rotated within the number of days + * specified in `maxDays`. + * + * @see https://docs.aws.amazon.com/config/latest/developerguide/access-keys-rotated.html + */ +export class AccessKeysRotated extends ManagedRule { + constructor(scope: Construct, id: string, props: AccessKeysRotatedProps = {}) { + super(scope, id, { + ...props, + identifier: 'ACCESS_KEYS_ROTATED', + inputParameters: { + ...props.maxDays + ? { + maxAccessKeyAge: props.maxDays + } + : {} + } + }); + } +} + +/** + * Construction properties for a CloudFormationStackDriftDetectionCheck + */ +export interface CloudFormationStackDriftDetectionCheckProps extends RuleProps { + /** + * Whether to check only the stack where this rule is deployed. + * + * @default false + */ + readonly ownStackOnly?: boolean; +} + +/** + * Checks whether your CloudFormation stacks' actual configuration differs, or + * has drifted, from its expected configuration. + * + * @see https://docs.aws.amazon.com/config/latest/developerguide/cloudformation-stack-drift-detection-check.html + */ +export class CloudFormationStackDriftDetectionCheck extends ManagedRule { + private role: iam.Role; + + constructor(scope: Construct, id: string, props: CloudFormationStackDriftDetectionCheckProps = {}) { + super(scope, id, { + ...props, + identifier: 'CLOUDFORMATION_STACK_DRIFT_DETECTION_CHECK', + inputParameters: { + cloudformationRoleArn: new Token(() => this.role.roleArn) + } + }); + + this.addResourceScope('AWS::CloudFormation::Stack', props.ownStackOnly ? this.node.stack.stackId : undefined); + + this.role = new iam.Role(this, 'Role', { + assumedBy: new iam.CompositePrincipal( + new iam.ServicePrincipal('config.amazonaws.com'), + new iam.ServicePrincipal('cloudformation.amazonaws.com') + ), + managedPolicyArns: [ + new iam.AwsManagedPolicy('ReadOnlyAccess', this).policyArn, + new iam.AwsManagedPolicy('AWSCloudFormationReadOnlyAccess', this).policyArn + ] + }); + } +} + +/** + * Construction properties for a CloudFormationStackNotificationCheck. + */ +export interface CloudFormationStackNotificationCheckProps extends RuleProps { + /** + * A list of allowed topics. At most 5 topics. + */ + readonly topics?: sns.Topic[]; +} + +/** + * Checks whether your CloudFormation stacks are sending event notifications to + * a SNS topic. Optionally checks whether specified SNS topics are used. + * + * @see https://docs.aws.amazon.com/config/latest/developerguide/cloudformation-stack-notification-check.html + */ +export class CloudFormationStackNotificationCheck extends ManagedRule { + constructor(scope: Construct, id: string, props: CloudFormationStackNotificationCheckProps = {}) { + if (props.topics && props.topics.length > 5) { + throw new Error('At most 5 topics can be specified.'); + } + + super(scope, id, { + ...props, + identifier: 'CLOUDFORMATION_STACK_NOTIFICATION_CHECK', + inputParameters: props.topics && props.topics.reduce( + (params, topic, idx) => ({ ...params, [`snsTopic${idx + 1}`]: topic.topicArn }), + {} + ) + }); + + this.addResourceScope('AWS::CloudFormation::Stack'); + } +} diff --git a/packages/@aws-cdk/aws-config/lib/rule.ts b/packages/@aws-cdk/aws-config/lib/rule.ts new file mode 100644 index 0000000000000..63aa098757b54 --- /dev/null +++ b/packages/@aws-cdk/aws-config/lib/rule.ts @@ -0,0 +1,385 @@ +import events = require('@aws-cdk/aws-events'); +import iam = require('@aws-cdk/aws-iam'); +import lambda = require('@aws-cdk/aws-lambda'); +import { CfnOutput, Construct, IResource, Resource, Token } from '@aws-cdk/cdk'; +import { CfnConfigRule } from './config.generated'; + +/** + * A config rule. + */ +export interface IRule extends IResource { + /** + * The name of the rule. + */ + readonly ruleName: string; + + /** + * Exports this rule from stack. + */ + export(): RuleImportProps; + + /** + * Defines a CloudWatch event rule which triggers for rule events. Use + * `rule.addEventPattern(pattern)` to specify a filter. + */ + onEvent(id: string, target?: events.IEventRuleTarget, options?: events.EventRuleProps): events.EventRule; + + /** + * Defines a CloudWatch event rule which triggers for rule compliance events. + */ + onComplianceChange(id: string, target?: events.IEventRuleTarget, options?: events.EventRuleProps): events.EventRule; + + /** + * Defines a CloudWatch event rule which triggers for rule re-evaluation status events. + */ + onReEvaluationStatus(id: string, target?: events.IEventRuleTarget, options?: events.EventRuleProps): events.EventRule; +} + +/** + * The maximum frequency at which the AWS Config rule runs evaluations. + */ +export enum MaximumExecutionFrequency { + ONE_HOUR = 'One_Hour', + THREE_HOURS = 'Three_Hours', + SIX_HOURS = 'Six_Hours', + TWELVE_HOURS = 'Twelve_Hours', + TWENTY_FOUR_HOURS = 'TwentyFour_Hours' +} + +/** + * A new or imported rule. + */ +export abstract class Rule extends Resource implements IRule { + /** + * Imports an existing rule. + */ + public static import(scope: Construct, id: string, props: RuleImportProps): IRule { + return new ImportedRule(scope, id, props); + } + + public abstract readonly ruleName: string; + + public abstract export(): RuleImportProps; + + /** + * Defines a CloudWatch event rule which triggers for rule events. Use + * `rule.addEventPattern(pattern)` to specify a filter. + */ + public onEvent(id: string, target?: events.IEventRuleTarget, options?: events.EventRuleProps) { + const rule = new events.EventRule(this, id, options); + rule.addEventPattern({ + source: ['aws.config'], + detail: { + configRuleName: [this.ruleName] + } + }); + rule.addTarget(target); + return rule; + } + + /** + * Defines a CloudWatch event rule which triggers for rule compliance events. + */ + public onComplianceChange(id: string, target?: events.IEventRuleTarget, options?: events.EventRuleProps): events.EventRule { + const rule = this.onEvent(id, target, options); + rule.addEventPattern({ + detailType: [ 'Config Rules Compliance Change' ], + }); + return rule; + } + + /** + * Defines a CloudWatch event rule which triggers for rule re-evaluation status events. + */ + public onReEvaluationStatus(id: string, target?: events.IEventRuleTarget, options?: events.EventRuleProps): events.EventRule { + const rule = this.onEvent(id, target, options); + rule.addEventPattern({ + detailType: [ 'Config Rules Re-evaluation Status' ], + }); + return rule; + } +} + +/** + * A new managed or custom rule. + */ +export abstract class RuleNew extends Rule implements IRule { + /** + * The arn of the rule. + */ + public abstract readonly ruleArn: string; + + /** + * The id of the rule. + */ + public abstract readonly ruleId: string; + + /** + * The compliance status of the rule. + */ + public abstract readonly ruleComplianceType: string; + + protected scope?: CfnConfigRule.ScopeProperty; + protected isManaged?: boolean; + protected isCustomWithChanges?: boolean; + + /** + * Exports this rule from the stack. + */ + public export(): RuleImportProps { + return { + ruleName: new CfnOutput(this, 'RuleName', { value: this.ruleName }).makeImportValue().toString() + }; + } + + /** + * Restrict scope of changes to a specific resource. + * + * @see https://docs.aws.amazon.com/config/latest/developerguide/resource-config-reference.html#supported-resources + * + * @param type the resource type + * @param identifier the resource identifier + */ + public addResourceScope(type: string, identifier?: string) { + this.addScope({ + complianceResourceId: identifier, + complianceResourceTypes: [type], + }); + } + + /** + * Restrict scope of changes to specific resource types. + * + * @see https://docs.aws.amazon.com/config/latest/developerguide/resource-config-reference.html#supported-resources + * + * @param types resource types + */ + public addResourcesScope(...types: string[]) { + this.addScope({ + complianceResourceTypes: types + }); + } + + /** + * Restrict scope of changes to a specific tag. + * + * @param key the tag key + * @param value the tag value + */ + public addTagScope(key: string, value?: string) { + this.addScope({ + tagKey: key, + tagValue: value + }); + } + + private addScope(scope: CfnConfigRule.ScopeProperty) { + if (!this.isManaged && !this.isCustomWithChanges) { + throw new Error('Cannot set scope when `configurationChanges` is set to false.'); + } + + this.scope = scope; + } +} + +/** + * Construction properties for a new rule. + */ +export interface RuleProps { + /** + * A name for the AWS Config rule. + * + * @default a CloudFormation genereated named + */ + readonly name?: string; + + /** + * A description about this AWS Config rule. + * + * @default no description + */ + readonly description?: string; + + /** + * Input parameter values that are passed to the AWS Config rule. + * + * @default no input parameters + */ + readonly inputParameters?: { [key: string]: any }; + + /** + * The maximum frequency at which the AWS Config rule runs evaluations. + * + * @default 24 hours + */ + readonly maximumExecutionFrequency?: MaximumExecutionFrequency +} + +/** + * Construction properties for a ManagedRule. + */ +export interface ManagedRuleProps extends RuleProps { + /** + * The identifier of the AWS managed rule. + * + * @see https://docs.aws.amazon.com/config/latest/developerguide/managed-rules-by-aws-config.html + */ + readonly identifier: string; +} + +/** + * A new managed rule. + */ +export class ManagedRule extends RuleNew implements IRule { + public readonly ruleName: string; + public readonly ruleArn: string; + public readonly ruleId: string; + public readonly ruleComplianceType: string; + + constructor(scope: Construct, id: string, props: ManagedRuleProps) { + super(scope, id); + + const rule = new CfnConfigRule(this, 'Resource', { + configRuleName: props.name, + description: props.description, + inputParameters: props.inputParameters, + maximumExecutionFrequency: props.maximumExecutionFrequency, + scope: new Token(() => this.scope), + source: { + owner: 'AWS', + sourceIdentifier: props.identifier + } + }); + + this.ruleName = rule.configRuleName; + this.ruleArn = rule.configRuleArn; + this.ruleId = rule.configRuleId; + this.ruleComplianceType = rule.configRuleComplianceType; + + this.isManaged = true; + } +} + +/** + * Consruction properties for a CustomRule. + */ +export interface CustomRuleProps extends RuleProps { + /** + * The Lambda function to run. + */ + readonly lambdaFunction: lambda.IFunction; + + /** + * Whether to run the rule on configuration changes. + * + * @default false + */ + readonly configurationChanges?: boolean; + + /** + * Whether to run the rule on a fixed frequency. + * + * @default false + */ + readonly periodic?: boolean; +} +/** + * A new custom rule. + */ +export class CustomRule extends RuleNew implements IRule { + public readonly ruleName: string; + public readonly ruleArn: string; + public readonly ruleId: string; + public readonly ruleComplianceType: string; + + constructor(scope: Construct, id: string, props: CustomRuleProps) { + super(scope, id); + + if (!props.configurationChanges && !props.periodic) { + throw new Error('At least one of `configurationChanges` or `periodic` must be set to true.'); + } + + const sourceDetails: any[] = []; + + if (props.configurationChanges) { + sourceDetails.push({ + eventSource: 'aws.config', + messageType: 'ConfigurationItemChangeNotification' + }); + sourceDetails.push({ + eventSource: 'aws.config', + messageType: 'OversizedConfigurationItemChangeNotification' + }); + } + + if (props.periodic) { + sourceDetails.push({ + eventSource: 'aws.config', + maximumExecutionFrequency: props.maximumExecutionFrequency, + messageType: 'ScheduledNotification' + }); + } + + props.lambdaFunction.addPermission('Permission', { + principal: new iam.ServicePrincipal('config.amazonaws.com') + }); + + if (props.lambdaFunction.role) { + props.lambdaFunction.role.attachManagedPolicy( + new iam.AwsManagedPolicy('service-role/AWSConfigRulesExecutionRole', this).policyArn + ); + } + + // The lambda permission must be created before the rule + this.node.addDependency(props.lambdaFunction); + + const rule = new CfnConfigRule(this, 'Resource', { + configRuleName: props.name, + description: props.description, + inputParameters: props.inputParameters, + maximumExecutionFrequency: props.maximumExecutionFrequency, + scope: new Token(() => this.scope), + source: { + owner: 'CUSTOM_LAMBDA', + sourceDetails, + sourceIdentifier: props.lambdaFunction.functionArn + } + }); + + this.ruleName = rule.configRuleName; + this.ruleArn = rule.configRuleArn; + this.ruleId = rule.configRuleId; + this.ruleComplianceType = rule.configRuleComplianceType; + + if (props.configurationChanges) { + this.isCustomWithChanges = true; + } + } +} + +/** + * Construction properties for an ImportedRule. + */ +export interface RuleImportProps { + /** + * The rule name. + */ + readonly ruleName: string; +} + +/** + * An imported rule. + */ +class ImportedRule extends Rule implements IRule { + public readonly ruleName: string; + + constructor(scope: Construct, id: string, private readonly props: RuleImportProps) { + super(scope, id); + + this.ruleName = props.ruleName; + } + + public export() { + return this.props; + } +} diff --git a/packages/@aws-cdk/aws-config/package.json b/packages/@aws-cdk/aws-config/package.json index c17f153c9c788..89a42af19e3f6 100644 --- a/packages/@aws-cdk/aws-config/package.json +++ b/packages/@aws-cdk/aws-config/package.json @@ -61,14 +61,23 @@ "devDependencies": { "@aws-cdk/assert": "^0.28.0", "cdk-build-tools": "^0.28.0", + "cdk-integ-tools": "^0.28.0", "cfn2ts": "^0.28.0", "pkglint": "^0.28.0" }, "dependencies": { + "@aws-cdk/aws-events": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/aws-lambda": "^0.28.0", + "@aws-cdk/aws-sns": "^0.28.0", "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { + "@aws-cdk/aws-events": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/aws-lambda": "^0.28.0", + "@aws-cdk/aws-sns": "^0.28.0", "@aws-cdk/cdk": "^0.28.0" }, "engines": { diff --git a/packages/@aws-cdk/aws-config/test/integ.rule.lit.expected.json b/packages/@aws-cdk/aws-config/test/integ.rule.lit.expected.json new file mode 100644 index 0000000000000..152ba3a28952d --- /dev/null +++ b/packages/@aws-cdk/aws-config/test/integ.rule.lit.expected.json @@ -0,0 +1,279 @@ +{ + "Resources": { + "CustomFunctionServiceRoleD3F73B79": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "lambda.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSConfigRulesExecutionRole" + ] + ] + } + ] + } + }, + "CustomFunctionBADD59E7": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = (event) => console.log(event);" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "CustomFunctionServiceRoleD3F73B79", + "Arn" + ] + }, + "Runtime": "nodejs8.10" + }, + "DependsOn": [ + "CustomFunctionServiceRoleD3F73B79" + ] + }, + "CustomFunctionPermission41887A5E": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "CustomFunctionBADD59E7", + "Arn" + ] + }, + "Principal": "config.amazonaws.com" + } + }, + "Custom8166710A": { + "Type": "AWS::Config::ConfigRule", + "Properties": { + "Source": { + "Owner": "CUSTOM_LAMBDA", + "SourceDetails": [ + { + "EventSource": "aws.config", + "MessageType": "ConfigurationItemChangeNotification" + }, + { + "EventSource": "aws.config", + "MessageType": "OversizedConfigurationItemChangeNotification" + } + ], + "SourceIdentifier": { + "Fn::GetAtt": [ + "CustomFunctionBADD59E7", + "Arn" + ] + } + }, + "Scope": { + "ComplianceResourceTypes": [ + "AWS::EC2::Instance" + ] + } + }, + "DependsOn": [ + "CustomFunctionPermission41887A5E", + "CustomFunctionBADD59E7", + "CustomFunctionServiceRoleD3F73B79" + ] + }, + "Drift34D3210F": { + "Type": "AWS::Config::ConfigRule", + "Properties": { + "Source": { + "Owner": "AWS", + "SourceIdentifier": "CLOUDFORMATION_STACK_DRIFT_DETECTION_CHECK" + }, + "InputParameters": { + "cloudformationRoleArn": { + "Fn::GetAtt": [ + "DriftRole8A5FB833", + "Arn" + ] + } + }, + "Scope": { + "ComplianceResourceTypes": [ + "AWS::CloudFormation::Stack" + ] + } + } + }, + "DriftRole8A5FB833": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": [ + { + "Fn::Join": [ + "", + [ + "config.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "cloudformation.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + ] + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/ReadOnlyAccess" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/AWSCloudFormationReadOnlyAccess" + ] + ] + } + ] + } + }, + "DriftComplianceChange4C0F2B82": { + "Type": "AWS::Events::Rule", + "Properties": { + "EventPattern": { + "source": [ + "aws.config" + ], + "detail": { + "configRuleName": [ + { + "Ref": "Drift34D3210F" + } + ] + }, + "detail-type": [ + "Config Rules Compliance Change" + ] + }, + "State": "ENABLED", + "Targets": [ + { + "Arn": { + "Ref": "ComplianceTopic0229448B" + }, + "Id": "ComplianceTopic" + } + ] + } + }, + "ComplianceTopic0229448B": { + "Type": "AWS::SNS::Topic" + }, + "ComplianceTopicPolicyF8BC46F0": { + "Type": "AWS::SNS::TopicPolicy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "sns:Publish", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "events.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + }, + "Resource": { + "Ref": "ComplianceTopic0229448B" + }, + "Sid": "0" + } + ], + "Version": "2012-10-17" + }, + "Topics": [ + { + "Ref": "ComplianceTopic0229448B" + } + ] + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-config/test/integ.rule.lit.ts b/packages/@aws-cdk/aws-config/test/integ.rule.lit.ts new file mode 100644 index 0000000000000..bd207f2b52432 --- /dev/null +++ b/packages/@aws-cdk/aws-config/test/integ.rule.lit.ts @@ -0,0 +1,40 @@ +import lambda = require('@aws-cdk/aws-lambda'); +import sns = require('@aws-cdk/aws-sns'); +import cdk = require('@aws-cdk/cdk'); +import config = require('../lib'); + +const app = new cdk.App(); + +class ConfigStack extends cdk.Stack { + constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + /// !show + // A custom rule that runs on configuration changes of EC2 instances + const fn = new lambda.Function(this, 'CustomFunction', { + code: lambda.AssetCode.inline('exports.handler = (event) => console.log(event);'), + handler: 'index.handler', + runtime: lambda.Runtime.NodeJS810 + }); + + const customRule = new config.CustomRule(this, 'Custom', { + configurationChanges: true, + lambdaFunction: fn + }); + + customRule.addResourceScope('AWS::EC2::Instance'); + + // A rule to detect stacks drifts + const driftRule = new config.CloudFormationStackDriftDetectionCheck(this, 'Drift'); + + // Topic for compliance events + const complianceTopic = new sns.Topic(this, 'ComplianceTopic'); + + // Send notification on compliance change + driftRule.onComplianceChange('ComplianceChange', complianceTopic); + /// !hide + } +} + +new ConfigStack(app, 'aws-cdk-config-rule-integ'); +app.run(); diff --git a/packages/@aws-cdk/aws-config/test/test.config.ts b/packages/@aws-cdk/aws-config/test/test.config.ts deleted file mode 100644 index 820f6b467f38f..0000000000000 --- a/packages/@aws-cdk/aws-config/test/test.config.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Test, testCase } from 'nodeunit'; - -export = testCase({ - notTested(test: Test) { - test.ok(true, 'No tests are specified for this package.'); - test.done(); - } -}); diff --git a/packages/@aws-cdk/aws-config/test/test.managed-rules.ts b/packages/@aws-cdk/aws-config/test/test.managed-rules.ts new file mode 100644 index 0000000000000..a077c266343ed --- /dev/null +++ b/packages/@aws-cdk/aws-config/test/test.managed-rules.ts @@ -0,0 +1,168 @@ +import { expect, haveResource } from '@aws-cdk/assert'; +import sns = require('@aws-cdk/aws-sns'); +import cdk = require('@aws-cdk/cdk'); +import { Test } from 'nodeunit'; +import config = require('../lib'); + +export = { + 'access keys rotated'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new config.AccessKeysRotated(stack, 'AccessKeys'); + + // THEN + expect(stack).to(haveResource('AWS::Config::ConfigRule', { + Source: { + Owner: 'AWS', + SourceIdentifier: 'ACCESS_KEYS_ROTATED' + }, + })); + + test.done(); + }, + + 'cloudformation stack drift detection check'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new config.CloudFormationStackDriftDetectionCheck(stack, 'Drift'); + + // THEN + expect(stack).to(haveResource('AWS::Config::ConfigRule', { + Source: { + Owner: 'AWS', + SourceIdentifier: 'CLOUDFORMATION_STACK_DRIFT_DETECTION_CHECK' + }, + InputParameters: { + cloudformationRoleArn: { + 'Fn::GetAtt': [ + 'DriftRole8A5FB833', + 'Arn' + ] + } + }, + Scope: { + ComplianceResourceTypes: [ + 'AWS::CloudFormation::Stack' + ] + } + })); + + expect(stack).to(haveResource('AWS::IAM::Role', { + AssumeRolePolicyDocument: { + Statement: [ + { + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { + Service: [ + { + 'Fn::Join': [ + '', + [ + 'config.', + { + Ref: 'AWS::URLSuffix' + } + ] + ] + }, + { + 'Fn::Join': [ + '', + [ + 'cloudformation.', + { + Ref: 'AWS::URLSuffix' + } + ] + ] + } + ] + } + } + ], + Version: '2012-10-17' + }, + ManagedPolicyArns: [ + { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition' + }, + ':iam::aws:policy/ReadOnlyAccess' + ] + ] + }, + { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition' + }, + ':iam::aws:policy/AWSCloudFormationReadOnlyAccess' + ] + ] + } + ] + })); + + test.done(); + }, + + 'cloudformation stack notification check'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const topic1 = new sns.Topic(stack, 'AllowedTopic1'); + const topic2 = new sns.Topic(stack, 'AllowedTopic2'); + + // WHEN + new config.CloudFormationStackNotificationCheck(stack, 'Notification', { + topics: [topic1, topic2] + }); + + // THEN + expect(stack).to(haveResource('AWS::Config::ConfigRule', { + Source: { + Owner: 'AWS', + SourceIdentifier: 'CLOUDFORMATION_STACK_NOTIFICATION_CHECK' + }, + InputParameters: { + snsTopic1: { + Ref: 'AllowedTopic10C9144F9' + }, + snsTopic2: { + Ref: 'AllowedTopic24ECF6C0D' + } + }, + Scope: { + ComplianceResourceTypes: [ + 'AWS::CloudFormation::Stack' + ] + } + })); + + test.done(); + }, + + 'cloudformation stack notification check throws with more than 5 topics'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const topic = new sns.Topic(stack, 'AllowedTopic1'); + + // THEN + test.throws(() => new config.CloudFormationStackNotificationCheck(stack, 'Notification', { + topics: [topic, topic, topic, topic, topic, topic] + }), /5 topics/); + + test.done(); + } +}; diff --git a/packages/@aws-cdk/aws-config/test/test.rule.ts b/packages/@aws-cdk/aws-config/test/test.rule.ts new file mode 100644 index 0000000000000..9a058124fcd1f --- /dev/null +++ b/packages/@aws-cdk/aws-config/test/test.rule.ts @@ -0,0 +1,303 @@ +import { expect, haveResource, ResourcePart } from '@aws-cdk/assert'; +import lambda = require('@aws-cdk/aws-lambda'); +import cdk = require('@aws-cdk/cdk'); +import { Test } from 'nodeunit'; +import config = require('../lib'); + +export = { + 'create a managed rule'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new config.ManagedRule(stack, 'Rule', { + description: 'really cool rule', + identifier: 'AWS_SUPER_COOL', + inputParameters: { + key: 'value' + }, + maximumExecutionFrequency: config.MaximumExecutionFrequency.THREE_HOURS, + name: 'cool rule' + }); + + // THEN + expect(stack).to(haveResource('AWS::Config::ConfigRule', { + Source: { + Owner: 'AWS', + SourceIdentifier: 'AWS_SUPER_COOL' + }, + ConfigRuleName: 'cool rule', + Description: 'really cool rule', + InputParameters: { + key: 'value' + }, + MaximumExecutionFrequency: 'Three_Hours' + })); + + test.done(); + }, + + 'create a custom rule'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const fn = new lambda.Function(stack, 'Function', { + code: lambda.AssetCode.inline('foo'), + handler: 'index.handler', + runtime: lambda.Runtime.NodeJS810 + }); + + // WHEN + new config.CustomRule(stack, 'Rule', { + configurationChanges: true, + description: 'really cool rule', + inputParameters: { + key: 'value' + }, + lambdaFunction: fn, + maximumExecutionFrequency: config.MaximumExecutionFrequency.SIX_HOURS, + name: 'cool rule', + periodic: true + }); + + // THEN + expect(stack).to(haveResource('AWS::Config::ConfigRule', { + Properties: { + Source: { + Owner: 'CUSTOM_LAMBDA', + SourceDetails: [ + { + EventSource: 'aws.config', + MessageType: 'ConfigurationItemChangeNotification' + }, + { + EventSource: 'aws.config', + MessageType: 'OversizedConfigurationItemChangeNotification' + }, + { + EventSource: 'aws.config', + MaximumExecutionFrequency: 'Six_Hours', + MessageType: 'ScheduledNotification' + } + ], + SourceIdentifier: { + 'Fn::GetAtt': [ + 'Function76856677', + 'Arn' + ] + } + }, + ConfigRuleName: 'cool rule', + Description: 'really cool rule', + InputParameters: { + key: 'value' + }, + MaximumExecutionFrequency: 'Six_Hours' + }, + DependsOn: [ + 'FunctionPermissionEC8FE997', + 'Function76856677', + 'FunctionServiceRole675BB04A' + ] + }, ResourcePart.CompleteDefinition)); + + expect(stack).to(haveResource('AWS::Lambda::Permission', { + Principal: 'config.amazonaws.com' + })); + + expect(stack).to(haveResource('AWS::IAM::Role', { + ManagedPolicyArns: [ + { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition' + }, + ':iam::aws:policy/service-role/AWSLambdaBasicExecutionRole' + ] + ] + }, + { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition' + }, + ':iam::aws:policy/service-role/AWSConfigRulesExecutionRole' + ] + ] + } + ] + })); + + test.done(); + }, + + 'add resource scope'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const rule = new config.ManagedRule(stack, 'Rule', { + identifier: 'AWS_SUPER_COOL' + }); + + // WHEN + rule.addResourceScope('AWS::EC2::Instance', 'i-1234'); + + // THEN + expect(stack).to(haveResource('AWS::Config::ConfigRule', { + Scope: { + ComplianceResourceId: 'i-1234', + ComplianceResourceTypes: [ + 'AWS::EC2::Instance' + ] + } + })); + + test.done(); + }, + + 'add resources scope'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const rule = new config.ManagedRule(stack, 'Rule', { + identifier: 'AWS_SUPER_COOL' + }); + + // WHEN + rule.addResourcesScope('AWS::S3::Bucket', 'AWS::CloudFormation::Stack'); + + // THEN + expect(stack).to(haveResource('AWS::Config::ConfigRule', { + Scope: { + ComplianceResourceTypes: [ + 'AWS::S3::Bucket', + 'AWS::CloudFormation::Stack' + ] + } + })); + + test.done(); + }, + + 'add tag scope'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const rule = new config.ManagedRule(stack, 'Rule', { + identifier: 'RULE' + }); + + // WHEN + rule.addTagScope('key', 'value'); + + // THEN + expect(stack).to(haveResource('AWS::Config::ConfigRule', { + Scope: { + TagKey: 'key', + TagValue: 'value' + } + })); + + test.done(); + }, + + 'throws when adding scope to custom rule without configuration changes'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const fn = new lambda.Function(stack, 'Function', { + code: lambda.AssetCode.inline('foo'), + handler: 'index.handler', + runtime: lambda.Runtime.NodeJS810 + }); + + // WHEN + const rule = new config.CustomRule(stack, 'Rule', { + lambdaFunction: fn, + periodic: true + }); + + // THEN + test.throws(() => rule.addResourceScope('resource'), /`configurationChanges`/); + + test.done(); + }, + + 'throws when both configurationChanges and periodic are falsy'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const fn = new lambda.Function(stack, 'Function', { + code: lambda.AssetCode.inline('foo'), + handler: 'index.handler', + runtime: lambda.Runtime.NodeJS810 + }); + + // THEN + test.throws(() => new config.CustomRule(stack, 'Rule', { + lambdaFunction: fn + }), /`configurationChanges`.*`periodic`/); + + test.done(); + }, + + 'import/export'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const rule = new config.ManagedRule(stack, 'Rule', { + identifier: 'RULE' + }); + + // WHEN + const exportedRule = rule.export(); + + const importedRule = config.Rule.import(stack, 'ImportedRule', { + ruleName: 'name' + }); + + // THEN + test.deepEqual(stack.node.resolve(exportedRule), { + ruleName: { 'Fn::ImportValue': 'Stack:RuleRuleName1741A255' } + }); + test.deepEqual(importedRule.ruleName, 'name'); + + test.done(); + }, + + 'on compliance change event'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const rule = new config.ManagedRule(stack, 'Rule', { + identifier: 'RULE' + }); + + const fn = new lambda.Function(stack, 'Function', { + code: lambda.Code.inline('dummy'), + handler: 'index.handler', + runtime: lambda.Runtime.NodeJS810 + }); + + // WHEN + rule.onComplianceChange('ComplianceChange', fn); + + expect(stack).to(haveResource('AWS::Events::Rule', { + EventPattern: { + 'source': [ + 'aws.config' + ], + 'detail': { + configRuleName: [ + { + Ref: 'Rule4C995B7F' + } + ] + }, + 'detail-type': [ + 'Config Rules Compliance Change' + ] + } + })); + + test.done(); + } +}; From 178397a3b4b86b19b0726cdd3d8780dedfee8a5c Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Wed, 24 Apr 2019 17:25:36 +0200 Subject: [PATCH 02/16] use aws-events-targets in tests --- packages/@aws-cdk/aws-config/package.json | 1 + packages/@aws-cdk/aws-config/test/integ.rule.lit.ts | 3 ++- packages/@aws-cdk/aws-config/test/test.rule.ts | 3 ++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-config/package.json b/packages/@aws-cdk/aws-config/package.json index 89a42af19e3f6..c794866485f2b 100644 --- a/packages/@aws-cdk/aws-config/package.json +++ b/packages/@aws-cdk/aws-config/package.json @@ -60,6 +60,7 @@ "license": "Apache-2.0", "devDependencies": { "@aws-cdk/assert": "^0.28.0", + "@aws-cdk/aws-events-targets": "^0.28.0", "cdk-build-tools": "^0.28.0", "cdk-integ-tools": "^0.28.0", "cfn2ts": "^0.28.0", diff --git a/packages/@aws-cdk/aws-config/test/integ.rule.lit.ts b/packages/@aws-cdk/aws-config/test/integ.rule.lit.ts index bd207f2b52432..8058aa4761ce5 100644 --- a/packages/@aws-cdk/aws-config/test/integ.rule.lit.ts +++ b/packages/@aws-cdk/aws-config/test/integ.rule.lit.ts @@ -1,3 +1,4 @@ +import targets = require('@aws-cdk/aws-events-targets'); import lambda = require('@aws-cdk/aws-lambda'); import sns = require('@aws-cdk/aws-sns'); import cdk = require('@aws-cdk/cdk'); @@ -31,7 +32,7 @@ class ConfigStack extends cdk.Stack { const complianceTopic = new sns.Topic(this, 'ComplianceTopic'); // Send notification on compliance change - driftRule.onComplianceChange('ComplianceChange', complianceTopic); + driftRule.onComplianceChange('ComplianceChange', new targets.SnsTopic(complianceTopic)); /// !hide } } diff --git a/packages/@aws-cdk/aws-config/test/test.rule.ts b/packages/@aws-cdk/aws-config/test/test.rule.ts index 9a058124fcd1f..4766d069d76a2 100644 --- a/packages/@aws-cdk/aws-config/test/test.rule.ts +++ b/packages/@aws-cdk/aws-config/test/test.rule.ts @@ -1,4 +1,5 @@ import { expect, haveResource, ResourcePart } from '@aws-cdk/assert'; +import targets = require('@aws-cdk/aws-events-targets'); import lambda = require('@aws-cdk/aws-lambda'); import cdk = require('@aws-cdk/cdk'); import { Test } from 'nodeunit'; @@ -278,7 +279,7 @@ export = { }); // WHEN - rule.onComplianceChange('ComplianceChange', fn); + rule.onComplianceChange('ComplianceChange', new targets.LambdaFunction(fn)); expect(stack).to(haveResource('AWS::Events::Rule', { EventPattern: { From f11337dac72ece27e9ad54a79d4bd3cc1628a68d Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Wed, 1 May 2019 22:05:11 +0200 Subject: [PATCH 03/16] make abstract classes internal and ImportedRule class local --- packages/@aws-cdk/aws-config/lib/rule.ts | 63 ++++++++----------- .../@aws-cdk/aws-config/test/test.rule.ts | 2 +- 2 files changed, 27 insertions(+), 38 deletions(-) diff --git a/packages/@aws-cdk/aws-config/lib/rule.ts b/packages/@aws-cdk/aws-config/lib/rule.ts index 63aa098757b54..8f2144a32a65c 100644 --- a/packages/@aws-cdk/aws-config/lib/rule.ts +++ b/packages/@aws-cdk/aws-config/lib/rule.ts @@ -36,25 +36,30 @@ export interface IRule extends IResource { } /** - * The maximum frequency at which the AWS Config rule runs evaluations. + * Construction properties for an imported rule. */ -export enum MaximumExecutionFrequency { - ONE_HOUR = 'One_Hour', - THREE_HOURS = 'Three_Hours', - SIX_HOURS = 'Six_Hours', - TWELVE_HOURS = 'Twelve_Hours', - TWENTY_FOUR_HOURS = 'TwentyFour_Hours' +export interface RuleImportProps { + /** + * The rule name. + */ + readonly ruleName: string; } /** * A new or imported rule. */ -export abstract class Rule extends Resource implements IRule { +abstract class RuleBase extends Resource implements IRule { /** * Imports an existing rule. */ public static import(scope: Construct, id: string, props: RuleImportProps): IRule { - return new ImportedRule(scope, id, props); + class Import extends RuleBase implements IRule { + public readonly ruleName = props.ruleName; + + public export() { return props; } + } + + return new Import(scope, id); } public abstract readonly ruleName: string; @@ -103,7 +108,7 @@ export abstract class Rule extends Resource implements IRule { /** * A new managed or custom rule. */ -export abstract class RuleNew extends Rule implements IRule { +abstract class RuleNew extends RuleBase implements IRule { /** * The arn of the rule. */ @@ -182,6 +187,17 @@ export abstract class RuleNew extends Rule implements IRule { } } +/** + * The maximum frequency at which the AWS Config rule runs evaluations. + */ +export enum MaximumExecutionFrequency { + ONE_HOUR = 'One_Hour', + THREE_HOURS = 'Three_Hours', + SIX_HOURS = 'Six_Hours', + TWELVE_HOURS = 'Twelve_Hours', + TWENTY_FOUR_HOURS = 'TwentyFour_Hours' +} + /** * Construction properties for a new rule. */ @@ -356,30 +372,3 @@ export class CustomRule extends RuleNew implements IRule { } } } - -/** - * Construction properties for an ImportedRule. - */ -export interface RuleImportProps { - /** - * The rule name. - */ - readonly ruleName: string; -} - -/** - * An imported rule. - */ -class ImportedRule extends Rule implements IRule { - public readonly ruleName: string; - - constructor(scope: Construct, id: string, private readonly props: RuleImportProps) { - super(scope, id); - - this.ruleName = props.ruleName; - } - - public export() { - return this.props; - } -} diff --git a/packages/@aws-cdk/aws-config/test/test.rule.ts b/packages/@aws-cdk/aws-config/test/test.rule.ts index 4766d069d76a2..7e069965d3113 100644 --- a/packages/@aws-cdk/aws-config/test/test.rule.ts +++ b/packages/@aws-cdk/aws-config/test/test.rule.ts @@ -252,7 +252,7 @@ export = { // WHEN const exportedRule = rule.export(); - const importedRule = config.Rule.import(stack, 'ImportedRule', { + const importedRule = config.ManagedRule.import(stack, 'ImportedRule', { ruleName: 'name' }); From 608de3f173d8364e95ea9e9ca1ac1ec7afaf863f Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Thu, 2 May 2019 16:33:45 +0200 Subject: [PATCH 04/16] fromRuleName --- packages/@aws-cdk/aws-config/lib/rule.ts | 16 +++++++++------- packages/@aws-cdk/aws-config/test/test.rule.ts | 4 +--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/@aws-cdk/aws-config/lib/rule.ts b/packages/@aws-cdk/aws-config/lib/rule.ts index 8f2144a32a65c..201b5b61e94d6 100644 --- a/packages/@aws-cdk/aws-config/lib/rule.ts +++ b/packages/@aws-cdk/aws-config/lib/rule.ts @@ -16,7 +16,7 @@ export interface IRule extends IResource { /** * Exports this rule from stack. */ - export(): RuleImportProps; + export(): RuleAttributes; /** * Defines a CloudWatch event rule which triggers for rule events. Use @@ -38,7 +38,7 @@ export interface IRule extends IResource { /** * Construction properties for an imported rule. */ -export interface RuleImportProps { +export interface RuleAttributes { /** * The rule name. */ @@ -52,11 +52,13 @@ abstract class RuleBase extends Resource implements IRule { /** * Imports an existing rule. */ - public static import(scope: Construct, id: string, props: RuleImportProps): IRule { + public static fromRuleName(scope: Construct, id: string, ruleName: string): IRule { class Import extends RuleBase implements IRule { - public readonly ruleName = props.ruleName; + public ruleName = ruleName; - public export() { return props; } + public export(): RuleAttributes { + return { ruleName }; + } } return new Import(scope, id); @@ -64,7 +66,7 @@ abstract class RuleBase extends Resource implements IRule { public abstract readonly ruleName: string; - public abstract export(): RuleImportProps; + public abstract export(): RuleAttributes; /** * Defines a CloudWatch event rule which triggers for rule events. Use @@ -131,7 +133,7 @@ abstract class RuleNew extends RuleBase implements IRule { /** * Exports this rule from the stack. */ - public export(): RuleImportProps { + public export(): RuleAttributes { return { ruleName: new CfnOutput(this, 'RuleName', { value: this.ruleName }).makeImportValue().toString() }; diff --git a/packages/@aws-cdk/aws-config/test/test.rule.ts b/packages/@aws-cdk/aws-config/test/test.rule.ts index 7e069965d3113..0d50e71ee46fc 100644 --- a/packages/@aws-cdk/aws-config/test/test.rule.ts +++ b/packages/@aws-cdk/aws-config/test/test.rule.ts @@ -252,9 +252,7 @@ export = { // WHEN const exportedRule = rule.export(); - const importedRule = config.ManagedRule.import(stack, 'ImportedRule', { - ruleName: 'name' - }); + const importedRule = config.ManagedRule.fromRuleName(stack, 'ImportedRule', 'name'); // THEN test.deepEqual(stack.node.resolve(exportedRule), { From 2816f55ee1e6491ce0bf23ecd8177fe11c02050b Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Fri, 3 May 2019 09:52:37 +0200 Subject: [PATCH 05/16] JSDoc and readonly --- packages/@aws-cdk/aws-config/lib/rule.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-config/lib/rule.ts b/packages/@aws-cdk/aws-config/lib/rule.ts index 201b5b61e94d6..ce6bcd38c905b 100644 --- a/packages/@aws-cdk/aws-config/lib/rule.ts +++ b/packages/@aws-cdk/aws-config/lib/rule.ts @@ -36,7 +36,7 @@ export interface IRule extends IResource { } /** - * Construction properties for an imported rule. + * Reference to an existing rule. */ export interface RuleAttributes { /** @@ -54,7 +54,7 @@ abstract class RuleBase extends Resource implements IRule { */ public static fromRuleName(scope: Construct, id: string, ruleName: string): IRule { class Import extends RuleBase implements IRule { - public ruleName = ruleName; + public readonly ruleName = ruleName; public export(): RuleAttributes { return { ruleName }; From 84adef0395ad15c808a97b1b8ee1b4c42b37c256 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Mon, 6 May 2019 21:56:58 +0200 Subject: [PATCH 06/16] @resource and @attribute --- .../@aws-cdk/aws-config/lib/managed-rules.ts | 6 ++ packages/@aws-cdk/aws-config/lib/rule.ts | 98 +++++++++++-------- .../@aws-cdk/aws-config/test/test.rule.ts | 6 +- 3 files changed, 65 insertions(+), 45 deletions(-) diff --git a/packages/@aws-cdk/aws-config/lib/managed-rules.ts b/packages/@aws-cdk/aws-config/lib/managed-rules.ts index d6ceafa25ead0..c68288331dbb6 100644 --- a/packages/@aws-cdk/aws-config/lib/managed-rules.ts +++ b/packages/@aws-cdk/aws-config/lib/managed-rules.ts @@ -20,6 +20,8 @@ export interface AccessKeysRotatedProps extends RuleProps { * specified in `maxDays`. * * @see https://docs.aws.amazon.com/config/latest/developerguide/access-keys-rotated.html + * + * @resource AWS::Config::ConfigRule */ export class AccessKeysRotated extends ManagedRule { constructor(scope: Construct, id: string, props: AccessKeysRotatedProps = {}) { @@ -54,6 +56,8 @@ export interface CloudFormationStackDriftDetectionCheckProps extends RuleProps { * has drifted, from its expected configuration. * * @see https://docs.aws.amazon.com/config/latest/developerguide/cloudformation-stack-drift-detection-check.html + * + * @resource AWS::Config::ConfigRule */ export class CloudFormationStackDriftDetectionCheck extends ManagedRule { private role: iam.Role; @@ -97,6 +101,8 @@ export interface CloudFormationStackNotificationCheckProps extends RuleProps { * a SNS topic. Optionally checks whether specified SNS topics are used. * * @see https://docs.aws.amazon.com/config/latest/developerguide/cloudformation-stack-notification-check.html + * + * @resource AWS::Config::ConfigRule */ export class CloudFormationStackNotificationCheck extends ManagedRule { constructor(scope: Construct, id: string, props: CloudFormationStackNotificationCheckProps = {}) { diff --git a/packages/@aws-cdk/aws-config/lib/rule.ts b/packages/@aws-cdk/aws-config/lib/rule.ts index ce6bcd38c905b..952754050bc3a 100644 --- a/packages/@aws-cdk/aws-config/lib/rule.ts +++ b/packages/@aws-cdk/aws-config/lib/rule.ts @@ -10,8 +10,27 @@ import { CfnConfigRule } from './config.generated'; export interface IRule extends IResource { /** * The name of the rule. + * @attribute */ - readonly ruleName: string; + readonly configRuleName: string; + + /** + * The arn of the rule. + * @attribute + */ + readonly configRuleArn?: string; + + /** + * The id of the rule. + * @attribute + */ + readonly configRuleId?: string; + + /** + * The compliance status of the rule. + * @attribute + */ + readonly configRuleComplianceType?: string; /** * Exports this rule from stack. @@ -42,7 +61,7 @@ export interface RuleAttributes { /** * The rule name. */ - readonly ruleName: string; + readonly configRuleName: string; } /** @@ -51,20 +70,22 @@ export interface RuleAttributes { abstract class RuleBase extends Resource implements IRule { /** * Imports an existing rule. + * + * @param configRuleName the name of the rule */ - public static fromRuleName(scope: Construct, id: string, ruleName: string): IRule { - class Import extends RuleBase implements IRule { - public readonly ruleName = ruleName; + public static fromConfigRuleName(scope: Construct, id: string, configRuleName: string): IRule { + class Import extends RuleBase { + public readonly configRuleName = configRuleName; public export(): RuleAttributes { - return { ruleName }; + return { configRuleName }; } } return new Import(scope, id); } - public abstract readonly ruleName: string; + public abstract readonly configRuleName: string; public abstract export(): RuleAttributes; @@ -77,7 +98,7 @@ abstract class RuleBase extends Resource implements IRule { rule.addEventPattern({ source: ['aws.config'], detail: { - configRuleName: [this.ruleName] + configRuleName: [this.configRuleName] } }); rule.addTarget(target); @@ -110,21 +131,10 @@ abstract class RuleBase extends Resource implements IRule { /** * A new managed or custom rule. */ -abstract class RuleNew extends RuleBase implements IRule { - /** - * The arn of the rule. - */ - public abstract readonly ruleArn: string; - - /** - * The id of the rule. - */ - public abstract readonly ruleId: string; - - /** - * The compliance status of the rule. - */ - public abstract readonly ruleComplianceType: string; +abstract class RuleNew extends RuleBase { + public abstract readonly configRuleArn: string; + public abstract readonly configRuleId: string; + public abstract readonly configRuleComplianceType: string; protected scope?: CfnConfigRule.ScopeProperty; protected isManaged?: boolean; @@ -135,7 +145,7 @@ abstract class RuleNew extends RuleBase implements IRule { */ public export(): RuleAttributes { return { - ruleName: new CfnOutput(this, 'RuleName', { value: this.ruleName }).makeImportValue().toString() + configRuleName: new CfnOutput(this, 'RuleName', { value: this.configRuleName }).makeImportValue().toString() }; } @@ -247,12 +257,14 @@ export interface ManagedRuleProps extends RuleProps { /** * A new managed rule. + * + * @resource AWS::Config::ConfigRule */ -export class ManagedRule extends RuleNew implements IRule { - public readonly ruleName: string; - public readonly ruleArn: string; - public readonly ruleId: string; - public readonly ruleComplianceType: string; +export class ManagedRule extends RuleNew { + public readonly configRuleName: string; + public readonly configRuleArn: string; + public readonly configRuleId: string; + public readonly configRuleComplianceType: string; constructor(scope: Construct, id: string, props: ManagedRuleProps) { super(scope, id); @@ -269,10 +281,10 @@ export class ManagedRule extends RuleNew implements IRule { } }); - this.ruleName = rule.configRuleName; - this.ruleArn = rule.configRuleArn; - this.ruleId = rule.configRuleId; - this.ruleComplianceType = rule.configRuleComplianceType; + this.configRuleName = rule.configRuleName; + this.configRuleArn = rule.configRuleArn; + this.configRuleId = rule.configRuleId; + this.configRuleComplianceType = rule.configRuleComplianceType; this.isManaged = true; } @@ -303,12 +315,14 @@ export interface CustomRuleProps extends RuleProps { } /** * A new custom rule. + * + * @resource AWS::Config::ConfigRule */ -export class CustomRule extends RuleNew implements IRule { - public readonly ruleName: string; - public readonly ruleArn: string; - public readonly ruleId: string; - public readonly ruleComplianceType: string; +export class CustomRule extends RuleNew { + public readonly configRuleName: string; + public readonly configRuleArn: string; + public readonly configRuleId: string; + public readonly configRuleComplianceType: string; constructor(scope: Construct, id: string, props: CustomRuleProps) { super(scope, id); @@ -364,10 +378,10 @@ export class CustomRule extends RuleNew implements IRule { } }); - this.ruleName = rule.configRuleName; - this.ruleArn = rule.configRuleArn; - this.ruleId = rule.configRuleId; - this.ruleComplianceType = rule.configRuleComplianceType; + this.configRuleName = rule.configRuleName; + this.configRuleArn = rule.configRuleArn; + this.configRuleId = rule.configRuleId; + this.configRuleComplianceType = rule.configRuleComplianceType; if (props.configurationChanges) { this.isCustomWithChanges = true; diff --git a/packages/@aws-cdk/aws-config/test/test.rule.ts b/packages/@aws-cdk/aws-config/test/test.rule.ts index 0d50e71ee46fc..b2851439a9772 100644 --- a/packages/@aws-cdk/aws-config/test/test.rule.ts +++ b/packages/@aws-cdk/aws-config/test/test.rule.ts @@ -252,13 +252,13 @@ export = { // WHEN const exportedRule = rule.export(); - const importedRule = config.ManagedRule.fromRuleName(stack, 'ImportedRule', 'name'); + const importedRule = config.ManagedRule.fromConfigRuleName(stack, 'ImportedRule', 'name'); // THEN test.deepEqual(stack.node.resolve(exportedRule), { - ruleName: { 'Fn::ImportValue': 'Stack:RuleRuleName1741A255' } + configRuleName: { 'Fn::ImportValue': 'Stack:RuleRuleName1741A255' } }); - test.deepEqual(importedRule.ruleName, 'name'); + test.deepEqual(importedRule.configRuleName, 'name'); test.done(); }, From d8f8fef5eca922d48025b612a724413b16210df1 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Mon, 20 May 2019 10:14:38 +0200 Subject: [PATCH 07/16] ITopic --- packages/@aws-cdk/aws-config/lib/managed-rules.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-config/lib/managed-rules.ts b/packages/@aws-cdk/aws-config/lib/managed-rules.ts index c68288331dbb6..21860e17933c8 100644 --- a/packages/@aws-cdk/aws-config/lib/managed-rules.ts +++ b/packages/@aws-cdk/aws-config/lib/managed-rules.ts @@ -93,7 +93,7 @@ export interface CloudFormationStackNotificationCheckProps extends RuleProps { /** * A list of allowed topics. At most 5 topics. */ - readonly topics?: sns.Topic[]; + readonly topics?: sns.ITopic[]; } /** From bc4a0f747ee2251456714371de948448eb95ce3b Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Mon, 20 May 2019 10:14:52 +0200 Subject: [PATCH 08/16] remove export() --- packages/@aws-cdk/aws-config/lib/rule.ts | 18 +--------------- .../@aws-cdk/aws-config/test/test.rule.ts | 21 ------------------- 2 files changed, 1 insertion(+), 38 deletions(-) diff --git a/packages/@aws-cdk/aws-config/lib/rule.ts b/packages/@aws-cdk/aws-config/lib/rule.ts index 952754050bc3a..077d402f4f1ee 100644 --- a/packages/@aws-cdk/aws-config/lib/rule.ts +++ b/packages/@aws-cdk/aws-config/lib/rule.ts @@ -1,7 +1,7 @@ import events = require('@aws-cdk/aws-events'); import iam = require('@aws-cdk/aws-iam'); import lambda = require('@aws-cdk/aws-lambda'); -import { CfnOutput, Construct, IResource, Resource, Token } from '@aws-cdk/cdk'; +import { Construct, IResource, Resource, Token } from '@aws-cdk/cdk'; import { CfnConfigRule } from './config.generated'; /** @@ -32,11 +32,6 @@ export interface IRule extends IResource { */ readonly configRuleComplianceType?: string; - /** - * Exports this rule from stack. - */ - export(): RuleAttributes; - /** * Defines a CloudWatch event rule which triggers for rule events. Use * `rule.addEventPattern(pattern)` to specify a filter. @@ -87,8 +82,6 @@ abstract class RuleBase extends Resource implements IRule { public abstract readonly configRuleName: string; - public abstract export(): RuleAttributes; - /** * Defines a CloudWatch event rule which triggers for rule events. Use * `rule.addEventPattern(pattern)` to specify a filter. @@ -140,15 +133,6 @@ abstract class RuleNew extends RuleBase { protected isManaged?: boolean; protected isCustomWithChanges?: boolean; - /** - * Exports this rule from the stack. - */ - public export(): RuleAttributes { - return { - configRuleName: new CfnOutput(this, 'RuleName', { value: this.configRuleName }).makeImportValue().toString() - }; - } - /** * Restrict scope of changes to a specific resource. * diff --git a/packages/@aws-cdk/aws-config/test/test.rule.ts b/packages/@aws-cdk/aws-config/test/test.rule.ts index b2851439a9772..489e765deb182 100644 --- a/packages/@aws-cdk/aws-config/test/test.rule.ts +++ b/packages/@aws-cdk/aws-config/test/test.rule.ts @@ -242,27 +242,6 @@ export = { test.done(); }, - 'import/export'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const rule = new config.ManagedRule(stack, 'Rule', { - identifier: 'RULE' - }); - - // WHEN - const exportedRule = rule.export(); - - const importedRule = config.ManagedRule.fromConfigRuleName(stack, 'ImportedRule', 'name'); - - // THEN - test.deepEqual(stack.node.resolve(exportedRule), { - configRuleName: { 'Fn::ImportValue': 'Stack:RuleRuleName1741A255' } - }); - test.deepEqual(importedRule.configRuleName, 'name'); - - test.done(); - }, - 'on compliance change event'(test: Test) { // GIVEN const stack = new cdk.Stack(); From 36aadd10682fea0061305b3a149dc583e0393c21 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Tue, 21 May 2019 14:17:21 +0200 Subject: [PATCH 09/16] typo --- packages/@aws-cdk/aws-config/lib/rule.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-config/lib/rule.ts b/packages/@aws-cdk/aws-config/lib/rule.ts index 077d402f4f1ee..0ffdec96abd2b 100644 --- a/packages/@aws-cdk/aws-config/lib/rule.ts +++ b/packages/@aws-cdk/aws-config/lib/rule.ts @@ -201,7 +201,7 @@ export interface RuleProps { /** * A name for the AWS Config rule. * - * @default a CloudFormation genereated named + * @default a CloudFormation generated name */ readonly name?: string; From 56e7be9a4c9231fbacc92ac73e675e5b3ab55792 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Tue, 21 May 2019 14:17:51 +0200 Subject: [PATCH 10/16] remove remaining export() --- packages/@aws-cdk/aws-config/lib/rule.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/@aws-cdk/aws-config/lib/rule.ts b/packages/@aws-cdk/aws-config/lib/rule.ts index 0ffdec96abd2b..531a368102092 100644 --- a/packages/@aws-cdk/aws-config/lib/rule.ts +++ b/packages/@aws-cdk/aws-config/lib/rule.ts @@ -71,10 +71,6 @@ abstract class RuleBase extends Resource implements IRule { public static fromConfigRuleName(scope: Construct, id: string, configRuleName: string): IRule { class Import extends RuleBase { public readonly configRuleName = configRuleName; - - public export(): RuleAttributes { - return { configRuleName }; - } } return new Import(scope, id); From 156d2c0d9077e1222a9aa0eeae43df0b49799f64 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Tue, 21 May 2019 14:19:45 +0200 Subject: [PATCH 11/16] full link to Managed Rules --- packages/@aws-cdk/aws-config/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-config/README.md b/packages/@aws-cdk/aws-config/README.md index e9006492d01e9..e2cb9e8a5edef 100644 --- a/packages/@aws-cdk/aws-config/README.md +++ b/packages/@aws-cdk/aws-config/README.md @@ -23,7 +23,7 @@ new ManagedRule(this, 'AccessKeysRotated', { Available identifiers and parameters are listed in the [List of AWS Config Managed Rules](https://docs.aws.amazon.com/config/latest/developerguide/managed-rules-by-aws-config.html). -Higher level constructs for managed rules are available, see [Managed Rules](lib/managed-rules.ts). Prefer to use those constructs when available (PRs welcome to add more of those). +Higher level constructs for managed rules are available, see [Managed Rules](https://github.com/awslabs/aws-cdk/blob/master/packages/%40aws-cdk/aws-config/lib/managed-rules.ts). Prefer to use those constructs when available (PRs welcome to add more of those). #### Custom rules To set up a custom rule, define a `CustomRule` and specify the Lambda Function to run and the trigger types: From 66bd7fce93e66171b1730a4e1658f99d4c068a73 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Tue, 21 May 2019 15:02:38 +0200 Subject: [PATCH 12/16] support IRole for drifts and update role following doc update --- .../@aws-cdk/aws-config/lib/managed-rules.ts | 18 +++++--- .../test/integ.rule.lit.expected.json | 45 +++++-------------- .../aws-config/test/test.managed-rules.ts | 45 +++++-------------- 3 files changed, 32 insertions(+), 76 deletions(-) diff --git a/packages/@aws-cdk/aws-config/lib/managed-rules.ts b/packages/@aws-cdk/aws-config/lib/managed-rules.ts index 21860e17933c8..0cc26059a4446 100644 --- a/packages/@aws-cdk/aws-config/lib/managed-rules.ts +++ b/packages/@aws-cdk/aws-config/lib/managed-rules.ts @@ -49,6 +49,16 @@ export interface CloudFormationStackDriftDetectionCheckProps extends RuleProps { * @default false */ readonly ownStackOnly?: boolean; + + /** + * The IAM role to use for this rule. It must have permissions to detect drift + * for AWS CloudFormation stacks. Ensure to attach `config.amazonaws.com` trusted + * permissions and `ReadOnlyAccess` policy permissions. For specific policy permissions, + * refer to https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-stack-drift.html. + * + * @default a role will be created + */ + readonly role?: iam.IRole; } /** @@ -67,20 +77,16 @@ export class CloudFormationStackDriftDetectionCheck extends ManagedRule { ...props, identifier: 'CLOUDFORMATION_STACK_DRIFT_DETECTION_CHECK', inputParameters: { - cloudformationRoleArn: new Token(() => this.role.roleArn) + cloudformationRoleArn: (props.role && props.role.roleArn) || new Token(() => this.role.roleArn) } }); this.addResourceScope('AWS::CloudFormation::Stack', props.ownStackOnly ? this.node.stack.stackId : undefined); this.role = new iam.Role(this, 'Role', { - assumedBy: new iam.CompositePrincipal( - new iam.ServicePrincipal('config.amazonaws.com'), - new iam.ServicePrincipal('cloudformation.amazonaws.com') - ), + assumedBy: new iam.ServicePrincipal('config.amazonaws.com'), managedPolicyArns: [ new iam.AwsManagedPolicy('ReadOnlyAccess', this).policyArn, - new iam.AwsManagedPolicy('AWSCloudFormationReadOnlyAccess', this).policyArn ] }); } diff --git a/packages/@aws-cdk/aws-config/test/integ.rule.lit.expected.json b/packages/@aws-cdk/aws-config/test/integ.rule.lit.expected.json index 152ba3a28952d..eff352b6a34f1 100644 --- a/packages/@aws-cdk/aws-config/test/integ.rule.lit.expected.json +++ b/packages/@aws-cdk/aws-config/test/integ.rule.lit.expected.json @@ -150,30 +150,17 @@ "Action": "sts:AssumeRole", "Effect": "Allow", "Principal": { - "Service": [ - { - "Fn::Join": [ - "", - [ - "config.", - { - "Ref": "AWS::URLSuffix" - } - ] - ] - }, - { - "Fn::Join": [ - "", - [ - "cloudformation.", - { - "Ref": "AWS::URLSuffix" - } - ] + "Service": { + "Fn::Join": [ + "", + [ + "config.", + { + "Ref": "AWS::URLSuffix" + } ] - } - ] + ] + } } } ], @@ -191,18 +178,6 @@ ":iam::aws:policy/ReadOnlyAccess" ] ] - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":iam::aws:policy/AWSCloudFormationReadOnlyAccess" - ] - ] } ] } diff --git a/packages/@aws-cdk/aws-config/test/test.managed-rules.ts b/packages/@aws-cdk/aws-config/test/test.managed-rules.ts index a077c266343ed..6ff4ce2c02ee2 100644 --- a/packages/@aws-cdk/aws-config/test/test.managed-rules.ts +++ b/packages/@aws-cdk/aws-config/test/test.managed-rules.ts @@ -58,30 +58,17 @@ export = { Action: 'sts:AssumeRole', Effect: 'Allow', Principal: { - Service: [ - { - 'Fn::Join': [ - '', - [ - 'config.', - { - Ref: 'AWS::URLSuffix' - } - ] + Service: { + 'Fn::Join': [ + '', + [ + 'config.', + { + Ref: 'AWS::URLSuffix' + } ] - }, - { - 'Fn::Join': [ - '', - [ - 'cloudformation.', - { - Ref: 'AWS::URLSuffix' - } - ] - ] - } - ] + ] + } } } ], @@ -99,18 +86,6 @@ export = { ':iam::aws:policy/ReadOnlyAccess' ] ] - }, - { - 'Fn::Join': [ - '', - [ - 'arn:', - { - Ref: 'AWS::Partition' - }, - ':iam::aws:policy/AWSCloudFormationReadOnlyAccess' - ] - ] } ] })); From 34fd2b9ee728dd04fb39c4411d8df0117bd99f9c Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Tue, 21 May 2019 15:21:54 +0200 Subject: [PATCH 13/16] better role --- packages/@aws-cdk/aws-config/lib/managed-rules.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/@aws-cdk/aws-config/lib/managed-rules.ts b/packages/@aws-cdk/aws-config/lib/managed-rules.ts index 0cc26059a4446..ec19a7d035f09 100644 --- a/packages/@aws-cdk/aws-config/lib/managed-rules.ts +++ b/packages/@aws-cdk/aws-config/lib/managed-rules.ts @@ -70,20 +70,20 @@ export interface CloudFormationStackDriftDetectionCheckProps extends RuleProps { * @resource AWS::Config::ConfigRule */ export class CloudFormationStackDriftDetectionCheck extends ManagedRule { - private role: iam.Role; + private readonly role: iam.IRole; constructor(scope: Construct, id: string, props: CloudFormationStackDriftDetectionCheckProps = {}) { super(scope, id, { ...props, identifier: 'CLOUDFORMATION_STACK_DRIFT_DETECTION_CHECK', inputParameters: { - cloudformationRoleArn: (props.role && props.role.roleArn) || new Token(() => this.role.roleArn) + cloudformationRoleArn: new Token(() => this.role.roleArn) } }); this.addResourceScope('AWS::CloudFormation::Stack', props.ownStackOnly ? this.node.stack.stackId : undefined); - this.role = new iam.Role(this, 'Role', { + this.role = props.role || new iam.Role(this, 'Role', { assumedBy: new iam.ServicePrincipal('config.amazonaws.com'), managedPolicyArns: [ new iam.AwsManagedPolicy('ReadOnlyAccess', this).policyArn, From 1a6786802ec16e5d5757b087711d956c5259b34c Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Tue, 21 May 2019 16:13:23 +0200 Subject: [PATCH 14/16] EventRule > Rule --- packages/@aws-cdk/aws-config/lib/rule.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/@aws-cdk/aws-config/lib/rule.ts b/packages/@aws-cdk/aws-config/lib/rule.ts index 531a368102092..d113d51d7c7b2 100644 --- a/packages/@aws-cdk/aws-config/lib/rule.ts +++ b/packages/@aws-cdk/aws-config/lib/rule.ts @@ -36,17 +36,17 @@ export interface IRule extends IResource { * Defines a CloudWatch event rule which triggers for rule events. Use * `rule.addEventPattern(pattern)` to specify a filter. */ - onEvent(id: string, target?: events.IEventRuleTarget, options?: events.EventRuleProps): events.EventRule; + onEvent(id: string, target?: events.IRuleTarget, options?: events.RuleProps): events.Rule; /** * Defines a CloudWatch event rule which triggers for rule compliance events. */ - onComplianceChange(id: string, target?: events.IEventRuleTarget, options?: events.EventRuleProps): events.EventRule; + onComplianceChange(id: string, target?: events.IRuleTarget, options?: events.RuleProps): events.Rule; /** * Defines a CloudWatch event rule which triggers for rule re-evaluation status events. */ - onReEvaluationStatus(id: string, target?: events.IEventRuleTarget, options?: events.EventRuleProps): events.EventRule; + onReEvaluationStatus(id: string, target?: events.IRuleTarget, options?: events.RuleProps): events.Rule; } /** @@ -82,8 +82,8 @@ abstract class RuleBase extends Resource implements IRule { * Defines a CloudWatch event rule which triggers for rule events. Use * `rule.addEventPattern(pattern)` to specify a filter. */ - public onEvent(id: string, target?: events.IEventRuleTarget, options?: events.EventRuleProps) { - const rule = new events.EventRule(this, id, options); + public onEvent(id: string, target?: events.IRuleTarget, options?: events.RuleProps) { + const rule = new events.Rule(this, id, options); rule.addEventPattern({ source: ['aws.config'], detail: { @@ -97,7 +97,7 @@ abstract class RuleBase extends Resource implements IRule { /** * Defines a CloudWatch event rule which triggers for rule compliance events. */ - public onComplianceChange(id: string, target?: events.IEventRuleTarget, options?: events.EventRuleProps): events.EventRule { + public onComplianceChange(id: string, target?: events.IRuleTarget, options?: events.RuleProps): events.Rule { const rule = this.onEvent(id, target, options); rule.addEventPattern({ detailType: [ 'Config Rules Compliance Change' ], @@ -108,7 +108,7 @@ abstract class RuleBase extends Resource implements IRule { /** * Defines a CloudWatch event rule which triggers for rule re-evaluation status events. */ - public onReEvaluationStatus(id: string, target?: events.IEventRuleTarget, options?: events.EventRuleProps): events.EventRule { + public onReEvaluationStatus(id: string, target?: events.IRuleTarget, options?: events.RuleProps): events.Rule { const rule = this.onEvent(id, target, options); rule.addEventPattern({ detailType: [ 'Config Rules Re-evaluation Status' ], From 7469643c7f8980a1e57a7e5e9be99623cd071624 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Wed, 22 May 2019 13:36:42 +0200 Subject: [PATCH 15/16] remove optionals from IRule --- packages/@aws-cdk/aws-config/lib/rule.ts | 44 ++++++++++++++---------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/packages/@aws-cdk/aws-config/lib/rule.ts b/packages/@aws-cdk/aws-config/lib/rule.ts index d113d51d7c7b2..6876d1d4e0108 100644 --- a/packages/@aws-cdk/aws-config/lib/rule.ts +++ b/packages/@aws-cdk/aws-config/lib/rule.ts @@ -10,28 +10,11 @@ import { CfnConfigRule } from './config.generated'; export interface IRule extends IResource { /** * The name of the rule. + * * @attribute */ readonly configRuleName: string; - /** - * The arn of the rule. - * @attribute - */ - readonly configRuleArn?: string; - - /** - * The id of the rule. - * @attribute - */ - readonly configRuleId?: string; - - /** - * The compliance status of the rule. - * @attribute - */ - readonly configRuleComplianceType?: string; - /** * Defines a CloudWatch event rule which triggers for rule events. Use * `rule.addEventPattern(pattern)` to specify a filter. @@ -121,8 +104,19 @@ abstract class RuleBase extends Resource implements IRule { * A new managed or custom rule. */ abstract class RuleNew extends RuleBase { + /** + * The arn of the rule. + */ public abstract readonly configRuleArn: string; + + /** + * The id of the rule. + */ public abstract readonly configRuleId: string; + + /** + * The compliance status of the rule. + */ public abstract readonly configRuleComplianceType: string; protected scope?: CfnConfigRule.ScopeProperty; @@ -241,9 +235,16 @@ export interface ManagedRuleProps extends RuleProps { * @resource AWS::Config::ConfigRule */ export class ManagedRule 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: ManagedRuleProps) { @@ -299,9 +300,16 @@ export interface CustomRuleProps extends RuleProps { * @resource AWS::Config::ConfigRule */ export class CustomRule 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: CustomRuleProps) { From 22a21de6318807b2870f602492f87c3e111ee8a3 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Wed, 22 May 2019 13:44:41 +0200 Subject: [PATCH 16/16] addXxx() > scopeToXxx() --- packages/@aws-cdk/aws-config/README.md | 8 ++++---- .../@aws-cdk/aws-config/lib/managed-rules.ts | 4 ++-- packages/@aws-cdk/aws-config/lib/rule.ts | 16 ++++++++-------- .../@aws-cdk/aws-config/test/integ.rule.lit.ts | 2 +- packages/@aws-cdk/aws-config/test/test.rule.ts | 16 ++++++++-------- 5 files changed, 23 insertions(+), 23 deletions(-) diff --git a/packages/@aws-cdk/aws-config/README.md b/packages/@aws-cdk/aws-config/README.md index e2cb9e8a5edef..98c190ed0b719 100644 --- a/packages/@aws-cdk/aws-config/README.md +++ b/packages/@aws-cdk/aws-config/README.md @@ -37,7 +37,7 @@ new CustomRule(this, 'CustomRule', { ``` #### Restricting the scope -By default rules are triggered by changes to all [resources](https://docs.aws.amazon.com/config/latest/developerguide/resource-config-reference.html#supported-resources). Use the `addResourceScope()`, `addResourcesScope()` or `addTagScope()` methods to restrict the scope of both managed and custom rules: +By default rules are triggered by changes to all [resources](https://docs.aws.amazon.com/config/latest/developerguide/resource-config-reference.html#supported-resources). Use the `scopeToResource()`, `scopeToResources()` or `scopeToTag()` methods to restrict the scope of both managed and custom rules: ```ts const sshRule = new ManagedRule(this, 'SSH', { @@ -45,7 +45,7 @@ const sshRule = new ManagedRule(this, 'SSH', { }); // Restrict to a specific security group -rule.addResourceScope('AWS::EC2::SecurityGroup', 'sg-1234567890abcdefgh'); +rule.scopeToResource('AWS::EC2::SecurityGroup', 'sg-1234567890abcdefgh'); const customRule = new CustomRule(this, 'CustomRule', { lambdaFunction: myFn, @@ -53,10 +53,10 @@ const customRule = new CustomRule(this, 'CustomRule', { }); // Restrict to a specific tag -customRule.addTagScope('Cost Center', 'MyApp'); +customRule.scopeToTag('Cost Center', 'MyApp'); ``` -Only one type of scope restriction can be added to a rule (the last call to `addXxx()` sets the scope). +Only one type of scope restriction can be added to a rule (the last call to `scopeToXxx()` sets the scope). #### Events To define Amazon CloudWatch event rules, use the `onComplianceChange()` or `onReEvaluationStatus()` methods: diff --git a/packages/@aws-cdk/aws-config/lib/managed-rules.ts b/packages/@aws-cdk/aws-config/lib/managed-rules.ts index ec19a7d035f09..0c54803799966 100644 --- a/packages/@aws-cdk/aws-config/lib/managed-rules.ts +++ b/packages/@aws-cdk/aws-config/lib/managed-rules.ts @@ -81,7 +81,7 @@ export class CloudFormationStackDriftDetectionCheck extends ManagedRule { } }); - this.addResourceScope('AWS::CloudFormation::Stack', props.ownStackOnly ? this.node.stack.stackId : undefined); + this.scopeToResource('AWS::CloudFormation::Stack', props.ownStackOnly ? this.node.stack.stackId : undefined); this.role = props.role || new iam.Role(this, 'Role', { assumedBy: new iam.ServicePrincipal('config.amazonaws.com'), @@ -125,6 +125,6 @@ export class CloudFormationStackNotificationCheck extends ManagedRule { ) }); - this.addResourceScope('AWS::CloudFormation::Stack'); + this.scopeToResource('AWS::CloudFormation::Stack'); } } diff --git a/packages/@aws-cdk/aws-config/lib/rule.ts b/packages/@aws-cdk/aws-config/lib/rule.ts index 6876d1d4e0108..3d9b742763d88 100644 --- a/packages/@aws-cdk/aws-config/lib/rule.ts +++ b/packages/@aws-cdk/aws-config/lib/rule.ts @@ -131,8 +131,8 @@ abstract class RuleNew extends RuleBase { * @param type the resource type * @param identifier the resource identifier */ - public addResourceScope(type: string, identifier?: string) { - this.addScope({ + public scopeToResource(type: string, identifier?: string) { + this.scopeTo({ complianceResourceId: identifier, complianceResourceTypes: [type], }); @@ -145,8 +145,8 @@ abstract class RuleNew extends RuleBase { * * @param types resource types */ - public addResourcesScope(...types: string[]) { - this.addScope({ + public scopeToResources(...types: string[]) { + this.scopeTo({ complianceResourceTypes: types }); } @@ -157,16 +157,16 @@ abstract class RuleNew extends RuleBase { * @param key the tag key * @param value the tag value */ - public addTagScope(key: string, value?: string) { - this.addScope({ + public scopeToTag(key: string, value?: string) { + this.scopeTo({ tagKey: key, tagValue: value }); } - private addScope(scope: CfnConfigRule.ScopeProperty) { + private scopeTo(scope: CfnConfigRule.ScopeProperty) { if (!this.isManaged && !this.isCustomWithChanges) { - throw new Error('Cannot set scope when `configurationChanges` is set to false.'); + throw new Error('Cannot scope rule when `configurationChanges` is set to false.'); } this.scope = scope; diff --git a/packages/@aws-cdk/aws-config/test/integ.rule.lit.ts b/packages/@aws-cdk/aws-config/test/integ.rule.lit.ts index 8058aa4761ce5..db58cda3f4979 100644 --- a/packages/@aws-cdk/aws-config/test/integ.rule.lit.ts +++ b/packages/@aws-cdk/aws-config/test/integ.rule.lit.ts @@ -23,7 +23,7 @@ class ConfigStack extends cdk.Stack { lambdaFunction: fn }); - customRule.addResourceScope('AWS::EC2::Instance'); + customRule.scopeToResource('AWS::EC2::Instance'); // A rule to detect stacks drifts const driftRule = new config.CloudFormationStackDriftDetectionCheck(this, 'Drift'); diff --git a/packages/@aws-cdk/aws-config/test/test.rule.ts b/packages/@aws-cdk/aws-config/test/test.rule.ts index 489e765deb182..38d8c9b2099f0 100644 --- a/packages/@aws-cdk/aws-config/test/test.rule.ts +++ b/packages/@aws-cdk/aws-config/test/test.rule.ts @@ -137,7 +137,7 @@ export = { test.done(); }, - 'add resource scope'(test: Test) { + 'scope to resource'(test: Test) { // GIVEN const stack = new cdk.Stack(); const rule = new config.ManagedRule(stack, 'Rule', { @@ -145,7 +145,7 @@ export = { }); // WHEN - rule.addResourceScope('AWS::EC2::Instance', 'i-1234'); + rule.scopeToResource('AWS::EC2::Instance', 'i-1234'); // THEN expect(stack).to(haveResource('AWS::Config::ConfigRule', { @@ -160,7 +160,7 @@ export = { test.done(); }, - 'add resources scope'(test: Test) { + 'scope to resources'(test: Test) { // GIVEN const stack = new cdk.Stack(); const rule = new config.ManagedRule(stack, 'Rule', { @@ -168,7 +168,7 @@ export = { }); // WHEN - rule.addResourcesScope('AWS::S3::Bucket', 'AWS::CloudFormation::Stack'); + rule.scopeToResources('AWS::S3::Bucket', 'AWS::CloudFormation::Stack'); // THEN expect(stack).to(haveResource('AWS::Config::ConfigRule', { @@ -183,7 +183,7 @@ export = { test.done(); }, - 'add tag scope'(test: Test) { + 'scope to tag'(test: Test) { // GIVEN const stack = new cdk.Stack(); const rule = new config.ManagedRule(stack, 'Rule', { @@ -191,7 +191,7 @@ export = { }); // WHEN - rule.addTagScope('key', 'value'); + rule.scopeToTag('key', 'value'); // THEN expect(stack).to(haveResource('AWS::Config::ConfigRule', { @@ -204,7 +204,7 @@ export = { test.done(); }, - 'throws when adding scope to custom rule without configuration changes'(test: Test) { + 'throws when scoping a custom rule without configuration changes'(test: Test) { // GIVEN const stack = new cdk.Stack(); const fn = new lambda.Function(stack, 'Function', { @@ -220,7 +220,7 @@ export = { }); // THEN - test.throws(() => rule.addResourceScope('resource'), /`configurationChanges`/); + test.throws(() => rule.scopeToResource('resource'), /`configurationChanges`/); test.done(); },