Securing traffic with ACM Private Certificate Authority

Overview

Configuring applications to remain unencrypted poses several security risks as user data traverses over networks in cleartext. Although it is widely accepted for utilizing https over the public internet, in most cases configured at the load balancer, configuring internal SSL communication is just as critical as you look to harden your systems. Utilizing an internal CA, rather it be root or subordinate, can help you validate and certify your internal communication.

Utilizing LoadBalancer Termination

Despite being able to set TLS encryption at the load balancer, utilizing unencrypted traffic to the application tier still poses a risk. To display this, create a load balancer which targets an HTTP web app. For this example, I created a simple app fleet that just returns a 200 on any post.

load balancer and host infra

Once set up, send a POST to the app server, while running tcpdump.

curl -X POST -d '{"data": "unencrypted" }' https://test.example.com

HTTP Packet Trace

As you can see, the body is visible, and despite the certain network protections AWS provides within your VPC, technologies such as traffic mirroring, or application vulnerabilities such as command injection, could put you at risk of more than just your application seeing this data.

However, utilizing an encrypted backend this would not be the case:

curl -X POST -d '{"data": "unencrypted" }' https://test.example.com

HTTPS Packet Trace

Host to Host Communication

In other cases where load balancers are not involved, there may be use cases where your servers have to speak directly to each other. Once your web applications have their signed certificate from a private CA, it is as simple as adding that CA to the host trust, which will allow you to enforce validation when sending data.

Implementation

This example will define how to certify Nginx, however, a majority of these steps can easily be modified to whatever framework you are utilizing.

Note: The infrastructure will be built with Cloudformation, although AWS CLI commands will also work. At the time of writing, this is not available to do directly with native terraform resources: See Issue

Create Private CA with ec2.internal domain or with your own internal host domain

RootInternalCA:
Type: AWS::ACMPCA::CertificateAuthority
Properties:
KeyAlgorithm: RSA_4096
SigningAlgorithm: SHA512WITHRSA
Subject:
CommonName: ec2.internal
Type: ROOT
view raw root_ca.yml hosted with ❤ by GitHub

Create a certificate and activate the CA

RootInternalCert:
Type: AWS::ACMPCA::Certificate
Properties:
CertificateAuthorityArn: !Ref RootInternalCA
CertificateSigningRequest: !GetAtt RootInternalCA.CertificateSigningRequest
SigningAlgorithm: SHA512WITHRSA
TemplateArn: 'arn:aws:acm-pca:::template/RootCACertificate/V1'
Validity:
Type: YEARS
Value: 10
RootCAActivation:
Type: 'AWS::ACMPCA::CertificateAuthorityActivation'
Properties:
CertificateAuthorityArn: !Ref RootInternalCA
Certificate: !GetAtt RootInternalCert.Certificate
Status: ACTIVE
view raw root_cert.yml hosted with ❤ by GitHub

Utilize openssl to generate a key and csr

# Create x509 cert if not building for cloud (common in local builds)
if [[ -z "${ROOTCA}" ]]; then flags="-x509 -days 365"; fi
mkdir -p /etc/ssl/{certs,private}
openssl req $flags -nodes -new -newkey rsa:4096 -keyout /etc/ssl/private/server.key -out /etc/ssl/certs/server.crt -subj "/CN=${HOSTNAME}"
view raw create_cert.sh hosted with ❤ by GitHub

Utilize issue-certificate and get-certififace acm-pca api calls to obtain the ssl_certifacte you will need to secure your internal domain. You will need the root CA ARN from created above.

aws acm-pca issue-certificate --certificate-authority-arn $ROOTCA --csr file:///etc/ssl/certs/server.crt --signing-algorithm "SHA512WITHRSA" --validity Type=DAYS,Value=365 --output text > /tmp/cert.arn
aws acm-pca get-certificate --certificate-authority-arn $ROOTCA --certificate-arn $(cat /tmp/cert.arn) --output json | jq -r '.Certificate' > /etc/ssl/certs/server.crt
aws acm-pca get-certificate --certificate-authority-arn $ROOTCA --certificate-arn $(cat /tmp/cert.arn) --output json | jq -r '.CertificateChain' >> /etc/ssl/certs/server.crt
view raw sign_cert.sh hosted with ❤ by GitHub

Update Nginx, security groups, load balancer target groups, and/or hosts to listen and communicate with SSL.

Nginx

server {
listen 443 ssl;
ssl_certificate /etc/ssl/certs/server.crt;
ssl_certificate_key /etc/ssl/private/server.key;
...
view raw nginx.conf hosted with ❤ by GitHub

Security groups

HostSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
SecurityGroupIngress:
# This may vary on what you need ingress
- SourceSecurityGroupId: !Ref LoadBalancerSecurityGroup
FromPort: 443
ToPort: 443
IpProtocol: tcp
view raw sg.yaml hosted with ❤ by GitHub

Target Groups

TargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
...
Port: 443
Protocol: HTTPS
...

Host

The following process will vary per Operating system. You should see this a guidance, but defer to your OS documentation for how to update the trust.

# Download root CA into CA Directory
aws acm-pca get-certificate-authority-certificate --certificate-authority-arn $ROOTCA --output text > /usr/local/share/ca-certificates/root.crt
# Ensure permissions and update the trust store
chmod 644 /usr/local/share/ca-certificates/root.crt
update-ca-certificates
view raw host_ca.sh hosted with ❤ by GitHub