-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Basic infrastructure setup for AWS * Remove incorrect comment * Stick with 2 AZs for now * Permit app to send email * Bump CDK version * Encapsulate deployment in construct * Allow redis access from app * Env vars and certificate * Fix cert * Configure postgres * Configure GitHub Actions user's permissions * Fix deploy user permissions * Remove unused tests * Exclude "infrastructure" folder from main tsconfig It has it's own tsconfig
- Loading branch information
1 parent
deb9869
commit 93ae53e
Showing
15 changed files
with
4,784 additions
and
1 deletion.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
*.js | ||
!jest.config.js | ||
*.d.ts | ||
node_modules | ||
|
||
# CDK asset staging directory | ||
.cdk.staging | ||
cdk.out |
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,6 @@ | ||
*.ts | ||
!*.d.ts | ||
|
||
# CDK asset staging directory | ||
.cdk.staging | ||
cdk.out |
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,12 @@ | ||
# Shape Docs AWS Infrastructure | ||
|
||
Infrastructure as code for deploying Shape Docs to AWS. | ||
|
||
## Useful commands | ||
|
||
* `npm run build` compile typescript to js | ||
* `npm run watch` watch for changes and compile | ||
* `npm run test` perform the jest unit tests | ||
* `npx cdk deploy` deploy this stack to your default AWS account/region | ||
* `npx cdk diff` compare deployed stack with current state | ||
* `npx cdk synth` emits the synthesized CloudFormation template |
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,30 @@ | ||
#!/usr/bin/env node | ||
import 'source-map-support/register'; | ||
import * as cdk from 'aws-cdk-lib'; | ||
import DocsDeployment from '../lib/docs-deployment'; | ||
|
||
const app = new cdk.App(); | ||
|
||
/** ACCOUNTS */ | ||
|
||
const nonProdAccount: cdk.Environment = { | ||
account: "841481405304", | ||
region: "eu-central-1", | ||
}; | ||
|
||
const prodAccount: cdk.Environment = { | ||
account: "721428964064", | ||
region: "eu-central-1", | ||
}; | ||
|
||
/** DEPLOYMENTS */ | ||
|
||
new DocsDeployment(app, 'Staging', { | ||
env: nonProdAccount, | ||
publicCertificateArn: 'arn:aws:acm:eu-central-1:841481405304:certificate/6d513b25-bbca-49ec-9de0-377e303c313f', | ||
}) | ||
|
||
new DocsDeployment(app, 'Prod', { | ||
env: prodAccount, | ||
publicCertificateArn: 'arn:aws:acm:eu-central-1:841481405304:certificate/6d513b25-bbca-49ec-9de0-377e303c313f', // TODO: Replace with prod cert | ||
}) |
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,7 @@ | ||
{ | ||
"availability-zones:account=841481405304:region=eu-central-1": [ | ||
"eu-central-1a", | ||
"eu-central-1b", | ||
"eu-central-1c" | ||
] | ||
} |
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,64 @@ | ||
{ | ||
"app": "npx ts-node --prefer-ts-exts bin/infrastructure.ts", | ||
"watch": { | ||
"include": [ | ||
"**" | ||
], | ||
"exclude": [ | ||
"README.md", | ||
"cdk*.json", | ||
"**/*.d.ts", | ||
"**/*.js", | ||
"tsconfig.json", | ||
"package*.json", | ||
"yarn.lock", | ||
"node_modules", | ||
"test" | ||
] | ||
}, | ||
"context": { | ||
"@aws-cdk/aws-lambda:recognizeLayerVersion": true, | ||
"@aws-cdk/core:checkSecretUsage": true, | ||
"@aws-cdk/core:target-partitions": [ | ||
"aws", | ||
"aws-cn" | ||
], | ||
"@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, | ||
"@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, | ||
"@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, | ||
"@aws-cdk/aws-iam:minimizePolicies": true, | ||
"@aws-cdk/core:validateSnapshotRemovalPolicy": true, | ||
"@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, | ||
"@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, | ||
"@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, | ||
"@aws-cdk/aws-apigateway:disableCloudWatchRole": true, | ||
"@aws-cdk/core:enablePartitionLiterals": true, | ||
"@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, | ||
"@aws-cdk/aws-iam:standardizedServicePrincipals": true, | ||
"@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, | ||
"@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true, | ||
"@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true, | ||
"@aws-cdk/aws-route53-patters:useCertificate": true, | ||
"@aws-cdk/customresources:installLatestAwsSdkDefault": false, | ||
"@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true, | ||
"@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true, | ||
"@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true, | ||
"@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true, | ||
"@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true, | ||
"@aws-cdk/aws-redshift:columnId": true, | ||
"@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true, | ||
"@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true, | ||
"@aws-cdk/aws-apigateway:requestValidatorUniqueId": true, | ||
"@aws-cdk/aws-kms:aliasNameRef": true, | ||
"@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true, | ||
"@aws-cdk/core:includePrefixInUniqueNameGeneration": true, | ||
"@aws-cdk/aws-efs:denyAnonymousAccess": true, | ||
"@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true, | ||
"@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true, | ||
"@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true, | ||
"@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true, | ||
"@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true, | ||
"@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true, | ||
"@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true | ||
} | ||
} |
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,110 @@ | ||
import * as cdk from 'aws-cdk-lib'; | ||
import * as sm from 'aws-cdk-lib/aws-secretsmanager'; | ||
import { Vpc } from 'aws-cdk-lib/aws-ec2'; | ||
import { EcrImage, FargateService, Secret } from 'aws-cdk-lib/aws-ecs'; | ||
import { ApplicationLoadBalancedFargateService } from 'aws-cdk-lib/aws-ecs-patterns'; | ||
import { Construct } from 'constructs'; | ||
import { Certificate } from 'aws-cdk-lib/aws-certificatemanager'; | ||
|
||
interface AppStackProps extends cdk.StackProps { | ||
vpc: Vpc; | ||
image: EcrImage; | ||
redisHostname: string, | ||
postgresHostname: string, | ||
postgresUser: string, | ||
postgresDb: string, | ||
postgresPassword: sm.ISecret, | ||
publicCertificateArn: string, | ||
} | ||
|
||
export class AppStack extends cdk.Stack { | ||
readonly service: FargateService; | ||
readonly loadBalancer: cdk.aws_elasticloadbalancingv2.ApplicationLoadBalancer; | ||
|
||
constructor(scope: Construct, id: string, props: AppStackProps) { | ||
super(scope, id, props); | ||
|
||
// list of all env vars to be stored in Secrets Manager | ||
const envVars = [ | ||
// GitHub | ||
"GITHUB_APP_ID", | ||
"GITHUB_CLIENT_ID", | ||
"GITHUB_CLIENT_SECRET", | ||
"GITHUB_ORGANIZATION_NAME", | ||
"GITHUB_PRIVATE_KEY_BASE_64", | ||
"GITHUB_WEBHOK_REPOSITORY_ALLOWLIST", | ||
"GITHUB_WEBHOK_REPOSITORY_DISALLOWLIST", | ||
"GITHUB_WEBHOOK_SECRET", | ||
// Auth0 | ||
"AUTH0_BASE_URL", | ||
"AUTH0_CLIENT_ID", | ||
"AUTH0_CLIENT_SECRET", | ||
"AUTH0_ISSUER_BASE_URL", | ||
"AUTH0_MANAGEMENT_CLIENT_ID", | ||
"AUTH0_MANAGEMENT_CLIENT_SECRET", | ||
"AUTH0_MANAGEMENT_DOMAIN", | ||
"AUTH0_SECRET", | ||
// SMTP for sending emails | ||
"SMTP_HOST", | ||
"SMTP_USER", | ||
"SMTP_PASS", | ||
// Other | ||
"SHAPE_DOCS_BASE_URL", // TODO: Could be part of config along with certificate issuing | ||
] | ||
|
||
// create the env vars as secrets in Secrets Manager | ||
// Note: secrets are created with an initial value which should be replaced via the AWS SecretsManager Console | ||
// https://eu-central-1.console.aws.amazon.com/secretsmanager/listsecrets?region=eu-central-1 | ||
const secrets = envVars.reduce((acc, curr) => { | ||
acc[curr] = new sm.Secret(this, `${id}Secret${curr}`, { | ||
secretName: `${id}/${curr}`, | ||
}); | ||
return acc; | ||
}, {} as { [key: string]: sm.Secret }); | ||
|
||
// must be created & validated in the AWS Console | ||
// https://eu-central-1.console.aws.amazon.com/acm/home?region=eu-central-1 | ||
const certificate = Certificate.fromCertificateArn(this, `${id}Certificate`, props.publicCertificateArn) | ||
|
||
const app = new ApplicationLoadBalancedFargateService(this, "AppService", { | ||
vpc: props.vpc, | ||
assignPublicIp: false, // run in private network | ||
desiredCount: 1, | ||
cpu: 256, | ||
memoryLimitMiB: 512, | ||
publicLoadBalancer: true, | ||
taskImageOptions: { | ||
image: props.image, | ||
environment: { | ||
REDIS_URL: props.redisHostname, | ||
POSTGRESQL_HOST: props.postgresHostname, | ||
POSTGRESQL_USER: props.postgresUser, | ||
POSTGRESQL_DB: props.postgresDb, | ||
NEXT_PUBLIC_SHAPE_DOCS_TITLE: 'Shape Docs', | ||
}, | ||
secrets: { | ||
...envVars.reduce((acc, curr) => { // get each env var from Secrets Manager | ||
acc[curr] = Secret.fromSecretsManager(secrets[curr]); | ||
return acc; | ||
}, {} as { [key: string]: Secret }), | ||
POSTGRESQL_PASSWORD: Secret.fromSecretsManager(props.postgresPassword), | ||
}, | ||
containerPort: 3000, | ||
}, | ||
circuitBreaker: { | ||
rollback: true, | ||
}, | ||
healthCheckGracePeriod: cdk.Duration.seconds(60), | ||
certificate: certificate, | ||
}); | ||
|
||
app.targetGroup.setAttribute('deregistration_delay.timeout_seconds', '15'); | ||
|
||
app.targetGroup.configureHealthCheck({ | ||
path: "/api/health", | ||
}); | ||
|
||
this.service = app.service; | ||
this.loadBalancer = app.loadBalancer; | ||
} | ||
} |
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,52 @@ | ||
import { Construct } from 'constructs' | ||
import { InfrastructureStack } from './infrastructure-stack'; | ||
import { Environment } from 'aws-cdk-lib'; | ||
import { PostgresStack } from './postgres-stack'; | ||
import { RedisStack } from './redis-stack'; | ||
import { AppStack } from './app-stack'; | ||
import { ContainerImage } from 'aws-cdk-lib/aws-ecs'; | ||
|
||
interface DocsDeploymentProps { | ||
env: Environment, | ||
publicCertificateArn: string, | ||
} | ||
|
||
export default class DocsDeployment extends Construct { | ||
readonly infrastructure: InfrastructureStack | ||
readonly postgres: PostgresStack | ||
readonly redis: RedisStack | ||
readonly app: AppStack | ||
|
||
constructor(scope: Construct, id: string, props: DocsDeploymentProps) { | ||
super(scope, id) | ||
|
||
this.infrastructure = new InfrastructureStack(scope, `${id}Infrastructure`, { | ||
env: props.env, | ||
}); | ||
|
||
this.postgres = new PostgresStack(scope, `${id}Postgres`, { | ||
env: props.env, | ||
vpc: this.infrastructure.vpc, | ||
}); | ||
|
||
this.redis = new RedisStack(scope, `${id}Redis`, { | ||
env: props.env, | ||
vpc: this.infrastructure.vpc, | ||
}); | ||
|
||
this.app = new AppStack(scope, `${id}App`, { | ||
env: props.env, | ||
vpc: this.infrastructure.vpc, | ||
image: ContainerImage.fromEcrRepository(this.infrastructure.dockerRepository, 'latest'), | ||
postgresHostname: this.postgres.dbInstance.instanceEndpoint.hostname, | ||
postgresUser: this.postgres.username, | ||
postgresDb: this.postgres.database, | ||
postgresPassword: this.postgres.password, | ||
redisHostname: this.redis.cluster.attrRedisEndpointAddress, | ||
publicCertificateArn: props.publicCertificateArn, | ||
}); | ||
|
||
this.app.service.connections.allowToDefaultPort(this.redis); | ||
this.app.service.connections.allowToDefaultPort(this.postgres.dbInstance); | ||
} | ||
} |
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,59 @@ | ||
import * as cdk from 'aws-cdk-lib'; | ||
import { IpAddresses, Vpc } from 'aws-cdk-lib/aws-ec2'; | ||
import { Repository } from 'aws-cdk-lib/aws-ecr'; | ||
import { Effect, Policy, PolicyStatement, User } from 'aws-cdk-lib/aws-iam'; | ||
import { Construct } from 'constructs'; | ||
|
||
export class InfrastructureStack extends cdk.Stack { | ||
readonly vpc: Vpc; | ||
readonly dockerRepository: Repository; | ||
|
||
constructor(scope: Construct, id: string, props?: cdk.StackProps) { | ||
super(scope, id, props); | ||
|
||
this.vpc = new Vpc(this, 'VPC', { | ||
ipAddresses: IpAddresses.cidr("10.0.0.0/16"), | ||
maxAzs: 2, | ||
}); | ||
|
||
this.dockerRepository = new Repository(this, 'Repository', { | ||
repositoryName: 'shapedocs', | ||
removalPolicy: cdk.RemovalPolicy.DESTROY, | ||
}); | ||
|
||
const deploymentPolicy = new Policy(this, 'DeploymentPolicy', { | ||
policyName: 'DeploymentPolicy', | ||
statements: [ | ||
new PolicyStatement({ | ||
effect: Effect.ALLOW, | ||
actions: [ | ||
// ECR | ||
"ecr:GetAuthorizationToken", | ||
"ecr:GetDownloadUrlForLayer", | ||
"ecr:BatchCheckLayerAvailability", | ||
"ecr:PutImage", | ||
"ecr:InitiateLayerUpload", | ||
"ecr:UploadLayerPart", | ||
"ecr:CompleteLayerUpload", | ||
// ECS | ||
"ecs:DescribeServices", | ||
"ecs:UpdateService", | ||
"ecs:RegisterTaskDefinition", | ||
"ecs:DeregisterTaskDefinition", | ||
"ecs:DescribeTaskDefinition", | ||
"iam:PassRole" | ||
], | ||
resources: [ | ||
"*" | ||
], | ||
}), | ||
], | ||
}); | ||
|
||
const deploymentUser = new User(this, 'GitHubActionsUser'); | ||
|
||
deploymentPolicy.attachToUser(deploymentUser); | ||
|
||
deploymentUser.attachInlinePolicy(deploymentPolicy); | ||
} | ||
} |
Oops, something went wrong.