-
Notifications
You must be signed in to change notification settings - Fork 4.4k
feat(events-targets): target systems manager run command and automation with event rules #22636
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
Changes from all commits
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 |
|---|---|---|
| @@ -0,0 +1,98 @@ | ||
| import * as events from '@aws-cdk/aws-events'; | ||
| import * as iam from '@aws-cdk/aws-iam'; | ||
| import * as ssm from '@aws-cdk/aws-ssm'; | ||
| import * as cdk from '@aws-cdk/core'; | ||
| import { addToDeadLetterQueueResourcePolicy, bindBaseTargetConfig, singletonEventRole, TargetBaseProps } from './util'; | ||
|
|
||
| /** | ||
| * Create an SSM Automation Event Target | ||
| */ | ||
| export interface SsmAutomationProps extends TargetBaseProps { | ||
| /** | ||
| * Role to be used for invoking the Automation from the Rule. This should be a | ||
| * role that allows the the events.amazonaws.com service principal to assume | ||
| * and execute the Automation. This role is not used by the Automation itself, | ||
| * to execute the actions in the document, see `automationAssumeRole` for that. | ||
| * | ||
| * @default - a new role is created. | ||
| */ | ||
| readonly role?: iam.IRole; | ||
|
|
||
| /** | ||
| * The input parameters for the Automation document. | ||
| * | ||
| * @default - no input parameters passed to the document | ||
| */ | ||
| readonly input?: { [key: string]: string[] }; | ||
|
|
||
| /** | ||
| * Role to be used to run the Automation on your behalf. This should be a role | ||
| * that allows the Automation service principal (ssm.amazonaws.com) to assume | ||
| * and run the actions in your Automation document. | ||
| * | ||
| * @default - no role assumed | ||
| */ | ||
| readonly automationAssumeRole?: iam.IRole; | ||
| } | ||
|
|
||
| /** | ||
| * Create an SSM Automation Event Target | ||
| */ | ||
| export class SsmAutomation implements events.IRuleTarget { | ||
| private documentArn: string; | ||
|
|
||
| constructor( | ||
| /** | ||
| * Can be an instance of `ssm.CfnDocument` or a share/managed document ARN. | ||
| */ | ||
| public readonly document: ssm.CfnDocument | string, | ||
|
Contributor
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. Union types don't work with jsii.
Contributor
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. In fact, I don't think we want to use the Cfn type of anything here. When Document gets implemented, this will then create a breaking change. This should use an
Contributor
Author
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. I had the exact same thought and started down that road.. but figured I would get your feedback first. Is there any existing or in-progress work already on implementing L2 constructs for
Contributor
Author
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. I'd be willing to start the implementation of Document. Is there any documentation, standards, or requirements I can follow or reference for that implementation? Other than looking at other similar L2 implementations.
Contributor
Author
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. One more thought.. once Document is implemented, there would be no need for the union type as theoretically (and hopefully) there would be some sort of
Contributor
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. See this package: https://github.com/aws/aws-cdk/tree/main/packages/%40aws-cdk/example-construct-library Specifically, the example resource, as that would be what the Document would be. Please also open that in a separate PR. From the documentation it looks like this should be a pretty simple implementation. |
||
| private readonly props: SsmAutomationProps, | ||
| ) { | ||
| this.documentArn = this.getDocumentArn(); | ||
| } | ||
|
|
||
| /** | ||
| * Returns a RuleTarget that can be used to trigger this SSM Automation as a | ||
| * result from an EventBridge event. | ||
| * | ||
| * @see https://docs.aws.amazon.com/eventbridge/latest/userguide/resource-based-policies-eventbridge.html | ||
| */ | ||
| public bind(rule: events.IRule, _id?: string): events.RuleTargetConfig { | ||
| const role = this.props.role ?? singletonEventRole(rule); | ||
| role.addToPrincipalPolicy(this.executeStatement()); | ||
|
|
||
| if (this.props.deadLetterQueue) { | ||
| addToDeadLetterQueueResourcePolicy(rule, this.props.deadLetterQueue); | ||
| } | ||
|
|
||
| return { | ||
| ...bindBaseTargetConfig(this.props), | ||
| arn: this.documentArn, | ||
| input: events.RuleTargetInput.fromObject({ ...this.props.input, AutomationAssumeRole: [this.props.automationAssumeRole?.roleArn] }), | ||
| role, | ||
| targetResource: (typeof this.document === 'string') ? undefined : this.document, | ||
| }; | ||
| } | ||
|
|
||
| private getDocumentArn(): string { | ||
| if (typeof this.document === 'string') { | ||
| return this.document; | ||
| } else { | ||
| return cdk.Arn.format({ | ||
| service: 'ssm', | ||
| resource: 'automation-definition', | ||
| resourceName: this.document.name, | ||
| region: cdk.Aws.REGION, | ||
| account: cdk.Aws.ACCOUNT_ID, | ||
| partition: cdk.Aws.PARTITION, | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| private executeStatement(): iam.PolicyStatement { | ||
| return new iam.PolicyStatement({ | ||
| actions: ['ssm:StartAutomationExecution'], | ||
| resources: [this.documentArn], | ||
| }); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,133 @@ | ||
| import * as events from '@aws-cdk/aws-events'; | ||
| import * as iam from '@aws-cdk/aws-iam'; | ||
| import * as ssm from '@aws-cdk/aws-ssm'; | ||
| import * as cdk from '@aws-cdk/core'; | ||
| import { addToDeadLetterQueueResourcePolicy, bindBaseTargetConfig, singletonEventRole, TargetBaseProps } from './util'; | ||
|
|
||
| /** | ||
| * SsmRunCommandProps | ||
| */ | ||
| export interface SsmRunCommandProps extends TargetBaseProps { | ||
| /** | ||
| * Role to be used to run the Document | ||
| * | ||
| * @default - a new role is created. | ||
| */ | ||
| readonly role?: iam.IRole; | ||
|
|
||
| /** | ||
| * Can be either `tag:` *tag-key* or `InstanceIds` . | ||
| * | ||
| * @link http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-events-rule-runcommandtarget.html#cfn-events-rule-runcommandtarget-key | ||
| */ | ||
| readonly targetKey: events.CfnRule.RunCommandTargetProperty['key']; | ||
|
|
||
| /** | ||
| * If Target Key is 'InstanceIds', Values are the list of EC2 Instance Ids. If Target Key is 'tag:<Amazon EC2 tag>', Values are list of tag values. | ||
| */ | ||
| readonly targetValues: string[]; | ||
|
|
||
| /** | ||
| * Specify input for the SSM Document if appropriate. | ||
| * | ||
| * @default - no input parameters passed to document | ||
| */ | ||
| readonly input?: { [key: string]: string[] }; | ||
| } | ||
|
|
||
| /** | ||
| * Create an SSM Run Command Event Target | ||
| */ | ||
| export class SsmRunCommand implements events.IRuleTarget { | ||
| private documentArn: string; | ||
|
|
||
| constructor( | ||
| /** | ||
| * Provide an instance of a `ssm.CfnDocument` | ||
| */ | ||
| public readonly document: ssm.CfnDocument, | ||
| private readonly props: SsmRunCommandProps, | ||
| ) { | ||
| this.documentArn = this.getDocumentArn(); | ||
| } | ||
|
|
||
| /** | ||
| * Returns a RuleTarget that can be used to trigger this SSM Run Command as a | ||
| * result from an EventBridge event. | ||
| * | ||
| * @see https://docs.aws.amazon.com/eventbridge/latest/userguide/resource-based-policies-eventbridge.html | ||
| */ | ||
| // eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
| public bind(rule: events.IRule, _id?: string): events.RuleTargetConfig { | ||
| const role = this.props.role ?? singletonEventRole(rule); | ||
| role.addToPrincipalPolicy(this.executeStatement()); | ||
|
|
||
| if (this.props.deadLetterQueue) { | ||
| addToDeadLetterQueueResourcePolicy(rule, this.props.deadLetterQueue); | ||
| } | ||
|
|
||
| return { | ||
| ...bindBaseTargetConfig(this.props), | ||
| arn: this.documentArn, | ||
| role, | ||
| input: events.RuleTargetInput.fromObject(this.props.input), | ||
| runCommandParameters: { | ||
| runCommandTargets: [ | ||
| { | ||
| key: this.props.targetKey, | ||
| values: this.props.targetValues, | ||
| }, | ||
| ], | ||
| }, | ||
| targetResource: this.document, | ||
| }; | ||
| } | ||
|
|
||
| private getDocumentArn(): string { | ||
| return cdk.Arn.format({ | ||
| service: 'ssm', | ||
| resource: 'document', | ||
| resourceName: this.document.name, | ||
| region: cdk.Aws.REGION, | ||
| account: cdk.Aws.ACCOUNT_ID, | ||
| partition: cdk.Aws.PARTITION, | ||
| }); | ||
| } | ||
|
|
||
| private executeStatement(): iam.PolicyStatement { | ||
| if (this.props.targetKey === 'InstanceIds') { | ||
| return new iam.PolicyStatement({ | ||
| actions: ['ssm:SendCommand'], | ||
| resources: this.props.targetValues.map(instanceId => | ||
| cdk.Arn.format({ | ||
| service: 'ec2', | ||
| resource: 'instance', | ||
| resourceName: instanceId, | ||
| region: cdk.Aws.REGION, | ||
| account: cdk.Aws.ACCOUNT_ID, | ||
| partition: cdk.Aws.PARTITION, | ||
| }), | ||
| ), | ||
| }); | ||
| } else { | ||
| return new iam.PolicyStatement({ | ||
| actions: ['ssm:SendCommand'], | ||
| resources: [ | ||
| cdk.Arn.format({ | ||
| service: 'ec2', | ||
| resource: 'instance', | ||
| resourceName: '*', | ||
| region: cdk.Aws.REGION, | ||
| account: cdk.Aws.ACCOUNT_ID, | ||
| partition: cdk.Aws.PARTITION, | ||
| }), | ||
| ], | ||
| conditions: { | ||
| StringEquals: { | ||
| 'ec2:ResourceTag/*': this.props.targetValues, | ||
| }, | ||
| }, | ||
| }); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| import * as events from '@aws-cdk/aws-events'; | ||
| import * as iam from '@aws-cdk/aws-iam'; | ||
| import * as sqs from '@aws-cdk/aws-sqs'; | ||
| import * as cdk from '@aws-cdk/core'; | ||
| import * as integ from '@aws-cdk/integ-tests'; | ||
| import { Construct } from 'constructs'; | ||
| import * as targets from '../../lib'; | ||
|
|
||
| class TestStack extends cdk.Stack { | ||
| constructor(scope: Construct, id: string) { | ||
| super(scope, id); | ||
|
|
||
| const event = new events.Rule(this, 'MyRule', { | ||
| schedule: events.Schedule.rate(cdk.Duration.minutes(1)), | ||
| }); | ||
|
|
||
| const automationArn = `arn:aws:ssm:${cdk.Stack.of(this).region}::automation-definition/AWS-StopRdsInstance:$DEFAULT`; | ||
|
|
||
| const deadLetterQueue = new sqs.Queue(this, 'MyDeadLetterQueue'); | ||
|
|
||
| const automationAssumeRole = new iam.Role(this, 'AutomationAssumeRole', { | ||
| assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), | ||
| }); | ||
|
|
||
| event.addTarget(new targets.SsmAutomation(automationArn, { | ||
| input: { | ||
| InstanceId: ['my-rds-instance'], | ||
| }, | ||
| automationAssumeRole, | ||
| deadLetterQueue, | ||
| })); | ||
| } | ||
| } | ||
|
|
||
| const app = new cdk.App(); | ||
|
|
||
| new integ.IntegTest(app, 'Testing', { | ||
| testCases: [ | ||
| new TestStack(app, 'aws-cdk-ssm-automation-event-target'), | ||
| ], | ||
| }); |
Uh oh!
There was an error while loading. Please reload this page.