SSL化したマルチドメイン構成
AWS SOAの出題範囲の1つでもある、セキュリティとコンプライアンスに関連する内容です。ELBには、複数のACM証明書をアタッチすることができるということを確認します。
2つのドメイン(Webサイト)用の構成を、1つのALBと2つのEC2インスタンスで作成することを目的とします。ACMでSSL証明書を取得して、ALBにアタッチすることで、両ドメインへのアクセスを、HTTPSで行えるように設定します。
構築する環境
Apacheのバーチャルホスト機能を使用して、1つのEC2インスタンスに2つのドメイン用の設定を行います。そして可用性を高めるために、同じ設定のインスタンスをもう1台用意し、ALBでロードバランシングする構成を取ります。
バーチャルホスト機能にはいくつか種類がありますが、今回は、ポートベースのバーチャルホスト機能を使用します。以下が今回の構築する構成におけるドメイン名とポート番号の対応となります。なおドメインはRoute 53で取得したものを使用します。
- awstut.net:81番
- awstut.link:82番
今回はクライアント〜ALB間の通信をSSL化します。そのために2ドメイン用のSSL証明書をACMで取得します。取得したACM証明書をALBにアタッチします。
CloudFormationテンプレートファイル
上記の構成をCloudFormationで構築します。以下のURLにCloudFormationテンプレートファイルを設置しています。
https://github.com/awstut-an-r/awstut-soa/tree/main/04/002
テンプレートファイルのポイント解説
プライベートサブネット内のEC2インスタンスをALBにアタッチする
今回の構成では、ALBにアタッチするEC2インスタンスは、プライベートサブネットに設置されています。これらのインスタンスをALBにアタッチするためには、パブリックサブネットを用意し、ALBに関連付ける必要があるという点がポイントです。詳細は以下のページをご確認ください。
ALBのマルチドメイン対応
soa-04-002-alb.yamlでALB関係のリソースを定義していますが、ALBをマルチドメイン対応するためにポイントとなる設定を取り上げます。
マルチドメイン用のALBターゲットグループを作成する
ポートベースのバーチャルホスト機能を使用するために、ポート番号ごとにALBターゲットグループを作成します。
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)
Portプロパティの値以外は、2つのターゲットグループは同じ設定です。一方は81、もう一方は82をPortプロパティに設定します。Targetsプロパティでは、両方のグループで、2つのインスタンスを指定します。このように設定することによって、どちらのポート向けの通信でも、つまりどちらのドメイン用の通信が行われる場合でも、ALBは2つのインスタンスに対してトラフィックを配信します。
ALBに複数のACM証明書をアタッチする
ACM証明書をALBにアタッチすることで、クライアント〜ALB間の通信をSSL化することができます。ACM証明書をALBにアタッチする方法の基本については、以下のページをご確認ください。
今回の構成が上記のページと異なる点は、証明書を複数アタッチするという点です。
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)
複数のACM証明書をALBにアタッチする場合、ListenerCertificateを作成します。ポイントはメインの証明書はListenerに設定し、それ以外の証明書はListenerCertificateに設定する必要があるという点です。例えばListenerに複数の証明書をアタッチしたり、ListenerCertificateだけに証明書をアタッチすることはできませんのでご注意ください。
ドメイン名に基づくリスナールールを作成する
先述の2つのターゲットグループに対して、どのようにトラフィックを振り分けるかルールを定義します。ポイントはHTTPヘッダーのHostの値、つまりリクエスト先のホスト名を条件としたルールを作成するという点です。
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)
Actionsプロパティで対象のターゲットグループを指定します。
Conditionsプロパティでルールの条件を設定します。Fieldプロパティに「host-header」を設定し、HTTPヘッダーのHostの値を確認するように設定します。HostHeaderConfigプロパティで、ターゲットグループに対応するホスト名を設定します。
Route 53にALBを登録して、独自ドメインでアクセスする
Route 53で取得したドメインをALBに関連付けます。ポイントはALBのDNS名をエイリアスレコードとして登録するという点です。詳細は以下のページをご確認ください。
プライベートサブネット内でAnsibleを使う
Ansibleを使用して、2つのEC2インスタンスに対して、バーチャルホスト機能を設定します。Ansibleの実行は、SSMドキュメントAWS-ApplyAnsiblePlaybooksの利用がオススメです。ただ今回の構成のように、プライベートサブネット内のインスタンスで、このSSMドキュメントを利用する場合は注意が必要です。それは依存関係にあるAnsibleのインストールに失敗してしまうというものです。ワークアラウンドとしては、SSMドキュメントAWS-RunShellScriptで、個別にAnsibleをインストールする方法があります。詳細は以下のページでご確認ください。
(参考)Playbook
以下が今回使用するPlaybookです。
- 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)
実行する内容ですが、Apacheをインストール後、httpd.confを置き換えます。これはバーチャルホスト用の設定がなされたものです。その後にApacheを起動し、2つのドメイン用のドキュメントルートに、index.htmlを用意します。
(参考)httpd.conf
以下がバーチャルホスト用の設定を行ったhttpd.confの一部です。
#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)
詳細はApache公式サイトをご確認ください。
2つのドメイン用の通信をセキュリティグループで許可する
本題とは少し外れますが、EC2インスタンスに適用するセキュリティグループもポイントです。ドメインに対応したポート番号で通信が行われることに注意してください。
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)
FromPortおよびToPortプロパティで、許可するインバウンド通信のポート番号をレンジで指定します。今回はそれぞれに81および82を設定して、2ドメイン用の通信を許可します。
環境構築
CloudFormationを使用して、本環境を構築し、実際の挙動を確認します。
Ansible Playbookの準備
CloudFormationスタックを作成する前に、Ansibleの準備を行います。具体的にはPlaybookをZip化し、S3バケットに設置します。具体的なコマンドは以下のページをご確認ください。
CloudFormationスタックを作成し、スタック内のリソースを確認する
CloudFormationスタックを作成します。スタックの作成および各スタックの確認方法については、以下のページをご確認ください。
各スタックのリソースを確認した結果、今回作成された主要リソースの情報は以下の通りです。
- インスタンス1:i-02c368c2cf2f4d84f
- インスタンス2:i-0086d086596a27762
- ドメイン名1:awstut.net
- ドメイン名2:awstut.link
- awstut.net用のACM証明書:44206de3-9a28-4a69-af54-a0a88f45d317
- awstut.link用のACM証明書:0079c615-b747-4780-ac00-a63c5fcdb5bb
- ALB:soa-04-002-ALB
AWS Management Consoleからも、リソースの作成状況を確認します。まずACM証明書を確認します。
2ドメイン用で証明書が作成されていることが確認できます。
続いてRoute 53の設定状況を確認します。
共通して、2つのドメインにALBが関連づいています。タイプが「A」とありますので、エイリアスレコードであることがわかります。
ALBにアタッチされた証明書を確認します。
「awstut.net」の証明書がデフォルトとして設定され、「awstut.link」のものがサブという扱いです。これはCloudFormationテンプレートにて、Listenerにアタッチしたものがデフォルトとした扱われることに起因します。
ALBの2つのターゲットグループを確認します。
2つのグループに、同じ2つのEC2インスタンスが登録されています。一方で、グループにポート番号が対応していることも確認できます。つまりどちらのポート番号に向けた通信でも、2つのインスタンスに対してトラフィックが流れるということになります。
マルチドメイン動作検証①
準備が整いましたので、2つのドメインにアクセスします。まず「awstut.net」にアクセスします。
正常にアクセスすることができました。アクセスする度に表示される内容が変化します。これはALBが2つのEC2インスタンスに対して、ラウンドロビンでトラフィックを配分しているためです。
マルチドメイン動作検証②
今度は「awstut.link」にアクセスします。
こちらも正常にアクセスすることができました。先ほどと同様のインスタンスにアクセスしていることがわかります。
まとめ
ALBにACM証明書を複数アタッチすることで、マルチドメイン構成に対応することができました。