GCP User GroupでStackdriverについて発表してきた

だいぶ間が空いてしまった。先日開催されたGCPUG Tokyo DevOps Day September 2017というイベントで発表してきたメモ。

「RocroにおけるGCP活用事例」という発表の後半を担当した。前半は現在開発中のサービスとそのデプロイについて、後半がStackdriverについて、という構成。以下が担当部分の発表資料。

一年ぐらいStackdriverを使ってノウハウが少し貯まってきたところだったので、こういう場でまとまった話ができたのはとても良かった。GCPUG運営の方に感謝。Stackdriverは監視サービスとしてはまだマイナーで、ノウハウとかあまり共有されていない印象があるので、今後もわかったことが出てきたらブログを書くか発表するかしていきたい。

CloudFormation Custom Resourceの意図しない削除を防ぐ

最近はCloudFormationをよく使っています。大変便利なのですが、S3オブジェクトやDynamoDBのItemの作成には対応していないため、Lambda-backed Custom Resourceで作成しています。ただCustom Resourceのライフサイクルは少し独特なところがあって、理解せずに使うと、意図せずResourceが削除されてしまうことがあります(あった)。

意図しない削除を起こさないようにするため、この記事ではいくつかの状況についてCustom Resourceがいつ作られ、いつ更新され、いつ削除されるのかまとめます。Lambda-backed Custom Resourceを想定していますが、SNS-backedでも基本は同じではないかと思います。

PhysicalResourceIDについて

まずは前提として、PhysicalResourceIDについて説明します。PhysicalResourceIDは、Custom Resourceから呼ばれるLambda関数がレスポンスに設定する値の一つです。レスポンスについて説明したドキュメントには以下のように書かれています。

This value should be an identifier unique to the custom resource vendor,
and can be up to 1 Kb in size. The value must be a non-empty string and
must be identical for all responses for the same resource. 

最後のmust be identical for all responses for the same resourceが重要です。ここの意味するところは、Custom Resourceのドキュメントに以下のようにはっきり書かれています。

When AWS CloudFormation receives the response, it compares the
PhysicalResourceId between the old and new custom resources.
If they are different, AWS CloudFormation recognizes the update
as a replacement and sends a delete request to the old resource.

要はPhysicalResourceIDが変わったら古いResourceは削除される、ということですね。

前述のように、PhysicalResourceIDはLambda関数がレスポンスの中に含める値です。レスポンスは、Node.jsの場合はcfn-responseモジュールのsendメソッドで送ることが多いかと思います。PhysicalResourceIDはsendメソッドの5つ目の引数で指定できます。例えば以下のような感じです。

var response = require('cfn-response');
exports.handler = function(event, context) {
  response.send(event, context, response.SUCCESS, {}, 'PhysicalResourceID');
};

cfn-responseモジュールのドキュメントを見ると、PhysicalResourceIDについては以下のように書かれています。

Optional. The unique identifier of the custom resource that invoked
the function. By default, the module uses the name of the Amazon
CloudWatch Logs log stream that is associated with the Lambda function.

PhysicalResourceIDの指定を省略するとLog stream名が使われるとのことです。これは例えば2017/05/13/[$LATEST]2582d4dedea542868953b34db229dd35のような値で、Lambda関数を更新するたびに変わっているようです。

ここまでのポイントとしては以下です。

  • PhysicalResourceIDはCustom ResourceのIDで、Lambda関数のレスポンスに含めることで指定できる。
  • このIDが変わると、古いCustom Resourceが削除される。
  • PhysicalResourceIDを指定しない場合、Lambda関数が更新されるたびに異なるPhysicalResourceIDが設定される。

ここからは、いくつかの状況について、Custom Resourceがいつ作られ、更新され、削除されるのか見ていきます。

PhysicalResourceIDを指定しない場合

以下のようなPhysicalResourceIDを指定しない場合を考えます。CloudFormationのTemplateはこちらから参照できます。

var response = require('cfn-response');
exports.handler = function(event, context) {
  response.send(event, context, response.SUCCESS, {});
};

まずはスタックの作成をします。以下はCloudFormationのEventsタブのスクショです。

f:id:ks888:20170513133719p:plain

Custom Resrouceが作成されています。

次にスタックの更新をします。何か変更しないと更新できないので、スタックに与えるパラメータを変えています。

f:id:ks888:20170513132222p:plain

Custom Resourceが更新されています。

ここでLambda関数を更新してみます。今回はLambda関数に割り当てるメモリサイズをAWSコンソールから変更しました。再びスタックの更新をします。

f:id:ks888:20170513133729p:plain

Custom Resourceが更新されているのに加え、最後に同Resourceが削除されています。これは、Lambda関数の更新に伴ってPhysicalResourceIDが更新されたからです。

もう一度スタックの更新をしてみます。

f:id:ks888:20170513132248p:plain

Custom Resourceが更新されていますが、Physical IDが変わっています。

最後にスタックを削除します。

f:id:ks888:20170513132257p:plain

Custom Resourceが削除されました。

作成から更新、削除までを一通りみました。PhysicalResourceIDを指定しない場合に重要なのは以下の2点かと思います。

  • Lambda関数を更新するとPhysicalResourceIDが変わるため、古いCustom Resourceが削除される。
  • 古いCustom Resourceが削除されても、新しいCustom ResourceのCreateは呼ばれない。

特に2点目はやや意外な挙動な気がするので、ID指定をしない場合は注意したいところです。

PhysicalResourceIDを指定する場合

以下のようにPhysicalResourceIDを指定する場合を考えます。Templateはこちらから参照できます。

var response = require('cfn-response');
exports.handler = function(event, context) {
  response.send(event, context, response.SUCCESS, {}, 'PhysicalResourceID');
};

まずはスタックの作成をします。

f:id:ks888:20170513133741p:plain

Custom Resrouceが作成されています。

次にLambda関数を更新してみた上で、スタックを更新します。

f:id:ks888:20170513132314p:plain

Custom Resourceが更新されています。今度はResourceは削除されていません。

最後にスタックを削除します。

f:id:ks888:20170513132325p:plain

Custom Resourceが削除されました。

シンプルですね。PhysicalResourceIDを指定しておけば、Lambda関数が更新されてもCustom Resourceは削除されません。

まとめ

PhysicalResourceIDを指定しない場合と指定する場合について、Custom Resourceがいつ作成・更新・削除されるかまとめました。Resourceの意図しない削除を防ぐには、PhysicalResourceIDを常に同じ値にしておくのがよさそうです。他にも削除されるケースをご存知でしたら、ぜひ教えてください。

mitmproxyでHTTPSプロキシする時、どんな通信が発生しているのか追ってみる

前回の記事で、ブラウザからアクセスするURLはそのままで接続先サーバだけ変える方法と、その際のHTTP通信の様子を確認しました。今回はその続きで、HTTPSの場合について、ブラウザとmitmproxy間、mitmproxyとサーバ間の通信内容を追っていきたいと思います。

処理の概要

HTTPSプロキシする場合のmitmproxyの挙動について書かれたドキュメントがあるので、そちらを参考にして概要を掴みます。以下はそのドキュメントから引用した図です。

f:id:ks888:20170504194435p:plain

この図に基づいて、順番に説明していきます。

1-2: ブラウザとプロキシ間でHTTP CONNECT

最初にHTTP CONNECTでブラウザとプロキシ間をつなぎます。通常のプロキシの場合はここでプロキシ先サーバとのTCP接続(HTTPトンネル)を確立するのですが、mitmproxyはしません。

mitmproxyはHTTPSリクエストの確認・変更に対応するため、ブラウザとプロキシ間、プロキシとサーバ間それぞれでTLS接続をします。そのため、ここでHTTPトンネルを作る必要はありません。

3,6: ブラウザとプロキシ間でTLS接続確立

次にブラウザとプロキシ間でTLSハンドシェイクをします。あとでWiresharkで確認しますが、3でClient Hello、6で残りのやりとりをしています。4-5ではプロキシとサーバ間でTLSハンドシェイクをしています。

なぜこのような順序なのかというと、mitmproxyが独自のサーバ証明書を作成する際、証明書に紐付けるドメイン名を明らかにするためです。この情報は、本物のサーバ証明書Common NameSubjectAltNameという箇所に書かれています。今回初めて知ったのですが、サーバ証明書には複数のドメイン名を紐付けられるらしいです。実際www.google.co.jpにアクセスしたときのサーバ証明書を見てみると、Common Name*.google.comで、SubjectAltNameは以下のようなドメイン名の列になっています。*.google.co.jpyoutube.comもここにあります。

f:id:ks888:20170504194946p:plain:w500

これらの情報を取得してからサーバ証明書を作るため、このような実行順序になっているようです。

4-5: プロキシとサーバ間でTLS接続確立

3,6で書いたような目的のために、ここでプロキシとサーバ間でTLS接続を確立します。図にはTLSハンドシェイクをどこまで進めるのか書かれていませんが、実際の挙動としては最後まで進めているようです。

7-8: HTTPSリクエス

ここまででブラウザとプロキシ間、プロキシとサーバ間それぞれでTLS接続が確立されたので、あとはいつもどおりリクエストを送るだけです。ただし、前回の記事に書いたような方法で接続先サーバを変える場合、プロキシとサーバ間ではもう一度TLS接続が必要になります。接続先変更後のサーバとはTLS接続していないためです。

実際の通信をみてみる

次にWiresharkで実際の通信をみてみます。

今回はブラウザからhttps://example.com/にアクセスすると、実際はhttps://www.google.co.jpにアクセスするようにしてみました。mitmproxyは以下のように実行しました。プロキシのポートは8080番です。その他の設定については前回の記事を参考にしてください。

mitmproxy --no-http2 -s "./mitmproxy_replace_host.py example.com www.google.co.jp"

ブラウザからhttps://example.com/にアクセスすると、TLSとHTTPのレベルでは以下のようなパケットが飛んでいました。

f:id:ks888:20170504200011p:plain

大体ドキュメント通りに並んでいます。8で再びTLS接続をしていますが、これはスクリプトがリクエストを書き換えているためです。4-5ではhttps://example.com/TLS接続したのに対し、8ではhttps://www.google.co.jpTLS接続しています。

まとめ

HTTPSの場合について、ブラウザとmitmproxy間、mitmproxyとサーバ間の通信内容を確認しました。mitmproxy組込みのCA証明書を使うために、通常のHTTPSプロキシとは異なる手順を踏んでいることがわかりました。

参考にしたページ: http://docs.mitmproxy.org/en/stable/howmitmproxy.html