Securing HTTP communication over an ALB using ACM and Copilot CLI in a Multi-Account environment.

Andres Solorzano
9 min readJun 1, 2023

One disadvantage when configuring an Application Load Balancer (ALB) is that it doesn’t provide a default SSL/TLS connection (as the API Gateway does). Of course, we can create our own SSL/TLS certificates and use them in the supported AWS services. So, let’s request our TLS certificate using the Amazon Certificate Manager (ACM) and attach it to our ALB using Copilot CLI.

To complete this guide, you’ll need the following tools:

NOTE: You can download the source code of the Task Service with all the configurations we talk about in this article from my GitHub repository.

Domain Registration on Route53 (optional).

Well, we need a domain name to complete this guide. For our Tasks Service project, I bought a DNS called “hiperium.cloud” directly in AWS using the Route53 service. So, if you want to register your domain name, go to the Route53 console and use the following component to verify the availability:

Let’s try to buy a new domain name so that you can see the process. I want to register “aosolorzano.com,” so this is the query result for this domain name:

For the “.com” domain names, the annual cost is 13 dollars (you can buy for more years, too).

This is the final message when you follow the required steps:

After you finalize the purchase, your domain name will stay pending in the Route53 console:

You could also transfer your existing domain names from other registrars like Nic.com to the Route53 service in AWS. I did this to transfer another domain name to Route53 before.

Remember that this activities are optional. You are not forced to use a domain mane from Route53 to create your SSL/TLS certificate. I did this because of the facility to control all the management duties in one single place.

Configuring DNSSEC (DNS Security Extensions).

As the AWS documentation mentions:

Attackers sometimes hijack traffic to internet endpoints such as web servers by intercepting DNS queries and returning their own IP addresses to DNS resolvers in place of the actual IP addresses for those endpoints. Users are then routed to the IP addresses provided by the attackers in the spoofed response, for example, to fake websites.

You can protect your domain from this type of attack, known as DNS spoofing or a man-in-the-middle attack, by configuring Domain Name System Security Extensions (DNSSEC), a protocol for securing DNS traffic.

Enabling DNSSEC for your domains is an effective best practice. So let’s do that in the Route53 console by going to the “DNSSEC signing” tab in your hosted zone:

A new web form will be opened where you must select a customer-managed customer master key (CMK) or create a new one:

If you request a new one, Route 53 creates the KSK with the provided name in the AWS Key Management Service (KMS), which you can use for signing other domain names.

Signing your domain name in the console will take a moment. After that, you must see something like this:

So, I’m using the “hiperium.cloud” domain name for our back-end services of the Hiperium Project :)

Request a Certificate in ACM.

Let’s run the following command to request our certificate using the preferred domain name and the profile you used to deploy the Tasks Service workloads. Don’t worry if your Route 53 service is in another account. Later we need to update the Hosted Zones with the newly created FQDN:

NOTE: Ignore the “- -profile” flag if you’re okay using your “default” profile.

aws acm request-certificate                               \
--domain-name 'example.com' \
--subject-alternative-names "api.example.com" \
--idempotency-token 'idempotency_token' \
--validation-method DNS \
--profile 'tasks-dev'

The “idempotency_token” tag indicates the following according to AWS’s official documentation:

Customer chosen string that can be used to distinguish between calls to RequestCertificate . Idempotency tokens time out after one hour. Therefore, if you call RequestCertificate multiple times with the same idempotency token within one hour, ACM recognizes that you are requesting only one certificate and will issue only one. If you change the idempotency token for each call, ACM recognizes that you are requesting multiple certificates.

Now, go to the ACM console in the account used in the previous profile command, and you will have a pending validation request:

Inside this request, you will find 2 CNAME records that you will load into your DNS service. In the following image, the “CNAME name” and “CNAME value” values are hidden, but you can assume that in those columns, there are the values you must use:

Go to the Route53 console in the account where you have your Hosted Zones and add the CNAME records. The default TTL value assigned in the CNAME record is 300 seconds. So you must wait at least 5 minutes for the ACM service to validate the certificate.

Then, the status of your certificate must change from “Pending” to “Issued” in the ACM console:

So far, so good. Now we need the ARN certificate. You can copy it from the ACM console or by executing the following command:

$ aws acm list-certificates   \
--profile 'tasks-dev' | jq -r '.CertificateSummaryList[] | select(.DomainName == "example.com") | .CertificateArn'

Then, modify the “copilot/environments/dev/manifest.yml” file adding the certificate ARN like this:

http:
public:
certificates:
- arn:aws:acm:<aws_region>:<aws_account_id>:certificate/123456789

Likewise, modify the “copilot/api/manifest.yml” file by adding the following config into the environments section like the next:

environments:
dev:
http:
alias: 'api.example.com'

We have done with the ACM certification request and obtained the ARN of the certificate to configure it in the Copilot CLI configuration files. So it’s time to deploy our micro-service on Fargate ECS.

Deploying Spring Boot Native micro-service.

We are ready to deploy our Spring Boot Native micro-service on ECS. So let’s run the following command at the project’s root directory:

$ ./run-script

Use option 1 to deploy our Spring Boot Native micro-service as we did in my previous tutorial:

The main script must execute all required Copilot CLI commands for us to deploy our Spring Boot Native micro-service on Fargate ECS.

Notice that Copilot CLI outputs some HTTPS configurations when deploying our micro-service on AWS:

When the command completes, Copilot CLI indicates the endpoint to access our Tasks Service, which now uses HTTPS:

The last configuration we must do is to manually create a new DNS Hosted Zone record with the ALB’s hostname as a CNAME record. But first, we need the ALB hostname to complete this appointment. So let’s execute the following command to try to get it:

$ aws cloudformation describe-stacks \
--stack-name city-tasks-dev \
--query "Stacks[0].Outputs[?OutputKey=='PublicLoadBalancerDNSName'].OutputValue" \
--output text \
--profile 'tasks-dev'

Copy the result, go to your Hosted Zones in the Route53 console, select your domain name, and click the “Create record” button. Enter the required information like this:

Click on the “Create records” button to save this information. A new record will be added to your Hosted Zone table. Notice that the default TTL is 300 seconds, so we must wait at least 5 minutes to access our ALB endpoint.

You can use the “dig” command to validate the DNS response for your domain name after a specific time:

The response shows the CNAME registry for my registered domain. Notice that the response also indicates DNS “A” records which are the IP address resolution for your ALB domain name.

So it’s the time of truth. We have deployed our MS in Fargate ECS, so let’s continue testing our Tasks Service endpoints.

Testing Spring Boot Native micro-service.

The first endpoint we must test is the health check. So open a new browser tab, enter your domain name using HTTPS, and add the “/actuator/health” path at the end. You must see the following response:

Notice that the connection is now secure. Also, remember that this URL is public because the ALB must validate if the micro-service is up or down. The other endpoints are private, so we must get our JWT before continuing.

Open the Postman tool. We will perform the same tests as in our previous tutorial but use HTTPS this time. Open a new Postman tab and try to access our created City Tasks:

I’m using HTTPS and my custom domain name in the endpoint. As we expected, we got an HTTP 401 error because we needed our JWT. So our second test is completed.

Open the Authorization tab and enter the required information to get the OAuth 2.0 access token from your Cognito User Pool:

Click the “Get New Access Token” button and follow the known process to obtain your valid JWT:

Click “Use Toten” and return to the “Params” section to send our HTTPS request again and see what happens:

Cool!! Now let’s try to query all City Tasks that are programmed to be executed on Wednesdays:

And that is!!!. Remember always to delete the created infra on AWS. For this purpose, I made a Bash Script (using option 2) that removes all created Copilot CLI infra and reverts the configuration files to their initial state. So you can deploy new infra using different parameters ;)

Finally, remember that our ALB has an “SSL Termination” setting, so the secure HTTPS communication is only from the client to the ALB. In our following tutorial, we will be configuring an End-to-End encryption so that the secure HTTPS communication will be from the client to the ECS cluster in AWS.

I hope this tutorial has been helpful to you, and I will see you in the next one!! Bye.

--

--