Skip to content

Commit

Permalink
feat: enable termination protection on CloudFormation stacks
Browse files Browse the repository at this point in the history
Signed-off-by: Austin Vazquez <[email protected]>
  • Loading branch information
austinvazquez committed Jun 12, 2024
1 parent 1fce297 commit b10e884
Show file tree
Hide file tree
Showing 15 changed files with 78 additions and 2 deletions.
3 changes: 3 additions & 0 deletions lib/artifact-bucket-cloudfront.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@ import { Construct } from 'constructs';

import { CloudfrontCdn } from './cloudfront_cdn';
import * as s3Deployment from 'aws-cdk-lib/aws-s3-deployment';
import { applyTerminationProtectionOnStacks } from './aspects/stack-termination-protection';

export class ArtifactBucketCloudfrontStack extends cdk.Stack {
public readonly urlOutput: CfnOutput;
public readonly bucket: s3.Bucket;
constructor(scope: Construct, id: string, stage: string, props?: cdk.StackProps) {
super(scope, id, props);
applyTerminationProtectionOnStacks([this]);

const bucketName = `finch-artifact-bucket-${stage.toLowerCase()}-${cdk.Stack.of(this)?.account}`;
const artifactBucket = new s3.Bucket(this, 'ArtifactBucket', {
bucketName,
Expand Down
2 changes: 2 additions & 0 deletions lib/asg-runner-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { PlatformType, RunnerType } from '../config/runner-config';
import { readFileSync } from 'fs';
import { ENVIRONMENT_STAGE } from './finch-pipeline-app-stage';
import { UpdatePolicy } from 'aws-cdk-lib/aws-autoscaling';
import { applyTerminationProtectionOnStacks } from './aspects/stack-termination-protection';

interface ASGRunnerStackProps extends cdk.StackProps {
env: cdk.Environment | undefined;
Expand All @@ -26,6 +27,7 @@ interface ASGRunnerStackProps extends cdk.StackProps {
export class ASGRunnerStack extends cdk.Stack {
constructor(scope: Construct, id: string, props: ASGRunnerStackProps) {
super(scope, id, props);
applyTerminationProtectionOnStacks([this]);

const platform = props.type.platform;
const arch = props.type.arch === 'arm' ? `arm64_${platform}` : `x86_64_${platform}`;
Expand Down
24 changes: 24 additions & 0 deletions lib/aspects/stack-termination-protection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/usr/bin/env node

// Finch Infrastructure
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.

import { Aspects, IAspect, Stack } from "aws-cdk-lib";
import { IConstruct } from "constructs";

/**
* Enable termination protection in all stacks.
*/
export class EnableTerminationProtectionOnStacks implements IAspect {
visit(construct: IConstruct): void {
if (Stack.isStack(construct)) {
(<any>construct).terminationProtection = true;
}
}
}

export function applyTerminationProtectionOnStacks(constructs: IConstruct[]) {
constructs.forEach((construct) => {
Aspects.of(construct).add(new EnableTerminationProtectionOnStacks());
})
}
2 changes: 2 additions & 0 deletions lib/continuous-integration-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as iam from 'aws-cdk-lib/aws-iam';
import { Construct } from 'constructs';

import { CloudfrontCdn } from './cloudfront_cdn';
import { applyTerminationProtectionOnStacks } from './aspects/stack-termination-protection';

interface ContinuousIntegrationStackProps extends cdk.StackProps {
rootfsEcrRepository: ecr.Repository;
Expand All @@ -14,6 +15,7 @@ interface ContinuousIntegrationStackProps extends cdk.StackProps {
export class ContinuousIntegrationStack extends cdk.Stack {
constructor(scope: Construct, id: string, stage: string, props: ContinuousIntegrationStackProps) {
super(scope, id, props);
applyTerminationProtectionOnStacks([this]);

const githubDomain = 'token.actions.githubusercontent.com';

Expand Down
3 changes: 3 additions & 0 deletions lib/ecr-repo-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ import * as cdk from 'aws-cdk-lib';
import { CfnOutput } from 'aws-cdk-lib';
import * as ecr from 'aws-cdk-lib/aws-ecr';
import { Construct } from 'constructs';
import { applyTerminationProtectionOnStacks } from './aspects/stack-termination-protection';

export class ECRRepositoryStack extends cdk.Stack {
public readonly repositoryOutput: CfnOutput;
public readonly repository: ecr.Repository;
constructor(scope: Construct, id: string, stage: string, props?: cdk.StackProps) {
super(scope, id, props);
applyTerminationProtectionOnStacks([this]);

const repoName = `finch-rootfs-image-${stage.toLowerCase()}`;
const ecrRepository = new ecr.Repository(this, 'finch-rootfs', {
repositoryName:repoName,
Expand Down
2 changes: 2 additions & 0 deletions lib/event-bridge-scan-notifs-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions';
import * as targets from 'aws-cdk-lib/aws-events-targets';
import { Construct } from 'constructs';
import path from 'path';
import { applyTerminationProtectionOnStacks } from './aspects/stack-termination-protection';

export class EventBridgeScanNotifsStack extends cdk.Stack {
constructor(scope: Construct, id: string, stage: string, props?: cdk.StackProps) {
super(scope, id, props);
applyTerminationProtectionOnStacks([this]);

const topic = new sns.Topic(this, 'ECR Image Inspector Findings');

Expand Down
4 changes: 2 additions & 2 deletions lib/finch-pipeline-app-stage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,10 @@ export class FinchPipelineAppStage extends cdk.Stage {
this,
'FinchContinuousIntegrationStack',
this.stageName,
{rootfsEcrRepository: this.ecrRepository}
{ rootfsEcrRepository: this.ecrRepository }
);
}

new PVREReportingStack(this, 'PVREReportingStack');
new PVREReportingStack(this, 'PVREReportingStack', { terminationProtection:true });
}
}
3 changes: 3 additions & 0 deletions lib/finch-pipeline-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ import { Construct } from 'constructs';
import { EnvConfig } from '../config/env-config';
import { RunnerConfig } from '../config/runner-config';
import { ENVIRONMENT_STAGE, FinchPipelineAppStage } from './finch-pipeline-app-stage';
import { applyTerminationProtectionOnStacks } from './aspects/stack-termination-protection';
import { release } from 'os';

export class FinchPipelineStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
applyTerminationProtectionOnStacks([this]);

const source = CodePipelineSource.gitHub('runfinch/infrastructure', 'main', {
authentication: cdk.SecretValue.secretsManager('pipeline-github-access-token')
Expand Down
2 changes: 2 additions & 0 deletions lib/pvre-reporting-stack.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import * as cdk from 'aws-cdk-lib';
import * as cfninc from 'aws-cdk-lib/cloudformation-include';
import { Construct } from 'constructs';
import { applyTerminationProtectionOnStacks } from './aspects/stack-termination-protection';

export class PVREReportingStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
applyTerminationProtectionOnStacks([this]);

new cfninc.CfnInclude(this, 'PVREReportingTemplate', {
templateFile: 'lib/pvre-reporting-template.yml'
Expand Down
2 changes: 2 additions & 0 deletions test/artifact-bucket-cloudfront.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,7 @@ describe('ArtifactBucketCloudfrontStack', () => {

// assert it creates the cloudfront distribution
template.resourceCountIs('AWS::CloudFront::Distribution', 1);

expect(cloudfront.terminationProtection).toBeTruthy();
});
});
6 changes: 6 additions & 0 deletions test/asg-runner-stack.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,10 @@ describe('ASGRunnerStack test', () => {
});
});
});

it('must have termination protection enabled', () => {
stacks.forEach((stack) => {
expect(stack.terminationProtection).toBeTruthy();
})
});
});
20 changes: 20 additions & 0 deletions test/aspects/stack-termination-protection.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Finch Infrastructure
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.

import { App, Stack } from "aws-cdk-lib";
import { Bucket } from "aws-cdk-lib/aws-s3";
import { applyTerminationProtectionOnStacks } from "../../lib/aspects/stack-termination-protection";

describe('Stack termination protection aspect', () => {
it('must enable termination protection when applied to a stack and synthesized', () => {
const app = new App();
const stack = new Stack(app, 'FooStack');
// stack needs one resource
new Bucket(stack, 'BarBucket');

applyTerminationProtectionOnStacks([stack]);
app.synth();

expect(stack.terminationProtection).toBeTruthy();
});
});
3 changes: 3 additions & 0 deletions test/ecr-repo-stack.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as cdk from 'aws-cdk-lib';
import { Template, Match } from 'aws-cdk-lib/assertions';
import { ECRRepositoryStack } from '../lib/ecr-repo-stack';
import { TerminationPolicy } from 'aws-cdk-lib/aws-autoscaling';

describe('ECRRepositoryStack', () => {
test('synthesizes the way we expect', () => {
Expand All @@ -21,5 +22,7 @@ describe('ECRRepositoryStack', () => {
},
},
});

expect(ecrRepo.terminationProtection).toBeTruthy();
});
})
2 changes: 2 additions & 0 deletions test/event-bridge-scan-notifs-stack.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,7 @@ describe('EventBridgeScanNotifsStack', () => {
],
}
});

expect(eventBridgeStack.terminationProtection).toBeTruthy();
});
})
2 changes: 2 additions & 0 deletions test/finch-pipeline-stack.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,5 +57,7 @@ describe('FinchPipelineStack', () => {
'Fn::GetAtt': ['FinchPipelineRole198D7E07', 'Arn']
}
});

expect(stack.terminationProtection).toBeTruthy();
});
});

0 comments on commit b10e884

Please sign in to comment.