Skip to content
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

CMDCT-4184 cdk-side prepping cdk to import Cloudfront Distribution #14991

Open
wants to merge 10 commits into
base: jon-cdk
Choose a base branch
from
Open
83 changes: 44 additions & 39 deletions deployment/app.ts
Original file line number Diff line number Diff line change
@@ -1,57 +1,62 @@
#!/usr/bin/env node
import "source-map-support/register";
import * as cdk from "aws-cdk-lib";
import { WithoutImportsParentStack } from "./stacks/without_imports/parent";
import { WithImportsParentStack} from "./stacks/with_imports/parent";
import { ParentStack } from "./stacks/parent";
import { determineDeploymentConfig } from "./deployment-config";
import { getSecret } from "./utils/secrets-manager";
import { getDeploymentConfigParameters } from "./utils/systems-manager";

async function main() {
try {
const app = new cdk.App({
defaultStackSynthesizer: new cdk.DefaultStackSynthesizer(
JSON.parse((await getSecret("cdkSynthesizerConfig"))!)
),
});
const app = new cdk.App({
defaultStackSynthesizer: new cdk.DefaultStackSynthesizer(
JSON.parse((await getSecret("cdkSynthesizerConfig"))!)
),
});

const stage = app.node.getContext("stage");
const config = await determineDeploymentConfig(stage);
const stage = app.node.getContext("stage");
const config = await determineDeploymentConfig(stage);

const parametersToFetch = {
cloudfrontCertificateArn: {
name: "cloudfront/certificateArn",
useDefault: true,
},
cloudfrontDomainName: {
name: "cloudfront/domainName",
useDefault: false,
},
vpnIpSetArn: { name: "vpnIpSetArn", useDefault: true },
vpnIpv6SetArn: { name: "vpnIpv6SetArn", useDefault: true },
hostedZoneId: { name: "route53/hostedZoneId", useDefault: true },
domainName: { name: "route53/domainName", useDefault: true },
};
const parametersToFetch = {
cloudfrontCertificateArn: {
name: "cloudfront/certificateArn",
useDefault: true,
},
cloudfrontDomainName: {
name: "cloudfront/domainName",
useDefault: false,
},
vpnIpSetArn: { name: "vpnIpSetArn", useDefault: true },
vpnIpv6SetArn: { name: "vpnIpv6SetArn", useDefault: true },
hostedZoneId: { name: "route53/hostedZoneId", useDefault: true },
domainName: { name: "route53/domainName", useDefault: true },
};

const deploymentConfigParameters = await getDeploymentConfigParameters(
parametersToFetch,
stage
);
const deploymentConfigParameters = await getDeploymentConfigParameters(
parametersToFetch,
stage
);

cdk.Tags.of(app).add("STAGE", stage);
cdk.Tags.of(app).add("PROJECT", config.project);
cdk.Tags.of(app).add("STAGE", stage);
cdk.Tags.of(app).add("PROJECT", config.project);

new ParentStack(app, `${config.project}-${stage}`, {
...config,
env: {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEFAULT_REGION,
},
deploymentConfigParameters,
});
} catch (error) {
console.error("Error:", error);
process.exit(1);
let correctParentStack;
if (process.env.WITHOUT_IMPORTS) {
correctParentStack = WithoutImportsParentStack
} else if (process.env.WITH_IMPORTS) {
correctParentStack = WithImportsParentStack
} else {
correctParentStack = ParentStack
}
new correctParentStack(app, `${config.project}-${stage}`, {
...config,
env: {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEFAULT_REGION,
},
deploymentConfigParameters,
});
}

main();
111 changes: 111 additions & 0 deletions deployment/import_instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# Import Instructions

## From `pete-sls` branch:

1. Deploy sls to get it ready for deletion with retained resources configured for import

```
./run deploy --stage <YOUR_BRANCH_NAME>
```

2. Collect information about the resources we're going to be importing into the new cdk stack.
```
cloudfront.Distribution -
cognito.UserPool -
```

3. Destroy sls

```
./run destroy --stage <YOUR_BRANCH_NAME>
```

## From `jon-cdk` branch:


1. Create just the new cdk stack without anything inside of it.

```bash
WITHOUT_IMPORTS=true ./run deploy --stage <YOUR_BRANCH_NAME>
```

2. Now import all the serverless ejected resources.

```bash
WITH_IMPORTS=true PROJECT=seds cdk import --context stage=<YOUR_BRANCH_NAME> --force
```
As this import occurs you'll have to provide the information you gathered just before destroying the serverless stacks.

3. Run a deploy on that same imported resource set.

```bash
WITH_IMPORTS=true ./run deploy --stage <YOUR_BRANCH_NAME>
```

4. Run a full deploy by kicking off the full cdk deploy via Github Action. Permissions for individual developers are limited so you must use Github Action to do this part.

5. Find the Cloudfront Url in the Github Action's logs (or in the outputs section of your Cloudformation Stack). Visit the site and confirm that you can login and use the application. :tada: Congrats, you did it!


## What if it all goes pear shaped?

### If during the middle of the migration, things begin to break and we need to reinstate the serverless stack, we need a way to bring the Cloudfront Distribution back into the newly rebuilt serverless stack. Fortunately this is possible if you follow these steps.

:grey_exclamation: These instructions are specific to reimporting a Cloudfront Distribution but the same pattern should also apply to any other imported resources that need un-importing should the need arise.

1) Get the Cloudfront Distribution unaffiliated with any Cloudformation stack. If it's already been successfully imported into the new cdk stack then you'll need to destroy the cdk stack to eject it from that stack.
```
# this assumes you're on `jon-cdk` branch
./run destroy --stage <YOUR_BRANCH_NAME>
```

2) Now you need switch to `pete-sls` branch and comment out any CloudfrontDistribution and dependent configuration.
These are the necessary changes: https://github.com/Enterprise-CMCS/macpro-mdct-seds/commit/8eb551f980a37355729dc1795f5d229987699c84

3) Deploy serverless stack (without CloudfrontDistribution) via Github Action (necessary because of permissions limitations) by pushing up changes made in the last step.

4) Now you need to import the CloudfrontDistribution to the existing ui-XXXXX stack created by the last step. First you'll need to get the existing stack's template and save it to a local file.
```
aws cloudformation get-template --stack-name ui-cmdct-4188-sls | jq '.TemplateBody' > deployment/cfn_template.json
```

5) Now open up the file you just created (deployment/cfn_template.json) and add the following Cloudfront Distribution config to it's resources section:
```json
"CloudFrontDistribution": {
"Type": "AWS::CloudFront::Distribution",
"DeletionPolicy": "Retain",
"Properties": {
"DistributionConfig": {
"CustomOrigin": {
"DNSName": "www.example.com",
"OriginProtocolPolicy": "http-only",
"OriginSSLProtocols": [
"TLSv1"
]
},
"Enabled": true,
"DefaultCacheBehavior": {
"CachePolicyId": "Managed-CachingDisabled",
"TargetOriginId": "some_target_origin_id",
"ViewerProtocolPolicy": "allow-all"
}
}
}
},
```

6) Now open up the AWS console and navigate to the Cloudformation console's show page for the particular ui-XXXXX stack.

7) Import the stack by doing the following:
- Under `Stack Actions` select `Import resources into stack`
- In the `Specify template` section choose upload a template file and upload the one we just created: `deployment/cfn_template.json`
- In the `Identify resources` section you'll have to provide the ID of the incoming Cloudfront Distribution
- Next and confirm until it begins the import

8) Once the import is complete, take a breath.

9) Revert the changes where you commented out the Serverless definition of Cloudfront Distribution (undo step 2).

10) Verify that the Serverless definition now contains the Cloudfront Distribution then deploy via Github Action.

11) Verify that Cloudfront Distribution is back into the stack and appropriately pointing at the application.
2 changes: 1 addition & 1 deletion deployment/stacks/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ export function createApiComponents(props: CreateApiComponentsProps) {
BOOTSTRAP_BROKER_STRING_TLS: brokerString,
stage,
...Object.fromEntries(
tables.map((table) => [`${table.id}Name`, table.name])
tables.map((table) => [`${table.id}TableName`, table.name])
),
};

Expand Down
35 changes: 19 additions & 16 deletions deployment/stacks/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,26 +79,29 @@ export function createDataComponents(props: CreateDataComponentsProps) {
"service-role/AWSLambdaVPCAccessExecutionRole"
),
],
inlinePolicies: {
DynamoPolicy: new iam.PolicyDocument({
statements: [
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
"dynamodb:DescribeTable",
"dynamodb:Query",
"dynamodb:Scan",
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:UpdateItem",
"dynamodb:DeleteItem",
],
resources: ["*"],
})
]
})
},
permissionsBoundary: props.iamPermissionsBoundary,
path: props.iamPath,
});

lambdaApiRole.addToPolicy(
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
"dynamodb:DescribeTable",
"dynamodb:Query",
"dynamodb:Scan",
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:UpdateItem",
"dynamodb:DeleteItem",
],
resources: ["*"],
})
);

// TODO: test deploy and watch performance with this using lambda.Function vs lambda_nodejs.NodejsFunction
const seedDataFunction = new lambda_nodejs.NodejsFunction(scope, "seedData", {
entry: "services/database/handlers/seed/seed.js",
Expand Down
55 changes: 25 additions & 30 deletions deployment/stacks/ui-auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
aws_lambda_nodejs as lambda_nodejs,
aws_wafv2 as wafv2,
aws_ssm as ssm,
RemovalPolicy,
Aws,
Duration,
custom_resources as cr,
Expand Down Expand Up @@ -40,7 +39,6 @@ export function createUiAuthComponents(props: CreateUiAuthComponentsProps) {

const userPool = new cognito.UserPool(scope, "UserPool", {
userPoolName: `${stage}-user-pool`,
removalPolicy: RemovalPolicy.DESTROY,
signInAliases: {
email: true,
},
Expand Down Expand Up @@ -208,36 +206,33 @@ export function createUiAuthComponents(props: CreateUiAuthComponentsProps) {
"service-role/AWSLambdaVPCAccessExecutionRole"
),
],
inlinePolicies: {
LambdaApiRolePolicy: new iam.PolicyDocument({
statements: [
new iam.PolicyStatement({
actions: [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
],
resources: ["arn:aws:logs:*:*:*"],
effect: iam.Effect.ALLOW,
}),
new iam.PolicyStatement({
actions: ["*"],
resources: [userPool.userPoolArn],
effect: iam.Effect.ALLOW,
}),
new iam.PolicyStatement({
actions: ["ssm:GetParameter"],
resources: [bootstrapUsersPasswordArn],
effect: iam.Effect.ALLOW,
}),
],
}),
},
});

lambdaApiRole.addToPolicy(
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
],
resources: ["arn:aws:logs:*:*:*"],
})
);

lambdaApiRole.addToPolicy(
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ["*"],
resources: [userPool.userPoolArn],
})
);

lambdaApiRole.addToPolicy(
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ["ssm:GetParameter"],
resources: [bootstrapUsersPasswordArn],
})
);

// TODO: test deploy and watch performance with scope using lambda.Function vs lambda_nodejs.NodejsFunction
bootstrapUsersFunction = new lambda_nodejs.NodejsFunction(
scope,
Expand Down
26 changes: 0 additions & 26 deletions deployment/stacks/ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import {
aws_cloudfront_origins as cloudfrontOrigins,
aws_iam as iam,
aws_kinesisfirehose as firehose,
aws_route53 as route53,
aws_route53_targets as route53Targets,
aws_s3 as s3,
aws_wafv2 as wafv2,
Aws,
Expand Down Expand Up @@ -136,7 +134,6 @@ export function createUiComponents(props: CreateUiComponentsProps) {
const applicationEndpointUrl = `https://${distribution.distributionDomainName}/`;

setupWaf(scope, stage, project, deploymentConfigParameters);
setupRoute53(scope, distribution, deploymentConfigParameters);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was never actually being created or used.


createFirehoseLogging(
scope,
Expand Down Expand Up @@ -229,29 +226,6 @@ function setupWaf(
});
}

function setupRoute53(
scope: Construct,
distribution: cloudfront.Distribution,
deploymentConfigParameters: { [name: string]: string | undefined }
) {
if (
deploymentConfigParameters.hostedZoneId &&
deploymentConfigParameters.domainName
) {
const zone = route53.HostedZone.fromHostedZoneAttributes(scope, "Zone", {
hostedZoneId: deploymentConfigParameters.hostedZoneId,
zoneName: deploymentConfigParameters.domainName,
});

new route53.ARecord(scope, "AliasRecord", {
zone,
target: route53.RecordTarget.fromAlias(
new route53Targets.CloudFrontTarget(distribution)
),
});
}
}

function createFirehoseLogging(
scope: Construct,
stage: string,
Expand Down
Loading
Loading