[アップデート] Amazon CloudFrontがmTLSをサポートしたのでクライアント証明書の検証をしてみた
CloudFront側でクライアント証明書による認証を行いたい
こんにちは、のんピ(@non____97)です。
皆さんはCloudFront側でクライアント証明書による認証を行たいなと思ったことはありますか? 私はあります。
CloudFrontには署名付きCookieや署名付きURLといった。一方、そのための認証を考える必要があります。クライアントの実装として署名付きCookieや署名付きURLを発行するための認証に対応することが難しい、そもそも署名付きCookieや署名付きURL自体の相性が悪い場合もあるでしょう。
例えば、IoTデバイスやAPI間通信の認証では証明書ベースの認証を行う場面が多いように感じています。
また、クライアント証明書の検証をオリジン側で行なっている場合、オリジンリクエストを行わせる関係でCloudFrontのキャッシュを上手く活かせない、もしくは活かそうとすると処理が複雑になるケースもあるでしょう。
今回、CloudFrontがmTLSをサポートしました。
AWS Blogsにも投稿されています。
ALBでは2023年にサポートしていましたが、CloudFrontもついにサポートといった形です。
実際に試してみました。
いきなりまとめ
- CloudFrontのエッジでクライアント証明書の検証ができるようになった
- mTLSの設定はCloudFrontディストリビューション単位
- 有効/無効の切り替えが可能
- ビヘイビアでパスごとにmTLSを行うということはできない
- CloudFrontのトラストストアはCRLをサポートしていないが、Connectio FunctionとKeyValueStoreを組み合わせることで同様の処理を実現することは可能
- クライアント証明書の検証のログはコネクションログとして出力可能
- 標準ログV2で出力できる
- クライアント証明書の検証に失敗したものはCloudFrontのアクセスログに記録されない
- mTLS利用時の追加の料金はなし
- Connection FunctionはおそらくCloudFront Functionsと同等の料金が発生
- Connection Functionsを利用する前にはクォータの引き上げが必要
- デフォルトは0
ドキュメントから仕様の確認
概要
まず、ドキュメントから仕様を確認します。
CloudFrontがmTLSをサポートしたことにより、サーバーとクライアントの両方がX.509 証明書を使用して相互認証することを要求することができるようになりました。これにより、信頼できる証明書を提示してきたクライアントのみに接続をさせることが可能です。
mTLSの設定はCloudFrontディストリビューション単位で行います。AWS CLIでもマネジメントコンソールでも簡単に有効/無効の切り替えが可能です。ディストリビューション単位での設定であるため、ビヘイビアでパスごとにmTLSを行うということはできません。
mTLSを設定するにはプライベート認証局の証明書をS3バケットにアップロードし、トラストストアとして指定する必要があります。これはALBのmTLSと同じですね。
ALBのトラストストアと異なる点としてCRLをサポートしていない点が挙げられます。何らかの理由で有効期限前に失効した証明書は後述のConnection FunctionとKeyValue Storeを用いて制御することになります。
CloudFront Connection Function
CloudFront Connection Functionは、mTLSの証明書検証後に実行されるJavaScript関数です。
AWS Client VPNでいうところのクライアント接続ハンドラーのようなもので、クライアント証明書の検証とは別に追加の検証処理を行うことが可能です。
mTLS接続を確立しようとすると、次のフローで処理が行われます。
- クライアントとCloudFrontエッジロケーションとのTLSハンドシェイクを開始する
- CloudFrontはクライアント証明書を要求し、受信する
- CloudFrontはトラストストアに対して証明書検証を実行する
- 証明書が検証に合格した場合、Connection Functionを実行する
ViewerMtlsConfigでIgnoreCertificateExpiryが有効になっている場合、有効期限が切れているものの有効な証明書もConnection Functionに渡される- クライアント証明書が無効な場合、Connection Functionは呼び出されない
- Connection Functionは解析された証明書情報と接続の詳細を受け取る
- Connection Functionにてはカスタムロジックに基づいて許可/拒否の決定を行う
- ユーザーの指示に基づいてTLS 接続を完了または終了する
Connection Functionが受け取るイベントは以下のとおりです。
{
"connectionId": "Fdb-Eb7L9gVn2cFakz7wWyBJIDAD4-oNO6g8r3vXDV132BtnIVtqDA==", // Unique identifier for this TLS connection
"clientIp": "203.0.113.42", // IP address of the connecting client (IPv4 or IPv6)
"clientCertificate": {
"certificates": {
"leaf": {
"subject": "CN=client.example.com,O=Example Corp,C=US", // Distinguished Name (DN) of the certificate holder
"issuer": "CN=Example Corp Intermediate CA,O=Example Corp,C=US", // Distinguished Name (DN) of the certificate authority that issued this certificate
"serialNumber": "4a:3f:5c:92:d1:e8:7b:6c", // Unique serial number assigned by the issuing CA (hexadecimal)
"validity": {
"notBefore": "2024-01-15T00:00:00Z", // Certificate validity start date (ISO 8601 format)
"notAfter": "2025-01-14T23:59:59Z" // Certificate expiration date (ISO 8601 format)
},
"sha256Fingerprint": "a1b2c3d4e5f6...abc123def456", // SHA-256 hash of the certificate (64 hex characters)
},
},
},
}
抜粋 : Associate a CloudFront Connection Function - Amazon CloudFront
IPアドレスでの制御はAWS WAFでも可能なので、Connection Functionでの検証時の主な判断要素は証明書のシリアル番号やSubjectでしょう。
Connection Functionsの一連の処理はAWS Blogsに投稿されている記事の以下図が分かりやすかったです。

抜粋 : 信頼は双方向: Amazon CloudFront がビューワー mTLS をサポート | ネットワーキングとコンテンツ配信
また、Connection Functionは後述のクライアント証明書検証モードで必須かオプショナルかでトリガーされる条件が異なります。
- 必須の場合 : クライアント証明書の検証を行い、検証に成功した場合に実行
- オプショナルの場合 : クライアント証明書の検証/失敗に関わらず事項
なお、Connection Functionが受け取るイベントにパスやHostヘッダーの情報は含まれていないため、「オプショナルモードにしてConnection Functionでパスやホスト名ベースで制御を行う」ということはできません。
クライアント証明書の検証モード
クライアント証明書の検証は必須モードとオプショナルモードの2つがあります。
必須モードはその名の通り、クライアント証明書の検証を必ず行います。クライアント証明書を受信しなかった場合は拒否します。
一方、オプショナルモードでは以下の挙動をします。
- 有効な証明書を持つクライアントへの接続を許可する
- 証明書のないクライアントへの接続を許可する
そのため、オプションナルモードでは、mTLS 認証への段階的な移行や証明書を持つクライアントと証明書を持たないクライアントの両方のサポートをする必要が場面に使用します。
ログ
ALBのmTLSと同様に通常のアクセスログとは別にコネクションログの設定が可能です。
コネクションログはアクセスログと同様に標準ログV2です。
そのため、以下の指定が可能です
- 出力先 (CloudWatch Logs / S3 / Data Firehose)
- フィールド
- パーティション
- Hive互換フォーマットの有無
- 出力フォーマット (JSON / Parquet / W3C / Plain-text)
指定可能なフィールドは以下のとおりです。
| Field | Description | Example |
|---|---|---|
eventTimestamp |
接続が確立または失敗したときの ISO 8601 タイムスタンプ | 1731620046814 |
connectionId |
TLS接続の一意の識別子 | oLHiEKbQSn8lkvJfA3D4gFowK3_iZ0g4i5nMUjE1Akod8TuAzn5nzg== |
connectionStatus |
mTLS 接続試行のステータス。 | Success or Failed |
clientIp |
接続クライアントのIPアドレス | 2001:0db8:85a3:0000:0000:8a2e:0370:7334 |
clientPort |
クライアントが使用するポート | 12137 |
serverIp |
CloudFront エッジサーバーの IP アドレス | 99.84.71.136 |
distributionId |
CloudFront ディストリビューション ID | E2DX1SLDPK0123 |
distributionTenantId |
CloudFront ディストリビューションテナント ID(該当する場合) | dt_2te1Ura9X3R2iCGNjW123 |
tlsProtocol |
使用されるTLSプロトコルバージョン | TLSv1.3 |
tlsCipher |
接続に使用されるTLS暗号スイート | TLS_AES_128_GCM_SHA256 |
tlsHandshakeDuration |
TLSハンドシェイクの所要時間(ms) | 153 |
tlsSni |
TLS SNI | d111111abcdef8.cloudfront.net |
clientLeafCertSerialNumber |
クライアント証明書のシリアル番号 | 00:b1:43:ed:93:d2:d8:f3:9d |
clientLeafCertSubject |
クライアント証明書のSubjectフィールド | C=US, ST=WA, L=Seattle, O=Amazon.com, OU=CloudFront, CN=client.test.mtls.net |
clientLeafCertIssuer |
クライアント証明書の発行者フィールド | C=US, ST=WA, L=Seattle, O=Amazon.com, OU=CloudFront, CN=test.mtls.net |
clientLeafCertValidity |
クライアント証明書の有効期間 | NotBefore=2025-06-05T23:28:21Z;NotAfter=2125-05-12T23:28:21Z |
connectionLogCustomData |
Connection Functionを介して追加されたカスタムデータ | REVOKED:00:b1:43:ed:93:d2:d8:f3:9d |
抜粋 : Observability using connection logs - Amazon CloudFront
connectionStatusに記録されるエラーコードは以下のとおりです。
| Code | Description |
|---|---|
| ClientCertMaxChainDepthExceeded | 証明書チェーンの最大深度を超えました |
| ClientCertMaxSizeExceeded | 証明書の最大サイズを超えました |
| ClientCertUntrusted | 証明書は信頼できません |
| ClientCertNotYetValid | 証明書はまだ有効ではありません |
| ClientCertExpired | 証明書の有効期限が切れています |
| ClientCertTypeUnsupported | 証明書の種類はサポートされていません |
| ClientCertInvalid | 証明書が無効です |
| ClientCertIntentInvalid | 証明書の意図が無効です |
| ClientCertRejected | カスタム検証により証明書が拒否されました |
| ClientCertMissing | 証明書がありません |
| TcpError | 接続を確立しようとしたときにエラーが発生しました |
| TcpTimeout | タイムアウト期間内に接続を確立できませんでした |
| ConnectionFunctionError | Connection Functionの実行中にキャッチされない例外がスローされました |
| Internal | 内部サービスエラーが発生しました |
| UnmappedConnectionError | 他のカテゴリに該当しないエラーが発生しました |
キャッシュポリシーで使用できるヘッダーとオリジンアクセス時に付与されるヘッダー
CloudFrontではキャッシュを効かせていることも多いでしょう。
キャッシュポリシーで使用できるヘッダーは以下のとおりです。
| Header name | Description | Example value |
|---|---|---|
| CloudFront-Viewer-Cert-Serial-Number | 証明書のシリアル番号の16進表現 | 4a:3f:5c:92:d1:e8:7b:6c |
| CloudFront-Viewer-Cert-Issuer | 発行者の識別名(DN)のRFC2253文字列表現 | CN=rootcamtls.com,OU=rootCA,O=mTLS,L=Seattle,ST=Washington,C=US |
| CloudFront-Viewer-Cert-Subject | Subjectの識別名(DN)のRFC2253文字列表現 | CN=client_.com,OU=client-3,O=mTLS,ST=Washington,C=US |
| CloudFront-Viewer-Cert-Present | 証明書が存在するかどうかを示す 1(存在する場合)または 0(存在しない場合)のいずれかの値。必須モードでは、この値は常に 1 です。 | 1 |
| CloudFront-Viewer-Cert-Sha256 | クライアント証明書のSHA256ハッシュ | 01fbf94fef5569753420c349f49adbfd80af5275377816e3ab1fb371b29cb586 |
抜粋 : Viewer mTLS headers for cache policies and forwarded to origin - Amazon CloudFront
また、オリジンリクエスト時には上述のヘッダーに加えて、以下ヘッダーも付与することが可能です。
| Header name | Description | Example value |
|---|---|---|
| CloudFront-Viewer-Cert-Validity | notBefore と notAfter 日付の ISO8601 形式 | CloudFront-Viewer-Cert-Validity: NotBefore=2023-09-21T01:50:17Z;NotAfter=2024-09-20T01:50:17Z |
| CloudFront-Viewer-Cert-Pem | リーフ証明書のURLエンコードされたPEM形式 | CloudFront-Viewer-Cert-Pem: -----BEGIN%20CERTIFICATE-----%0AMIIG<...reduced...>NmrUlw%0A-----END%20CERTIFICATE-----%0A |
クライアント証明書ごとにもキャッシュを効かせられるのは個人的には便利そうで嬉しいです。
料金
無料です。やったね。
なお、Connection Functionの料金についての言及はAWS公式ドキュメントにはありませんでした。
CloudFront Functionsと同じエッジコンピューティングではあると思うので、Connection Functionにおいても以下料金が適用されると想像しています。
| Info | Price |
|---|---|
| Requests | $0.60 per 1M requests |
| Duration | $0.00005001 for every GB-second |
抜粋 : Amazon CloudFront CDN - Plans & Pricing - Try For Free
注意点
その他の注意点は以下のとおりです。
- HTTPは非サポート
HTTPS onlyもしくはRedirect HTTP to HTTPSとする必要がある
- HTTP/3は未サポート
- Connection FunctionはTLS ハンドシェイク中にクライアント接続ごとに1回だけ実行される
- Connection Functionは接続を許可または拒否することしかできず、HTTPリクエスト/レスポンスを変更することはできない
- CloudFrontディストリビューションに関連付けることができるのは、パブリッシュ済みのConnection Functionのみ
- 各CloudFrontディストリビューションに設定可能なConnection Functionは1つまで
やってみた
検証環境
実際に試してみましょう。
検証環境は以下のとおりです。

mTLS設定とコネクションログの出力設定以外は全て設定済みです。
CA証明書とクライアント証明書の発行
以下記事を参考にCA証明書とクライアント証明書の発行を行います。
適当にAmazon Linux 2023のEC2インスタンスを立てます。
このEC2インスタンス上でCA証明書を作成します。
$ openssl req \
-x509 \
-new \
-days 7 \
-keyout rootCA_key.pem \
-out rootCA_cert.pem
..+++++++++++++++++++++++++++++++++++++++*..
.
.
(中略)
.
.
.....++++++
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [XX]:JP
State or Province Name (full name) []:Tokyo
Locality Name (eg, city) [Default City]:
Organization Name (eg, company) [Default Company Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (eg, your name or your server's hostname) []:root-ca
Email Address []:
$ cat rootCA_cert.pem
-----BEGIN CERTIFICATE-----
MIIDqTCCApGgAwIBAgIUdEGHxctVOm29JJnc35PRA4jT4tEwDQYJKoZIhvcNAQEL
BQAwZDELMAkGA1UEBhMCSlAxDjAMBgNVBAgMBVRva3lvMRUwEwYDVQQHDAxEZWZh
dWx0IENpdHkxHDAaBgNVBAoME0RlZmF1bHQgQ29tcGFueSBMdGQxEDAOBgNVBAMM
B3Jvb3QtY2EwHhcNMjUxMTI3MTAxNjIxWhcNMjUxMjA0MTAxNjIxWjBkMQswCQYD
VQQGEwJKUDEOMAwGA1UECAwFVG9reW8xFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEc
MBoGA1UECgwTRGVmYXVsdCBDb21wYW55IEx0ZDEQMA4GA1UEAwwHcm9vdC1jYTCC
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIbee0pd1JakvdeCxoTQrz1C
qes4f4Sva5s75V19+aWqHMICljpplQUZUrSbWBNzG4WL8PTZe1elQIrC0invDQE+
cq1Ol/iv8QSOeKgiTiYFLBpsKxQuZ6hnfl4jwimEoytdJlBdmJ1CIXLjAJBQmrgj
NkBMH3KE6EyiY/u/vBOEvF3zA8lPq2/KSq8SiW9u+WYRHgxS6WNNAkeHXyMPBPi+
5AcBIaT5ZpN4Ay5pQ0B010YTWo1ZLWuwg9YEcFLvDTe5wll0LxGOvUNICKYKtsX2
/PJOU0WvUPspHI0+Poek6SCWfAodt+iZVXvh5WRIsh8NMqNeGLul7jHHeUNdR8UC
AwEAAaNTMFEwHQYDVR0OBBYEFNx8y9Y/6GGFz09UrJcQFZVnGfydMB8GA1UdIwQY
MBaAFNx8y9Y/6GGFz09UrJcQFZVnGfydMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI
hvcNAQELBQADggEBABzPLW3lttnvHHj9YfMDvSEE0RLqyRSCp163vD3JvxewigSE
RmsO7NTPt0rKgHg21jFGSX0Bv/h7MePzrbWiJD/QSwJIYnzx+IYGZLnl8sd76syu
c0yRD8ABmVo+n7W+kTYzAZhCFDviLPG+04CergDtDBAG8efzKpKxpfbBTOC23h64
k7qcaizjQHWM0bL7MNDG13YaOAnMdcMRNp4cj468FzCYHAzCBC5QVjk0A1U4p9qJ
SSYOqLwyHzYfUebiuJp1+d/pGFWZRswovQUcqZYIgNnEWCVdIQsUDJgIKbnxdpip
P+dwjlFcaEUqPEtai9ptNIUtb8be1W42Z1DB0aA=
-----END CERTIFICATE-----
発行したCA証明書は後ほどトラストストアとして登録します。トラストストア用のS3バケットにアップロードしておきます。
クライアント証明書も発行します。
$ openssl req \
-x509 \
-CA rootCA_cert.pem \
-CAkey rootCA_key.pem \
-days 7 \
-new \
-nodes \
-keyout client_key_test1.pem \
-out client_cert_test1.pem
...+...+..............
.
.
(中略)
.
.
...+..++++++
-----
Enter pass phrase for rootCA_key.pem:
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [XX]:JP
State or Province Name (full name) []:Tokyo
Locality Name (eg, city) [Default City]:
Organization Name (eg, company) [Default Company Ltd]:
Organizational Unit Name (eg, section) []:Test 1 BU
Common Name (eg, your name or your server's hostname) []:Test 1
Email Address []:
$ openssl x509 -text -noout -in client_cert_test1.pem
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
01:e5:f0:60:df:9a:92:dd:40:c7:31:61:2e:72:7b:ae:0e:20:00:b9
Signature Algorithm: sha256WithRSAEncryption
Issuer: C=JP, ST=Tokyo, L=Default City, O=Default Company Ltd, CN=root-ca
Validity
Not Before: Nov 27 10:18:17 2025 GMT
Not After : Dec 4 10:18:17 2025 GMT
Subject: C=JP, ST=Tokyo, L=Default City, O=Default Company Ltd, OU=Test 1 BU, CN=Test 1
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:ea:ff:73:9a:2d:b1:94:44:2e:a2:7d:ef:29:28:
78:19:3a:06:c8:5c:7e:7b:ef:74:67:96:e7:64:05:
d9:d7:b5:43:10:a4:de:c0:99:1e:57:1f:6b:3e:18:
08:a9:ac:f9:8d:29:fe:f8:74:82:c1:67:f3:48:51:
08:56:4a:6f:00:c1:f4:80:68:41:fa:89:38:33:6a:
5b:c8:a1:67:d0:8a:32:e1:29:e1:ce:8a:31:62:f0:
57:57:9f:de:29:4a:ba:ea:b8:63:cf:b2:60:5e:b9:
be:78:a6:e3:6a:da:7b:dc:98:d2:58:1b:e8:85:42:
a5:56:bc:bf:33:eb:7e:73:0e:82:51:f8:2f:c3:57:
e7:27:b6:f2:60:73:18:70:cb:52:7c:b1:b6:08:6f:
f8:34:3e:7c:37:44:af:2b:9d:b0:ec:44:f5:0b:4a:
09:96:f9:ba:f3:92:92:e7:a3:ba:65:cf:77:12:66:
4b:1e:68:07:35:91:85:74:9e:ac:be:8a:92:02:b0:
df:fb:71:9b:0d:a2:3b:b5:d8:15:bf:e5:92:15:27:
c5:f2:9d:a9:f6:dd:78:9d:60:4a:74:0b:2f:12:6d:
3e:f1:c4:59:01:73:29:54:b7:8b:f8:e1:69:f1:72:
95:47:58:36:c0:57:5f:9a:49:c2:db:e6:73:25:0d:
78:07
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Subject Key Identifier:
34:45:36:CE:E4:1B:A6:1F:5F:EE:81:A2:C0:70:CA:58:7A:E4:B0:4A
X509v3 Authority Key Identifier:
DC:7C:CB:D6:3F:E8:61:85:CF:4F:54:AC:97:10:15:95:67:19:FC:9D
X509v3 Basic Constraints: critical
CA:TRUE
Signature Algorithm: sha256WithRSAEncryption
Signature Value:
7f:b5:f5:a7:3c:4b:7f:dc:b5:f2:4c:cf:61:87:3c:4a:14:0a:
35:eb:fd:ba:e7:22:f6:74:46:ef:b3:3d:a9:28:93:36:9d:f9:
3f:a5:9f:d0:74:e2:32:ba:16:67:d4:89:23:29:52:66:c9:da:
42:e9:ad:35:b0:58:96:d8:b7:50:8a:15:4f:a4:62:65:e3:e5:
7e:d8:0c:71:0e:a6:44:15:01:bc:69:68:bd:19:27:2f:d5:9e:
ed:1d:9e:f2:6e:51:1d:b4:04:6a:bf:66:c0:46:69:6f:af:77:
14:f5:80:f5:94:77:16:5a:12:be:dc:2a:ee:90:8d:ad:77:5c:
b7:5b:65:19:53:2d:f9:00:ba:4b:bb:df:05:73:29:09:d0:26:
3d:3c:be:53:b1:b2:82:82:49:f8:7c:e8:6e:ca:f7:00:87:fc:
a1:3d:ae:de:31:aa:38:00:b6:9d:6e:91:e7:73:48:55:b8:ad:
70:25:81:86:9a:09:bb:fc:1a:e8:e1:c1:52:76:6d:fb:19:8b:
a9:47:d4:82:ba:47:6a:44:b9:22:dd:38:bc:97:a6:93:74:11:
76:4b:6c:e9:90:4e:11:a0:cf:44:cd:7a:ce:ee:3f:43:59:0c:
ef:5f:bb:86:e9:4b:4c:9c:1b:48:2e:67:79:95:58:c1:b8:d9:
ee:2d:40:9b
トラストストアの作成
トラストストアの作成をします。
CloudFrontのコンソールのTrust storesから作成します。

トラストストア名とCA証明書を配置している場所を指定します。

トラストストアの作成が完了しました。

Associate to distributionをクリックすると、そのままCloudFrontディストリビューションと関連付けができるようでした。

現在はCloudFrontディストリビューションでHTTP/3をサポートしている関係でグレーアウトしています。
CloudFrontディストリビューションでのmTLSの設定
mTLSの設定をします。
CloudFrontディストリビューションを選択して編集をクリックします。

HTTP/3が有効である関係でmTLSの有効化ができない状態です。

HTTP/3のチェックを外すとmTLSを有効化できるようになりました。先ほどのトラストストアを指定して更新します。

デプロイが開始されました。トラストストア名が確認できますね。

状態を確認します。

1-2分ほどでデプロイが完了しました。かなり早いですね。

コネクションログの設定
mTLSの設定が完了したので、続いてコネクションログの設定をします。
Loggingタブをクリックすると、Connection log destinationsと項目が生えていました。

mTLS有効化前はこちらの項目はなかったので、有効化すると生えてくるのでしょう。
今回はS3バケットにParquet形式で出力します。フィールドはデフォルトで有効なものをそのまま採用します。

設定が完了しました。パーティションは未指定だったのですが、自動でcloudfront-connection/{region}/{yyyy}/{MM}/{dd}/{HH}/が付与されていました。

動作確認
それでは動作確認です。
まず、クライアント証明書を渡さない場合です。
$ curl https://www.non-97.net -v
* Host www.non-97.net:443 was resolved.
* IPv6: (none)
* IPv4: 108.138.85.24, 108.138.85.20, 108.138.85.82, 108.138.85.44
* Trying 108.138.85.24:443...
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* CAfile: /etc/pki/tls/certs/ca-bundle.crt
* CApath: none
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Request CERT (13):
* 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, Certificate (11):
* 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: CN=www.non-97.net
* start date: Feb 24 00:00:00 2025 GMT
* expire date: Mar 25 23:59:59 2026 GMT
* subjectAltName: host "www.non-97.net" matched cert's "www.non-97.net"
* issuer: C=US; O=Amazon; CN=Amazon RSA 2048 M03
* SSL certificate verify ok.
* Certificate level 0: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
* Certificate level 1: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
* Certificate level 2: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
* Connected to www.non-97.net (108.138.85.24) port 443
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://www.non-97.net/
* [HTTP/2] [1] [:method: GET]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: www.non-97.net]
* [HTTP/2] [1] [:path: /]
* [HTTP/2] [1] [user-agent: curl/8.11.1]
* [HTTP/2] [1] [accept: */*]
> GET / HTTP/2
> Host: www.non-97.net
> User-Agent: curl/8.11.1
> Accept: */*
>
* Request completely sent off
* TLSv1.3 (IN), TLS alert, close notify (256):
* closing connection #0
curl: (16) Error in the HTTP2 framing layer
はい、エラーになりました。
続いてブラウザからアクセスします。
アクセスすると証明書の選択を行うポップアップが表示されました。

キャンセルしても適当な証明書を選択しても接続できませんでした。

いいですね。
では、証明書を指定してアクセスします。
$ curl https://www.non-97.net \
--key client_key_test1.pem \
--cert client_cert_test1.pem \
-v
* Host www.non-97.net:443 was resolved.
* IPv6: (none)
* IPv4: 108.138.85.44, 108.138.85.24, 108.138.85.82, 108.138.85.20
* Trying 108.138.85.44:443...
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* CAfile: /etc/pki/tls/certs/ca-bundle.crt
* CApath: none
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Request CERT (13):
* 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, Certificate (11):
* TLSv1.3 (OUT), TLS handshake, CERT verify (15):
* 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: CN=www.non-97.net
* start date: Feb 24 00:00:00 2025 GMT
* expire date: Mar 25 23:59:59 2026 GMT
* subjectAltName: host "www.non-97.net" matched cert's "www.non-97.net"
* issuer: C=US; O=Amazon; CN=Amazon RSA 2048 M03
* SSL certificate verify ok.
* Certificate level 0: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
* Certificate level 1: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
* Certificate level 2: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
* Connected to www.non-97.net (108.138.85.44) port 443
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://www.non-97.net/
* [HTTP/2] [1] [:method: GET]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: www.non-97.net]
* [HTTP/2] [1] [:path: /]
* [HTTP/2] [1] [user-agent: curl/8.11.1]
* [HTTP/2] [1] [accept: */*]
> GET / HTTP/2
> Host: www.non-97.net
> User-Agent: curl/8.11.1
> Accept: */*
>
* Request completely sent off
< HTTP/2 200
< content-type: text/html
< content-length: 12
< date: Thu, 27 Nov 2025 10:53:15 GMT
< last-modified: Tue, 25 Feb 2025 02:38:39 GMT
< etag: "56aec8b7843df637b3fb2ec0b027e5b6"
< x-amz-server-side-encryption: AES256
< accept-ranges: bytes
< server: AmazonS3
< x-cache: Miss from cloudfront
< via: 1.1 c6bba20dc3ec8526b729f039a2fdf7ae.cloudfront.net (CloudFront)
< x-amz-cf-pop: IAD12-P2
< x-amz-cf-id: QrU7g3CmAEB-yv3lzqK3hHjV8bWZq61lFrqd-GLB9Kfktc1BTz92Ww==
< x-xss-protection: 1; mode=block
< x-frame-options: SAMEORIGIN
< referrer-policy: strict-origin-when-cross-origin
< x-content-type-options: nosniff
< strict-transport-security: max-age=31536000
<
/index.html
* Connection #0 to host www.non-97.net left intact
正常にアクセスできましたね。
この時のログの確認をします。
AWSLogs/<AWSアカウントID>/cloudfront-connection/us-east-1/2025/11/28/10/<AWSアカウントID>_cloudfront-connection_us-east-1_ELGQOVCCUO3ME_20251128T10Z_79e73179.log.parquetというオブジェクトが出力されていました。
AWSLogs/<AWSアカウントID>はパーティションで指定していないですが自動で挿入されるようです。
ログには以下のように記録されていました。
{
"eventTimestamp": 1764240547413,
"connectionId": "jcjbyIT6plZdRHKVAUunc0OAntEtmgNhiXbI2nuWOvu9n4J5my4jhA==",
"distributionId": "ELGQOVCCUO3ME",
"connectionStatus": "Failed:ClientCertMissing",
"clientIp": "18.207.134.47",
"clientPort": "37092",
"serverIp": "108.138.85.24",
"distributionTenantId": "-",
"tlsProtocol": "TLSv1.3",
"tlsCipher": "TLS_AES_128_GCM_SHA256",
"tlsHandshakeDuration": 15,
"tlsSni": "www.non-97.net",
"clientLeafCertSerialNumber": "-",
"clientLeafCertSubject": "-",
"clientLeafCertIssuer": "-",
"clientLeafCertValidity": "-",
"connectionLogCustomData": "-"
}
{
"eventTimestamp": 1764240588632,
"connectionId": "oztvKdh3ftSYifMJHkcbnX6jFgKfFlKE6BT7PDc-KcuQ8PBUNt5_LA==",
"distributionId": "ELGQOVCCUO3ME",
"connectionStatus": "Failed:UnmappedConnectionError",
"clientIp": "104.28.236.107",
"clientPort": "40170",
"serverIp": "3.173.254.16",
"distributionTenantId": "-",
"tlsProtocol": "TLSv1.3",
"tlsCipher": "TLS_AES_128_GCM_SHA256",
"tlsHandshakeDuration": 821,
"tlsSni": "www.non-97.net",
"clientLeafCertSerialNumber": "-",
"clientLeafCertSubject": "-",
"clientLeafCertIssuer": "-",
"clientLeafCertValidity": "-",
"connectionLogCustomData": "-"
}
{
"eventTimestamp": 1764240794284,
"connectionId": "e8Cx1UpCdn9lp2ZaUxQaUQ47O_oo7C1ezy5JBcG-wLCP_Wlt7IU-1w==",
"distributionId": "ELGQOVCCUO3ME",
"connectionStatus": "Success",
"clientIp": "18.207.134.47",
"clientPort": "47384",
"serverIp": "108.138.85.44",
"distributionTenantId": "-",
"tlsProtocol": "TLSv1.3",
"tlsCipher": "TLS_AES_128_GCM_SHA256",
"tlsHandshakeDuration": 11,
"tlsSni": "www.non-97.net",
"clientLeafCertSerialNumber": "01:e5:f0:60:df:9a:92:dd:40:c7:31:61:2e:72:7b:ae:0e:20:00:b9",
"clientLeafCertSubject": "C=JP,%20ST=Tokyo,%20L=Default%20City,%20O=Default%20Company%20Ltd,%20OU=Test%201%20BU,%20CN=Test%201",
"clientLeafCertIssuer": "C=JP,%20ST=Tokyo,%20L=Default%20City,%20O=Default%20Company%20Ltd,%20CN=root-ca",
"clientLeafCertValidity": "NotBefore=2025-11-27T10:18:17Z;NotAfter=2025-12-04T10:18:17Z",
"connectionLogCustomData": "-"
}
いい具合ですね。
なお、CloudFrontのアクセスログを確認すると、正常にクライアント証明書の検証が成功したもの以外の通信は記録されていませんでした。mTLSを導入した際に、うまく接続できないという場合はまずコネクションログを見るという形が良いでしょう。
CA アドバタイズメント
次に、CAアドバタイズメントを試します。
こちらの機能を有効化すると、TLS ハンドシェイク中に信頼できるCA名のリストをクライアントに送信してくれます。
有効にしてみます。

この状態でアクセスをします。
$ curl https://www.non-97.net -v
* Host www.non-97.net:443 was resolved.
* IPv6: (none)
* IPv4: 108.138.85.20, 108.138.85.24, 108.138.85.82, 108.138.85.44
* Trying 108.138.85.20:443...
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* CAfile: /etc/pki/tls/certs/ca-bundle.crt
* CApath: none
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Request CERT (13):
* 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, Certificate (11):
* 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: CN=www.non-97.net
* start date: Feb 24 00:00:00 2025 GMT
* expire date: Mar 25 23:59:59 2026 GMT
* subjectAltName: host "www.non-97.net" matched cert's "www.non-97.net"
* issuer: C=US; O=Amazon; CN=Amazon RSA 2048 M03
* SSL certificate verify ok.
* Certificate level 0: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
* Certificate level 1: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
* Certificate level 2: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
* Connected to www.non-97.net (108.138.85.20) port 443
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://www.non-97.net/
* [HTTP/2] [1] [:method: GET]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: www.non-97.net]
* [HTTP/2] [1] [:path: /]
* [HTTP/2] [1] [user-agent: curl/8.11.1]
* [HTTP/2] [1] [accept: */*]
> GET / HTTP/2
> Host: www.non-97.net
> User-Agent: curl/8.11.1
> Accept: */*
>
* Request completely sent off
* TLSv1.3 (IN), TLS alert, close notify (256):
* closing connection #0
curl: (16) Error in the HTTP2 framing layer
特にCA名は表示されないですね。
ログの詳細度をもう少しあげます。
$ curl https://www.non-97.net -vvv
04:19:21.727292 [0-x] == Info: [READ] client_reset, clear readers
04:19:21.743075 [0-0] == Info: Host www.non-97.net:443 was resolved.
04:19:21.743401 [0-0] == Info: IPv6: (none)
04:19:21.743478 [0-0] == Info: IPv4: 108.138.85.44, 108.138.85.82, 108.138.85.24, 108.138.85.20
04:19:21.743728 [0-0] == Info: [HTTPS-CONNECT] added
04:19:21.743872 [0-0] == Info: [HTTPS-CONNECT] connect, init
04:19:21.744063 [0-0] == Info: [HTTPS-CONNECT] connect, check h21
04:19:21.744530 [0-0] == Info: Trying 108.138.85.44:443...
04:19:21.744746 [0-0] == Info: [HTTPS-CONNECT] connect -> 0, done=0
04:19:21.744936 [0-0] == Info: [HTTPS-CONNECT] adjust_pollset -> 1 socks
04:19:21.745285 [0-0] == Info: [HTTPS-CONNECT] connect, check h21
04:19:21.745480 [0-0] == Info: [SSL] cf_connect()
04:19:21.747474 [0-0] == Info: [SSL] No cached session ID for https://www.non-97.net:443
04:19:21.747742 [0-0] == Info: ALPN: curl offers h2,http/1.1
04:19:21.748162 [0-0] => Send SSL data, 5 bytes (0x5)
0000: .....
04:19:21.748386 [0-0] == Info: TLSv1.3 (OUT), TLS handshake, Client hello (1):
04:19:21.748579 [0-0] => Send SSL data, 512 bytes (0x200)
0000: ............3......-^.....#.....>..... wvH.......~.w......^..PvF
.
.
(中略)
.
.
01c0: ................................................................
04:19:21.750354 [0-0] == Info: [SSL] ossl_bio_cf_out_write(len=517) -> 517, err=0
04:19:21.750573 [0-0] == Info: [SSL] ossl_bio_cf_in_read(len=5) -> -1, err=81
04:19:21.750782 [0-0] == Info: [SSL] populate_x509_store, path=/etc/pki/tls/certs/ca-bundle.crt, blob=0
04:19:21.759457 [0-0] == Info: CAfile: /etc/pki/tls/certs/ca-bundle.crt
04:19:21.759582 [0-0] == Info: CApath: none
04:19:21.759700 [0-0] == Info: [SSL] SSL_connect() -> err=-1, detail=2
04:19:21.759889 [0-0] == Info: [SSL] SSL_connect() -> want recv
04:19:21.760023 [0-0] == Info: [SSL] cf_connect() -> 0, done=0
04:19:21.760126 [0-0] == Info: [HTTPS-CONNECT] connect -> 0, done=0
04:19:21.760257 [0-0] == Info: [SSL] adjust_pollset, POLLIN fd=4
04:19:21.760413 [0-0] == Info: [HTTPS-CONNECT] adjust_pollset -> 1 socks
04:19:21.760589 [0-0] == Info: [HTTPS-CONNECT] connect, check h21
04:19:21.760765 [0-0] == Info: [SSL] cf_connect()
04:19:21.760904 [0-0] == Info: [SSL] ossl_bio_cf_in_read(len=5) -> 5, err=0
04:19:21.761095 [0-0] <= Recv SSL data, 5 bytes (0x5)
0000: ....z
04:19:21.761315 [0-0] == Info: [SSL] ossl_bio_cf_in_read(len=122) -> 122, err=0
04:19:21.761553 [0-0] == Info: TLSv1.3 (IN), TLS handshake, Server hello (2):
04:19:21.761775 [0-0] <= Recv SSL data, 122 bytes (0x7a)
0000: ...v...#..}Y@!.J[...L-:...jU %.J...... wvH.......~.w......^..PvF
0040: ....WD.......+.....3.$... Y..*g......\.....5.+..FVD.....c.
04:19:21.762589 [0-0] == Info: [SSL] ossl_bio_cf_in_read(len=5) -> 5, err=0
04:19:21.762795 [0-0] <= Recv SSL data, 5 bytes (0x5)
0000: .....
04:19:21.762962 [0-0] == Info: [SSL] ossl_bio_cf_in_read(len=1) -> 1, err=0
04:19:21.763171 [0-0] == Info: [SSL] ossl_bio_cf_in_read(len=5) -> 5, err=0
04:19:21.763382 [0-0] <= Recv SSL data, 5 bytes (0x5)
0000: ....$
04:19:21.763556 [0-0] == Info: [SSL] ossl_bio_cf_in_read(len=36) -> 36, err=0
04:19:21.763789 [0-0] <= Recv SSL data, 1 bytes (0x1)
0000: .
04:19:21.763971 [0-0] == Info: TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
04:19:21.764129 [0-0] <= Recv SSL data, 19 bytes (0x13)
0000: .................h2
04:19:21.764310 [0-0] == Info: [SSL] ossl_bio_cf_in_read(len=5) -> 5, err=0
04:19:21.764511 [0-0] <= Recv SSL data, 5 bytes (0x5)
0000: .....
04:19:21.764680 [0-0] == Info: [SSL] ossl_bio_cf_in_read(len=164) -> 164, err=0
04:19:21.764907 [0-0] <= Recv SSL data, 1 bytes (0x1)
0000: .
04:19:21.765077 [0-0] == Info: TLSv1.3 (IN), TLS handshake, Request CERT (13):
04:19:21.765272 [0-0] <= Recv SSL data, 147 bytes (0x93)
0000: ....................................../.j.h.f0d1.0...U....JP1.0.
0040: ..U....Tokyo1.0...U....Default City1.0...U....Default Company Lt
0080: d1.0...U....root-ca
04:19:21.765962 [0-0] == Info: [SSL] ossl_bio_cf_in_read(len=5) -> 5, err=0
04:19:21.766148 [0-0] <= Recv SSL data, 5 bytes (0x5)
0000: .....
04:19:21.766341 [0-0] == Info: [SSL] ossl_bio_cf_in_read(len=3820) -> 3820, err=0
04:19:21.766570 [0-0] <= Recv SSL data, 1 bytes (0x1)
0000: .
04:19:21.766662 [0-0] == Info: TLSv1.3 (IN), TLS handshake, Certificate (11):
04:19:21.766863 [0-0] <= Recv SSL data, 3803 bytes (0xedb)
0000: ...........0...0..............}.r.5.U.....0...*.H........0<1.0..
.
.
(以下略)
.
.
Recv SSL dataでroot-caとCA証明書の名前が表示されていますね。
Wiresharkでも確認したいので、手元の端末からTLS 1.2でアクセスしてみます。
TLS 1.2とした都合はTLS 1.3だと証明書のやり取りが暗号化されてしまい、私のWiresharkの設定だとCA証明書名がアドバタイズされてているか確認できないためです。なお、TLS 1.2をサポートする関係でセキュリティポリシーをTLSv1.2_2025に変更しています。
Wiresharkを確認すると、確かにCA証明書のDNを確認できました。

個人的には別に有効にしなくとも良いかなと思いました。
Connection Functionを用いた失効した証明書の対応
せっかくなのでConnection Functionを用いた失効した証明書の対応をしてみます。
まずCRLの役割を果たすKeyValueStoresを準備します。

適当に名前を入力して作成します。

数十秒ほどで作成完了しました。

KeyValueStoreにCNがTest 1のクライアント証明書のシリアル番号を登録します。

これでKeyValueStoreは準備完了です。
続いて、Connection Functionの準備です。

名前を入力して作成します。

はい、To create your first connection function, you must first contact AWS Support to request a limit increase.とエラーになりました。

どうやらConnection Functionを初めて作成する場合はAWSサポートにクォータ引き上げのリクエストを行う必要があるようです。
Service Quotasを確認すると、Maximum number of connection functions per AWS accountでクォータコードL-D0422299のものがありました。

デフォルトクォータは0となっていますね。
引き上げのリクエストをしましょう。

1をリクエストしましたが、裏側でサポートケースが上がるタイプでした。
30分ほどで引き上げが完了しました。AWSサポートのご担当者の方、いつもありがとうございます。

クォータ引き上げ完了後Connection Functionを作成しようとすると、正常に受け付けられました。

KeyValueStoreに該当するキーがあれば拒否するコードをデプロイします。
import cf from 'cloudfront';
async function connectionHandler(connection) {
const kvsHandle = cf.kvs();
// Get client certificate serial number
const clientSerialNumber = connection.clientCertificate.certificates.leaf.serialNumber;
// Check if the serial number exists in the KeyValueStore
const isRevoked = await kvsHandle.exists(clientSerialNumber);
if (isRevoked) {
console.log(`Certificate ${clientSerialNumber} is revoked. Denying connection.`);
connection.logCustomData(`REVOKED:${clientSerialNumber}`);
connection.deny();
} else {
connection.logCustomData(`Valid certificate: ${clientSerialNumber}`);
console.log(`Certificate ${clientSerialNumber} is valid. Allowing connection.`);
connection.allow();
}
}
KeyValueStoreのアタッチ完了後にテストをすると、意図したとおり拒否してくれました。

Liveステージにパブリッシュします。

この状態でKeyValueStoreに登録されている証明書を指定してアクセスします。
$ curl https://www.non-97.net \
--key client_key_test1.pem \
--cert client_cert_test1.pem \
-v
* Host www.non-97.net:443 was resolved.
* IPv6: (none)
* IPv4: 108.138.85.24, 108.138.85.20, 108.138.85.44, 108.138.85.82
* Trying 108.138.85.24:443...
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* CAfile: /etc/pki/tls/certs/ca-bundle.crt
* CApath: none
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (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, Request CERT (13):
* 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 handshake, Certificate (11):
* TLSv1.3 (OUT), TLS handshake, CERT verify (15):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256 / secp256r1 / RSASSA-PSS
* ALPN: server accepted h2
* Server certificate:
* subject: CN=www.non-97.net
* start date: Feb 24 00:00:00 2025 GMT
* expire date: Mar 25 23:59:59 2026 GMT
* subjectAltName: host "www.non-97.net" matched cert's "www.non-97.net"
* issuer: C=US; O=Amazon; CN=Amazon RSA 2048 M03
* SSL certificate verify ok.
* Certificate level 0: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
* Certificate level 1: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
* Certificate level 2: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
* Connected to www.non-97.net (108.138.85.24) port 443
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://www.non-97.net/
* [HTTP/2] [1] [:method: GET]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: www.non-97.net]
* [HTTP/2] [1] [:path: /]
* [HTTP/2] [1] [user-agent: curl/8.11.1]
* [HTTP/2] [1] [accept: */*]
> GET / HTTP/2
> Host: www.non-97.net
> User-Agent: curl/8.11.1
> Accept: */*
>
* Request completely sent off
* TLSv1.3 (IN), TLS alert, close notify (256):
* closing connection #0
curl: (16) Error in the HTTP2 framing layer
はい、失敗しました。
次に別のクライアント証明書を指定してアクセスする場合です。
$ openssl req \
-x509 \
-CA rootCA_cert.pem \
-CAkey rootCA_key.pem \
-days 7 \
-new \
-nodes \
-keyout client_key_test2.pem \
-out client_cert_test2.pem
.....+..........+
.
.
(中略)
.
.
....+...++++++
-----
Enter pass phrase for rootCA_key.pem:
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [XX]:JP
State or Province Name (full name) []:Tokyo
Locality Name (eg, city) [Default City]:
Organization Name (eg, company) [Default Company Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (eg, your name or your server\'s hostname) []:Test 2
Email Address []:
$ curl https://www.non-97.net \
--key client_key_test2.pem \
--cert client_cert_test2.pem \
-v
* Host www.non-97.net:443 was resolved.
* IPv6: (none)
* IPv4: 108.138.85.20, 108.138.85.24, 108.138.85.82, 108.138.85.44
* Trying 108.138.85.20:443...
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* CAfile: /etc/pki/tls/certs/ca-bundle.crt
* CApath: none
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (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, Request CERT (13):
* 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 handshake, Certificate (11):
* TLSv1.3 (OUT), TLS handshake, CERT verify (15):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256 / secp256r1 / RSASSA-PSS
* ALPN: server accepted h2
* Server certificate:
* subject: CN=www.non-97.net
* start date: Feb 24 00:00:00 2025 GMT
* expire date: Mar 25 23:59:59 2026 GMT
* subjectAltName: host "www.non-97.net" matched cert's "www.non-97.net"
* issuer: C=US; O=Amazon; CN=Amazon RSA 2048 M03
* SSL certificate verify ok.
* Certificate level 0: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
* Certificate level 1: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
* Certificate level 2: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
* Connected to www.non-97.net (108.138.85.20) port 443
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://www.non-97.net/
* [HTTP/2] [1] [:method: GET]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: www.non-97.net]
* [HTTP/2] [1] [:path: /]
* [HTTP/2] [1] [user-agent: curl/8.11.1]
* [HTTP/2] [1] [accept: */*]
> GET / HTTP/2
> Host: www.non-97.net
> User-Agent: curl/8.11.1
> Accept: */*
>
* Request completely sent off
< HTTP/2 200
< content-type: text/html
< content-length: 12
< date: Fri, 28 Nov 2025 04:45:38 GMT
< last-modified: Tue, 25 Feb 2025 02:38:39 GMT
< etag: "56aec8b7843df637b3fb2ec0b027e5b6"
< x-amz-server-side-encryption: AES256
< accept-ranges: bytes
< server: AmazonS3
< x-cache: Hit from cloudfront
< via: 1.1 4685cae701bd588fa0176a1c8b1e52f4.cloudfront.net (CloudFront)
< x-amz-cf-pop: IAD12-P2
< x-amz-cf-id: 9vsFTFRwsg-vdtNIcxV-BTDtq7wNdjdNz-YgYrqsAQlMOg5lQjf10g==
< age: 19269
< x-xss-protection: 1; mode=block
< x-frame-options: SAMEORIGIN
< referrer-policy: strict-origin-when-cross-origin
< x-content-type-options: nosniff
< strict-transport-security: max-age=31536000
<
/index.html
* Connection #0 to host www.non-97.net left intact
はい、こちらは正常にアクセスできました。
この時のコネクションログは以下のとおりです。
{
"eventTimestamp": 1764324261055,
"connectionId": "dHAOAFd3fARRQ3dvj1ldEAkGSZPaDw8GLOud1LB17xYY_Wahx076cg==",
"distributionId": "ELGQOVCCUO3ME",
"connectionStatus": "Failed:ConnectionFunctionDenied",
"clientIp": "44.220.54.32",
"clientPort": "43670",
"serverIp": "108.138.85.24",
"distributionTenantId": "-",
"tlsProtocol": "TLSv1.3",
"tlsCipher": "TLS_AES_128_GCM_SHA256",
"tlsHandshakeDuration": 127,
"tlsSni": "www.non-97.net",
"clientLeafCertSerialNumber": "01:e5:f0:60:df:9a:92:dd:40:c7:31:61:2e:72:7b:ae:0e:20:00:b9",
"clientLeafCertSubject": "C=JP,%20ST=Tokyo,%20L=Default%20City,%20O=Default%20Company%20Ltd,%20OU=Test%201%20BU,%20CN=Test%201",
"clientLeafCertIssuer": "C=JP,%20ST=Tokyo,%20L=Default%20City,%20O=Default%20Company%20Ltd,%20CN=root-ca",
"clientLeafCertValidity": "NotBefore=2025-11-27T10:18:17Z;NotAfter=2025-12-04T10:18:17Z",
"connectionLogCustomData": "REVOKED:01:e5:f0:60:df:9a:92:dd:40:c7:31:61:2e:72:7b:ae:0e:20:00:b9"
}
{
"eventTimestamp": 1764324406448,
"connectionId": "K85pU88FYUohutYb1vpTJdxdoW8LMTik5lqCmmyBllkVXHevWVR_kA==",
"distributionId": "ELGQOVCCUO3ME",
"connectionStatus": "Success",
"clientIp": "44.220.54.32",
"clientPort": "37594",
"serverIp": "108.138.85.20",
"distributionTenantId": "-",
"tlsProtocol": "TLSv1.3",
"tlsCipher": "TLS_AES_128_GCM_SHA256",
"tlsHandshakeDuration": 64,
"tlsSni": "www.non-97.net",
"clientLeafCertSerialNumber": "72:ef:fd:8b:79:26:e7:11:d1:36:f2:80:f0:61:75:82:90:34:d7:d5",
"clientLeafCertSubject": "C=JP,%20ST=Tokyo,%20L=Default%20City,%20O=Default%20Company%20Ltd,%20CN=Test%202",
"clientLeafCertIssuer": "C=JP,%20ST=Tokyo,%20L=Default%20City,%20O=Default%20Company%20Ltd,%20CN=root-ca",
"clientLeafCertValidity": "NotBefore=2025-11-28T09:52:08Z;NotAfter=2025-12-05T09:52:08Z",
"connectionLogCustomData": "Valid%20certificate:%2072:ef:fd:8b:79:26:e7:11:d1:36:f2:80:f0:61:75:82:90:34:d7:d5"
}
connectionLogCustomDataにしっかり記録されていますね。
CloudFrontで配信する環境においてクライアント証明書による認証をしたい場合に
Amazon CloudFrontがmTLSをサポートしたアップデートを紹介しました。
エッジ側でクライアント証明書による認証をさせたい場合は積極的に使っていきましょう。
この記事が誰かの助けになれば幸いです。
以上、クラウド事業本部 コンサルティング部の のんピ(@non____97)でした!






