これは何?

AWS Private Certificate Authority のプライベート認証局で発行したプライベート証明書を ALB に設定してクライアント認証を実現する方法を記した記事です!

構成図

理解しやすくなると思うので最終的な状態の構成図を先に見ていただきます!

前提

  • EC2 インスタンスはすでに存在しており、Apache での Web サーバーとして機能している
  • ALB がすでに存在している
    • HTTPS:443 のリスナーが設定済み
    • プロトコル HTTP:80 のターゲットグループが設定されており、そのターゲットグループには EC2 インスタンスが登録されている
  • 上記により https://****.****.jp という URL に対するリクエストを処理する環境ができている

用語説明

AWS Private Certificate Authority

AWS 上でプライベート認証局を作成するサービスです。

※ 汎用モードだと 月額 400 USD なので検証でも注意が必要です。。。(2024/10/01 現在の情報となります)

https://aws.amazon.com/jp/private-ca/pricing/

汎用モードの場合、プライベート CA あたり月額 400 USD

トラストストア

プライベート認証局の CA 証明書を格納するものです。
クライアント証明書での認証を ALB で実現するには CA 証明書を ALB のリスナーに設定する必要がありますが、そのためには CA 証明書を設定した「トラストストア」というものに ALB を割り当てる必要があります。

手順

AWS Private Certificate Authority でプライベート認証局作成

AWS マネージドコンソールで以下のように画面遷移します。

[AWS Private Certificate Authority] → [プライベート認証機関] → [プライベート CA を作成]

今回は検証目的なので以下のように設定して作成します。

自分で入力したのは「国名 (C)」と「共通名 (CN)」のみです。

プライベート認証局で CA 証明書をインストール

AWS マネージドコンソールで以下のように画面遷移します。

[AWS Private Certificate Authority] → [プライベート認証機関] → [※先ほど作成した プライベート CA] → [CA 証明書をインストール]

「有効性」に関してはデフォルトで 10 年後が入力されています。これにより有効期限が 10 年後になります。今回はこのままにします。
署名アルゴリズムもデフォルトのままにします。

「確認してインストール」を押下します。

CA 証明書を取得

AWS マネージドコンソールで以下のように画面遷移します。

[AWS Private Certificate Authority] → [プライベート認証機関] → [※先ほど作成した プライベート CA] → [CA 証明書の取得]

以下のような画面になるので [証明書本文をファイルにエクスポート] を押下します。

すると自身の端末に CA 証明書がダウンロードされます。

CA 証明書を S3 バケットに保存

S3 バケットを作成してダウンロードした CA 証明書を配置します。(※ 詳しい手順は割愛します)

トラストストア作成

AWS マネージドコンソールで以下のように画面遷移します。

[EC2] → [トラストストア] → [トラストストアを作成]

以下のようにしてトラストストアを作成します。
入力したのは「トラストストアの名前」と「S3 URI」です。

「トラストストアの名前」は任意の値です。
「S3 URI」は S3 バケットに保存した CA 証明書の S3 URI です。

ALB のリスナーにトラストストアを関連付け

AWS マネージドコンソールで以下のように画面遷移します。

[EC2] → [ロードバランサー] → [※本件で利用する ALB] → [リスナーとルール] → [HTTPS:443] → [セキュリティ] → [セキュアリスナーの設定を編集]

以下のように設定します。

端末で秘密鍵を作成する

※ 以降は端末側での作業となります。

openssl コマンドが入っている端末にて以下のコマンドで秘密鍵を作成します。

openssl genrsa -out hirata-test-ca-20240926-01.key 2048

※今回は秘密鍵の名前を hirata-test-ca-20240926-01.key としています。

端末で CSR を作成する

端末にて以下のコマンドで CSR を作成します。(上記で作成した秘密鍵をオプションで指定します)

openssl req -new \
    -key hirata-test-ca-20240926-01.key \
    -out hirata-test-ca-20240926-01.csr

※今回は CSR の名前を hirata-test-ca-20240926-01.csr としています。

ディスティングイッシュネーム情報の入力を求められます。
今回は検証目的なので Country Name のみ JP と入力して、他は空エンターします。

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) []:
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) []:
Email Address []:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:

作成した CSR を使ってプライベート認証局でプライベート証明書を発行をする

証明書発行には AWS CLI を利用します。

以下のコマンドを実行します。

aws acm-pca issue-certificate \
    --certificate-authority-arn [プライベート認証局の ARN] \
    --csr fileb://hirata-test-ca-20240926-01.csr \
    --signing-algorithm "SHA256WITHRSA" \
    --validity Value=365,Type="DAYS"

成功すればプライベート証明書の ARN が返されます。

{
    "CertificateArn": "[発行されたプライベート証明書の ARN]"
}

発行されたプライベート証明書を取得する

以下のコマンドでプライベート証明書を取得します。

aws acm-pca get-certificate \
    --certificate-authority-arn [プライベート認証局の ARN] \
    --certificate-arn [発行されたプライベート証明書の ARN] | \
    jq -r .'Certificate' > hirata-test-ca-client-certificate-20240926-01.pem

成功すればカレントディレクトリに hirata-test-ca-client-certificate-20240926-01.pem というファイルが作成されます。これがクライアント用のプライベート証明書です。

※今回は クライアント用のプライベート証明書 の名前を hirata-test-ca-client-certificate-20240926-01.pem としています。

動作確認

動作確認は curl で行います。

クライアント証明書と秘密鍵を指定せずに動作確認 (エラーになる想定)

先にエラーになる想定の操作を確認します。

以下のコマンドでクライアント証明書と秘密鍵を指定せずに curl で本件の環境の URL https://****.****.jp にリクエストします。

curl https://****.****.jp -v

結果は以下です。

curl https://****.****.jp -v
*   Trying ***.***.***.***:443...
* Connected to ****.****.jp (***.***.***.***) port 443 (#0)
* ALPN: offers h2,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
* ALPN: server accepted h2
* Server certificate:
*  subject: CN=****.****.jp
*  start date: Sep 24 06:44:34 2024 GMT
*  expire date: Oct 24 07:44:34 2025 GMT
*  subjectAltName: host "****.****.jp" matched cert's "****.****.jp"
*  issuer: C=JP; CN=hirata-test-ca
*  SSL certificate verify ok.
* using HTTP/2
* h2 [:method: GET]
* h2 [:scheme: https]
* h2 [:authority: ****.****.jp]
* h2 [:path: /]
* h2 [user-agent: curl/8.1.2]
* h2 [accept: */*]
* Using Stream ID: 1 (easy handle 0x11f80c600)
> GET / HTTP/2
> Host: ****.****.jp
> User-Agent: curl/8.1.2
> Accept: */*
>
* Recv failure: Connection reset by peer
* LibreSSL SSL_read: LibreSSL/3.3.6: error:02FFF036:system library:func(4095):Connection reset by peer, errno 54
* Failed receiving HTTP2 data: 56(Failure when receiving data from the peer)
* Connection #0 to host ****.****.jp left intact
curl: (56) Recv failure: Connection reset by peer

クライアント認証ができずにエラーになりました。
想定した結果が確認できました。

クライアント証明書と秘密鍵を指定して動作確認 (正常に処理される想定)

最後に正常に処理される想定の操作を確認します。

以下のコマンドでクライアント証明書と秘密鍵を指定して curl で本件の環境の URL https://****.****.jp にリクエストします。

curl --key ./hirata-test-ca-20240926-01.key --cert ./hirata-test-ca-client-certificate-20240926-01.pem https://****.****.jp -v

結果は以下です。

curl --key ./hirata-test-ca-20240926-01.key --cert ./hirata-test-ca-client-certificate-20240926-01.pem https://****.****.jp -v
*   Trying ***.***.***.***:443...
* Connected to ****.****.jp (***.***.***.***) port 443 (#0)
* ALPN: offers h2,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, CERT verify (15):
* (304) (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / AEAD-AES128-GCM-SHA256
* ALPN: server accepted h2
* Server certificate:
*  subject: CN=****.****.jp
*  start date: Sep 24 06:44:34 2024 GMT
*  expire date: Oct 24 07:44:34 2025 GMT
*  subjectAltName: host "****.****.jp" matched cert's "****.****.jp"
*  issuer: C=JP; CN=hirata-test-ca
*  SSL certificate verify ok.
* using HTTP/2
* h2 [:method: GET]
* h2 [:scheme: https]
* h2 [:authority: ****.****.jp]
* h2 [:path: /]
* h2 [user-agent: curl/8.1.2]
* h2 [accept: */*]
* Using Stream ID: 1 (easy handle 0x127013400)
> GET / HTTP/2
> Host: ****.****.jp
> User-Agent: curl/8.1.2
> Accept: */*
>
< HTTP/2 200
< date: Tue, 01 Oct 2024 05:36:54 GMT
< content-type: text/html; charset=UTF-8
< content-length: 21
< server: Apache/2.4.61 ()
< last-modified: Wed, 31 Jul 2024 10:09:58 GMT
< etag: "15-61e884be6a03a"
< accept-ranges: bytes
<
hirata test 20240731
* Connection #0 to host ****.****.jp left intact

クライアント認証が通ってリクエストが正常に処理されたことが分かりました。
想定した結果が確認できました。


以上となります!