exanubes

Application on ECS #2Adding SSL Certificate to Fargate app

This article is part of a series:

  1. Deploying ECS Fargate Application
  2. Adding SSL Certificate to Fargate app
  3. CI/CD pipeline for ECS application
  4. Connecting to RDS via Parameter Store config

Previously, we were able to deploy a simple Nestjs web server to ECS fargate and serve it through a load balancer. However, that connection is not secure and the url is not very user friendly so in this article we will go over serving the application with our own domain name and securing it with a SSL Certificate.

The starting point for this article is the repository for deploying application to ECS Fargate. You can find the finished code on github.

Requirements #

To follow along you will definitely need a domain name. Should you have a domain outside of aws you can checkout this article to create a hosted zone in AWS with your existing domain – moving domain over to AWS not required. Other than that, you will also need to generate a certificate for your domain in the AWS Certificate Manager in the same region as your ECS deployment.

Adding a certificate to load balancer #

To serve our application over https we're going to need a couple things. First we need to add a listener to our Load Balancer with https protocol and port 443, this will also require from us to provide a certificate. Then, we will need to open up the port 443 in Load Balancer's security group to allow traffic on that port.

typescript
Double click to copy
1// lib/elastic-container.stack.ts
2const CERTIFICATE_ARN =
3 'arn:aws:acm:eu-central-1:123456789012:certificate/uuid';
4
5albSg.addIngressRule(Peer.anyIpv4(), Port.tcp(443));
6const sslListener = this.loadBalancer.addListener('secure https listener', {
7 port: 443,
8 open: true,
9 sslPolicy: SslPolicy.RECOMMENDED,
10 certificates: [{ certificateArn: CERTIFICATE_ARN }],
11});
12
13const targetGroup = sslListener.addTargets('tcp-listener-target', {
14 targetGroupName: 'tcp-target-ecs-service',
15 protocol: ApplicationProtocol.HTTP,
16 protocolVersion: ApplicationProtocolVersion.HTTP1,
17});

Here, we have also assigned our target group to the sslListener rather than httpListener. Speaking of which, it would be good to redirect users to https even if they're using http.

typescript
Double click to copy
1// lib/elastic-container.stack.ts
2const httpListener = this.loadBalancer.addListener('http listener', {
3 port: 80,
4 open: true,
5 defaultAction: ListenerAction.redirect({
6 port: '443',
7 protocol: ApplicationProtocol.HTTPS,
8 }),
9});

By changing the default behaviour, we can reroute all requests on port 80 to port 443 – HTTPS – then we will use our target group and route users to our app.

Last but not least, we will need access to the load balancer in the next step, so we need to assign it to a property on the stack instance.

typescript
Double click to copy
1// lib/elastic-container.stack.ts
2this.loadBalancer = new ApplicationLoadBalancer(...)

Assigning domain #

Now that we've assigned a certificate to the load balancer, we only have to create an appropriate DNS Record to be able to use the domain along with our SSL Certificate.

For this we will need to things: hosted zone id and hosted zone name. The hosted zone name is the domain name or subdomain you used to create your hosted zone, e.g., dev.exanubes.com. Both hosted zone id and hosted zone name can be found in Route 53 > Hosted zones

Now, once you have this, we can create an instance of our existing hosted zone.

typescript
Double click to copy
1// lib/route53.stack.ts
2interface Props extends StackProps {
3 loadBalancer: IApplicationLoadBalancer;
4}
5const HOSTED_ZONE_ID = 'YOUR_HOSTED_ZONE_ID';
6export class Route53Stack extends Stack {
7 constructor(scope: Construct, id: string, props: Props) {
8 super(scope, id, props);
9 const hostedZone = HostedZone.fromHostedZoneAttributes(
10 this,
11 'hosted-zone',
12 {
13 hostedZoneId: HOSTED_ZONE_ID,
14 zoneName: 'dev.exanubes.com',
15 }
16 );
17 }
18}

And finally, we can create an Alias and use the Load Balancer as target.

typescript
Double click to copy
1// lib/route53.stack.ts
2new ARecord(this, 'ecs-alb-alias-record', {
3 zone: hostedZone,
4 target: RecordTarget.fromAlias(
5 new targets.LoadBalancerTarget(props.loadBalancer)
6 ),
7});

Deployment #

Now it's time to create our stacks and deploy

typescript
Double click to copy
1// bin/ecs-fargate-deployment.ts
2const app = new cdk.App();
3const ecr = new EcrStack(app, EcrStack.name, {});
4const vpc = new VpcStack(app, VpcStack.name, {});
5const ecs = new ElasticContainerStack(app, ElasticContainerStack.name, {
6 vpc: vpc.vpc,
7 repository: ecr.repository,
8});
9new Route53Stack(app, Route53Stack.name, { loadBalancer: ecs.loadBalancer });

Here, you can see why we needed to assign load balancer to a public property on the class instance as we are passing it to the Route53Stack.

When deploying, keep in mind you will have to upload a Docker Image, otherwise it will hang when deploying the ElasticContainerStack, you can do it while it's deploying all stacks or deploy EcrStack first:

Double click to copy
npm run build && npm run cdk:deploy EcrStack

and after pushing a Docker Image to ECR:

Double click to copy
npm run cdk:deploy -- --all

Now you can go to the domain name you chose for the application and it should automatically redirect you to a secure connection – https.

Fargate application with SSL Certificate

Don't forget to tear it all down when you're done:

Double click to copy
npm run cdk:destroy -- --all

Summary #

To sum up, in this article we've successfully reached our application via our own domain name and secured it with an SSL Certificate.

However, this is not yet a modern application as each time we'd like to release a new version, we'd have to manually go through the release process. Next up, deploying Fargate application with a CI/CD Pipeline.

Thanks!

A verification email has been sent to

Keep in Touch

Join other developers in Exanubes Newsletter

© 2023