Skip to content

Commit

Permalink
refactor(form-ecs): extract into own construct
Browse files Browse the repository at this point in the history
  • Loading branch information
LoneRifle committed Aug 29, 2024
1 parent 2b38aef commit 38a1d33
Show file tree
Hide file tree
Showing 3 changed files with 180 additions and 90 deletions.
152 changes: 152 additions & 0 deletions lib/constructs/form-ecs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import { Construct } from 'constructs'
import * as ecs from 'aws-cdk-lib/aws-ecs';
import { FormsgS3Buckets } from './s3';
import { ApplicationLoadBalancer, ApplicationProtocol } from 'aws-cdk-lib/aws-elasticloadbalancingv2';
import { PolicyStatement } from 'aws-cdk-lib/aws-iam';
import { LogGroup } from 'aws-cdk-lib/aws-logs';
import { Duration } from 'aws-cdk-lib';


export class FormEcs extends Construct {
readonly service: ecs.FargateService
readonly hostname: string

constructor(
scope: Construct,
{
cluster,
logGroupSuffix,
s3Buckets,
environment,
secrets,
loadBalancer,
} : {
cluster: ecs.Cluster;
logGroupSuffix: string;
s3Buckets: FormsgS3Buckets;
environment: Record<string, string>;
secrets: Record<string, ecs.Secret>;
loadBalancer: ApplicationLoadBalancer
}
) {
super(scope, 'form')
const port = 5000
const taskDefinition = new ecs.FargateTaskDefinition(this, 'task')
taskDefinition
.addContainer('web', {
image: ecs.ContainerImage.fromRegistry('opengovsg/formsg-intl'),
containerName: 'web',
environment,
secrets,
logging: ecs.LogDriver.awsLogs({
logGroup: new LogGroup(this, 'cloudwatch', {
logGroupName: `/aws/ecs/logs/form/${logGroupSuffix}`,
}),
streamPrefix: 'form',
}),
portMappings: [
{ containerPort: port, hostPort: port },
],
})
taskDefinition.addToTaskRolePolicy(
new PolicyStatement({
actions: [
's3:PutObject',
's3:GetObject',
's3:DeleteObject',
's3:PutObjectAcl',
],
resources: [
s3Buckets.s3Attachment,
s3Buckets.s3Image,
s3Buckets.s3Logo,
].map(({ bucketArn }) => `${bucketArn}/*`),
})
)

taskDefinition.addToTaskRolePolicy(
new PolicyStatement({
actions: [
's3:PutObject',
's3:GetObject',
],
resources: [`${s3Buckets.s3PaymentProof.bucketArn}/*`],
})
)

taskDefinition.addToTaskRolePolicy(
new PolicyStatement({
actions: [
's3:PutObject',
],
resources: [`${s3Buckets.s3VirusScannerQuarantine.bucketArn}/*`],
})
)

taskDefinition.addToTaskRolePolicy(
new PolicyStatement({
actions: [
's3:GetObjectVersion',
],
resources: [`${s3Buckets.s3VirusScannerClean.bucketArn}/*`],
})
)

taskDefinition.addToTaskRolePolicy(
new PolicyStatement({
actions: [
's3:GetObject',
's3:GetObjectTagging',
's3:GetObjectVersion',
's3:DeleteObject',
's3:DeleteObjectVersion',
],
resources: [`${s3Buckets.s3VirusScannerQuarantine.bucketArn}/*`],
})
)
taskDefinition.addToTaskRolePolicy(
new PolicyStatement({
actions: [
's3:PutObject',
's3:PutObjectTagging',
],
resources: [`${s3Buckets.s3VirusScannerClean.bucketArn}/*`],
})
)

const service = new ecs.FargateService(this, 'service', {
cluster,
taskDefinition,
})

const listener = loadBalancer.addListener('alb-listener', {
port: 80,
protocol: ApplicationProtocol.HTTP,
})

const scaling = service.autoScaleTaskCount({ maxCapacity: 2 })
scaling.scaleOnCpuUtilization('scaling', {
targetUtilizationPercent: 50,
scaleInCooldown: Duration.seconds(60),
scaleOutCooldown: Duration.seconds(60),
})

service.registerLoadBalancerTargets({
containerName: 'web',
containerPort: port,
listener: ecs.ListenerConfig.applicationListener(
listener,
{
protocol: ApplicationProtocol.HTTP,
port,
healthCheck: {
healthyHttpCodes: ['200', '403'].join(',')
}
},
),
newTargetGroupId: 'ecs',
})

this.service = service
}
}
2 changes: 1 addition & 1 deletion lib/constructs/virus-scanner-ecs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export class VirusScannerEcs extends Construct {
cpu: 1024,
})
taskDefinition
.addContainer('task-container', {
.addContainer('web', {
image: ecs.ContainerImage.fromRegistry('opengovsg/lambda-virus-scanner:latest-ecs'),
containerName: 'web',
environment: {
Expand Down
116 changes: 27 additions & 89 deletions lib/formsg-on-cdk-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,18 @@ import * as cdk from 'aws-cdk-lib'
import { Construct } from 'constructs'

import * as ecs from 'aws-cdk-lib/aws-ecs'
import * as ecsPatterns from 'aws-cdk-lib/aws-ecs-patterns'
import * as ec2 from 'aws-cdk-lib/aws-ec2'
import { ApplicationLoadBalancer } from 'aws-cdk-lib/aws-elasticloadbalancingv2'
import { Secret } from 'aws-cdk-lib/aws-secretsmanager'
import { PolicyStatement } from 'aws-cdk-lib/aws-iam'
import { AllowedMethods, CachePolicy, Distribution, OriginProtocolPolicy, OriginRequestPolicy, ViewerProtocolPolicy } from 'aws-cdk-lib/aws-cloudfront'
import { LoadBalancerV2Origin } from 'aws-cdk-lib/aws-cloudfront-origins'

import { FormsgS3Buckets } from './constructs/s3'
import { FormsgEcr } from './constructs/ecr'
import defaultEnvironment from './formsg-env-vars'
import { LogGroup } from 'aws-cdk-lib/aws-logs'
import defaultEnvironment from './formsg-env-vars';
import { OriginVerify } from '@alma-cdk/origin-verify'
import { VirusScannerEcs } from './constructs/virus-scanner-ecs'
import { FormEcs } from './constructs/form-ecs'

export class FormsgOnCdkStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
Expand Down Expand Up @@ -210,6 +208,18 @@ export class FormsgOnCdkStack extends cdk.Stack {
},
})

// Create Session Secret
const sessionSecret = ecs.Secret.fromSecretsManager(
new Secret(this, 'session-secret', {
secretName: 'session-secret',
removalPolicy: cdk.RemovalPolicy.DESTROY,
generateSecretString: {
excludePunctuation: true,
excludeCharacters: "/¥'%:{}",
},
})
)

const s3Suffix = suffixSecret.secretValue.unsafeUnwrap()
const s3Buckets = new FormsgS3Buckets(this, { s3Suffix, origin: distributionUrl })

Expand Down Expand Up @@ -245,100 +255,28 @@ export class FormsgOnCdkStack extends cdk.Stack {
VIRUS_SCANNER_LAMBDA_ENDPOINT: `http://${virusScanner.hostname}`,
}

// Create Session Secret
const sessionSecret = ecs.Secret.fromSecretsManager(
new Secret(this, 'session-secret', {
secretName: 'session-secret',
removalPolicy: cdk.RemovalPolicy.DESTROY,
generateSecretString: {
excludePunctuation: true,
excludeCharacters: "/¥'%:{}",
},
})
)
const secrets = {
DB_HOST: dbHostString,
SESSION_SECRET: sessionSecret,
SES_USER: sesUserSecret,
SES_PASS: sesPassSecret,
GOOGLE_CAPTCHA: googleCaptchaSecret,
}

// Create Fargate Service
const fargate = new ecsPatterns.ApplicationLoadBalancedFargateService(this, 'app', {
const form = new FormEcs(this, {
cluster,
taskImageOptions: {
image: ecs.ContainerImage.fromRegistry('opengovsg/formsg-intl'),
environment,
secrets: {
DB_HOST: dbHostString,
SESSION_SECRET: sessionSecret,
SES_USER: sesUserSecret,
SES_PASS: sesPassSecret,
GOOGLE_CAPTCHA: googleCaptchaSecret,
},
logDriver: ecs.LogDriver.awsLogs({
logGroup: new LogGroup(this, 'cloudwatch', {
logGroupName: `/aws/ecs/logs/form/${logGroupSuffix}`,
}),
streamPrefix: 'form',
}),
containerPort: 5000,
},
logGroupSuffix,
s3Buckets,
environment,
secrets,
loadBalancer,
healthCheckGracePeriod: cdk.Duration.seconds(0),
})

const scaling = fargate.service.autoScaleTaskCount({ maxCapacity: 2 })
scaling.scaleOnCpuUtilization('scaling', {
targetUtilizationPercent: 50,
scaleInCooldown: cdk.Duration.seconds(60),
scaleOutCooldown: cdk.Duration.seconds(60),
})

fargate.service.connections.securityGroups.forEach((securityGroup) => {
form.service.connections.securityGroups.forEach((securityGroup) => {
dbSecurityGroup.addIngressRule(securityGroup, ec2.Port.tcp(27017))
})

;[
s3Buckets.s3Attachment,
s3Buckets.s3Image,
s3Buckets.s3Logo,
].forEach(({ bucketArn }) =>
fargate.taskDefinition.addToTaskRolePolicy(
new PolicyStatement({
actions: [
's3:PutObject',
's3:GetObject',
's3:DeleteObject',
's3:PutObjectAcl',
],
resources: [`${bucketArn}/*`],
})
)
)

fargate.taskDefinition.addToTaskRolePolicy(
new PolicyStatement({
actions: [
's3:PutObject',
's3:GetObject',
],
resources: [`${s3Buckets.s3PaymentProof.bucketArn}/*`],
})
)

fargate.taskDefinition.addToTaskRolePolicy(
new PolicyStatement({
actions: [
's3:PutObject',
],
resources: [`${s3Buckets.s3VirusScannerQuarantine.bucketArn}/*`],
})
)

fargate.taskDefinition.addToTaskRolePolicy(
new PolicyStatement({
actions: [
's3:GetObjectVersion',
],
resources: [`${s3Buckets.s3VirusScannerClean.bucketArn}/*`],
})
)

new cdk.CfnOutput(this, 'url', { value: cloudFront.distributionDomainName })
}
}

0 comments on commit 38a1d33

Please sign in to comment.