hkoba blog

プログラマーです。プログラミング言語ミーハーです。ツッコミ歓迎です。よろしくどうぞ(能代口調)

Plack middleware 書く時もJSON指向のOOモジュリーノが便利ですよ〜

この記事は Perl Advent Calendar 202420日目の記事です。


こんにちは、Perl でモジュリーノ書くようになって四半世紀の hkoba です。最近は艦これアーケードレイテ後編イベントが熱いです。

さて皆さんは Plack の middleware を開発する時、どのように開発を進めるでしょうか? 普通なら、簡易的な app.psgi を書いて、plackup -R でテストサーバーを起動して…という手順で進めることでしょう。あるいは plackup -e で頑張る人もいるかもしれません。どちらも、慣れた人なら全く手間に感じない手順かもしれません。でも、もっと直接的な方法…そう、そのモジュール自体をプログラムとして直接実行して試す方法は、無いのでしょうか?

この記事では、Plack middleware を JSON 指向のOO モジュリーノとして記述することで開発の try and error をコマンド行から直接行えるようにする方法を紹介します。

JSON 指向の、オブジェクト指向モジュリーノ(JSON Aware OO Modulino)とは

モジュリーノ(Modulino)とはライブラリー(モジュール)として使えると同時にプログラムとして直接実行できるような、そんなスクリプトファイルのことです。

オブジェクト指向(OO)モジュリーノ という単語は私が勝手に提唱している概念で、git や cvs などのサブコマンドを持った CLI コマンドの呼び出し規約をモジュリーノとしての挙動にマップする…つまり、

  • モジュールの各メソッドを CLI からサブコマンドとして呼び出すことが出来る
  • モジュールのインスタンスを new するときの引数をサブコマンド名の前の --name=value オプションで指定できる
  • CLI からメソッドを呼び出した時、戻り値はシリアライズされて標準出力に出力される

ようなモジュリーノです。

JSON 指向の OOモジュリーノ とは、

ものです。このように JSON 限定で コマンド行から Perl の HASH や ARRAY を安全に渡せる ようにすることで、モジュリーノとしてコマンド行から試せる事柄を飛躍的に増やすことが出来ます。詳しくは過去の紹介スライドをご覧下さい。

コマンド行で PSGI call を直接試せると、こんな風に嬉しいのです

Plack::Middleware が実装するべき APIcall メソッドです。 JSON 指向の OOモジュリーノなら、引数として $envjson で渡して呼び出せば、出力として PSGI tuple が得られるでしょう。

ここでは例として拙作の Plack::Middleware::Validate_Google_IAP_JWT をとりあげます。
(これは Google Cloud のゼロトラスト保護機能である Identity-Aware Proxy - IAP 保護下の Web アプリに組み込んで使う middleware で、Cloud IAP が HTTP ヘッダーに設定してくれた認証情報入り JWT に対して、それが正当なものかどうかを検証するためのものです。 うちの現場で1年以上使ってうまく機能しているので、先程 CPAN に公開しました)

まずは http://localhost/ をアクセスした場合を想定して、 $env->{PATH_INFO}/ を設定して呼び出してみましょう。 期待する HTTP Status は 403、Forbidden です。

% cd ./lib/Plack/Middleware
% ./Validate_Google_IAP_JWT.pm call '{"PATH_INFO":"/"}'         
[403,[],["Forbidden (no JWT assertion)\n"]]

次に、試しに偽の JWT を入れて試してみましょう。壊れたリクエストなので、 HTTP Status 400 (Bad request) を返すのが良いでしょう。

% ./Validate_Google_IAP_JWT.pm \
 call '{"PATH_INFO":"/","HTTP_X_GOOG_IAP_JWT_ASSERTION":"fake.foo.bar"}'        
[400,[],["JWS: invalid header part at ./Validate_Google_IAP_JWT.pm line 132.\n"]]

このモジュールには一部の path に guest アクセスを許す(JWT 検証をバイパスする)機能があります。これも試してみましょう。 この場合、middleware を通過した後のアプリケーションが無いとだめなので、ダミーの app を渡す機能を予め実装してあります。

% ./Validate_Google_IAP_JWT.pm \
 --app='[200,[],["OK"]]' --guest_subpath=/guest/ \
 call '{"PATH_INFO":"/guest/"}'
[200,[],["OK"]]

# 念のため、 / の Forbidden も確認
% ./Validate_Google_IAP_JWT.pm \
 --app='[200,[],["OK"]]' --guest_subpath=/guest/ \ 
call '{"PATH_INFO":"/"}' 
[403,[],["Forbidden (no JWT assertion)\n"]]

では最後に、実際の JWT を使って、検証が通るか調べてみましょう。call だと OK が返るだけで面白くないので、ここではこのモジュールの内部メソッド decode_jwt_env を呼び出してみます。

期限切れした JWT を渡した場合>

% ./Validate_Google_IAP_JWT.pm decode_jwt_env \
  '{"HTTP_X_GOOG_IAP_JWT_ASSERTION":"'$(<../../../t/sample.jwt )'"}'
JWT: exp claim check failed (1734493203/0 vs. 1734505329) at ./Validate_Google_IAP_JWT.pm line 132.

今現在有効な jwt を貼り付けた場合>

有効な jwt を decode_jwt_env に与えた結果

このように、JWT を正しくデコード出来るかをコマンド行から簡単に確かめることが出来ます。

まとめ

Plack の Middleware を書くときも JSON 指向な OO モジュリーノにしておくと開発がはかどりますよ、という話でした。 明日の記事は @teckl さんです。

ここまで読んで下さりありがとうございました〜