AWS ALB の相互認証 (mTLS) を使ってアクセスを制限する

# はじめに

あるお客様から、「セキュリティを意識したいので、契約しているエンドクライアントに対してのみ公開するWEBサービスを用意したい。」とご要望をいただきました。
インフラはAWSで構成しているので、
- シンプルな対応としては セキュリティグループ でIP制限する
- ヘビーな対応するなら ClientVPN でアクセスを制限する
- etc
色々と方法があります。

そんな中お客様から追加の言葉で 「相互認証でアクセスを制限したい」 といただきました。
調べたところ割と簡単にアクセスを制限できる仕組みとわかったので、皆さんに共有します。

# AWSの環境構成と全体の流れ

相互認証については以下に詳しく表記されています
https://aws.amazon.com/jp/blogs/news/mutual-authentication-for-application-load-balancer-to-reliably-verify-certificate-based-client-identities/
AWS の ALB を使って行う相互認証とは、ALB へアクセスしてくるクライアントに対して証明書の提示を要求し、その証明書を検証して信頼できる相手と認めた場合のみ通信を許可する仕組みです。(トラストストア検証)

証明書の用意やらどう設置するのか、を図にするとこんな感じ。
![mtls-導入の流れ.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1353602/964b2bda-4358-4eb3-979c-6d3eed55891b.png)

構成図はこんなイメージになるかと思います。
![mtls-構成図.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1353602/4fd89f60-a63f-4868-8b79-d45313bec771.png)

では、順を追って実践してみます。

# 1. RootCAの準備と設定

RootCA秘密鍵/証明書は、AWSのサービスによって発行することができます。

AWS Private Certificate Authority
https://aws.amazon.com/jp/private-ca/pricing/
記事をみてもらえば分かる通り、月額で費用がかかる(それなりに高い)ので、今回はEC2でopensslコマンドを使って発行してみます。

使っているopensslのバージョンを確認 (私が今回確認した環境のopensslバージョン)
```
[ec2-user@dev work]$ openssl version
OpenSSL 3.0.8 7 Feb 2023 (Library: OpenSSL 3.0.8 7 Feb 2023)
```

RootCA秘密鍵/証明書の発行
```
[ec2-user@dev work]$ openssl req -x509 -new -nodes -days 3650 -keyout root_ca.key -out root_ca.crt
.+++++++
-----
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) []:Miyagi
Locality Name (eg, city) [Default City]:Sendai
Organization Name (eg, company) [Default Company Ltd]:Test Company
Organizational Unit Name (eg, section) []:dev
Common Name (eg, your name or your server's hostname) []:RootCA Test1
Email Address []:
```

こんな感じ。これで

- RootCA秘密鍵 : root_ca.key
- RootCA証明書 : root_ca.crt

が作れました。

この証明書を使ってサーバ側からもクライアント検証を行うことになりますが、証明書の配置方法は
s3バケットにRootCA証明書を配置し、トラストストアを作成してこの証明書のURIを指定するというもの。

ということでs3バケットを作成します。

- バケットタイプ : 汎用
- バケット名 : 今回は trust-store-01 としました

バケットを作成したら RootCA証明書 をアップロード。

![cap_s3_upload.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1353602/3a99c765-89fc-4d06-a4c5-dc6356fcbc5c.png)

とりあえずここまで。

# 2. Client秘密鍵/証明書/p12ファイルの用意

こちらもRootCA秘密鍵/証明書と同様に、EC2でopensslコマンドを使って発行してみます。

```
[ec2-user@dev work]$ openssl req -x509 -new -nodes -days 3650 -CAkey root_ca.key -CA root_ca.crt -keyout client.key -out client.crt
.+......
-----
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) []:Miyagi
Locality Name (eg, city) [Default City]:Sendai
Organization Name (eg, company) [Default Company Ltd]:Test Company
Organizational Unit Name (eg, section) []:dev
Common Name (eg, your name or your server's hostname) []:Client Cert Test1
Email Address []:
```

これで

- Client秘密鍵 : client.key
- Client証明書 : client.crt

が作れました。
秘密鍵/証明書が作れたので、利便性を上げるためp12ファイルにします。
(p12ファイルは、公開鍵/秘密鍵/証明書などを1つのファイルにまとめたファイル形式。PKCS#12規格に基づく。)

これもopensslコマンド。
```
[ec2-user@dev work]$ openssl pkcs12 -export -in client.crt -inkey client.key -out client.p12 -name "Client p12 Test1" -legacy
Enter Export Password: pass
Verifying - Enter Export Password: pass
```

パスワードを求められるので入れておきます。私は「pass」と設定しました。

これで

- Client p12 : client.p12

が作れました。

# 3. ロードバランサの相互認証を有効化

ロードバランサに相互認証を設定します。

![cap_alb_listener_before.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1353602/00b6edd7-db4e-4554-8c4c-ed1908a1ef0f.png)

httpsのリスナーの「セキュリティ」が、最初は「相互認証(mTLS)」がオフになっているかと思います。
「セキュアリスナーの設定を編集」を押下し
「クライアント証明書の処理」の「相互認証 (mTLS)」のチェックをオンに切り替えます。

![cap_alb_listener_edit.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1353602/a373a4c3-8e04-4f3c-96a2-e2c9dd66c80f.png)

キャプチャに見せたように、検証については「トラストストアで検証」を選択します。
トラストストアは「新しいトラストストア」を選択し、
「認証局バンドル」→「S3 URI」に、先程設定したRootCA証明書のURIを設定します。
変更内容の保存を押下し、再度httpsのリスナーの「セキュリティ」を確認します。

![cap_alb_listener_after.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1353602/e5bf835f-f59b-45c3-92e4-38401e7b4197.png)

これで相互認証のためのサーバ側準備ができました。

# 4. p12ファイルをPCに取り込む

一応MAC導入とWIN導入手順両方キャプチャしました。特に説明はないです。

MAC導入の場合
![cap_p12_mac_import.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1353602/2d731277-3352-41f6-abe6-a6a7ac75f572.png)
![cap_p12_mac_installed.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1353602/0707b2e3-6d7f-4774-90b5-7637170eb87a.png)

WIN導入の場合
![cap_win_p12_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1353602/f4976a81-600d-4f2e-b9c4-6ae7d36c285f.png)
![cap_win_p12_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1353602/ec3987d4-90a6-4ab5-ba8a-64e26f97907e.png)
![cap_win_p12_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1353602/505b3bd2-99cb-4c0c-af7d-5790b810d9a2.png)
![cap_win_p12_4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1353602/bfdbb84f-e104-4324-965f-f0e9fe17033e.png)
![cap_win_p12_5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1353602/adb20234-d5d9-4d73-9db4-5da377f7d2e6.png)

インストールができたら最後の確認へ。

# 5. 相互認証を使ってアクセスを制御

p12ファイルを使ってClient証明書をインストールしたら、ブラウザで確認してみます。

![cap_chrome_cert_select.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1353602/23cfaab0-94a2-484e-9a13-da32254d2f94.png)

こんな感じで、証明書の選択が行われると思います。
ただしい証明書を選択せず進むと、、、

![cap_chrome_ng.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1353602/e8cc2953-1696-42ca-97d9-2885f9c0dfcf.png)

だめです。証明書を選択すると
![cap_chrome_ok.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1353602/ba1bb065-45be-46d1-98df-24f20eb955df.png)

正しくアクセスできることを確認できました。
相互認証でアクセスを制限したい顧客要望はこれで叶えられそうです。
皆さんもセキュアレベルをひとつ上げる検討の際は、これを頭の片隅に置いてみてはいかが？

私からは以上です。

# 付録. p12ファイルを分解する

色々記事を見る中で、p12ファイルから秘密鍵と証明書を取り出すコマンドもあることを知った。
自分用に備忘録。

```
// p12ファイルから秘密鍵取得
[ec2-user@dev work]$ openssl pkcs12 -provider legacy -provider default -in client.p12 -nocerts -out client2.key
Enter Import Password:pass
Enter PEM pass phrase: pass
Verifying - Enter PEM pass phrase: pass
// p12ファイルから証明書取得
[ec2-user@dev work]$ openssl pkcs12 -provider legacy -provider default -in client.p12 -clcerts -nokeys -out client2.crt
Enter Import Password: pass
```

秘密鍵と証明書が取り出せたらcurlでも相互認証を使ったアクセスができる。

```
[ec2-user@dev work]$ curl https://sample.tttenda.com/ --key client2.key --cert client2.crt
Enter PEM pass phrase: pass
<html><body><p>ようこそ世界！</p></body></html>
```