-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
CDK API stack with Fargate / ALB (#817)
- Loading branch information
1 parent
3add2e3
commit 5d72051
Showing
14 changed files
with
2,791 additions
and
3 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 |
---|---|---|
@@ -0,0 +1,29 @@ | ||
/* eslint-env node */ | ||
module.exports = { | ||
extends: [ | ||
'eslint:recommended', | ||
'plugin:@typescript-eslint/recommended-type-checked', | ||
'plugin:@typescript-eslint/stylistic-type-checked', | ||
'plugin:@typescript-eslint/strict-type-checked', | ||
], | ||
parser: '@typescript-eslint/parser', | ||
parserOptions: { | ||
project: true, | ||
tsconfigRootDir: __dirname, | ||
}, | ||
plugins: ['@typescript-eslint'], | ||
root: true, | ||
ignorePatterns: ['node_modules', 'cdk.out'], | ||
rules: { | ||
'@typescript-eslint/init-declarations': 'error', | ||
'@typescript-eslint/no-misused-promises': [ | ||
'error', | ||
{ | ||
checksVoidReturn: false, | ||
}, | ||
], | ||
'@typescript-eslint/unbound-method': ['error', { ignoreStatic: true }], | ||
'prefer-template': 'error', | ||
eqeqeq: 'error', | ||
}, | ||
}; |
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 @@ | ||
*.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 @@ | ||
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,17 @@ | ||
# CDK project for Prompt Injection | ||
|
||
This project uses AWS CDK (using TypeScript) to define CloudFormation templates for remote deployment of all resources | ||
for the Prompt Injection application. | ||
|
||
The architecture is a typical containerized node-express API managed by AWS Fargate and ECS, with load-balancing (ELB), | ||
plus an S3-hosted UI served through CloudFront, all secured using Cognito. | ||
|
||
The `cdk.json` file tells the CDK Toolkit how to execute your app. | ||
|
||
## Commands | ||
|
||
- `npm run cdk:synth` - generates the CloudFormation templates, outputting into `./cdk.out` dir. | ||
- `npm run cdk:deploy` - deploys the application stacks into the remote DEV stage. | ||
_Caution! This will overwrite existing resources, so ensure the team are notified before running this manually._ | ||
|
||
Note that once the CDK Pipeline is in place, DEV deployment will happen automatically on merging into dev branch. |
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,28 @@ | ||
#!/usr/bin/env node | ||
import { App } from 'aws-cdk-lib'; | ||
import 'source-map-support/register'; | ||
|
||
import { | ||
appName, | ||
environmentName, | ||
resourceDescription, | ||
stackName, | ||
ApiStack, | ||
} from '../lib'; | ||
|
||
const app = new App(); | ||
const tags = { | ||
owner: appName, | ||
classification: 'unrestricted', | ||
'environment-type': environmentName(app), | ||
'keep-alive': '8-6-without-weekends', | ||
IaC: 'CDK', | ||
}; | ||
|
||
const generateStackName = stackName(app); | ||
const generateDescription = resourceDescription(app); | ||
|
||
new ApiStack(app, generateStackName('api'), { | ||
tags, | ||
description: generateDescription('API stack'), | ||
}); |
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 @@ | ||
Resources: | ||
PermissionsBoundary: | ||
Type: AWS::IAM::ManagedPolicy | ||
Properties: | ||
PolicyDocument: | ||
Statement: | ||
# ----- Begin base policy --------------- | ||
# If permission boundaries do not have an explicit allow | ||
# then the effect is deny | ||
- Sid: ExplicitAllowAll | ||
Action: '*' | ||
Effect: Allow | ||
Resource: '*' | ||
# Default permissions to prevent privilege escalation | ||
- Sid: DenyAccessIfRequiredPermBoundaryIsNotBeingApplied | ||
Action: | ||
- iam:CreateUser | ||
- iam:CreateRole | ||
- iam:PutRolePermissionsBoundary | ||
- iam:PutUserPermissionsBoundary | ||
Condition: | ||
StringNotEquals: | ||
iam:PermissionsBoundary: | ||
Fn::Sub: arn:${AWS::Partition}:iam::${AWS::AccountId}:policy/cdk-developer-policy | ||
Effect: Deny | ||
Resource: '*' | ||
- Sid: DenyPermBoundaryIAMPolicyAlteration | ||
Action: | ||
- iam:CreatePolicyVersion | ||
- iam:DeletePolicy | ||
- iam:DeletePolicyVersion | ||
- iam:SetDefaultPolicyVersion | ||
Effect: Deny | ||
Resource: | ||
Fn::Sub: arn:${AWS::Partition}:iam::${AWS::AccountId}:policy/cdk-developer-policy | ||
- Sid: DenyRemovalOfPermBoundaryFromAnyUserOrRole | ||
Action: | ||
- iam:DeleteUserPermissionsBoundary | ||
- iam:DeleteRolePermissionsBoundary | ||
Effect: Deny | ||
Resource: '*' | ||
# ----- End base policy --------------- | ||
# -- Begin Custom Organization Policy -- | ||
- Sid: DenyModifyingConfig | ||
Effect: Deny | ||
Action: config:* | ||
Resource: '*' | ||
# -- End Custom Organization Policy -- | ||
Version: '2012-10-17' | ||
Description: 'CDK Bootstrap Permission Boundary' | ||
ManagedPolicyName: cdk-developer-policy | ||
Path: / |
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,61 @@ | ||
{ | ||
"app": "npx ts-node --prefer-ts-exts bin/cloud.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:permissionsBoundary": { | ||
"name": "cdk-developer-policy" | ||
}, | ||
"@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 | ||
} | ||
} |
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,81 @@ | ||
import { Stack, StackProps } from 'aws-cdk-lib/core'; | ||
import { Vpc } from 'aws-cdk-lib/aws-ec2'; | ||
import { DockerImageAsset } from 'aws-cdk-lib/aws-ecr-assets'; | ||
import { | ||
Cluster, | ||
ContainerImage, | ||
Secret as EnvSecret, | ||
} from 'aws-cdk-lib/aws-ecs'; | ||
import { ApplicationLoadBalancedFargateService } from 'aws-cdk-lib/aws-ecs-patterns'; | ||
import { Secret } from 'aws-cdk-lib/aws-secretsmanager'; | ||
import { Construct } from 'constructs'; | ||
import { join } from 'node:path'; | ||
|
||
import { resourceName, stageName } from './resourceNamingUtils'; | ||
|
||
export class ApiStack extends Stack { | ||
stage: string; | ||
|
||
constructor(scope: Construct, id: string, props: StackProps) { | ||
super(scope, id, props); | ||
this.stage = stageName(scope); | ||
|
||
const generateResourceName = resourceName(scope); | ||
|
||
const dockerImageAsset = new DockerImageAsset( | ||
this, | ||
generateResourceName('container-image'), | ||
{ | ||
directory: join(__dirname, '../../backend/'), | ||
} | ||
); | ||
|
||
// Default AZs is all in region, but for environment-agnostic stack, max is 2! | ||
const vpcName = generateResourceName('vpc'); | ||
const vpc = new Vpc(this, vpcName, { vpcName, maxAzs: 2 }); | ||
const clusterName = generateResourceName('cluster'); | ||
const cluster = new Cluster(this, clusterName, { clusterName, vpc }); | ||
|
||
const apiKeySecret = Secret.fromSecretNameV2( | ||
this, | ||
generateResourceName('apiKey'), | ||
'dev/SpyLogic/ApiKey' | ||
); | ||
|
||
// Create a load-balanced Fargate service and make it public | ||
const containerPort = 3001; | ||
const serviceName = generateResourceName('fargate'); | ||
const fargateService = new ApplicationLoadBalancedFargateService( | ||
this, | ||
serviceName, | ||
{ | ||
serviceName, | ||
cluster, | ||
cpu: 256, // Default is 256 | ||
desiredCount: 1, // Bump this up for prod! | ||
taskImageOptions: { | ||
image: ContainerImage.fromDockerImageAsset(dockerImageAsset), | ||
containerPort, | ||
environment: { | ||
NODE_ENV: 'production', | ||
PORT: `${containerPort}`, | ||
}, | ||
secrets: { | ||
OPENAI_API_KEY: EnvSecret.fromSecretsManager( | ||
apiKeySecret, | ||
'OPENAI_API_KEY' | ||
), | ||
SESSION_SECRET: EnvSecret.fromSecretsManager( | ||
apiKeySecret, | ||
'SESSION_SECRET' | ||
), | ||
}, | ||
}, | ||
memoryLimitMiB: 512, // Default is 512 | ||
loadBalancerName: generateResourceName('elb'), | ||
publicLoadBalancer: true, // Default is true | ||
} | ||
); | ||
fargateService.targetGroup.configureHealthCheck({ path: '/health' }); | ||
} | ||
} |
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,2 @@ | ||
export * from './resourceNamingUtils'; | ||
export { ApiStack } from './api-stack'; |
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,26 @@ | ||
import { Construct } from 'constructs'; | ||
|
||
export const appName = 'SpyLogic'; | ||
|
||
export const stageName = (construct: Construct) => | ||
(construct.node.tryGetContext('STAGE') as string) || 'dev'; | ||
export const environmentName = (() => { | ||
const environments = { | ||
dev: 'development', | ||
test: 'testing', | ||
prod: 'production', | ||
}; | ||
return (construct: Construct) => { | ||
const stage = stageName(construct) as keyof typeof environments; | ||
return environments[stage] || 'unknown'; | ||
}; | ||
})(); | ||
|
||
export const resourceName = (construct: Construct) => (suffix: string) => | ||
`${stageName(construct)}-${appName}-${suffix}`.toLowerCase(); | ||
|
||
export const resourceDescription = (construct: Construct) => (prefix: string) => | ||
`${prefix} for ${appName} (${stageName(construct)})`; | ||
|
||
export const stackName = (construct: Construct) => (name: string) => | ||
resourceName(construct)(`${name}-stack`); |
Oops, something went wrong.