Adding SSL Certificate to Fargate app
Tags: aws, cdk, typescript, cloud
Published: 2022-09-20 12:04:49

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](https://github.com/exanubes/ecs-fargate-deployment).
You can find the finished code on [github](https://github.com/exanubes/fargate-app-with-ssl-certificate).

## 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](https://exanubes.com/blog/sharing-a-domain-name-across-multiple-aws-accounts) 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
// lib/elastic-container.stack.ts
const CERTIFICATE_ARN =
  'arn:aws:acm:eu-central-1:123456789012:certificate/uuid';

albSg.addIngressRule(Peer.anyIpv4(), Port.tcp(443));
const sslListener = this.loadBalancer.addListener('secure https listener', {
  port: 443,
  open: true,
  sslPolicy: SslPolicy.RECOMMENDED,
  certificates: [{ certificateArn: CERTIFICATE_ARN }],
});

const targetGroup = sslListener.addTargets('tcp-listener-target', {
  targetGroupName: 'tcp-target-ecs-service',
  protocol: ApplicationProtocol.HTTP,
  protocolVersion: ApplicationProtocolVersion.HTTP1,
});
```

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
// lib/elastic-container.stack.ts
const httpListener = this.loadBalancer.addListener('http listener', {
  port: 80,
  open: true,
  defaultAction: ListenerAction.redirect({
    port: '443',
    protocol: ApplicationProtocol.HTTPS,
  }),
});
```

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
// lib/elastic-container.stack.ts
this.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](https://exanubes.com/blog/route-53-under-the-hood) 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
// lib/route53.stack.ts
interface Props extends StackProps {
  loadBalancer: IApplicationLoadBalancer;
}
const HOSTED_ZONE_ID = 'YOUR_HOSTED_ZONE_ID';
export class Route53Stack extends Stack {
  constructor(scope: Construct, id: string, props: Props) {
    super(scope, id, props);
    const hostedZone = HostedZone.fromHostedZoneAttributes(
      this,
      'hosted-zone',
      {
        hostedZoneId: HOSTED_ZONE_ID,
        zoneName: 'dev.exanubes.com',
      }
    );
  }
}
```

And finally, we can create an [Alias](https://exanubes.com/blog/route-53-under-the-hood/#alias) and use the Load Balancer as target.

```typescript
// lib/route53.stack.ts
new ARecord(this, 'ecs-alb-alias-record', {
  zone: hostedZone,
  target: RecordTarget.fromAlias(
    new targets.LoadBalancerTarget(props.loadBalancer)
  ),
});
```

## Deployment

Now it's time to create our stacks and deploy

```typescript
// bin/ecs-fargate-deployment.ts
const app = new cdk.App();
const ecr = new EcrStack(app, EcrStack.name, {});
const vpc = new VpcStack(app, VpcStack.name, {});
const ecs = new ElasticContainerStack(app, ElasticContainerStack.name, {
  vpc: vpc.vpc,
  repository: ecr.repository,
});
new 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](https://exanubes.com/blog/ecs-fargate-deployment/#uploading-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:

```
npm run build && npm run cdk:deploy EcrStack
```

and after pushing a Docker Image to ECR:

```
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](https://exanubes.com/images/ecs/fargate-with-ssl.png)

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

```
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.