AWS lambda(Node.js)でオレオレ証明書(self-signed)を一時的に信頼してSSL通信を行う方法
この記事は2020年時点で検証した内容を記載しております。現時点でAWSのサービスUPDATE等により別の方式での回避策があるかもしれません。
はじめに
AWS lambdaのNode.js(https標準モジュール)で実装した、WEBサイトへhttpsのリクエストを投げる処理で、以下の2つのエラーが発生した際の対応についての記事です。 ※急いでいる方、ソースコードだけ見たい方はここから見ればOKです
これ何
調べたところ、リクエスト先のWEBサーバから送出されているサーバ証明書に対してのnodejs内部での検証失敗のため生じたエラーでした。 2つのエラーの違いは、
👉2つのエラーは共に、Node.jsとして信頼1していないCA証明書にチェーンしていたため生じていました。
環境
- AWS lambda
- Node.js ランタイム12.x
- 接続イメージ
[AWS lambda]-->(WAN HTTP over SSL)-->[WEBサーバ]
対処案
3つ考えましたが、妥協点で私の環境では3つ目で実装しましたので以下にまとめます。
1.WEBサーバ側にちゃんとしたサーバ証明書2に変更してもらう
この対応ができたらこんな記事はいらない気もする。
自局署名の問題については、ネット上でも従前から議論されおり3、本記事をご覧になっている方でも既知のことと思います。WEBサーバ管理者に対しては、セキュリティの観点で懸念点を伝えてさしあげる程度にしました。
2.TLSハンドシェイクエラーを無視・無効にする
NODE_TLS_REJECT_UNAUTHORIZED
を環境変数に定義し、値を0
とすれば検証自体がdisableになる模様。これは、curlでいうところの--insecure
オプションと類似していますが、検証を全て無視するため有効期限やトラストアンカーとのチェーン等々を丸っとすっ飛ばす模様。(詳細は未検証のため割愛)
https://nodejs.org/api/cli.html#cli_node_tls_reject_unauthorized_value
3.オレオレ証明書(self-signed)を一時的に信頼する
公式ドキュメントに書いてありました。
https://nodejs.org/api/tls.html#tls_tls_connect_options_callback
具体的には、通信先のWEBサーバのオレオレ証明書(サーバ証明書自体)ないしは、サーバ証明書のissuerとなっている現状信頼されていない自局CA証明書(pem形式)を取得し、https.requestのoptionにca
を追加するというものです。以下にサンプルを用いて実装例を示しています。
※今回の実装ではpemファイルの読み込みを、諸般の事情によりlambdaのみで完結したかったため、環境変数に事前に設定して、そこから読み込ませるという方法を使っています。(S3に入れて読み込むという方法もありかと思います。)
3-1.pemファイルを1行化する
環境変数にpemを入れるため、改行を¥nに置換します。
[work@localhost]$ awk 'NF {sub(/\r/, ""); printf "%s\\n",$0;}' cacert.pem -----BEGIN CERTIFICATE-----\nAIIDQjCCAiHCEQCQzvg6BX4eF(中略)v2wk3xtME7i8Jb2aUQH9vFVYuXUN2\nUO+j8l1OA4p1ew0kCsit2HOn\n-----END CERTIFICATE-----\n [work@localhost]$
余談ですが、当初は、pem形式って改行まで含んで形式であるということを理解しておらず、改行を単純に削除して環境変数に入れていたためエラーが発生して、ここで地味に悩みました。ちなみに、pemの改行は、Windows形式(CR+LF)、Unix形式(LF)どちらで良い模様なのでCRを削除後に置換している。4
3-2.AWS lamda環境変数の設定
上コマンドで表示された内容を環境変数にCA_PEMとして保存します。
3-3. 実際のスクリプト(sample)
const https = require('https'); //ここで環境変数からcacertにPEMを読み込んでいる var cacert = process.env['CA_PEM'].replace(/\\n/g, '\n'); exports.handler = (event, context) => { //caを追加してcacertを設定 const options = { protocol: 'https:', host: 'example.com', path: '/index.html', port: 443, method: 'GET', timeout: 8000, ca: cacert, headers: { 'User-Agent': 'AWS-lambda', } }; let req = https.request(options, (res) => { res.setEncoding('utf8'); let body = ''; res.on('data', (chunk) => { body += chunk; }); res.on('end', () => { console.log(body); }); }); req.end(); }
3-4. エラー解消。取れました
Response: null Request ID: "XXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" Function Logs: START RequestId: XXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX Version: $LATEST 2020-04-16T14:26:29.930Z XXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX INFO <!DOCTYPE html> <html lang="jp"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Hello</title> </head> <body> This is test page ;> </body> </html> END RequestId: XXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX REPORT RequestId: XXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX Duration: 210.24 ms Billed Duration: 300 ms Memory Size: 128 MB Max Memory Used: 68 MB
-
Node.jsのSSL通信時のトラストアンカーってなんだろ?と思い調べた。https://github.com/nodejs/node/blob/bf7409e9740ce602b09e088aac70b7c817f5d27c/doc/guides/maintaining-root-certs.md 読んでみると、このソースに書いてあるよとのこと。Mozilla NSSのトラストアンカーと合わせているみたいですね、定期的にメンテはされている模様。ここに自己署名をここに入れるのは影響が大きそうです。↩
-
Qiitaですと、こちらの記事がとても参考になりました。オレオレ証明書を使いたがる人を例を用いて説得する↩
-
出典:https://stackoverflow.com/questions/57870914/how-to-create-a-single-line-x509-certificate-that-can-be-parsed-by-openssl-comma↩