diff --git a/dotcom-rendering/cdk/lib/__snapshots__/dotcom-rendering.test.ts.snap b/dotcom-rendering/cdk/lib/__snapshots__/dotcom-rendering.test.ts.snap index 05ed1d23799..41aad4d43b0 100644 --- a/dotcom-rendering/cdk/lib/__snapshots__/dotcom-rendering.test.ts.snap +++ b/dotcom-rendering/cdk/lib/__snapshots__/dotcom-rendering.test.ts.snap @@ -45,9 +45,10 @@ exports[`The DotcomRendering stack matches the snapshot 1`] = ` "Metadata": { "gu:cdk:constructs": [ "GuVpcParameter", - "GuSecurityGroup", "GuSubnetListParameter", + "GuSecurityGroup", "GuStringParameter", + "GuClassicLoadBalancer", "GuSecurityGroup", "GuAllowPolicy", "GuAllowPolicy", @@ -128,8 +129,8 @@ exports[`The DotcomRendering stack matches the snapshot 1`] = ` "Type": "CommaDelimitedList", }, "PublicSubnets": { - "Default": "/TEST/frontend/rendering/vpc.subnets.public", - "Description": "Public subnets", + "Default": "/account/vpc/primary/subnets/public", + "Description": "A list of public subnets", "Type": "AWS::SSM::Parameter::Value>", }, "Stack": { @@ -654,8 +655,9 @@ exports[`The DotcomRendering stack matches the snapshot 1`] = ` "Listeners": [ { "InstancePort": "9000", + "InstanceProtocol": "http", "LoadBalancerPort": "80", - "Protocol": "HTTP", + "Protocol": "http", }, ], "LoadBalancerName": "frontend-TEST-rendering-ELB", @@ -672,6 +674,10 @@ exports[`The DotcomRendering stack matches the snapshot 1`] = ` "Ref": "PublicSubnets", }, "Tags": [ + { + "Key": "App", + "Value": "rendering", + }, { "Key": "gu:cdk:version", "Value": "TEST", @@ -692,6 +698,55 @@ exports[`The DotcomRendering stack matches the snapshot 1`] = ` }, "Type": "AWS::ElasticLoadBalancing::LoadBalancer", }, + "InternalLoadBalancerRenderingSecurityGroup53E3F9A7": { + "Properties": { + "GroupDescription": "DotcomRendering/InternalLoadBalancerRendering/SecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "255.255.255.255/32", + "Description": "Disallow all traffic", + "FromPort": 252, + "IpProtocol": "icmp", + "ToPort": 86, + }, + ], + "SecurityGroupIngress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Default rule allow on 80", + "FromPort": 80, + "IpProtocol": "tcp", + "ToPort": 80, + }, + ], + "Tags": [ + { + "Key": "App", + "Value": "rendering", + }, + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/dotcom-rendering", + }, + { + "Key": "Stack", + "Value": "frontend", + }, + { + "Key": "Stage", + "Value": "TEST", + }, + ], + "VpcId": { + "Ref": "VpcId", + }, + }, + "Type": "AWS::EC2::SecurityGroup", + }, "InternalLoadBalancerSecurityGroup": { "Properties": { "GroupDescription": "Allows HTTP and HTTPS inbound connections from within the VPC", diff --git a/dotcom-rendering/cdk/lib/dotcom-rendering.ts b/dotcom-rendering/cdk/lib/dotcom-rendering.ts index 2d376d88302..43430de01f6 100644 --- a/dotcom-rendering/cdk/lib/dotcom-rendering.ts +++ b/dotcom-rendering/cdk/lib/dotcom-rendering.ts @@ -1,20 +1,21 @@ import { join } from 'node:path'; +import { GuStack, GuStringParameter } from '@guardian/cdk/lib/constructs/core'; import { - GuStack, - GuStringParameter, - GuSubnetListParameter, -} from '@guardian/cdk/lib/constructs/core'; -import { GuSecurityGroup, GuVpc } from '@guardian/cdk/lib/constructs/ec2'; + GuSecurityGroup, + GuVpc, + SubnetType, +} from '@guardian/cdk/lib/constructs/ec2'; import { GuAllowPolicy, GuInstanceRole, } from '@guardian/cdk/lib/constructs/iam'; +import { GuClassicLoadBalancer } from '@guardian/cdk/lib/constructs/loadbalancing'; import type { App } from 'aws-cdk-lib'; -import { CfnOutput } from 'aws-cdk-lib'; +import { CfnOutput, Duration } from 'aws-cdk-lib'; import { Peer } from 'aws-cdk-lib/aws-ec2'; -import { CfnLoadBalancer } from 'aws-cdk-lib/aws-elasticloadbalancing'; +import { LoadBalancingProtocol } from 'aws-cdk-lib/aws-elasticloadbalancing'; import { CfnInclude } from 'aws-cdk-lib/cloudformation-include'; -import { DCRProps } from './types'; +import type { DCRProps } from './types'; export class DotcomRendering extends GuStack { constructor(scope: App, id: string, props: DCRProps) { @@ -28,6 +29,9 @@ export class DotcomRendering extends GuStack { // and specifies the CIDR block to use with it here const vpcCidrBlock = '10.248.136.0/22'; const vpc = GuVpc.fromIdParameter(this, 'vpc', { vpcCidrBlock }); + const publicSubnets = GuVpc.subnetsFromParameter(this, { + type: SubnetType.PUBLIC, + }); const lbSecurityGroup = new GuSecurityGroup( this, @@ -56,40 +60,57 @@ export class DotcomRendering extends GuStack { reason: 'Retaining a stateful resource previously defined in YAML', }); - const lb = new CfnLoadBalancer(this, 'InternalLoadBalancer', { - listeners: [ - { - instancePort: '9000', - protocol: 'HTTP', - loadBalancerPort: '80', + const loadBalancer = new GuClassicLoadBalancer( + this, + 'InternalLoadBalancer', + { + app, + vpc, + accessLoggingPolicy: { + enabled: true, + s3BucketName: new GuStringParameter( + this, + 'ELBLogsParameter', + { + default: `${ssmPrefix}/elb.logs.bucketName`, + fromSSM: true, + description: 'S3 Bucket Name for ELB logs', + }, + ).valueAsString, + emitInterval: 5, + s3BucketPrefix: `ELBLogs/${stack}/${app}/${stage}`, + }, + crossZone: true, + healthCheck: { + interval: Duration.seconds(30), + port: 9000, + protocol: LoadBalancingProtocol.HTTP, + timeout: Duration.seconds(10), + healthyThreshold: 2, + unhealthyThreshold: 10, + path: '/_healthcheck', + }, + listeners: [ + { + externalPort: 80, + externalProtocol: LoadBalancingProtocol.HTTP, + internalPort: 9000, + internalProtocol: LoadBalancingProtocol.HTTP, + }, + ], + subnetSelection: { subnets: publicSubnets }, + propertiesToOverride: { + LoadBalancerName: `${stack}-${stage}-${app}-ELB`, + // Note: this does not prevent the GuClassicLoadBalancer + // from creating a default security group, though it does + // override which one is used/associated with the load balancer + SecurityGroups: [lbSecurityGroup.securityGroupId], }, - ], - healthCheck: { - target: 'HTTP:9000/_healthcheck', - interval: '30', - timeout: '10', - unhealthyThreshold: '10', - healthyThreshold: '2', - }, - subnets: new GuSubnetListParameter(this, 'PublicSubnets', { - default: `${ssmPrefix}/vpc.subnets.public`, - fromSSM: true, - description: 'Public subnets', - }).valueAsList, - scheme: 'internal', - securityGroups: [lbSecurityGroup.securityGroupId], - crossZone: true, - accessLoggingPolicy: { - enabled: true, - emitInterval: 5, - s3BucketName: new GuStringParameter(this, 'ELBLogsParameter', { - default: `${ssmPrefix}/elb.logs.bucketName`, - fromSSM: true, - description: 'S3 Bucket Name for ELB logs', - }).valueAsString, - s3BucketPrefix: `ELBLogs/${stack}/${app}/${stage}`, }, - loadBalancerName: `${stack}-${stage}-${app}-ELB`, + ); + this.overrideLogicalId(loadBalancer, { + logicalId: 'InternalLoadBalancer', + reason: 'Retaining a stateful resource previously defined in YAML', }); const instanceSecurityGroup = new GuSecurityGroup( @@ -165,13 +186,13 @@ export class DotcomRendering extends GuStack { parameters: { VpcId: vpc.vpcId, InstanceSecurityGroup: instanceSecurityGroup.securityGroupId, - InternalLoadBalancer: lb.ref, + InternalLoadBalancer: loadBalancer.loadBalancerName, InstanceRole: instanceRole.roleName, }, }); new CfnOutput(this, 'LoadBalancerUrl', { - value: lb.attrDnsName, + value: loadBalancer.loadBalancerDnsName, }); } }