Terraformで自己署名証明書の作成からALBの適用までを一発で実施する
お疲れさまです。とーちです。
TerraformはAWSのようなクラウド以外にもSaaS等様々なリソースの設定をできるのが素晴らしいですよね。
そんなTerraformですが、実は自己署名証明書を作ることもできるというのを最近知りました。
この記事ではTerraformを使って、自己署名証明書の作成からそれをACMに登録してALBに紐づけるところまでを一発で実行する方法をご紹介します。
本来、AWS Certificate Manager(ACM)で正式な証明書を発行するのが推奨される方法ですが、DNSを変更する権限がなかったり、開発環境用のドメインが用意されていない等の理由で証明書が気軽に発行できないケースもあると思います。今回ご紹介する方法はそういった場合に気軽に検証環境を用意する方法としてお使い頂ければと思います。
コードの全体像
コードの全量は以下のリポジトリに格納しました。
ディレクトリ構成は以下のようになっています。
.
├── README.md # readme
├── aqua.yaml # aquaの設定ファイル(ツールのバージョン管理用)
├── certs/ # 証明書関連ファイルを格納(Terraform実行時に作成される)
├── environments/dev/ # terraform plan,applyを実行するディレクトリ
├── modules/ # Terraformモジュール
│ ├── acm_self_signed_cert/ # 自己署名証明書及び証明書をACMに登録するためのモジュール
│ └── alb/ # ALBを作成するためのモジュール
└── trivy.yaml # Trivyのセキュリティスキャン設定
メインの設定ファイル
まずはenvironments/dev/main.tf
から見ていきましょう。とはいえ、ここではそこまで特筆すべきことはやっていません。
このmain.tfでは以下のことを行っています
- VPC関連リソースの作成
- Route53 Hostedゾーンリソースの作成
- acm_self_signed_certモジュールの呼び出し
- albモジュールの呼び出し
- Route53へのレコード登録(ALBを自己署名証明書につけたドメインで名前解決させるため)
自己署名証明書の作成とACMへの登録
次に、キモとなる自己署名証明書を作成しACMに登録するモジュールを見ていきましょう。
証明書作成の流れ
先に図で示すと以下のようなことをしています
それでは上記の証明書作成の流れにそってコードのどの部分と対応しているのかみていきましょう。
1. CA証明書の作成
1-1. CA用の秘密鍵の生成
まず最初に、CA証明書の基となる秘密鍵を作成します。CAはこの秘密鍵を使ってサーバ証明書に署名をすることで、証明書の信頼性を証明します。
従来のopensslコマンドでは以下のように実行していました
# 従来のコマンドライン操作の例
openssl genrsa -out ca.key 4096
Terraformでは、以下の部分で実現しています。
resource "tls_private_key" "root_ca" {
algorithm = "RSA"
rsa_bits = 4096
}
1-2. CA証明書の自己署名
次に、作成した秘密鍵を使ってCA証明書を自己署名します。
従来のopensslコマンドだと以下のように作成していました
# 従来のコマンドライン操作の例
openssl req -new -x509 -key ca.key -sha512 -extensions v3_ca -out ca.pem -subj "/C=JP/ST=Tokyo/O=mycorp./CN=root"
Terraformだと以下の部分です
resource "tls_self_signed_cert" "root_ca" {
private_key_pem = tls_private_key.root_ca.private_key_pem
validity_period_hours = var.root_ca.validity_period_hours
# ...
}
2. サーバー証明書の作成
2-1. サーバー用の秘密鍵の生成
次にサーバ証明書を作成していきます。
サーバ証明書もまず基となる秘密鍵を作成します。この部分はCA証明書と同様です。
## PEM形式の秘密鍵の作成
resource "tls_private_key" "server" {
algorithm = "RSA"
rsa_bits = 2048
}
2-2. サーバー証明書のCSR作成
次に証明書署名要求(CSR)を作成します。サーバ証明書はCAの秘密鍵で署名する必要があるのでまずCSRを作成することになります。
従来のopensslコマンドでは以下のような形です
openssl x509 -req -in server.csr -sha512 -CA ca.crt -CAkey ca.key -days 365 -out server.crt
Terraformでの実装部分は以下です
## PEM形式のCSR(証明書署名要求)の作成
resource "tls_cert_request" "server" {
private_key_pem = tls_private_key.server.private_key_pem
dns_names = [var.server.subject.common_name] # サブジェクト代替名として追加するドメイン名のリスト
# ...
}
2-3. CAによるサーバー証明書の署名
最後に、CA証明書の秘密鍵でCSRに署名し、サーバー証明書を作成します。
従来のopensslコマンドの例は以下
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -out server.crt
Terraformでの実装だと以下です
## CSRを使用してPEM形式のTLS自己署名証明書の作成(CA証明書を使用して署名)
resource "tls_locally_signed_cert" "server" {
cert_request_pem = tls_cert_request.server.cert_request_pem # CSRのPEM形式データを指定
ca_private_key_pem = tls_private_key.root_ca.private_key_pem # 署名するCAのプライベートキー
ca_cert_pem = tls_self_signed_cert.root_ca.cert_pem # 署名するCAの証明書
# ...
}
ACMへの証明書登録
作成した証明書をACMに登録します。以下のコードで、サーバー証明書、秘密鍵、CA証明書をインポートします
resource "aws_acm_certificate" "main" {
private_key = tls_private_key.server.private_key_pem # サーバ証明書の秘密鍵
certificate_body = tls_locally_signed_cert.server.cert_pem # サーバ証明書
certificate_chain = tls_self_signed_cert.root_ca.cert_pem # ルート証明書(CA証明書)
lifecycle {
create_before_destroy = true
}
}
証明書ファイルのローカル保存について
# 証明書ファイルのローカル保存
## ルート証明書(CA証明書)
resource "local_file" "root_ca_cert" {
content = tls_self_signed_cert.root_ca.cert_pem
filename = "${path.module}/../../certs/${var.env}/root_ca.pem"
}
# ...
作成したCA証明書やサーバ証明書ですが、最終的にはローカルにファイルとして保存をしています。
公的なCAに署名してもらうCSR等は然るべき場所に保存する必要があると思いますが、今回はあくまでも検証用の一時的な環境ということでローカルに保存しています。
ローカルに保存することで例えばクライアント側となるEC2等でCA証明書をインポートしたい(それによって証明書の検証エラーが出ないようにしたい)等の状況にも対応できます
ALBモジュール
最後にALBを作成するモジュールについて説明します。
普通にalbを作ってるだけなので詳しい説明は割愛しますが、モジュールのvariablesとして自己署名証明書をインポートしたACMのARNを受け取ることで、listenerに設定しています
動作確認
それでは最後に動作確認をしてみましょう。今回、ALBはVPCのプライベートサブネットに作りました。そのためVPC上からALBにアクセスする必要があります。
少し前ならEC2等をわざわざ立てていたところですが、今はCloudShellをVPCに接続することができるようになったので、これでさくっと動作確認をします。
- cloudshellでVPCenvironmentを作成
- curlコマンドで確認
-
~ $ curl -k -v https://selfsigned.example.com * Host selfsigned.example.com:443 was resolved. * IPv6: (none) * IPv4: 172.18.1.7, 172.18.0.46, 172.18.2.88 * Trying 172.18.1.7:443... * Connected to selfsigned.example.com (172.18.1.7) port 443 * ALPN: curl offers h2,http/1.1 * TLSv1.3 (OUT), TLS handshake, Client hello (1): * TLSv1.3 (IN), TLS handshake, Server hello (2): * TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8): * TLSv1.3 (IN), TLS handshake, Certificate (11): * TLSv1.3 (IN), TLS handshake, CERT verify (15): * TLSv1.3 (IN), TLS handshake, Finished (20): * TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1): * TLSv1.3 (OUT), TLS handshake, Finished (20): * SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256 / X25519 / RSASSA-PSS * ALPN: server accepted h2 * Server certificate: * subject: C=JP; ST=Tokyo; O=mycorp.; CN=selfsigned.example.com * start date: Feb 13 21:07:28 2025 GMT * expire date: Feb 13 21:07:28 2026 GMT * issuer: C=JP; ST=Tokyo; O=mycorp.; CN=root * SSL certificate verify result: self-signed certificate in certificate chain (19), continuing anyway. * Certificate level 0: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption * Certificate level 1: Public key type RSA (4096/152 Bits/secBits), signed using sha256WithRSAEncryption * using HTTP/2 * [HTTP/2] [1] OPENED stream for https://selfsigned.example.com/ * [HTTP/2] [1] [:method: GET] * [HTTP/2] [1] [:scheme: https] * [HTTP/2] [1] [:authority: selfsigned.example.com] * [HTTP/2] [1] [:path: /] * [HTTP/2] [1] [user-agent: curl/8.5.0] * [HTTP/2] [1] [accept: */*] > GET / HTTP/2 > Host: selfsigned.example.com > User-Agent: curl/8.5.0 > Accept: */* > * TLSv1.3 (IN), TLS handshake, Newsession Ticket (4): < HTTP/2 200 < date: Thu, 13 Feb 2025 21:56:28 GMT < content-type: text/plain; charset=utf-8 < content-length: 11 < * Connection #0 to host selfsigned.example.com left intact Hello World~ $
-
実行結果を見ると、以下のような出力が確認できます:
subject: C=JP; ST=Tokyo; O=mycorp.; CN=selfsigned.example.com
この出力から、確かに今回作成した証明書が使用されていることが確認できました。
まとめ
今回は、Terraformを使って自己署名証明書を作成し、ALBに設定するまでの方法をご紹介しました。検証環境での利用に限られますが、手軽に環境を用意できる方法として参考になれば幸いです。
以上、とーちでした。