-
Notifications
You must be signed in to change notification settings - Fork 4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(pipelines): confirm IAM changes before starting the deployment (#…
…15441) Add an option under `addApplication` for a given stage to create a CodeBuild that checks if there are any security changes within the stage's assembly. * If the changes exist: **manual approval is required** * else: a lambda function will automatically approve the manual approval action Adding a security check to an application creates two actions that precede the prepare and deploy actions of an application: 1. A CodeBuild Project that runs a security diff on the stage 2. A Manual Approval Action that can be approved via a shared Lambda function. ```txt Pipeline ├── Stage: Build │ └── ... ├── Stage: Synth │ └── ... ├── Stage: UpdatePipeline │ └── ... ├── Stage: MyApplicationStage │ └── Actions │ ├── MyApplicationSecurityCheck // Security Diff Action │ ├── MyApplicationManualApproval // Manual Approval Action │ ├── Stack.Prepare │ └── Stack.Deploy └── ... ``` <details> <summary>Example Usage</summary> You can enable the security check in one of two ways: 1. Enable security check across the entire `CdkStage` ```ts const pipeline = new CdkPipeline(app, 'Pipeline', { // ...source and build information here (see above) }); const stage = pipeline.addApplicationStage(new MyApplication(this, 'Testing'), { securityCheck: true, }); // The 'PreProd' application is also run against a security diff because we configured // the stage to enable security checks stage.addApplication(new MyApplication(this, 'PreProd')); ``` 2. Enable security check for a single application ```ts const pipeline = new CdkPipeline(app, 'Pipeline', { // ...source and build information here (see above) }); const stage = pipeline.addApplicationStage(new MyApplication(this, 'NoCheck')); stage.addApplication(new MyApplication(this, 'RunSecurityDiff'), { securityCheck: true, }); ``` </details> Fixes: #12748 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
- Loading branch information
1 parent
22f2499
commit ebba618
Showing
15 changed files
with
3,526 additions
and
36 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -495,4 +495,60 @@ const validationAction = new ShellScriptAction({ | |
// 'test.js' was produced from 'test/test.ts' during the synth step | ||
commands: ['node ./test.js'], | ||
}); | ||
``` | ||
``` | ||
|
||
### Confirm permissions broadening | ||
|
||
To keep tabs on the security impact of changes going out through your pipeline, | ||
you can insert a security check before any stage deployment. This security check | ||
will check if the upcoming deployment would add any new IAM permissions or | ||
security group rules, and if so pause the pipeline and require you to confirm | ||
the changes. | ||
|
||
The security check will appear as two distinct actions in your pipeline: first | ||
a CodeBuild project that runs `cdk diff` on the stage that's about to be deployed, | ||
followed by a Manual Approval action that pauses the pipeline. If it so happens | ||
that there no new IAM permissions or security group rules will be added by the deployment, | ||
the manual approval step is automatically satisfied. The pipeline will look like this: | ||
|
||
```txt | ||
Pipeline | ||
├── ... | ||
├── MyApplicationStage | ||
│ ├── MyApplicationSecurityCheck // Security Diff Action | ||
│ ├── MyApplicationManualApproval // Manual Approval Action | ||
│ ├── Stack.Prepare | ||
│ └── Stack.Deploy | ||
└── ... | ||
``` | ||
|
||
You can enable the security check by passing `confirmBroadeningPermissions` to | ||
`addApplicationStage`: | ||
|
||
```ts | ||
const stage = pipeline.addApplicationStage(new MyApplication(this, 'PreProd'), { | ||
confirmBroadeningPermissions: true, | ||
}); | ||
``` | ||
|
||
To get notified when there is a change that needs your manual approval, | ||
create an SNS Topic, subscribe your own email address, and pass it in via | ||
`securityNotificationTopic`: | ||
|
||
```ts | ||
import * as sns from '@aws-cdk/aws-sns'; | ||
import * as subscriptions from '@aws-cdk/aws-sns-subscriptions'; | ||
import * as pipelines from '@aws-cdk/pipelines'; | ||
|
||
const topic = new sns.Topic(this, 'SecurityChangesTopic'); | ||
topic.addSubscription(new subscriptions.EmailSubscription('[email protected]')); | ||
|
||
const pipeline = new CdkPipeline(app, 'Pipeline', { /* ... */ }); | ||
const stage = pipeline.addApplicationStage(new MyApplication(this, 'PreProd'), { | ||
confirmBroadeningPermissions: true, | ||
securityNotificationTopic: topic, | ||
}); | ||
``` | ||
|
||
**Note**: Manual Approvals notifications only apply when an application has security | ||
check enabled. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -840,7 +840,7 @@ and orphan the old bucket. You should manually delete the orphaned bucket | |
after you are sure you have redeployed all CDK applications and there are no | ||
more references to the old asset bucket. | ||
|
||
## Security Tips | ||
## Security Considerations | ||
|
||
It's important to stay safe while employing Continuous Delivery. The CDK Pipelines | ||
library comes with secure defaults to the best of our ability, but by its | ||
|
@@ -862,6 +862,68 @@ We therefore expect you to mind the following: | |
changes can be deployed through git. Avoid the chances of credentials leaking | ||
by not having them in the first place! | ||
|
||
### Confirm permissions broadening | ||
|
||
To keep tabs on the security impact of changes going out through your pipeline, | ||
you can insert a security check before any stage deployment. This security check | ||
will check if the upcoming deployment would add any new IAM permissions or | ||
security group rules, and if so pause the pipeline and require you to confirm | ||
the changes. | ||
|
||
The security check will appear as two distinct actions in your pipeline: first | ||
a CodeBuild project that runs `cdk diff` on the stage that's about to be deployed, | ||
followed by a Manual Approval action that pauses the pipeline. If it so happens | ||
that there no new IAM permissions or security group rules will be added by the deployment, | ||
the manual approval step is automatically satisfied. The pipeline will look like this: | ||
|
||
```txt | ||
Pipeline | ||
├── ... | ||
├── MyApplicationStage | ||
│ ├── MyApplicationSecurityCheck // Security Diff Action | ||
│ ├── MyApplicationManualApproval // Manual Approval Action | ||
│ ├── Stack.Prepare | ||
│ └── Stack.Deploy | ||
└── ... | ||
``` | ||
|
||
You can insert the security check by using a `ConfirmPermissionsBroadening` step: | ||
|
||
```ts | ||
const stage = new MyApplicationStage(this, 'MyApplication'); | ||
pipeline.addStage(stage, { | ||
pre: [ | ||
new ConfirmPermissionsBroadening('Check', { stage }), | ||
], | ||
}); | ||
``` | ||
|
||
To get notified when there is a change that needs your manual approval, | ||
create an SNS Topic, subscribe your own email address, and pass it in as | ||
as the `notificationTopic` property: | ||
|
||
```ts | ||
import * as sns from '@aws-cdk/aws-sns'; | ||
import * as subscriptions from '@aws-cdk/aws-sns-subscriptions'; | ||
import * as pipelines from '@aws-cdk/pipelines'; | ||
|
||
const topic = new sns.Topic(this, 'SecurityChangesTopic'); | ||
topic.addSubscription(new subscriptions.EmailSubscription('[email protected]')); | ||
|
||
const stage = new MyApplicationStage(this, 'MyApplication'); | ||
pipeline.addStage(stage, { | ||
pre: [ | ||
new ConfirmPermissionsBroadening('Check', { | ||
stage, | ||
notificationTopic: topic, | ||
}), | ||
], | ||
}); | ||
``` | ||
|
||
**Note**: Manual Approvals notifications only apply when an application has security | ||
check enabled. | ||
|
||
## Troubleshooting | ||
|
||
Here are some common errors you may encounter while using this library. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
88 changes: 88 additions & 0 deletions
88
packages/@aws-cdk/pipelines/lib/codepipeline/confirm-permissions-broadening.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
import { IStage } from '@aws-cdk/aws-codepipeline'; | ||
import * as cpa from '@aws-cdk/aws-codepipeline-actions'; | ||
import * as sns from '@aws-cdk/aws-sns'; | ||
import { Stage } from '@aws-cdk/core'; | ||
import { Node } from 'constructs'; | ||
import { Step } from '../blueprint'; | ||
import { ApplicationSecurityCheck } from '../private/application-security-check'; | ||
import { CodePipeline } from './codepipeline'; | ||
import { CodePipelineActionFactoryResult, ICodePipelineActionFactory, ProduceActionOptions } from './codepipeline-action-factory'; | ||
|
||
/** | ||
* Properties for a `PermissionsBroadeningCheck` | ||
*/ | ||
export interface PermissionsBroadeningCheckProps { | ||
/** | ||
* The CDK Stage object to check the stacks of | ||
* | ||
* This should be the same Stage object you are passing to `addStage()`. | ||
*/ | ||
readonly stage: Stage; | ||
|
||
/** | ||
* Topic to send notifications when a human needs to give manual confirmation | ||
* | ||
* @default - no notification | ||
*/ | ||
readonly notificationTopic?: sns.ITopic | ||
} | ||
|
||
/** | ||
* Pause the pipeline if a deployment would add IAM permissions or Security Group rules | ||
* | ||
* This step is only supported in CodePipeline pipelines. | ||
*/ | ||
export class ConfirmPermissionsBroadening extends Step implements ICodePipelineActionFactory { | ||
constructor(id: string, private readonly props: PermissionsBroadeningCheckProps) { | ||
super(id); | ||
} | ||
|
||
public produceAction(stage: IStage, options: ProduceActionOptions): CodePipelineActionFactoryResult { | ||
const sec = this.getOrCreateSecCheck(options.pipeline); | ||
this.props.notificationTopic?.grantPublish(sec.cdkDiffProject); | ||
|
||
const variablesNamespace = Node.of(this.props.stage).addr; | ||
|
||
const approveActionName = `${options.actionName}.Confirm`; | ||
stage.addAction(new cpa.CodeBuildAction({ | ||
runOrder: options.runOrder, | ||
actionName: `${options.actionName}.Check`, | ||
input: options.artifacts.toCodePipeline(options.pipeline.cloudAssemblyFileSet), | ||
project: sec.cdkDiffProject, | ||
variablesNamespace, | ||
environmentVariables: { | ||
STAGE_PATH: { value: Node.of(this.props.stage).path }, | ||
STAGE_NAME: { value: stage.stageName }, | ||
ACTION_NAME: { value: approveActionName }, | ||
...this.props.notificationTopic ? { | ||
NOTIFICATION_ARN: { value: this.props.notificationTopic.topicArn }, | ||
NOTIFICATION_SUBJECT: { value: `Confirm permission broadening in ${this.props.stage.stageName}` }, | ||
} : {}, | ||
}, | ||
})); | ||
|
||
stage.addAction(new cpa.ManualApprovalAction({ | ||
actionName: approveActionName, | ||
runOrder: options.runOrder + 1, | ||
additionalInformation: `#{${variablesNamespace}.MESSAGE}`, | ||
externalEntityLink: `#{${variablesNamespace}.LINK}`, | ||
})); | ||
|
||
return { runOrdersConsumed: 2 }; | ||
} | ||
|
||
private getOrCreateSecCheck(pipeline: CodePipeline): ApplicationSecurityCheck { | ||
const id = 'PipelinesSecurityCheck'; | ||
const existing = Node.of(pipeline).tryFindChild(id); | ||
if (existing) { | ||
if (!(existing instanceof ApplicationSecurityCheck)) { | ||
throw new Error(`Expected '${Node.of(existing).path}' to be 'ApplicationSecurityCheck' but was '${existing}'`); | ||
} | ||
return existing; | ||
} | ||
|
||
return new ApplicationSecurityCheck(pipeline, id, { | ||
codePipeline: pipeline.pipeline, | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
export * from './artifact-map'; | ||
export * from './codebuild-step'; | ||
export * from './confirm-permissions-broadening'; | ||
export * from './codepipeline'; | ||
export * from './codepipeline-action-factory'; | ||
export * from './codepipeline-source'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.