AWSネットワーク内の通信を暗号化してみた
はじめに
仕事でAWS Private Certificate Authority(AWSプライベートCA)を使用する機会があったので、今回はAWSプライベートCAを使い、ACM(AWS Certificate Manager)でプライベート証明書を発行して、プライベートサブネット内のEC2や内部ALBに証明書をインストールし、HTTPSで通信することを検証していきたいと思います。
AWSネットワーク内の通信を暗号化することだけが目的であれば、プライベート証明書を使わず、ACM発行のパブリック証明書を使用して暗号化することも可能ですが、今回は普段触ることの少ないプライベートCAが使えるので、プライベート証明書を発行して、プライベートネットワーク内の暗号化をしていきたいと思います。
Q: パブリックなインターネットアクセスのない内部 Elastic Load Balancing ロードバランサーに、パブリック証明書を使用できますか?
https://aws.amazon.com/jp/certificate-manager/faqs/
プライベートCAについて
まず、AWSプライベートCAには「汎用モード」と「有効期間の短い証明書モード」の2つの動作モードがあります。
- 汎用モード
任意の有効期間を持つ証明書を発行できる
1CAあたり月額$400 - 有効期間の短い証明書モード
最大7日間有効な証明書のみを発行できる
1CAあたり月額$50
「汎用モード」は月額$400と気軽に手を出せる金額ではないですが、初回であれば、無料トライアルが使えます。詳しくは公式サイトでご確認ください。
https://aws.amazon.com/jp/private-ca/pricing/
私事ですが30日間は無料と勘違いして、CAを作り直して課金が発生してしまいかなり焦りました。「最初に作成したプライベートCAの最初の30日が無料」ということなので、30日以内であっても、テストなどでCAを作り直したりすると料金が発生しますので注意してください。請求書には月末までの金額が暫定で入るようでいきなり$300近く請求されてしまいましたが、使用を止めると正しく再計算されるようです。
プライベートCAの構築
それでは、プライベートCAを構築していきたいと思います。今回は「汎用モード」で構築します。
失効オプションは今回は指定しません。
ACMからのアクセスを許可します。
CA証明書のインストール
CAが作成されので、次にCA証明書をインストールしていきます。この時点では、ステータスは「保留中の証明書」という状態です。
CA証明書の有効期限はデフォルトで10年です。
ステータスがアクティブになりました。
プライベート証明書の発行
次にACMから「プライベート証明書をリクエスト」します。
先ほど構築したプライベートCAを選択。ドメイン名は「example.local」とします。
プライベート証明書はリクエストすると「即時」発行され、エクスポートできるようになります。ACMのパブリック証明書はエクスポートできませんが、プライベート証明書は「エクスポートすることが可能」で、普通のEC2でも利用することができます。
プライベートキーを暗号化するパスフレーズを入力します。
証明書、証明書チェーン、証明書のプライベートキーが「PEM形式」で出力されます。大切に保管しましょう。
- private_key.txt
- certificate_chain.txt
- certificate.txt
の3つのファイルがダウンロードされます。
サーバーの構築
今回はプライベートサブネット上にEC2(AmazonLinux 2023)を構築し、プライベート証明書をインストールして、nginxを立ち上げHTTPSで通信できるかをテストしてみます。(検証目的なのでサラッと進めていきます)
Cloud9を使用して、プライベートサブネット内のEC2にアクセスします。キーペアやセキュリティグループなどは事前に設定済みとします。
$ ssh -i ~/.ssh/demo.pem ec2-user@10.0.1.132 # from cloud9
$ sudo dnf -y update
$ sudo dnf -y install nginx
$ nginx -v
nginx version: nginx/1.24.0
$ echo "Hello, HTTPS" | sudo tee /usr/share/nginx/html/index.html
Hello, HTTPS
$ sudo systemctl start nginx.service
$ sudo systemctl status nginx.service
$ curl -s http://localhost | grep Hello
Hello, HTTPS
$ sudo mkdir /etc/nginx/ssl
$ cd $_
ダウンロードした「private_key.txt」「certificate_chain.txt」「certificate.txt」を作業ディレクトリにコピーしておきます。
$ openssl x509 -in certificate.txt -text -noout | grep Subject # 念の為、証明書の中を確認
Subject: CN = example.local
$ cat certificate.txt certificate_chain.txt | sudo tee fullchain.pem # certificate.txtとcertificate_chain.txtを連結
$ sudo openssl rsa -in private_key.txt -out private_key.pem # パスフレーズを解除
Enter pass phrase for private_key.txt:
writing RSA key
$ sudo vi /etc/nginx/conf.d/ssl.conf
server {
listen 443 ssl;
server_name example.local;
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/private_key.pem;
}
$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
$ sudo systemctl reload nginx.service
nginxの場合、証明書と証明書チェーンは連結して、「ssl_certificate」で指定する必要があります。
プライベートホストゾーンの作成
「example.local」というプライベートホストゾーンを作成します。
プライベートホストゾーンとVPCの関連付けを行います。
動作確認1
Cloud9からcurlコマンドを実行してみます。
$ curl http://example.local | grep Hello # httpでアクセスできる
$ curl https://example.local # httpsにするとアクセスできない
curl: (60) SSL certificate problem: self-signed certificate in certificate chain
More details here: https://curl.se/docs/sslcerts.html
curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the web page mentioned above.
クライアント側にプライベートCAのルート証明書が必要となるので、AWSプライベートCAのコンソールから対象のプライベートCAを指定して「CA 証明書」タブを選択し、CA証明書をダウンロードします。
$ vi ~/Certificate.pem # CA証明書の中身をコピペ
$ curl --cacert ~/Certificate.pem https://example.local # httpsでアクセスできた
<HTML><BODY>Hello, HTTPS</BODY></HTML>
「--cacert」でダウンロードしたCA証明書を指定してしてアクセスするとうまくいきました。
クライアントへのCA証明書のインストール
毎回、CA証明書を指定するのも面倒なので、クライアントにCA証明書をインストールしたいと思います。
Cloud9(Linux)の場合
$ sudo cp ~/Certificate.pem /etc/pki/ca-trust/source/anchors/
$ sudo update-ca-trust
$ sudo trust list | grep Example
label: Example CA
$ curl https://example.local # CA証明書を指定しないでアクセス
<HTML><BODY>Hello, HTTPS</BODY></HTML> # ok
今度は何も指定せずにうまくアクセスできました。
Windows Serverの場合
せっかくなのでブラウザからも確認したいので、パブリックサブネット上に、Windows Serverを立ち上げRDPでアクセスして確認したいと思います。
CA証明書がないと、アラートがでます。
「Certificate.pem」をデスクトップ上にコピー
「Control Panel / Network and Internet / Internet Options / Content / Certificates」と移動し、「Trusted Root Certification Authority」タブを選択し、「Import...」からインポートします。
アラートがでなくなりました。
内部ALBの追加
クライアント側のCloud9(Linux)やWindows Serverから、nginxサーバーへのHTTPS通信は確認できたので、今度は、内部ALBを間に挟んでHTTPS通信できるかを確認したいと思います。
ターゲットグループの作成
まずはターゲットグループを作成します。
「プロトコル:ポート」でHTTPSを指定します。
ヘルスチェックプロトコルもHTTPSを指定します。
既存のWebサーバーのEC2からイメージ(AMI)を作成し、AMIからもう片方のAZに新しいWebサーバーを起動します。起動したEC2インスタンスも合わせてターゲットグループに指定します。「index.html」の中身もそれぞれ「... HTTPS 1」「... HTTPS 2」に変更しました。
ロードバランサの作成
「内部」を選択します。
VPCを選択し、プライベートサブネットを指定します。
適切なセキュリティグループは事前に作成しておきます。
HTTPSに対するリスナーを作成し、作成したターゲットグループを転送先に指定します。
ACMから証明書を設定します。
Route53の「example.local」のAレコードのルーティング先を内部ALBに変更します。
動作確認2
Cloud9(Linux)の場合
curlコマンドからランダムでWebサーバー1とWebサーバー2が表示されるようになりました。
$ curl https://example.local
<HTML><BODY>Hello, HTTPS 1</BODY></HTML> # ok
$ curl https://example.local
<HTML><BODY>Hello, HTTPS 2</BODY></HTML> # ok
Windows Serverの場合
ブラウザからもランダムでWebサーバー1とWebサーバー2が表示されるようになりました。
まとめ
今回は、AWSプライベートCAを利用し、プライベート証明書を発行して、AWSプライベートネットワーク内での通信の暗号化を行いました。検証目的ということもあり、とりあえず動くところを目指しましたが、本番環境を想定するともっといろいろ考慮する部分も出てくるかと思います。
今回作成したもの
ACMやパブリック証明書を使用することはあっても、プライベートCAやプライベート証明書を使用する機会はなかなか少ないかと思います。汎用モードでは月額$400というなかなかの金額なので、ちょっと試しに検証するというわけにはいかないかもしれませんが、プライベートCAやプライベート証明書を使用される際は参考になれば幸いです。