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
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
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
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.
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.
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
(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.
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.
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.
You can see that the certificates have been created for the two domains.
Next, we will check the Route 53 configuration.
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.
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.
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”.
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”.
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.