Skip to content

Commit

Permalink
feat(config): AWS Config, Managed and Custom rules (#2326)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
jogold authored and rix0rrr committed May 23, 2019
1 parent 7dd0e1a commit deed353
Show file tree
Hide file tree
Showing 10 changed files with 1,310 additions and 8 deletions.
70 changes: 70 additions & 0 deletions packages/@aws-cdk/aws-config/README.md
Original file line number Diff line number Diff line change
@@ -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](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:

```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 `scopeToResource()`, `scopeToResources()` or `scopeToTag()` 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.scopeToResource('AWS::EC2::SecurityGroup', 'sg-1234567890abcdefgh');

const customRule = new CustomRule(this, 'CustomRule', {
lambdaFunction: myFn,
configurationChanges: true
});

// Restrict to a specific tag
customRule.scopeToTag('Cost Center', 'MyApp');
```

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:

```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)
3 changes: 3 additions & 0 deletions packages/@aws-cdk/aws-config/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
export * from './rule';
export * from './managed-rules';

// AWS::Config CloudFormation Resources:
export * from './config.generated';
130 changes: 130 additions & 0 deletions packages/@aws-cdk/aws-config/lib/managed-rules.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
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
*
* @resource AWS::Config::ConfigRule
*/
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;

/**
* 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;
}

/**
* 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
*
* @resource AWS::Config::ConfigRule
*/
export class CloudFormationStackDriftDetectionCheck extends ManagedRule {
private readonly role: iam.IRole;

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.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'),
managedPolicyArns: [
new iam.AwsManagedPolicy('ReadOnlyAccess', this).policyArn,
]
});
}
}

/**
* Construction properties for a CloudFormationStackNotificationCheck.
*/
export interface CloudFormationStackNotificationCheckProps extends RuleProps {
/**
* A list of allowed topics. At most 5 topics.
*/
readonly topics?: sns.ITopic[];
}

/**
* 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
*
* @resource AWS::Config::ConfigRule
*/
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.scopeToResource('AWS::CloudFormation::Stack');
}
}
Loading

0 comments on commit deed353

Please sign in to comment.