Containers
Securing Amazon Elastic Container Service applications using Application Load Balancer and Amazon Cognito
Introduction
Designing and maintaining secure user management, authentication and other related features for applications is not an easy task. Amazon Cognito takes care of this work, which allows developers to focus on building the core business logic of the application.
Amazon Cognito provides user management, authentication, and authorization for applications where users can log in in directly or through their pre-existing social or corporate credentials.
Amazon Elastic Containers Service (Amazon ECS) is a fully managed container orchestration service that makes it easy for customers to deploy, manage, and scale their container-based applications. When building using Amazon ECS, it is common to use Application Load Balancer (ALB) for application high availability and other features like SSL/TLS offloading, host based routing, and other application-aware traffic handling.
Another benefit of using the ALB with Amazon ECS is that the ALB has in-built support for Amazon Cognito.
When setting up the ALB, you can chose if you want incoming user traffic to be redirected to Amazon Cognito for authentication. By building secure containerized applications using Amazon ECS, and using ALB and its Amazon Cognito integration, you get the benefits of the ease of container orchestration and user authentication and authorization.
Flow of how Application Load Balancer authenticates users using Amazon Cognito
For an application fronted by ALB that integrates with Amazon Cognito and has been set up to authenticate users, the following stepwise flow describes what happens when a user attempts to access the application.
For more information, see the example built by the AWS Elastic Load Balancing Demos.
You need to understand what the ALB is doing to secure user access with Amazon Cognito:
- A user sends a request to the application fronted by the ALB, which has a set of rules that it evaluates for all traffic to determine what action to carry out. The rule (such as the path-based rule saying all traffic for/
login
) when matched triggers the authentication action on the ALB. The ALB then inspects the user’s HTTP payload for an authentication cookie. - Because this is the user’s first visit, this cookie isn’t present. The ALB doesn’t see any cookie and redirects the user to the configured Amazon Cognito’s authorization endpoint.
- The user is presented with an authentication page from Amazon Cognito, where the user inputs their credentials. Amazon Cognito redirects the user back to the ALB and passes an authorization code to the user in the redirect URL.
- The load balancer takes this authorization code and makes a request to Amazon Cognito’s token endpoint.
- Amazon Cognito validates the authorization code and presents the ALB with an ID and access token.
- The ALB forwards the access token to Amazon Cognito’s user info endpoint.
- Amazon Cognito’s user information endpoint presents the ALB with user claims.
- The ALB redirects the user who is trying to access the application (step 1) to the same URL while inserting the authentication cookie in the redirect response.
- The user makes the request to the ALB with the cookie and the ALB validates it and forwards the request to the ALB’s target. The ALB inserts information (such as user claims, access token, and the subject field) into a set of
X-AMZN-OIDC-*
HTTP headers to the target. - The target generates a response and forwards to the ALB.
- The ALB sends the response to the authenticated user.
When the user makes subsequent requests for HTTP request and response, the flow will go through steps 9–11. If the user makes a new request without the authentication cookie, it goes through steps 1–11.
For more information, see the authentication flow between the ALB and Amazon Cognito.
Solution overview
You will use a PHP application built for demonstration purpose. The application is published and verified in the public docker hub. We use and configure Amazon Route 53 for Domain Name Service (DNS) handling and AWS Certificate Manager (ACM) to provision Transport Layer Security (TLS) Certificates for usage.
Amazon Cognito handles the Authentication flows and Amazon ECS handles the container scheduling and orchestration.
The following solution architecture diagram presents an overview of the solution.
Prerequisites
To complete this tutorial you need the following tools, which can be installed with the links:
- aws cliv2: The AWS Command Line Interface (AWS CLI) is an open source tool that allows you interact with AWS services using commands in your command-line shell.
- ecs-cli: The Amazon Elastic Container Service (Amazon ECS) CLI provides high-level commands to simplify creating, updating, and monitoring tasks and clusters from a local development environment.
Environment
In this post, I used the AWS Cloud9 as an Integrated Development Environment (IDE) to configure the settings in this tutorial. You can use AWS Cloud9 or your own IDE. The commands used were tested using Amazon Linux 2 running in the Amazon Cloud9 environment.
Follow the steps linked to install and configure Amazon Cloud9:
Create a workspace to deploy this solution, which includes creating an AWS Identity and Access Management (IAM) role that will be attached to the workspace instance.
Launch the base infrastructure platform that the resources reside in
The Amazon ECS needs to be launched into a Virtual Private Cloud (VPC) infrastructure. To create this infrastructure, you use an AWS CloudFormation template that automates the creation of the platform.
Download the zip file that contains an AWS CloudFormation yaml file: codebuild-vpc-cfn.yaml
.
Once deployed, the following resources are created into your AWS account: a Virtual Private Cloud (VPC), an internet gateway, two public subnets, two private subnets, two Network Address Translation (NAT) Gateways, and one security group.
To launch the stack, follow these steps:
- Sign in to the AWS Management Console.
- In your Region of choice, you will see the Region list in the top right-hand corner.
- Search for the AWS CloudFormation service in the Console search bar.
- Choose Create Stack and select with new resources (standard).
- To specify template, choose upload a template file.
- Upload the previously downloaded:
codebuild-vpc-cfn.yaml
file. - To create the stack and configure stack options, choose Next.
- Enter ecsplatform for stack name and ecsplatform for
EnvironmentName
. Choose Next. - Leave the rest of the default settings and choose Next.
- Choose Create Stack.
- When CloudFormation has completed its deployment its the resources, the status is CREATE_COMPLETE.
Next on your Amazon Cloud9 workspace terminal, set the below environment variables:
You will set additional variables later, but these are enough to begin building your solution.
Configure the security group rules needed for web traffic access
When users access the ALB, the security group attached to it needs to allow ingress port 443 (https) traffic. In addition, when the ALB forwards the web traffic to the Amazon ECS tasks there needs to be a ingress rules attached to the Amazon ECS container instances that allows ingress port 80 (http) traffic.
You can achieve this access with the following:
Create a public Application Load Balancer
As described earlier, the ALB will receive and terminate all client requests to validate for authentication using Amazon Cognito. The ALB also handles TLS offloading where the TLS certificates for the domain name will be deployed on it.
To create the ALB do the below:
Configure a Domain Name System
Clients will need a domain name that points to the ALB to type into their browsers.
In this post, the Domain Name System (DNS) name is registered using the DNS Resolution service, Amazon Route 53.
You can configure your domain name (such as www.example.com) where it‘s known as the record and placed in a Route 53-hosted zone.
Configure both the Route 53 hosted zone and record
If you already have a Route53
publicly hosted zone for the apex domain and this is the location where you plan to add the record, then you will set its host zone ID (AUTH_ECS_R53HZ
). For more information, see the hosted zone ID documentation.
The first command line shown below demonstrates how to identify a hosted zone ID. You can substitute example.com for your apex domain name. The other commands create a record that points to the ALB.
Request a public certificate
To ensure that web traffic sent by clients to the ALB is encrypted, integrate an ACM (AWS Certificate Manager) Certificate into the ALB’s listener. This ensures that the ALB serves HTTPS traffic and communications from clients to ALB is encrypted. Public SSL/TLS certificates provisioned through AWS Certificate Manager are free. You pay only for the AWS resources you create to run your application.
Provision an ACM certificate
When you create an SSL/TLS Certificate using ACM, it will try to confirm that you’re the owner of the domain name before fully provisioning the certificate for you to use. One method of confirmation is through DNS validation.
Through this method ACM creates two CNAME records that you must add in your Route53
hosted zone.
To add the ACM CNAME records in your Route53
hosted zones:
It takes some time before the certificate will change from ‘Pending Validation’ to ‘Success’.
Once the status shows ‘Issued’ on the ACM console then you can use the certificate.
Create an HTTPS listener and listener rule on the ALB
Now that you’ve created the ALB. In addition, you’ve also created a certificate to configure the HTTPS listener to accept incoming HTTPS request from clients and to terminate them.
You integrate the certificate into the listener and add a default rule action on the ALB:
Create an Amazon Cognito user pool
As previously described, Amazon Cognito provides user management, authentication and authorization for applications where users can login in directly or through their pre-existing social/corporate credentials.
Create a user pool, which is a user directory in Amazon Cognito that helps clients to access the website. Clients sign in with their credentials before they get access to the site.
To fully configure Amazon Cognito for integration with the ALB, create a user pool, a user pool application client, and a user pool domain. The following steps show you how to accomplish these tasks.
Create an Amazon Cognito user pool
Create an Amazon Cognito user pool application client
Create an Amazon Cognito user pool domain
Create and configure a target group for the ALB
Create a target group for the ALB. The target group is used to route requests to the Amazon ECS tasks.
When an ALB receives the HTTPS traffic from the web clients, it routes the requests to the target group (after authentication of the client has occurred) for a web response. (Amazon ECS tasks are registered to the target group in a later section “Configuring the ECS Service”).
Create an empty target group:
Host-based routing and an authentication rule on the ALB
The ALB routes requests based on the host name in the HTTP host header.
It is possible to configure multiple domains that all point to a single ALB because the ALB can route requests based on the incoming host header and forward the requests to the right target group for handling.
You can configure an authentication rule which tells the ALB what to do to the incoming requests. In this post, we want the requests to first be authenticated and, if successful, the request should get forwarded to the target group we created earlier.
Configure host-based routing and an authentication rule on the ALB
Amazon ECS configuration
The ALB and Amazon Cognito are now configured for processing incoming requests and authentication.
Next you will configure Amazon ECS to orchestrate and deploy running tasks to generate response for the client’s web request.
An Amazon ECS cluster is a logical grouping of tasks or services. Amazon ECS instances are part of the Amazon ECS infrastructure registered to a cluster that the Amazon ECS tasks run on.
Two t3.small
Amazon ECS instances will be configured to run the tasks. Amazon ECS will run and maintain two tasks, which are configured based on parameters and settings contained in the task definition (a JSON
text file).
For more information on Amazon ECS basics, constructs, and orchestration read the Amazon ECS components documentation.
Configure the Amazon ECS CLI
Amazon ECS CLI is the tool that you’d use to configure and launch the Amazon ECS components.
To download Amazon ECS CLI, follow the following steps:
Amazon ECS CLI needs a CLI profile, to proceed generate an access key ID, and access key using the AWS credentials documentation.
Set the $AWS_ACCESS_KEY_ID
and $AWS_SECRET_ACCESS_KEY
variables to the copied values generated by AWS IAM.
Configure the Amazon ECS CLI for a CLI profile
Create the Amazon ECS cluster
Create the Amazon ECS cluster, which consists of two t3.small
instance types deployed in the VPC and residing in the two private subnets from earlier. For the instance-role, use the AWS IAM role created when configuring the AWS Cloud9 environment workspace (ecsworkshop-admin
).
The first command creates a keypair and the second command configures the Amazon ECS cluster. The keypair is useful if you need to SSH into the Amazon ECS instances for troubleshooting.
Configure the Amazon EC2 key pair and bring up the ECS cluster
The cluster creation will take some time, when fully deployed the AWS CloudFormation stack status will output ‘Cluster creation succeeded’.
Configure the AWS Region and ECS cluster name using the configure command:
The EC2
launch type, with Amazon ECS instances, is created and launched in your VPC. If you prefer not to manage the underlying instances hosting the tasks, then Fargate
launch type is the option to use. Fargate is the serverless way to host your Amazon ECS workloads.
Create the ECS service
The ecs-cli compose service
up command will create the Amazon ECS service and tasks from a Docker Compose file (ecsauth-compose.yaml
) that you create. This service is configured to use the ALB that you created earlier. A task definition is created by the command.
The Docker Compose file contains the configuration settings that the Amazon ECS service is spun up with. This includes the Docker image to pull and use, the ports to expose on the Amazon ECS instance, and the Amazon ECS task for network forwarding. In this post, we configured it to use the AWS published sample demo PHP application verified and published to Docker Hub. Also, the Transmission Control Protocol (TCP) port 80
will be opened on the Amazon ECS instance and traffic received on this port will be forwarded to the task on TCP port 80
).
Configuring the ECS service
Testing the solution end to end
We now the have working components of the solution. To test the solution end to end, you can navigate to the https site of the domain name used in your browser (such as https://www.example.com).
The sequence of events that follows is as we described in the flow of how the ALB authenticates users using Amazon Cognito (section “Flow of how Application Load Balancer authenticates users using Amazon Cognito”).
After redirection by the ALB to the Amazon Cognito configured domain’s login page (a hosted UI by Amazon Cognito), enter input your credentials.
Since this is the first time the page is accessed we will sign up as a new user. Amazon Cognito stores this information in the user pool. If you navigate to the Amazon Cognito user pool console after, you’ll see this new user.
After signing in to the ALB, it redirects you to the landing page of the sample demonstration PHP application, which is shown in the diagram below.
User claims encoding and security
In this post, we configured the target group to use HTTP, because the ALB has handled the TLS offloading. However, for enhanced security, you should restrict the traffic getting to the Amazon ECS instances to only the load balancer using the security group.
After the load balancer authenticates a user successfully, it passes the claims of the user to the target. If you inspect traffic forwarded to the sample demonstration application through custom HTTP header logging in your access logs, you can see three HTTP headers. These headers contain information about the user claims and is signed by the ALB with a signature and algorithm that you can verify.
The three HTTP headers include the following:
The access token from the token endpoint, in plain text.
The subject field (sub) from the user info endpoint, in plain text.
The user claims, in JSON web tokens (JWT) format.
From information encoded in the x-amzn-oidc-data
, it is possible to extract information about the user.
The following is an example Python 3.x application that can decode the payload portion of the x-amzn-oidc-data
to reveal the user claims passed by Amazon Cognito.
Cleanup
Now that you are done building the solution and testing it to clean up all the resources you can run the following commands:
Conclusion
In this post, we showed you how to authenticate users accessing your containerized application without writing authentication code, using the ALB’s inbuilt integration with Amazon Cognito. Maintaining and securing user management and authentication is offloaded from the application, which allows you to focus on building core business logic into the application. You don’t need to worry about platform tasks for managing, scheduling, and scaling containers for the web traffic because Amazon ECS handles all of that.