3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AWSとその不確かな壁(2) ~ALBとmTLS認証(クライアント証明書トラストストア)編~

3
Last updated at Posted at 2025-12-01

本記事は、BIPROGY / ユニアデックス社内AWSコミュニティ「BIPROGY AWS SPARK」の
定期投稿企画第12回目の記事です。他の定期投稿企画の記事は、
#BIPROGY_AWS_SPARKタグ または Organizationページ をご覧ください

1. はじめに

前回、ALBのmTLS認証を取り上げました。第2弾は別の城門についてを考えていたのですが、ALBのmTLS認証の中でもパススルーだけではなく、トラストストアでの認証についても知りたいというリクエストがありましたので、この回では、mTLS認証の続きとして記載したいと思います。
「トラストストア」とは、認証局が発行した信頼できる証明書を保存・管理する場所のことで、AWSのALBではどのような仕組みでこれを実現しているかについても確認していきます。

1.1 通行許可

前回の記事では、ALBは通行者に通行許可証(クライアント証明書)を見せるように指示しますが、その許可証が正しいものかどうかを検証はせず、バックエンドのアプリケーションにその対応を委ねていました。
今回は、その通行許可証が正式に発行されたものであるかを「ALBが検証」します。
ALBがどのように検証するかについて、見ていきましょう。

2. 環境構築

構成は、ほぼ同じですが、ALBが検証を行うために、プライベート認証局(CA)の証明書が必要です。それがないと、通行許可証の署名が本当に正式なものであるかを照合する手段がありません。
そのため、そのCAの証明書をS3に配置し、ALBから参照できるようにします。
そのS3バケットに配置された情報から、証明書の検証のために、ALBがトラストストアとして利用します。

アーキテクチャ図

2.1 プライベート認証局(CA)の自己署名の証明書やクライアント証明書の作成

今回も検証で使用する証明書を作成します。前回の記事にCAの証明書やクライアント証明書(client1用)などの作成方法を載せているので、詳細は、こちらを参照して作成します。

記事中の画面キャプチャやプログラム出力で表示される固有値については加工しています。

2.2 CAの自己署名の証明書をS3に配置

準備として、S3バケットを作成し、そのバケットの任意の場所にCAの証明書(pem形式)を配置します。

S3.png

2.3 ALBからトラストストアを参照

S3にはトラストストアで利用するデータ(証明書、失効リスト)を配置しますが、それだけではALB側が認識できないため、ALBから参照できるように設定する必要があります。
AWS Application Load Balancer(ALB) を作成しますが、前回と異なる部分が、「クライアント証明書の処理」の相互認証 (mTLS)の設定値です。
ここをパススルーではなく、トラストストアを選択します。

  • 基本的な設定
    • ロードバランサー名:uncertwall-dev-alb01
    • スキーム:インターネット向け
  • ネットワークマッピング
    • VPC
    • アベイラビリティーゾーンとサブネット
  • セキュリティグループ:(設定する)
  • リスナーとルーティング
    • プロトコル:HTTPS
    • アクションのルーティング:ターゲットグループへ転送
      *ターゲットグループ:上記で構築したEC2をターゲットしたターゲットグループ
  • セキュアリスナーの設定
    • デフォルト SSL/TLS サーバー証明書
      • 証明書の取得先:ACMから
      • 証明書 (ACM から):ACMでインポートした証明書
  • クライアント証明書の処理
    • 相互認証 (mTLS):トラストストアで検証

ALB-TrustStore02.png

2.4 AWS EC2にアプリケーションサーバーを設定

クライアント証明書が問題ないときに接続されるサーバーとして、以下のEC2をセットアップします。

  • Amazon Linux2023 プライベートサブネットに配置
  • Apache+PHPを導入
    * CA証明書ファイルは、EC2には配置しません

3. 検証

それでは、構築した環境を使って、検証していきましょう。

3.1 ALBによるクライアント証明書の検証

まずはCAによって署名されたクライアント証明書を使って接続してみます。この場合は、何の問題もなく、ALBは通過し、Webサーバーへ接続できることが確認できます。

web01.png

別のCAによって署名されたクライアント証明書を使って接続した場合は、Webサーバーには到達せず、以下のエラーとなります。

ERR.png

3.2 アプリケーションに渡されるクライアントの情報を確認

前回の記事では、パススルーの場合は、クライアント証明書のチェーンの全情報は、HTTPヘッダー:X-Amzn-Mtls-Clientcertに格納してサーバーアプリケーションに渡されるということを記載しました。
今回の検証モードでは、以下のヘッダー情報が渡されます。

ヘッダー名 内容 備考
X-Amzn-Mtls-Clientcert-Serial-Number リーフ証明書の16進数表記、クライアント証明書のシリアル番号 例: 0ABC1234
X-Amzn-Mtls-Clientcert-Issuer 発行者の識別名(DN) プライベート認証局(CA)作成時の情報
X-Amzn-Mtls-Clientcert-Subject クライアントのサブジェクト 国名(C)、組織名(O)、クライアントのコモンネーム(CN)などの要素で構成される識別名
X-Amzn-Mtls-Clientcert-Validity notBefore と notAfter 日付の ISO8601 形式 例: NotBefore=2023-09-21T01:50:17Z; NotAfter=2024-09-20T01:50:17Z
X-Amzn-Mtls-Clientcert-Leaf URLエンコーディングされたPEM形式のリーフ証明書 証明書チェーンの末端に位置する証明書

こちらのヘッダーの情報をPHPプログラムで表示させます。

vars.php
<?php

$mtls_clientcert_leaf_key = 'HTTP_X_AMZN_MTLS_CLIENTCERT_LEAF';
$mtls_clientcert_leaf_value = null;

foreach ($_SERVER as $key => $value) {
    if (str_starts_with($key, 'HTTP_X_AMZN')) {
        if ($key === $mtls_clientcert_leaf_key) {
            $mtls_clientcert_leaf_value = $value;
        } else {
            echo htmlspecialchars($key) . ': ' . htmlspecialchars($value) . "<br>\n";
        }
   }
}

if ($mtls_clientcert_leaf_value !== null) {
    $leaf = rawurldecode($mtls_clientcert_leaf_value);

    echo  $mtls_clientcert_leaf_key . ': ';
    echo  mb_substr($leaf, 0, 70, 'UTF-8') . "..." . mb_substr($leaf, -70, null, 'UTF-8') . "<br>";

    $cert = openssl_x509_parse($leaf);
    echo "<pre>";
    echo var_dump($cert);
    echo "</pre>";}
?>
HTTP_X_AMZN_MTLS_CLIENTCERT_VALIDITY: NotBefore=2025-10-23T05:50:23Z;NotAfter=2026-10-23T05:50:23Z
HTTP_X_AMZN_MTLS_CLIENTCERT_SUBJECT: CN=bipual-client1,O=AWSTeam,ST=Osaka,C=JP
HTTP_X_AMZN_MTLS_CLIENTCERT_ISSUER: CN=CA Private.Local,OU=AWS Team,O=BIPUAL Ltd.,L=Kita Ku,ST=Osaka,C=JP
HTTP_X_AMZN_MTLS_CLIENTCERT_SERIAL_NUMBER: 0123456789ABCDEF0123456789ABCDEF01234567
HTTP_X_AMZN_MTLS_CLIENTCERT_LEAF: -----BEGIN CERTIFICATE----- MIIDiTCCAnGgAwIBAgIUZHO0XafZ5qGOfUxY8+dCDX...piKIbEp7Iq/naImMwQvozdYGj4Qn6eQzCtYoSxV44UJ -----END CERTIFICATE-----

array(16) {
  ["name"]=>
  string(42) "/C=JP/ST=Osaka/O=AWSTeam/CN=bipual-client1"
  ["subject"]=>
  array(4) {
    ["C"]=>
    string(2) "JP"
    ["ST"]=>
    string(5) "Osaka"
    ["O"]=>
    string(7) "AWSTeam"
    ["CN"]=>
    string(14) "bipual-client1"
  }
  ["hash"]=>
  string(8) "e2ccf8f2"
  ["issuer"]=>
  array(5) {
    ["C"]=>
    string(2) "JP"
    ["ST"]=>
    string(5) "Kyoto"
    ["L"]=>
    string(5) "Kyoto"
    ["O"]=>
    string(16) "Private UAL Ltd."
    ["CN"]=>
    string(16) "CA Private.Local"
  }
  ["version"]=>
  int(2)
  ["serialNumber"]=>
  string(42) "0x0123456789ABCDEF0123456789ABCDEF01234567"
  ["serialNumberHex"]=>
  string(40) "0123456789ABCDEF0123456789ABCDEF01234567"
  ["validFrom"]=>
  string(13) "251028120832Z"
  ["validTo"]=>
  string(13) "261028120832Z"
  ["validFrom_time_t"]=>
  int(1761653312)
  ["validTo_time_t"]=>
  int(1793189312)
  ["signatureTypeSN"]=>
  string(10) "RSA-SHA256"
  ["signatureTypeLN"]=>
  string(23) "sha256WithRSAEncryption"
  ["signatureTypeNID"]=>
  int(668)
  ["purposes"]=>
  array(10) {
    [1]=>
    array(3) {
      [0]=>
      bool(true)
      [1]=>
      bool(false)
      [2]=>
      string(9) "sslclient"
    }
    [2]=>
    array(3) {
      [0]=>
      bool(true)
      [1]=>
      bool(false)
      [2]=>
      string(9) "sslserver"
    }
    [3]=>
    array(3) {
      [0]=>
      bool(true)
      [1]=>
      bool(false)
      [2]=>
      string(11) "nssslserver"
    }
    [4]=>
    array(3) {
      [0]=>
      bool(true)
      [1]=>
      bool(false)
      [2]=>
      string(9) "smimesign"
    }
    [5]=>
    array(3) {
      [0]=>
      bool(true)
      [1]=>
      bool(false)
      [2]=>
      string(12) "smimeencrypt"
    }
    [6]=>
    array(3) {
      [0]=>
      bool(true)
      [1]=>
      bool(false)
      [2]=>
      string(7) "crlsign"
    }
    [7]=>
    array(3) {
      [0]=>
      bool(true)
      [1]=>
      bool(true)
      [2]=>
      string(3) "any"
    }
    [8]=>
    array(3) {
      [0]=>
      bool(true)
      [1]=>
      bool(false)
      [2]=>
      string(10) "ocsphelper"
    }
    [9]=>
    array(3) {
      [0]=>
      bool(false)
      [1]=>
      bool(false)
      [2]=>
      string(13) "timestampsign"
    }
    [10]=>
    array(3) {
      [0]=>
      bool(false)
      [1]=>
      bool(false)
      [2]=>
      string(8) "codesign"
    }
  }
  ["extensions"]=>
  array(2) {
    ["subjectKeyIdentifier"]=>
    string(59) "A0:B1:C2:D3:E4:F5:00:00:00:00:00:00:00:00:00:00:00:00:00:00"
    ["authorityKeyIdentifier"]=>
    string(59) "A0:B1:C2:D3:E4:F5:00:00:00:00:00:00:00:00:00:00:00:00:00:00"
  }
}

4. クライアント証明書の失効について

もし、通行許可証(クライアント証明書)を失くしてしまったり、そのクライアント証明書が悪意のある者に渡ってしまったりしたら、どのように対応したらよいでしょうか。

この場合は、その証明書が不正に利用されてしまう可能性があるため、それを失効させます。そのためには、失効させたいという情報を、証明書を検証するALBに伝える必要があります。
以下の手順で伝えます。

  1. 失効させたいクライアント証明書の情報が記載された証明書失効リスト(CRL)を所定のフォーマットで作成します
  2. それをトラストストアに格納することにより、クライアント証明書の検証時に参照されるように設定します

もし、接続しようとしているユーザーの提示した証明書がその失効リストに記載されていれば、ALBは接続を遮断します。

4.1 失効リストを作成

こちらもopensslを使って作成できます。それでは、2.1で発行したclient1向けのクライアント証明書を失効させるためのリストを作成してみます。

4.1.1 プライベート認証局(CA)の設定ファイルの作成

2.1で作成したCAの秘密鍵と証明書を作成したディレクトリに移動して、以下を実行します。

PS> cat > ca.cnf
[ ca ]
default_ca = CA_default
[ CA_default ]
database = ./db.txt
private_key = ./ca-key.pem
certificate = ./ca-cert.pem
default_crl_days = 30
crl_extensions = crl_ext
default_md = sha256
[ crl_ext ]
authorityKeyIdentifier = keyid:always
(CTRL-D)

4.1.2 失効させるクライアント証明書のデータベースに登録

PS> openssl ca -config .\ca.cnf -crl_reason superseded -revoke .\client.pfx
Using configuration from .\ca.cnf
Enter pass phrase for ./ca-key.pem:

Adding Entry with serial number 0123456789ABCDEF0123456789ABCDEF01234567 to DB for /C=JP/ST=Osaka/O=AWSTeam/CN=bipual-client1
Revoking Certificate 0123456789ABCDEF0123456789ABCDEF01234567.
Database updated

PS> cat db.txt
R	261023055023Z	251120073840Z,superseded	0123456789ABCDEF0123456789ABCDEF01234567	unknown	/C=JP/ST=Osaka/O=AWSTeam/CN=bipual-client1

4.1.3 証明書失効リスト(CRL)を作成

PS> openssl ca -gencrl -config ca.cnf -out crl.pem		
Using configuration from ca.cnf		
Enter pass phrase for ./ca-key.pem:	

作成したCRLを確認します。

PS> openssl crl -in crl.pem -noout -text		
Certificate Revocation List (CRL):		
    Version 2 (0x1)		
    Signature Algorithm: sha256WithRSAEncryption		
    Issuer: C=JP, ST=Osaka, L=Kita Ku, O=BIPUAL Ltd., OU=AWS Team, CN=bipual-dev.net		
    Last Update: Nov 20 07:41:50 2025 GMT		
    Next Update: Dec 20 07:41:50 2025 GMT		
    CRL extensions:		
        X509v3 Authority Key Identifier:		
            A0:B1:C2:D3:E4:F5:00:00:00:00:00:00:00:00:00:00:00:00:00:00
Revoked Certificates:		
    Serial Number: 0123456789ABCDEF0123456789ABCDEF01234567		
        Revocation Date: Nov 20 07:38:40 2025 GMT		
        CRL entry extensions:		
            X509v3 CRL Reason Code:		
                Superseded		
    Signature Algorithm: sha256WithRSAEncryption		
    Signature Value:		
        00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:		
        00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:		

(中略)
		
        00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:		
        00:11:22:33		

4.2 失効リストをトラストストアへ配置

作成した失効リストを、S3バケット経由で、トラストストアへ登録します。

CRL.png

CRL03.png

この状態で、失効したクライアント証明書を使ってALBに接続すると、以下の画面となり接続できません。

ERR.png

同じCAで別のクライアント証明書(client2用)を作成して、接続してみます。
証明書の選択では、client2用の証明書を選択します。

ClientCert.png

こちらの証明書は失効していないので、問題なく接続できました。

web01.png

アプリケーションに渡されるHTTPヘッダーも表示できました。

web02.png

5. おわりに

こちらのALBのトラストストアの仕組みを使えば、通行許可証(クライアント証明書)の有効性の検証を、アプリケーションではなくALB側にオフロードできます。この仕組みで、運用が可能であるか、それともアプリケーション側できめ細かくプログラム制御したいかによって、トラストストアで検証させるか、パススルーにするかの使い分けができるということが分かりました。

このように通行証(クライアント証明書)の署名が正しいかを確認するために、署名を照合させるための仕組みを整えてあげれば、門番(ALB)側でチェックができます。

ALBは、アプリケーションロードバランサーという負荷分散の役割を持っているサービスですが、お城の城門や領主が治める領地にある関所で交通整理(ロードバランス)をしながら、通行証のチェック(クライアント証明書検証)をしたり、どの道が通れるかを把握したり(ヘルスチェック)、文書の暗号の解読(SSL/TLSの終端)したり、その文書に不正がないかをチェックする役人(WAF)に依頼したり、など入口のところで大活躍していることが分かりました。

AWSというクラウドの中で不確かに見える部分も、このようにQiitaで記事を書いたり、現実の世界にも照らし合わせると、いろいろと見えてくるような気がします。

6. 参考

We Are Hiring!

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?