[アップデート] Amazon CloudFront がオリジン間の相互 TLS をサポートしました
はじめに
これまで、CloudFront からオリジンへの通信を保護する方法としては、以下のような手段が一般的でした。
- カスタムヘッダーによるオリジンアクセス制限
- AWS WAF による IP 制限
- OAC (Origin Access Control) による S3 アクセス制限
これまでの手法は『CloudFront からのリクエストであること』を間接的に検証する方式でしたが、新たに CloudFront でオリジン間の相互 TLS がサポートされ、TLS ハンドシェイクレベルでの相互認証が可能になりました。
やってみた
ということで試してみます。構成図としては以下のようになります。

今回は、カスタムドメインで相互 TLS 認証を有効にした API Gateway をオリジンとして試してみました。
証明書の作成
証明書を準備するため、まずルート CA を作成します。
# ルート CA の秘密鍵を生成
openssl genrsa -out RootCA.key 4096
# ルート CA の証明書を生成 (有効期間: 10 年)
openssl req -new -x509 -days 3650 -key RootCA.key -out RootCA.pem -subj "/C=JP/ST=Tokyo/O=MyOrg/CN=MyRootCA"
続いて、クライアント証明書を作成します。CSR 作成時に EKU 拡張領域の設定を忘れないようにしましょう。
# クライアントの秘密鍵を生成
openssl genrsa -out my-client.key 2048
# CSR を生成
openssl req -new -key my-client.key -out my-client.csr \
-subj "/C=JP/ST=Tokyo/O=MyOrg/CN=my-client" \
-addext "basicConstraints = CA:FALSE" \
-addext "keyUsage = digitalSignature" \
-addext "extendedKeyUsage = clientAuth"
# ルート CA で署名 (有効期間: 1 年)
openssl x509 -req -days 365 \
-in my-client.csr \
-CA RootCA.pem \
-CAkey RootCA.key \
-CAcreateserial \
-copy_extensions copyall \
-out my-client.pem
トラストストアの準備と S3 へのアップロード
API Gateway の mTLS では、信頼する CA 証明書をまとめたトラストストアを S3 に配置する必要があります。今回は中間証明書などはないので、ルート CA 証明書をトラストストアにしてアップロードします。
# ルート CA 証明書をトラストストアとしてコピー
cp RootCA.pem truststore.pem
# S3 にアップロード
aws s3 cp truststore.pem s3://<your-bucket-name>/truststore.pem
ACM へのクライアント証明書インポート
CloudFront がオリジンに提示するクライアント証明書を ACM に登録します。リージョンは、CloudFront と同じ us-east-1 にしましょう。
aws acm import-certificate \
--certificate fileb://my-client.pem \
--private-key fileb://my-client.key \
--certificate-chain fileb://RootCA.pem \
--region us-east-1 \
--query 'CertificateArn' \
--output text
API Gateway の mTLS 設定
ここでは今回の本筋ではないのでスキップしますが、以下のような設定にしておきました。ターゲットはサンプル API (PetStore) にしています。

補足として、API Gateway のデフォルトエンドポイントの無効化についても、合わせて設定しておくと良さそうですね。
CloudFront の設定
では、CloudFront ディストリビューションのオリジン設定で mTLS を有効にしていきましょう。

オリジンタイプにはカスタムオリジン (Other) を選択し、オリジン設定から mTLS を有効にします。

クライアント証明書のフォームをクリックすると、ACM にインポートした証明書が選べるようになります。
動作確認
CloudFront 経由でアクセスし、オリジンへの mTLS 接続が成功することを確認しましょう。
$ curl https://XXXXXXXXXXXXX.cloudfront.net/pets
[
{
"id": 1,
"type": "dog",
"price": 249.99
},
{
"id": 2,
"type": "cat",
"price": 124.99
},
{
"id": 3,
"type": "fish",
"price": 0.99
}
]
クライアント証明書を指定せずに API Gateway のカスタムドメインに直接アクセスすると、
$ curl -v --http1.1 https://<カスタムドメイン>/pets
* Host <カスタムドメイン>:443 was resolved.
* IPv6: (none)
* IPv4: xxx.xxx.xxx.xxx
* Trying xxx.xxx.xxx.xxx:443...
* Connected to <カスタムドメイン> (xxx.xxx.xxx.xxx) port 443
* ALPN: curl offers http/1.1
* (304) (OUT), TLS handshake, Client hello (1):
* CAfile: /etc/ssl/cert.pem
* CApath: none
* (304) (IN), TLS handshake, Server hello (2):
* (304) (IN), TLS handshake, Unknown (8):
* (304) (IN), TLS handshake, Request CERT (13):
* (304) (IN), TLS handshake, Certificate (11):
* (304) (IN), TLS handshake, CERT verify (15):
* (304) (IN), TLS handshake, Finished (20):
* (304) (OUT), TLS handshake, Certificate (11):
* (304) (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / AEAD-AES128-GCM-SHA256 / [blank] / UNDEF
* ALPN: server accepted http/1.1
* Server certificate:
* subject: CN=<カスタムドメイン>
* start date: Oct 10 00:00:00 2025 GMT
* expire date: Nov 8 23:59:59 2026 GMT
* subjectAltName: host "<カスタムドメイン>" matched cert's "<カスタムドメイン>"
* issuer: C=US; O=Amazon; CN=Amazon RSA 2048 M04
* SSL certificate verify ok.
* using HTTP/1.x
> GET /pets HTTP/1.1
> Host: <カスタムドメイン>
> User-Agent: curl/8.7.1
> Accept: */*
>
* Request completely sent off
* Empty reply from server
* Closing connection
curl: (52) Empty reply from server
というように、何も返ってきません。想定通りですね。
注意点
CloudFront は TLS ハンドシェイク時にクライアント証明書を提示しますが、その証明書の有効性検証(署名検証、失効確認など)はオリジン側で実装する必要があります。
API Gateway の mTLS 機能はこの検証を自動で行いますが、他のオリジンタイプを使用する場合はこの点に注意しましょう。
おわりに
CloudFront の mTLS 機能により、CloudFront とオリジン間の通信を TLS ハンドシェイクレベルで相互認証できるようになりました。
これまでカスタムヘッダーや IP 制限に頼っていたオリジン保護に、証明書ベースの認証という選択肢が加わったことで、ハイブリッド / マルチクラウド環境でのセキュリティ要件に、よりスマートに対応できるようになったかなと思います。
構成や要件に応じて従来の方式と使い分けていきましょう。






