この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
こんにちは、アノテーション テクニカルサポートチームの中野です。
API Gateway のバックエンドに S3 を設定するパターンと API Gateway の通信で相互 TLS 認証(mTLS)を行うパターンの組み合わせを試す機会があったので、以下に手順をまとめてみました。
構成
以下のような構成で構築してみます。
また、mTLS の詳しい仕組みについては、以下がわかりやすかったので参照ください。
やってみた
前提条件
構築するまえに、前提として API Gateway のカスタムドメインを作成するために、以下を準備しておきます。
- Route53 へのドメインの登録
- ACM で証明書発行
準備が完了したら、以下手順を進めてきます。
ステップ 1: S3 に静的ファイルをアップロード
まず、S3 を作成します。
何らかの適当な文字を描画する HTML ファイルをアップロードしておきます。
また、API Gateway から今回作った S3 のみにアクセスできるように、以下のような IAM ポリシーを作成しておきます。
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"s3:Get*",
"s3:List*"
],
"Resource": [
"arn:aws:s3:::<S3 バケット名>",
"arn:aws:s3:::<S3 バケット名>/*"
],
"Effect": "Allow"
}
]
}
このポリシーで、API Gateway が信頼されたエンティティ(ロールをアタッチできる対象)となるように IAM ロールを作成します。
IAM ロールは、ステップ 2 の API Gateway を構築する際に利用します。
ステップ 2: API Gateway の構築
では、S3 へプロキシする API Gateway を作成します。
API のタイプは、REST API で作成していきます。
リソースを新たに作成します。 ここでは、/page というパスで作成しました。
リソースに対して GET メソッドを追加して、以下のような設定を行います。
注意点として、「パスの上書きの使用」の部分に {S3 バケット名}/{取得するファイル名} となるように記載します。
また、実行ロール部分は、ステップ 1 で作成した API Gateway から S3 へのアクセスの許可を行うための IAM ロールの ARN を記載します。
作成が完了したら、適切なステージ名でデプロイします。
なお、ここで API Gateway のデフォルトのエンドポイントを無効化しておきます。
デフォルトのエンドポイントを無効化していない状態で、相互 TLS 認証を API Gateway へ設定すると、クライアント証明書なしでのアクセスができてしまいますので、運用前に見落としがないように注意してください。
再度、設定を反映するために、再度ステージへデプロイしておきます。
ステップ 3: カスタムドメインの作成
カスタムドメインを作成します。
このとき、一旦相互 TLS 認証を無効にしたまま、ドメイン作成を実行します。
ステップ 4: クライアント証明書の発行
クライアント証明書を自分のローカルマシンで作成します。
以下のブログの手順で実行しました。
ルート CA の秘密鍵を作成します。
$ openssl genrsa -out RootCA.key 4096
Generating RSA private key, 4096 bit long modulus
......................................................................................................................................................++
..................................++
e is 65537 (0x10001)
続いて、自己署名 CA 証明書作成します。
$ openssl req -new -x509 -days 36500 -key RootCA.key -out RootCA.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) []:JP
State or Province Name (full name) []:FUKUOKA
Locality Name (eg, city) []:
Organization Name (eg, company) []:Annotation
Organizational Unit Name (eg, section) []:Technical Support
Common Name (eg, fully qualified host name) []:an-nakano ca
Email Address []:
クライアント側の秘密鍵を作成します。
$ openssl genrsa -out my_client.key 2048
Generating RSA private key, 2048 bit long modulus
.............................................................................................................................+++
.....................................+++
e is 65537 (0x10001)
秘密鍵をもとに CSR を作成します。
$ openssl req -new -key my_client.key -out my_client.csr
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) []:JP
State or Province Name (full name) []:FUKUOKA
Locality Name (eg, city) []:
Organization Name (eg, company) []:Annotation
Organizational Unit Name (eg, section) []:Technical Support
Common Name (eg, fully qualified host name) []:an-nakano client
Email Address []:
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
最後にクライント証明書を作成します。
$ openssl x509 -req -in my_client.csr -CA RootCA.pem -CAkey RootCA.key -set_serial 01 -out my_client.pem -days 36500 -sha256
Signature ok
subject=/C=JP/ST=FUKUOKA/O=Annotation/OU=Technical Support/CN=an-nakano client
Getting CA Private Key
作成した CA 証明書を適当な S3 バケットに保存します。
$ aws s3 cp RootCA.pem s3://<CA証明書用S3バケット名>/
upload: ./RootCA.pem to s3://<CA証明書用S3バケット名>/RootCA.pem
ステップ 5: API Gateway で相互 TLS 認証を設定
では、ステップ 3 で作成したカスタムドメインに、ステップ 4 で作成した S3 にアップロード済みの CA 証明書を設定します。
カスタムドメインのステータスが「利用可能」になるまで待ちます。
最後に、カスタムドメインの API マッピング設定で、API Gateway のステージを関連付けます。
ここで、Route53 のエイリアスレコードで API Gateway へトラフィック転送するように設定して完了です。
ステップ 6: 検証
では、クライアント証明書ありで、curl でアクセスしてみます。
$ curl -v -i --key my_client.key --cert my_client.pem https://api.example.com/page
* Trying 54.249.165.209...
* TCP_NODELAY set
* Connected to api.example.com (54.249.165.209) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
* CAfile: /etc/ssl/cert.pem
CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Request CERT (13):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Certificate (11):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS handshake, CERT verify (15):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
* ALPN, server accepted to use h2
* Server certificate:
* subject: CN=example.com
* start date: Nov 15 00:00:00 2021 GMT
* expire date: Dec 14 23:59:59 2022 GMT
* subjectAltName: host "api.example.com" matched cert's "*.example.com"
* issuer: C=US; O=Amazon; OU=Server CA 1B; CN=Amazon
* SSL certificate verify ok.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x7ffd2f008200)
> GET /page HTTP/2
> Host: api.example.com
> User-Agent: curl/7.64.1
> Accept: */*
>
* Connection state changed (MAX_CONCURRENT_STREAMS == 128)!
< HTTP/2 200
HTTP/2 200
< x-amzn-requestid: a0469407-1a6b-4472-xxxxxxxxxx
x-amzn-requestid: a0469407-1a6b-4472-xxxxxxxxxx
< x-amz-apigw-id: M_xxxxxxxxxx
x-amz-apigw-id: M_xxxxxxxxxx
< x-amzn-trace-id: Root=1-61fc83ab-xxxxxxxxxx
x-amzn-trace-id: Root=1-61fc83ab-xxxxxxxxxx
< content-type: application/json
content-type: application/json
< content-length: 38
content-length: 38
< date: Fri, 04 Feb 2022 01:38:51 GMT
date: Fri, 04 Feb 2022 01:38:51 GMT
<
<html>
Hello World!! Yeah!!
</html>
* Connection #0 to host api.example.com left intact
* Closing connection 0
無事、アクセスできることを確認しました。
次に、クライアント証明書なしで、curl でアクセスしてみます。
$ curl -v -i https://api.example.com/page
* Trying 18.180.7.23...
* TCP_NODELAY set
* Connected to api.example.com (18.180.7.23) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
* CAfile: /etc/ssl/cert.pem
CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Request CERT (13):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Certificate (11):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* LibreSSL SSL_connect: SSL_ERROR_SYSCALL in connection to api.example.com:443
* Closing connection 0
curl: (35) LibreSSL SSL_connect: SSL_ERROR_SYSCALL in connection to api.example.com:443
アクセスされないことが確認できました。
最後に
API Gateway で S3 へ簡単にプロキシできる環境を構築できて、さらに API Gateway の認証として相互 TLS 認証を利用する例をまとめてみました。
クライアント証明書をもつユーザのみにサイトを公開するようなパターンに有用なのではないでしょうか。
アノテーション株式会社について
アノテーション株式会社は、クラスメソッド社のグループ企業として「オペレーション・エクセレンス」を担える企業を目指してチャレンジを続けています。「らしく働く、らしく生きる」のスローガンを掲げ、様々な背景をもつ多様なメンバーが自由度の高い働き方を通してお客様へサービスを提供し続けてきました。現在当社では一緒に会社を盛り上げていただけるメンバーを募集中です。少しでもご興味あれば、アノテーション株式会社 WEB サイトをご覧ください。