Attaching multiple ACM certificates to ALB to support multiple domains

TOC

SSL-enabled multi-domain configuration

This topic is related to security and compliance, one of the AWS SOA questions, and confirms that multiple ACM certificates can be attached to the ELB.

The goal is to create a configuration for two domains (websites) with one ALB and two EC2 instances, and to obtain an SSL certificate from ACM and attach it to the ALB so that both domains can be accessed using HTTPS.

Environment

Diagram of attaching multiple ACM certificates to ALB to support multiple domains.

Using the virtual host function of Apache, we will configure one EC2 instance for two domains. Then, to increase availability, we will prepare another instance with the same settings and configure it for load balancing using ALB.

There are several types of virtual host functions, but in this case, we will use the port-based virtual host function. The following is the correspondence between the domain name and the port number in the configuration we will build. We will use the domain name obtained from Route 53.

  • awstut.net: 81
  • awstut.link: number 82
Diagram of port numbers and domains.

This time, we will use SSL for the communication between the client and the ALB. For this purpose, we will obtain an SSL certificate for two domains from ACM. Attach the obtained ACM certificate to the ALB.

CloudFormation template files

We will build the above configuration using CloudFormation. The CloudFormation template file is available at the following URL.

https://github.com/awstut-an-r/awstut-soa/tree/main/04/002

Explanation of points in the template files

Attaching EC2 instances in private subnet to ALB

In this configuration, the EC2 instances to be attached to the ALB are located in a private subnet. The key point is that in order to attach these instances to the ALB, you need to prepare a public subnet and associate it with the ALB. For more details, please refer to the following page

あわせて読みたい
Attaching instances in private subnet to ALB 【Configure instances in private subnets to be attached to ALB】 We will see how to attach an instance located in a private subnet to an ALB. The following m...

Multi-domain support for ALB

We have defined ALB related resources in soa-04-002-alb.yaml. We will cover the key settings to support multi-domain support for ALB.

Create ALB target group for multi-domain

In order to use the port-based virtual host feature, we will create an ALB target group for each port number.

Resources:
  # domain1
  ALBTargetGroup1:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      VpcId: !Ref VPC
      Name: !Sub "${Prefix}-ALBTargetGroup1"
      Protocol: HTTP
      Port: !Ref Port1
      HealthCheckProtocol: HTTP
      HealthCheckPath: /
      HealthCheckPort: traffic-port
      HealthyThresholdCount: !Ref HealthyThresholdCount
      UnhealthyThresholdCount: !Ref UnhealthyThresholdCount
      HealthCheckTimeoutSeconds: !Ref HealthCheckTimeoutSeconds
      HealthCheckIntervalSeconds: !Ref HealthCheckIntervalSeconds
      Matcher:
        HttpCode: !Ref HttpCode
      Targets:
        - Id: !Ref Instance1
        - Id: !Ref Instance2

  # domain2
  ALBTargetGroup2:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      VpcId: !Ref VPC
      Name: !Sub "${Prefix}-ALBTargetGroup2"
      Protocol: HTTP
      Port: !Ref Port2
      HealthCheckProtocol: HTTP
      HealthCheckPath: /
      HealthCheckPort: traffic-port
      HealthyThresholdCount: !Ref HealthyThresholdCount
      UnhealthyThresholdCount: !Ref UnhealthyThresholdCount
      HealthCheckTimeoutSeconds: !Ref HealthCheckTimeoutSeconds
      HealthCheckIntervalSeconds: !Ref HealthCheckIntervalSeconds
      Targets:
        - Id: !Ref Instance1
        - Id: !Ref Instance2
Code language: YAML (yaml)

Other than the value of the Port property, the two target groups have the same settings. Set the Port property to 81 for one and 82 for the other; in the Targets property, specify the two instances in both groups. By setting it this way, the ALB will deliver traffic to the two instances regardless of which port the communication is destined for, i.e. which domain the communication is for.

Attaching multiple ACM certificates to ALB

By attaching an ACM certificate to an ALB, the communication between the client and the ALB can be SSL-enabled. for the basics of how to attach an ACM certificate to an ALB, please refer to the following page.

あわせて読みたい
On SSL your own domain using ACM Certificate 【Configuration to access your own domain with HTTPS using ACM certificate】 In the following page, we have introduced the configuration to access ALB using ...

The difference between this configuration and the above page is that we will be attaching multiple certificates.

Resources:
  ALBListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      Certificates:
        - CertificateArn: !Ref Certificate1
      DefaultActions:
        - TargetGroupArn: !Ref ALBTargetGroup1
          Type: forward
      LoadBalancerArn: !Ref ALB
      Port: !Ref HTTPSPort
      Protocol: HTTPS

  ALBListenerCertificates:
    Type: AWS::ElasticLoadBalancingV2::ListenerCertificate
    Properties:
      Certificates:
        - CertificateArn: !Ref Certificate2
      ListenerArn: !Ref ALBListener
Code language: YAML (yaml)

If you want to attach multiple ACM certificates to the ALB, you can create a ListenerCertificate. The point is that the main certificate must be set in the Listener and the other certificates must be set in the ListenerCertificate. Note that you cannot, for example, attach multiple certificates to a Listener, or attach a certificate only to a ListenerCertificate.

Creating Listener Rule Based on Domain Name

Define the rules for how to distribute traffic to the two target groups described above. The key point is to create a rule based on the Host value in the HTTP header, that is, the host name of the request destination.

Resources:
  # domain1
  ALBListenerRule1:
    Type: AWS::ElasticLoadBalancingV2::ListenerRule
    Properties:
      Actions:
        - Type: forward
          TargetGroupArn: !Ref ALBTargetGroup1
      Conditions:
        - Field: host-header
          HostHeaderConfig:
            Values:
              - !Ref DomainName1
      ListenerArn: !Ref ALBListener
      Priority: 1

  # domain2
  ALBListenerRule2:
    Type: AWS::ElasticLoadBalancingV2::ListenerRule
    Properties:
      Actions:
        - Type: forward
          TargetGroupArn: !Ref ALBTargetGroup2
      Conditions:
        - Field: host-header
          HostHeaderConfig:
            Values:
              - !Ref DomainName2
      ListenerArn: !Ref ALBListener
      Priority: 2
Code language: YAML (yaml)

Specify the target group in the Actions property.
Set the conditions of the rule in the Conditions property, and set the Field property to “host-header” to check the Host value in the HTTP header. Set the hostname corresponding to the target group in the HostHeaderConfig property.

Register ALB to Route 53 and access it with your own domain

Associate the domain obtained from Route 53 with the ALB. The key point is to register the DNS name of ALB as an alias record. Please refer to the following page for details.

あわせて読みたい
Registering ALB to Route 53 and Accessing with Your Own Domain 【Configuration for Registering ALB to Route 53】 In the following page, we have introduced how to attach an EC2 instance in a private subnet to ALB. https:/...

Using Ansible in Private Subnet

We will use Ansible to configure the virtual host function for two EC2 instances. To run Ansible, I recommend using the SSM document AWS-ApplyAnsiblePlaybooks, but be careful when using this SSM document on an instance in a private subnet, as in this configuration. That is, it will fail to install Ansible as a dependency. As a workaround, you can use the SSM document AWS-RunShellScript to install Ansible separately. Please refer to the following page for details

あわせて読みたい
Ansible on Private Subnet 【Configuring instances in private subnet to run AWS-ApplyAnsiblePlaybooks document】 In the following page, we have introduced four ways to initialize proce...

(Reference) Playbook

The following is the Playbook we will be using.

- hosts: all
  gather_facts: no
  become: yes

  tasks:
    - name: update yum
      yum: name=*
    - name: install the latest version of Apache
      yum: name=httpd state=latest
    - name: copy httpd.conf
      copy:
        src: ./httpd.conf
        dest: /etc/httpd/conf
        owner: root
        group: root
        mode: '0644'
    - name: start and enable Apache
      service: name=httpd state=started enabled=yes
    - name: make index.html for domain1
      shell: mkdir -p /var/www/html/domain1; echo 'domain1' > /var/www/html/domain1/index.html; ec2-metadata -i >> /var/www/html/domain1/index.html
    - name: make index.html for domain2
      shell: mkdir -p /var/www/html/domain2; echo 'domain2' > /var/www/html/domain2/index.html; ec2-metadata -i >> /var/www/html/domain2/index.html
Code language: YAML (yaml)

What you need to do is to install Apache and then replace httpd.conf. This is the one that is configured for virtual hosting. After that, start Apache and prepare index.html in the document root for the two domains.

(Reference) httpd.conf

The following is a part of httpd.conf with settings for virtual hosts.

#Listen 80
Listen 81
Listen 82

...

NameVirtualHost *:81
NameVirtualHost *:82

...

<VirtualHost *:81>
  ServerName [domain1]
  DocumentRoot /var/www/html/domain1
</VirtualHost>

<VirtualHost *:82>
  ServerName [domain2]
  DocumentRoot /var/www/html/domain2
</VirtualHost>

...
Code language: plaintext (plaintext)

Please check the official Apache website for details.

Allowing communication for two domains with security group

This is a little off topic, but the security group you apply to your EC2 instance is also a key point. Note that the communication will take place on the port number corresponding to the domain.

Resources:
  InstanceSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: !Sub "${Prefix}-InstanceSecurityGroup"
      GroupDescription: Allow HTTP(81/tcp, 82/tcp) from ALBSecurityGroup.
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: !Ref Port1
          ToPort: !Ref Port2
          SourceSecurityGroupId: !Ref ALBSecurityGroup
Code language: YAML (yaml)

In the FromPort and ToPort properties, specify the range of port numbers for inbound communication to be allowed. This time, we will set 81 and 82 for each to allow communication for two domains.

Architecting

We will use CloudFormation to build this environment and check the actual behavior.

Preparing Ansible Playbook

Before creating the CloudFormation stack, we will prepare Ansible. Specifically, we will zip up the Playbook and place it in an S3 bucket. Please refer to the following page for specific commands.

あわせて読みたい
Four ways to initialize Linux instance 【Four ways to initialize a Linux instance】 Consider how to perform the initialization process when an EC2 instance is started. We will cover the following ...

Create CloudFormation stacks and check resources in stacks

Create CloudFormation stacks.
Please refer to the following page for information on how to create a stack and check each stack.

あわせて読みたい
CloudFormation’s nested stack 【How to build an environment with a nested CloudFormation stack】 Examine nested stacks in CloudFormation. CloudFormation allows you to nest stacks. Nested ...

After checking the resources in each stack, the information for the main resources created this time is as follows

  • Instance 1: i-02c368c2cf2f4d84f
  • Instance 2: i-0086d086596a27762
  • Domain name 1: awstut.net
  • Domain name 2: awstut.link
  • ACM certificate for awstut.net: 44206de3-9a28-4a69-af54-a0a88f45d317
  • ACM certificate for awstut.link: 0079c615-b747-4780-ac00-a63c5fcdb5bb
  • ALB: soa-04-002-ALB

We will also check the resource creation status from the AWS Management Console. First, check the ACM certificate.

ACM certificates for the two domains have been created.

You can see that the certificates have been created for the two domains.

Next, we will check the Route 53 configuration.

The ALB is associated with domain 1.
The ALB is associated with domain 2.

Commonly, two domains are associated with ALB. The type is “A”, which indicates that it is an alias record.

Check the certificate attached to the ALB.

Two ACM certificates are attached to the ALB.

The certificate for “awstut.net” is set as the default, and the one for “awstut.link” is treated as a sub. This is due to the fact that the certificate attached to the Listener in the CloudFormation template is treated as the default.
Check the two target groups of the ALB.

Two EC2 instances are registered in the ALB target group 1.
Two EC2 instances are registered in the ALB target group 2.

The same two EC2 instances are registered in the two groups. On the other hand, we can also see that the port numbers correspond to the groups. This means that traffic will flow to the two instances regardless of which port number it is directed to.

Multi-domain operation verification (1)

Now that we are ready, let’s access the two domains.
First, we will access “awstut.net”.

Access result 2 for domain 1.
Access result 1 for domain 1.

We were able to access it normally. Each time you access the site, the contents displayed will change. This is because the ALB is distributing traffic to the two EC2 instances in a round-robin fashion.

Multidomain Operation Verification (2)

Now we will access “awstut.link”.

Access result 1 for domain 2.
Access result 2 for domain 2.

We were able to access this site normally as well. We can see that we are accessing the same instance as before.

Summary

By attaching multiple ACM certificates to the ALB, we were able to support multi-domain configuration.

TOC