1. ホーム
  2. 記事一覧
  3. 【Terraformハンズオン】AWS Certificate Manager (ACM) で発行したSSL証明書とALBを紐付けてみよう

2024.06.30

【Terraformハンズオン】AWS Certificate Manager (ACM) で発行したSSL証明書とALBを紐付けてみよう

インターネット上での通信を安全に保つためには、SSL/TLS証明書が必要です。ACM(AWS Certificate Manager)は、AWSが提供するSSL/TLS証明書の管理サービスで、証明書の発行、管理、更新を簡単に行えます。こちらの記事では、ACMの特徴、利用するメリット、実際の利用方法について解説します。

ACMとは

ACM(AWS Certificate Manager)は、SSL/TLSサーバー証明書を管理する機能を提供するAWSのマネージドサービスです。

SSL/TLS(SecureSocketsLayer/ TransportLayerSecurity)は、インターネット上でデータを暗号化して送受信する仕組み(プロトコル)です。一般的にはSSLやTLSと記述され、SSLを有効化するためには、通信を行うサーバーに対してSSL/TLSサーバー証明書を発行し、紐づけることが必要になります。

この証明書をサーバーへ紐づけることで、インターネット上でデータを送受信する際にHTTP通信を暗号化したHTTPS通信を利用することができます。

なぜHTTPSが必要なのか

インターネット通信をHTTPS化することで、Webサイトとユーザーの間の通信を暗号化することができます。通信の暗号化をすることで、インターネット上でのデータ転送の安全性を確保し、第三者によるデータの盗聴や改ざんを防止できます。

通信を暗号化していない場合、ECサイトなどで買い物をした時の個人情報やクレジットカード情報などの重要なデータが、悪意ある第三者に盗まれてしまう可能性が高まります。

このように、重要なデータを保護しユーザーに安全なインターネット環境を提供するために、SSL/TLSの仕組みを利用してインターネット通信をHTTPS化することが必要です。

https://www.xserver.ne.jp/bizhp/homepage-https/

実際の現場ではどのように使われている?

AWSを扱った実際のインフラエンジニアの業務では、CloudFrontディストリビューションへの関連付けや、ALBのHTTPSリスナーへの関連付けなどを行って運用しています。

CloudFrontディストリビューションへ証明書を関連づけた場合、コンテンツ配信側のCloudFrontクライアントと接続する側のクライアントとの通信が暗号化され、安全性が向上します。

ALBの場合も同様に、クライアントとALB間の通信も暗号化されるため、安全性が向上します。

ACMの主な機能

ACMでは主に、以下のような機能を提供しています。

項目内容
証明書発行ドメイン検証済みのパブリック証明書(DV)、プライベート証明書を発行し、AWSで利用可能にする
証明書のインポートオンプレミスなどで使用されている既存の証明書をインポートし、AWSで利用可能にする
証明書のデプロイ機能AWSサービスに証明書を配備する

https://docs.aws.amazon.com/ja_jp/acm/latest/userguide/acm-overview.html

ACMのメリット

ここからは、ACMを利用するメリットについて解説します。

インターネット上の通信を暗号化できる

ACMを利用することで、SSL/TLS証明書を発行し対応するAWSサービスへ配備することができます。証明書を配備したAWSサービスでは、SSL/TLSを利用し通信を暗号化することができるため、前述した個人情報やクレジットカード情報などの重要な情報が第三者に盗まれてしまうリスクを軽減することができます。

証明書の発行、更新が簡単

ACMでは、SSL/TLS証明書のリクエストをマネジメントコンソール上で行うことができます。必要なドメイン情報をコンソール上で入力し、ドメインの所有権確認が完了すれば証明書を発行することができます。

証明書には有効期限があり、2024年6月時点で13ヶ月(395日)とされています。

ACMではDNS検証を使用している場合には自動的に証明書を更新してくれます。DNS検証を使用していない場合、有効期限切れが近づくとEメールでの通知を行ってくれるため、証明書の期限切れ、失効を防ぐことが可能です。

https://docs.aws.amazon.com/ja_jp/acm/latest/userguide/managed-renewal.html

証明書の発行が無料

ACMでは、パブリックSSL/TLS証明書、プライベートSSL/TLS証明書の2種類があり、パブリック証明書は無償で発行できます。

パブリック証明書、プライベート証明書の違いを簡単に説明します。

パブリック証明書は、一般公開されるウェブサイトやサービスで使用される証明書です。アプリケーションとブラウザは、デフォルトで自動的にパブリック証明書を信頼するよう設計されているため、こちらを利用します。

対してプライベート証明書は、プライベートなネットワーク(社内ネットワーク)など限定された環境で使用される証明書です。特定の組織やネットワーク内でのみ信頼されるため、外部のブラウザやデバイスではデフォルトで信頼されません。

また、プライベート証明書を利用する際は料金が発生するため注意が必要です。

パブリック証明書は「広く公開されたウェブサービスに適しているもの」、プライベート証明書は「限定された環境での使用に適しているもの」になります。ACMを利用することで、これらの証明書を比較的簡単に発行し、管理することができます。

https://aws.amazon.com/jp/certificate-manager/faqs/

ACMのデメリット

ACMのメリットについて触れましたが、デメリットはどんなものがあるか解説します。

利用可能な証明書の種類が限定されている

ACM は、ドメイン検証型(DV)証明書のみを提供しています。DV証明書は、ドメインの所有権を確認するための基本的な証明書で、組織検証型(OV)や拡張検証型(EV)証明書は提供していません。そのため、特定のセキュリティ要件や信頼性の高い証明書が必要な場合には、ACM以外のサービスを使用する必要があります。

証明書の種類に関しては、以下の記事で解説しています。

https://envader.plus/article/288

SSL/TLS プロトコル以外の証明書を提供していない

ACMは、SSL/TLS証明書のみを提供しているため、他のタイプの証明書(S/MIME証明書、コード署名証明書など)は提供していません。メールの暗号化やコード署名など、SSL/TLS以外の用途で証明書を使用したい場合には、ACM以外のプロバイダから証明書を取得する必要があります。

Amazon EC2 に直接インストールできない

ACMでは、発行した証明書をEC2インスタンスに直接インストールして使用することができません。主にELB、CloudFront、API GatewayなどのAWSサービスで使用されることを前提としてい流ため、EC2インスタンスでSSL/TLS証明書を使用したい場合には、別の手段で証明書を取得し、手動でインストールする必要があります。

https://docs.aws.amazon.com/ja_jp/acm/latest/userguide/acm-certificate.html

ACMでパブリック証明書をリクエスト

実際にACMを使用して、パブリック証明書を発行してみましょう。

注意点として、パブリック証明書を発行するにはRoute53やお名前.comなどのサービスを利用し、ドメインを取得しておく必要があります。

はじめに、AWSコンソールへアクセスしACMへ遷移します。

今回は「東京リージョン」を選択します。

左メニューの「証明書をリクエスト」を選択し、「パブリック証明書をリクエスト」をチェック後次へ進みます。

すでに取得済みのドメイン名を入力欄に記述します。

この時、*.test.siteとして追加で登録することで、サブドメインとなるwww.test.siteなどに対しても証明書を適用することが可能になります。

キーアルゴリズムは「RSA 2048」を選択し、「リクエスト」をクリックします。

リクエストが完了すると、ステータスが「保留中の検証」になります。

「ドメイン」の部分に、CNAME名、CNAME値が表示されるため、コピーしておきます。

Route53でホストゾーンを作成する

続いてRoute53コンソールへ遷移します。

左ペイン「ホストゾーン」を選択し、「ホストゾーンの作成」をクリックします。

証明書リクエスト時に入力したドメイン名を入力し、「パブリックホストゾーン」を選択、ホストゾーンを作成します。

レコードが作成されたことを確認します。

証明書の検証に必要がCNAMEをRoute53のレコードへ登録する

次に、証明書リクエスト時に発行されたCNAMEを作成したホストゾーンのレコードへ登録します。

ACMから発行されたCNAMEをRoute53のレコードへ登録することで、ACMのドメイン検証が完了します。

注意点として、お名前.comなどAWS以外で取得したドメインを登録する際には、ドメイン取得元のサービスでネームサーバーの設定が必要になります。

https://www.onamae.com/guide/p/67?_bdld=x-Lmh.o4z7YT8.1654573070&_ga=2.65334124.372064914.1654474203-1174236169.1654143093

作成したホストゾーン右上の、「レコードを作成」をクリックします。

「レコード名」に、ACMで発行した「CNAME名」を入力します。CNAME名にはドメイン名も含まれているため、test.siteの部分は削除して貼り付けます。

レコードタイプは「CNAME」を選択し、値に「CNAME値」を入力し、レコードを作成します。

ここまでの設定が完了すると、ACMでのドメイン検証が完了し、証明書のステータスが「発行済み」に変更されます。

TerraformでHTTPS化を実践

証明書の発行が完了したら、Terraformを使用してALBのリスナーへACMで発行したSSL/TLS証明書をアタッチし、HTTPS化を実践していきます。

Terraformの環境設定

Terraformを使用するためには、AWS CLIやTerraformのinstallなどが必要になります。

まだ環境設定をされていない方は、以下の記事で解説していますので設定しましょう。

https://envader.plus/article/162

ALBの作成

今回のハンズオンでは、ALBの作成が必要になります。

ALB、Terraformのコードは以下記事にて解説していますので、今回具体的な解説は省略します。

https://envader.plus/article/366

コードの全体

今回実装するコードの全体像は以下になります。

locals {
 
   region_azs    = ["ap-northeast-1a", "ap-northeast-1c"]
   instance_type = "t2.micro"
   ami_id        = "ami-02181a724aa2ad10b"
 }
 
 # ap-northeast-1のACM証明書のARNを取得
 data "aws_acm_certificate" "ap_northeast_1_cert" {
   provider = aws.ap-northeast-1 # ALBの証明書はap-northeast-1リージョンになければならないため、version.tfでap-northeast-1を指定
   domain   = "*.test.site" # 作成した証明書のドメイン名
 }
 
 # Route53のホストゾーンを取得
 data "aws_route53_zone" "service_domain" {
   name = "test.site"
 }
 
 # VPC
 resource "aws_vpc" "myapp_vpc" {
   cidr_block           = "10.0.0.0/16"
   enable_dns_support   = true
   enable_dns_hostnames = true
   tags = {
     "Name" = "myapp_vpc"
   }
 }
 
 # Subnets
 resource "aws_subnet" "myapp_subnet_a" {
   vpc_id            = aws_vpc.myapp_vpc.id
   cidr_block        = "10.0.1.0/24"
   availability_zone = local.region_azs[0]
   tags = {
     "Name" = "myapp_subnet_a"
   }
 }
 resource "aws_subnet" "myapp_subnet_c" {
   vpc_id            = aws_vpc.myapp_vpc.id
   cidr_block        = "10.0.2.0/24"
   availability_zone = local.region_azs[1]
   tags = {
     "Name" = "myapp_subnet_c"
   }
 }
 
 # Internet Gateway
 resource "aws_internet_gateway" "myapp_igw" {
   vpc_id = aws_vpc.myapp_vpc.id
 }
 
 # Route Table
 resource "aws_route_table" "myapp_rt" {
   vpc_id = aws_vpc.myapp_vpc.id
 
   route {
     cidr_block = "0.0.0.0/0"
     gateway_id = aws_internet_gateway.myapp_igw.id
   }
 }
 
 # Route Table Association
 resource "aws_route_table_association" "myapp_rta" {
   subnet_id      = aws_subnet.myapp_subnet_a.id
   route_table_id = aws_route_table.myapp_rt.id
 }
 
 resource "aws_route_table_association" "myapp_rta2" {
   subnet_id      = aws_subnet.myapp_subnet_c.id
   route_table_id = aws_route_table.myapp_rt.id
 }
 
 # Security Group for EC2 instances
 resource "aws_security_group" "myapp_ec2_sg" {
   vpc_id = aws_vpc.myapp_vpc.id
   ingress {
     from_port       = 0
     to_port         = 0
     protocol        = "-1"
     security_groups = [aws_security_group.myapp_alb_sg.id]
   }
   egress {
     from_port   = 0
     to_port     = 0
     protocol    = "-1"
     cidr_blocks = ["0.0.0.0/0"]
   }
   tags = {
     "Name" = "myapp_ec2_sg"
   }
 }
 
 # ALB用のセキュリティグループ
 resource "aws_security_group" "myapp_alb_sg" {
   vpc_id = aws_vpc.myapp_vpc.id
 
   ingress {
     from_port   = 443
     to_port     = 443
     protocol    = "tcp"
     cidr_blocks = ["0.0.0.0/0"]
   }
 
   egress {
     from_port   = 0
     to_port     = 0
     protocol    = "-1"
     cidr_blocks = ["0.0.0.0/0"]
   }
 
   tags = {
     "Name" = "myapp_alb_sg"
   }
 }
 
 
 # key-pair
 resource "aws_key_pair" "myapp_key_pair" {
   key_name   = "myapp_key_pair"
   public_key = file("~/.ssh/xxxxx.pub")
 }
 
 # Launch Template
 resource "aws_launch_template" "myapp_launch_template" {
   name          = "myapp-lt"
   image_id      = local.ami_id
   instance_type = local.instance_type
   key_name      = aws_key_pair.myapp_key_pair.key_name
 
 user_data = base64encode(<<-EOF
               #!/bin/bash
               yum update -y
               amazon-linux-extras install -y nginx1.12
               echo "Hello, world" > /usr/share/nginx/html/index.html
 
               systemctl start nginx
               systemctl enable nginx
 EOF
 )
   network_interfaces {
     associate_public_ip_address = true
     security_groups             = [aws_security_group.myapp_ec2_sg.id]
   }
 
   tag_specifications {
     resource_type = "instance"
     tags = {
       "Name" = "myapp-instance"
     }
   }
 }
 
 
 # Autoscaling Group
 resource "aws_autoscaling_group" "myapp_asg" {
   name                      = "myapp-asg"
   max_size                  = 6
   min_size                  = 1
   desired_capacity          = 4
   health_check_grace_period = 300
   health_check_type         = "EC2"
   enabled_metrics           = ["GroupInServiceInstances"]
   launch_template {
     id      = aws_launch_template.myapp_launch_template.id
     version = "$Latest"
   }
   vpc_zone_identifier = [aws_subnet.myapp_subnet_a.id, aws_subnet.myapp_subnet_c.id]
   target_group_arns   = [aws_lb_target_group.myapp_tg.arn]
 }
 
 # Autoscaling Policy
 resource "aws_autoscaling_policy" "myapp_scale_out" {
   name                   = "myapp-scale-out"
   autoscaling_group_name = aws_autoscaling_group.myapp_asg.name
   policy_type            = "TargetTrackingScaling"
 
   target_tracking_configuration {
     predefined_metric_specification {
       predefined_metric_type = "ASGAverageCPUUtilization"
     }
     target_value = 50.0
   }
 }
 
 # ALBのリソース定義
 resource "aws_lb" "myapp_alb" {
   name                       = "myapp-alb"
   internal                   = false
   load_balancer_type         = "application"
   security_groups            = [aws_security_group.myapp_alb_sg.id]
   subnets                    = [aws_subnet.myapp_subnet_a.id, aws_subnet.myapp_subnet_c.id]
   enable_deletion_protection = false
 
   tags = {
     "Name" = "myappALB"
   }
 }
 
 # HTTPSリスナーの設定
 resource "aws_lb_listener" "myapp_https_listener" {
   load_balancer_arn = aws_lb.myapp_alb.arn
   port              = 443
   protocol          = "HTTPS"
   ssl_policy        = "ELBSecurityPolicy-TLS-1-2-2017-01"
   certificate_arn   = data.aws_acm_certificate.ap_northeast_1_cert.arn
 
   default_action {
     type = "fixed-response"
     fixed_response {
       content_type = "text/plain"
       message_body = "Not Found"
       status_code  = "404"
     }
   }
 }
 
 # リスナールールの設定
 resource "aws_lb_listener_rule" "myapp_listener_rule" {
   listener_arn = aws_lb_listener.myapp_https_listener.arn
   priority     = 10
 
   action {
     type             = "forward"
     target_group_arn = aws_lb_target_group.myapp_tg.arn
   }
 
   condition {
     path_pattern {
       values = ["/"]
     }
   }
 }
 
 # ターゲットグループの定義
 resource "aws_lb_target_group" "myapp_tg" {
   name     = "myapp-tg"
   port     = 80
   protocol = "HTTP"
   vpc_id   = aws_vpc.myapp_vpc.id
 
   health_check {
     enabled             = true
     interval            = 30
     path                = "/"
     protocol            = "HTTP"
     healthy_threshold   = 5
     unhealthy_threshold = 2
     timeout             = 20
     matcher             = "200"
   }
 
   tags = {
     "Name" = "myappTG"
   }
 }
 
 # Route53のレコードセット
 resource "aws_route53_record" "myapp_dns" {
   zone_id = data.aws_route53_zone.service_domain.zone_id
   name    = "myapp.test.site"
   type    = "A"
   alias {
     name                   = aws_lb.myapp_alb.dns_name
     zone_id                = aws_lb.myapp_alb.zone_id
     evaluate_target_health = true
   }
 }
 
 output "alb_dns_name" {
   value = aws_lb.myapp_alb.dns_name
 }
 
 output "myapp_dns_name" {
   value = aws_route53_record.myapp_dns.name
 }

dataリソース

# ap-northeast-1のACM証明書のARNを取得
 data "aws_acm_certificate" "ap_northeast_1_cert" {
   provider = aws.ap-northeast-1 # ALBの証明書はap-northeast-1リージョンになければならないため、version.tfでap-northeast-1を指定
   domain   = "*.test.site"
 }
 
 # Route53のホストゾーンを取得
 data "aws_route53_zone" "service_domain" {
   name = "test.site"
 }

この箇所では、作成したSSL証明書の情報と、Route53で作成したホストゾーンの情報を取得しています。この情報をもとに、ALBリスナーに証明書をアタッチします。

HTTPSリスナーの設定

# HTTPSリスナーの設定
 resource "aws_lb_listener" "myapp_https_listener" {
   load_balancer_arn = aws_lb.myapp_alb.arn
   port              = 443
   protocol          = "HTTPS"
   ssl_policy        = "ELBSecurityPolicy-TLS-1-2-2017-01"
   certificate_arn   = data.aws_acm_certificate.ap_northeast_1_cert.arn
 
   default_action {
     type = "fixed-response"
     fixed_response {
       content_type = "text/plain"
       message_body = "Not Found"
       status_code  = "404"
     }
   }
 }

HTTPSで通信を受け付けるリスナーを作成しています。このリスナーに、dataリソースで取得しているSSL証明書をアタッチします。

Route53にエイリアスレコードを作成

 # Route53のレコードセット
 resource "aws_route53_record" "myapp_dns" {
   zone_id = data.aws_route53_zone.service_domain.zone_id
   name    = "myapp.test.site"
   type    = "A"
   alias {
     name                   = aws_lb.myapp_alb.dns_name
     zone_id                = aws_lb.myapp_alb.zone_id
     evaluate_target_health = true
   }
 }

ここでは、作成したRoute53のホストゾーンにレコードを追加しています。

具体的には、myapp.test.siteへのリクエストをALBへ転送するよう設定しています。こうすることで、ユーザーがmyapp.test.siteにアクセスすると、自動的にALBにリクエストがルーティングされます。

エイリアスについて

エイリアスとは、AWS内の特定のリソースを指すDNSの設定です。通常の設定ではIPアドレスを指定しますが、エイリアスでは直接AWSのリソースを指定できます。

例えば、myapp.test.siteをALBに向けたい場合、エイリアスを使うことでALBのIPアドレスが変わっても自動で対応してくれます。

CNAMEでも実現することは可能ですが、公式ではAWSリソースを指定する場合エイリアスを指定することが推奨されています。

https://docs.aws.amazon.com/ja_jp/Route53/latest/DeveloperGuide/resource-record-sets-choosing-alias-non-alias.html

まとめ

今回は、ACMの説明と、Terraformを使ってALBのリスナーへ証明書をアタッチするためのハンズオン解説を行いました。

ACMのパブリック証明書は無料で発行でき、更新も自動的に行なってくれるため比較的簡単に運用していくことが可能です。ただ、ドメイン検証型(DV)証明書のみを提供しているため信頼性には欠ける点があることを理解しておく必要があります。

今回の内容は入門編と捉えていただき、ここからさらに発展していただけたら幸いです。

【番外編】USBも知らなかった私が独学でプログラミングを勉強してGAFAに入社するまでの話

IT未経験者必見 USBも知らなかった私が独学でプログラミングを勉強してGAFAに入社するまでの話

プログラミング塾に半年通えば、一人前になれると思っているあなた。それ、勘違いですよ。「なぜ間違いなの?」「正しい勉強法とは何なの?」ITを学び始める全ての人に知って欲しい。そう思って書きました。是非読んでみてください。

「フリーランスエンジニア」

近年やっと世間に浸透した言葉だ。ひと昔まえ、終身雇用は当たり前で、大企業に就職することは一種のステータスだった。しかし、そんな時代も終わり「優秀な人材は転職する」ことが当たり前の時代となる。フリーランスエンジニアに高価値が付く現在、ネットを見ると「未経験でも年収400万以上」などと書いてある。これに釣られて、多くの人がフリーランスになろうとITの世界に入ってきている。私もその中の1人だ。数年前、USBも知らない状態からITの世界に没入し、そこから約2年間、毎日勉学を行なった。他人の何十倍も努力した。そして、企業研修やIT塾で数多くの受講生の指導経験も得た。そこで私は、伸びるエンジニアとそうでないエンジニアをたくさん見てきた。そして、稼げるエンジニア、稼げないエンジニアを見てきた。

「成功する人とそうでない人の違いは何か?」

私が出した答えは、「量産型エンジニアか否か」である。今のエンジニア市場には、量産型エンジニアが溢れている!!ここでの量産型エンジニアの定義は以下の通りである。

比較的簡単に学習可能なWebフレームワーク(WordPress, Rails)やPython等の知識はあるが、ITの基本概念を理解していないため、単調な作業しかこなすことができないエンジニアのこと。

多くの人がフリーランスエンジニアを目指す時代に中途半端な知識や技術力でこの世界に飛び込むと返って過酷な労働条件で働くことになる。そこで、エンジニアを目指すあなたがどう学習していくべきかを私の経験を交えて書こうと思った。続きはこちらから、、、、

note記事3000いいね超えの殿堂記事 今すぐ読む

エンベーダー編集部

エンベーダーは、ITスクールRareTECHのインフラ学習教材として誕生しました。 「遊びながらインフラエンジニアへ」をコンセプトに、インフラへの学習ハードルを下げるツールとして運営されています。

RareTECH 無料体験授業開催中! オンラインにて実施中! Top10%のエンジニアになる秘訣を伝授します! RareTECH講師への質疑応答可

関連記事