初めに
先日、AWS SAM CLIのv1.106.0
がリリースされました。
本バージョン以前はローカル実行でstart-api
(API Gatewayのローカル実行相当)及びstart-lambda
(Lambda関数URLのローカル実行相当)のインターフェースはHTTPのみで提供されていました。
今回のバージョンでstart-api
側にSSL証明書が指定可能となりこちらを利用することでHTTPS通信が可能となります。なおstart-lambda
に関しては引き続きHTTP通信のみとなります。
一応PR作成当初ではWebサーバ機能の立ち上げの大元がstart-api
とstart-lambda
で共通であったため両方HTTPS対応に対応する形で進めていたようですが最終的にstart-lambda
側の対応はなくなったようです。
(該当PR(#5902)のコメントを読む限り作成者としても本来はstart-api
のみに適用したかったが作り上両方に実装するのが楽そうだったので当初は実装したが、最終的に良い方法が見つかったので変更したようです)
準備
HTTPS通信を行うにあたり利用されるSSL証明書はSAM側でよしなに組み込まれているものはなく自前で準備が必要です。
信頼している認証局から発行しているSSL証明書がある場合はhostsの設定をうまくすればそれらの証明書を使用してテストできますが、それ以外の場合は自己証明書等の証明書を別途用意する必要があります。
今回はopensslコマンドで自己証明書を発行しますが、ひとまず使えればなんでも良いので指定が最小限になるようにしています。
各環境に応じて必要な値を指定の上発行してください。
% openssl version
OpenSSL 3.1.4 24 Oct 2023 (Library: OpenSSL 3.1.4 24 Oct 2023)
# 秘密鍵を発行
% openssl genrsa -out ssl.key
# CSRを発行(CN以外はデフォルト値)
% openssl req -new -nodes -in ssl.key -out ssl.csr
...
-----
Country Name (2 letter code) [AU]:
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:localhost
Email Address []:
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
# 公開鍵を発行
% openssl x509 -req -in ssl.csr -signkey ssl.key -out ssl.pem
Certificate request self-signature ok
subject=C = AU, ST = Some-State, O = Internet Widgits Pty Ltd, CN = localhost
これで公開鍵がssl.pem
、秘密鍵がssl.key
で発行されました。
実行
sam local start-api
を実行して実際にHTTPS通信を行ってみます。
--ssl-cert-file
に公開鍵ファイルを、--ssl-key-file
に秘密鍵ファイルを指定してコマンドを実行することで受け口がHTTPSで準備されます。
よく見るとメッセージ部分のプロトコル部もきっちりhttpsになっています。
% sam local start-api --ssl-cert-file ssl.pem --ssl-key-file ssl.key
Initializing the lambda functions containers.
...
* Running on https://127.0.0.1:3000
2024-01-09 19:11:12 Press CTRL+C to quit
実際にcurlでアクセスするとHTTPSでのアクセスが可能なことが確認できます。
HTTPSで立ち上げる場合はHTTP側の受付はされないようなのでご注意ください。
一応別ウィンドウで証明書指定なし、--port
オプションで別ポートを指定の上実行すれば共存自体は可能です。
# 不正な証明書(自己証明書)なので-kオプションでエラーを無視
% curl -k https://localhost:3000/hello
{"message": "hello world"}%
# HTTPSとHTTP両方が提供されるのではなくオプションに応じてHTTP or HTTPSのどちらかのみの提供となるようです
% curl http://localhost:3000/hello
curl: (56) Recv failure: Connection reset by peer
# 一応start-lambdaも確認してみたがオプション自体が準備されていないため利用不可
% sam local start-lambda --ssl-cert-file ssl.pem --ssl-key-file ssl.key
Usage: sam local start-lambda [OPTIONS]
Try 'sam local start-lambda -h' for help.
Error: No such option: --ssl-cert-file Did you mean --log-file?
環境依存の可能性は捨てきれませんが少なくとも自分の環境ではTLSv1.2及びTLSv1.3のみで通信可能でTLSv1.1では通信できませんでした。
## TLSv1.3
% curl -k https://localhost:3000/hello --verbose --tls-max 1.3
* Trying 127.0.0.1:3000...
* Connected to localhost (127.0.0.1) port 3000 (#0)
* ALPN: offers h2,http/1.1
...
* SSL connection using TLSv1.3 / AEAD-AES256-GCM-SHA384
...
{"message": "hello world"}%
## TLSv1.2
% curl -k https://localhost:3000/hello --verbose --tls-max 1.2
* Trying 127.0.0.1:3000...
* Connected to localhost (127.0.0.1) port 3000 (#0)
* ALPN: offers h2,http/1.1
...
* SSL connection using TLSv1.3 / AEAD-AES256-GCM-SHA384
...
{"message": "hello world"}%
## TLSv1.1
% curl -k https://localhost:3000/hello --verbose --tls-max 1.1
...
* (304) (OUT), TLS handshake, Client hello (1):
* LibreSSL/3.3.6: error:1404B42E:SSL routines:ST_CONNECT:tlsv1 alert protocol version
...
curl: (35) LibreSSL/3.3.6: error:1404B42E:SSL routines:ST_CONNECT:tlsv1 alert protocol version
## Client Helloを飛ばす前に落ちていそうなのでクライアント側起因で実は行けるのかもしれない
% openssl s_client -connect localhost:3000 -state -tls1_1
CONNECTED(00000005)
SSL3 alert write:fatal:protocol version
SSL_connect:error in error
806023EE01000000:error:0A0000BF:SSL routines:tls_setup_handshake:no protocols available:ssl/statem/statem_lib.c:104:
...
終わりに
SAMで立ちげたローカル環境を利用してクライアント側でTLS部分を含めた事前確認ができるようになりました。
最近では最小TLSのバージョン1.2の対応を行っている話もちらほら見受けられるためそういった対応の事前確認等に利用できるのではないかなと思います。
あくまでローカルにそれ相当の機能を準備しているだけでサーバサイドのTLS周りの細かい設定ができるわけではないのであくまで事前の簡単な確認程度となる点はご注意ください。
余談ではありますがSAMのWebサーバ機能は内部的にはflaskを利用しており呼び出しはこの辺りとなるため、正規の方法ではないですがうまくコードを書き換えるとある程度の調整は可能ではあります。