こんにちは、SSTでWeb脆弱性診断用のツール(スキャンツール)開発をしている坂本(Twitter, GitHub)です。
普段は診断向けのスキャンツールを開発していますが、今年(2019年)の3月~8月にかけて、SST内部で使用する独自ローカルHTTPプロキシ「spindle-localproxy」を開発しました。
本記事(Part2)では公開しても差し支えない範囲で技術的な内容を紹介します。 開発の経緯や簡単な機能説明については、 Part1 を参照してください。 (以下、特に断りがない場合は「ローカルHTTPプロキシ」を「HTTPプロキシ」と表記します。)
- Part2. 技術的な内容紹介
Part2. 技術的な内容紹介
今回開発したHTTPプロキシは、診断サービスの事前調査における画面遷移図作成をサポートする社内ツールになります。 Part2 では技術選定や要件で考慮したポイント、HTTPプロキシの実装とWebSocket対応で苦労した点、MITMと非同期I/Oの処理、WebUI構築、配布とパッケージング方式などをざっくりとご紹介します。
技術選定時の考慮事項とローカルHTTPプロキシならではの要件
プログラミング言語やライブラリ・フレームワークを選定するにあたり、特に気をつけたのは以下のポイントでした。
- HTTPプロキシとして、WebSocketやストリーミングレスポンスに対応させたい。
- これらはスキャンツール側では未対応(2019-08時点)です。スキャンツールで未対応の通信を検知するためには、スキャンツールが対応していない通信方式に対応し、検知する必要があると考えました。
- → Java製のオープンソースのHTTPプロキシライブラリ「LittleProxy」をベースに独自にカスタマイズして対応することにしました。
- 必要な機能に絞った日本語UIにして、開発のしやすさと両立させたい。
- これにより教育コストを下げる効果を狙いました。
- → スキャンツールで利用実績とノウハウが蓄積された JavaScript フレームワーク「ExtJS」の最新版を利用して、開発期間の短縮とUIの品質向上を目指しました。
- エンジニア以外の人が使うため、コマンドラインからコマンドを叩いて実行するのは避けたい。できればデスクトップアプリケーションとして実行ファイルをダブルクリックしたら起動するようにしたい。
- → 実行可能なjarファイルを生成し、「launch4j」でexeファイルに変換し、JREと一緒に配布することにしました。
全体的に Part1 で紹介した課題を解決できる技術を使うよう注意しました。 とはいえ、開発期間短縮や技術検証の都合で、坂本がスキャンツールで十分な利用実績を積んでいた「Java エコシステムと ExtJS の組み合わせ」で固めてしまうというバイアスはありました。 開発担当の坂本の主観的な好みや嗜好に基づいた技術選定になってしまった、といえます。
「ローカル」で動かすHTTPプロキシならではの要件も考慮しました。
- 安定性はそこまで厳密に保証しなくても良い。
- クライアントで動くので、なにかおかしくなったら「とりあえず再起動」を気軽に試せるため。
- → HTTPプロキシ部分のコード品質に対して、現実的なラインで線引できた。
- データ量としてはそこまで多く見積もらなくても良い。
- ローカルには一人分の通信データしか蓄積されないため。
- 診断対象の事前調査という性質上、対象サイトを中心とした通信のみに限定されやすく、手動で遷移するため全体的な通信量は控えめに見積もれる。
- → データ圧縮についてひとまず考えず、ベタな実装により工数を短縮できた。
コード品質や内部実装について不必要に追求せず、工数について現実的なラインで線を引くための「逃げ」の要件となっています。
spindle-localproxy の内部構成
spindle-localproxy の内部構成を以下の図に示します。
- ExtJSを活用するためメインのUIは SpringBoot を使ったWebアプリケーションとして構築しました。
- デスクトップアプリケーションのUIとしては Swing を使ったランチャーを用意しました。
- このランチャーから、HTTPプロキシと SpringBoot のWebアプリケーションを起動します。
- SpringBoot のビルド設定で、executable jar から Swing を使ったランチャーウインドウを最初に起動するように設定しました。
- SpringBoot がビルドした executable jar を launch4j でexeにラップしています。
この構成により、デスクトップアプリケーションとして以下のように違和感無く利用できるようになりました。
- ユーザが exe をダブルクリック
- Swing ランチャーが起動し、HTTPプロキシとSpringBootによるWebUIを起動する。
- WebUIが起動したら、JavaのAPIによりデフォルトブラウザが立ち上がり、WebUIのURLにアクセス → WebUIが表示される。
LittleProxy をベースとしたHTTPプロキシの実装
技術選定のところで書いた通り、今回は LittleProxy というJava製のHTTPプロキシライブラリを採用しました。 LittleProxy は Netty という非同期I/Oのライブラリを活用し、ライセンスは Apache License Version 2.0 で公開されています。
- Netty : https://netty.io
- LittleProxy : https://github.com/adamfisk/LittleProxy
以前にも LittleProxy を使った実験や小さなツールは作ったことがあり、診断での利用実績もあるのが決め手となりました。
HTTPS通信は復号できるか?ですが、LittleProxy は MITM(Man-In-The-Middle) によるHTTPS通信の復号処理をサポートしています。 ただしAPIのインターフェイスのみの提供となっており、MITMを実現するにはインターフェイスを実装してSSL/TLS通信の設定をカスタマイズする必要があります。 実装例として browsermob-proxy のライブラリとしてオープンソースで公開されているもの があり、今回はこれを参考に独自実装に挑戦してみました。
今回はWebSocketへの対応が技術選定での必須条件となっています。中のデータまでは見れなくても、せめてトンネリングで通信を通すことだけは対応しておきたいので、そこが今回の検証ポイントとなりました(後述)。
以下、LittleProxy 内部の動作概要とWebSocket対応の検証結果について紹介します。
HTTP通信時の動作概要
基本となるHTTP(平文)通信時の動作は以下の図のようになります。
非同期I/Oフレームワークの Netty では、非同期に送受信した細切れのデータに対するアクションを ChannelPipeline というパイプライン構造で処理します。 LittleProxy はHTTPメッセージに復元するdecoderと、HTTPメッセージをsocket channel に書き込むためのバイナリデータに変換する encoder を ChannelPipeline に組み込みます。 これによりアプリケーション側では細切れのデータを直接扱う必要がなくなり、HTTPメッセージという論理的なデータ構造だけを扱えば良くなります。
HTTPメッセージをどう読み書きするか?ですが、LittleProxy側で Listener インターフェイスを用意しています。 アプリケーション側で実装した Listener を LittleProxy 側に登録すると、LittleProxyが送受信するHTTPメッセージの取得/編集, 接続成功/エラー情報の取得が可能となります。
ちなみに、HTTPクライアントでHTTPプロキシを有効化すると、クライアント -> プロキシに対しては GET http://www.example.com/... HTTP/1.1
のような absolute-form のHTTPリクエストが送信されます。プロキシはこれを GET /... HTTP/1.1
のような origin-form に変換し、本来の宛先であるオリジンサーバに送信します。
参考 :
- origin-form : https://tools.ietf.org/html/rfc7230#section-5.3.1
- absolute form : https://tools.ietf.org/html/rfc7230#section-5.3.2
HTTPS通信時の動作概要 (MITM無し)
HTTPSの場合はどうなるか?ですが、まずMITMが無い = HTTPS通信を復号せずそのままトンネリングして通す場合は以下の図のようになります。
HTTPクライアントは authority-form の CONNECT リクエストをプロキシに送信します。 MITM無しで設定したLittleProxyは200レスポンスを返した後、HTTPメッセージの decoder/encoder を ChannelPipeline から削除します。 これにより ChannelPipeline は何も処理せず無加工でデータを送受信するようになり、これによりTLSのトンネリングを実現します。
参考 :
- authority-form : https://tools.ietf.org/html/rfc7230#section-5.3.3
- CONNECT method : https://tools.ietf.org/html/rfc7231#section-4.3.6
HTTPS通信時の動作概要 (MITM有り)
MITMを有効にしてHTTPS通信を復号する時の動作は、以下の図のようになります。
CONNECTリクエストに対して200レスポンスを返した後、ChannelPipeline に SSL のdecrypt/encrypt レイヤーを追加します。 内部的にはJavaの SSLEngine クラスを使用して decrypt/encrypt を実装しています。 この時、既存の HTTP decoder/encoder を挟み込むようにします。
200レスポンスを受信したクライアントは、TLSハンドシェイクを開始し、成功すれば origin-form のリクエストをHTTPSで送信します。 プロキシで受信されたHTTPS通信は SSL decrypt により復号 -> HTTP decoder に渡ります。 これにより平文のHTTPリクエストを読み書きできるようになります。 レスポンスはその逆で、オリジンサーバから受信したレスポンスをHTTP encoder に書き込み -> SSL encrypt により暗号化され、クライアントに返されます。
LittleProxyが用意しているMITM用のAPIでは、SSL decrypt/encrypt レイヤーが使用する SSLEngine をカスタマイズできるようになっています。 CONNECT リクエストに含まれるホスト名からサーバ証明書を動的に生成し、プロキシのCA証明書で署名して Java の KeyStore に設定することが可能となります(詳細は後述)。
WebSocket対応
WebSocket対応については、2019-05時点での最終リリースである littleproxy-1.1.2 で検証しました。 結論として、 LittleProxy は WebSocket に対応していませんでした。
対応していない理由:
- WebSocket のハンドシェイクに必要な Connection ヘッダー中の "Upgrade" token や Upgrade リクエストヘッダーを LittleProxy 側で削除している。
- 上記削除コードを消去して正常にハンドシェイクできたとしても、サーバからの 101 Switching Protocols レスポンス中の Upgrade レスポンスヘッダーも LittleProxy 側で削除している。
- 上記削除コードを消去しても、以下の理由により LittleProxy は WebSocket の通信データを処理できない。
- プロキシONでのWebSocket のハンドシェイクは CONNECT リクエストを使ってトンネリングを確立する方式。
- LittleProxy は CONNECT リクエスト = HTTPSを前提とした処理となっている。
- → LittleProxy が WebSocket の CONNECT リクエストを受け取っても、その後に続くWebSocketのバイナリフォーマットのデータをHTTPSとして処理してしまうため、エラーとなる。
参考 :
- WebSocket : RFC6455, https://tools.ietf.org/html/rfc6455
Firefox/ChromeでWebSocketのハンドシェイク(HTTP通信, ws://)が違う
Firefox/Chrome それぞれでHTTP通信(ws://
)でのWebSocket ハンドシェイクを検証したところ、全く違うことに驚きました。(2019-05時点: Firefox 66 / Chrome 73 を使用)
プロキシを使わない場合、Firefox/Chromeともに以下のようなハンドシェイクをしていました。 これは RFC6455 1.3. Opening Handshake に記述されている通りの動きです。
[request] GET /path-to-ws-endpoint HTTP/1.1 Host: localhost:18088 Connection: Upgrade Upgrade: websocket Origin: http://localhost:18088 Sec-WebSocket-Version: 13 Sec-WebSocket-Key: ... Sec-WebSocket-Extensions: ... User-Agent: ... Chrome/73.0.3683.86 ... [response] HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: ... (以降、WebSocketのバイナリデータの送受信 = トンネリング)
プロキシをONにした Chrome では、HTTPSと同様にCONNECTリクエストを送信します。 200が返されたら、WebScoketのハンドシェイクとなる origin-form のGETリクエストを送信し、以降は通常のハンドシェイクの流れとなります。
[request] CONNECT localhost:18088 HTTP/1.1 Host: localhost:18088 Proxy-Connection: keep-alive User-Agent: ... Chrome/73.0.3683.86 ... [response] HTTP/1.1 200 Connection established Connection: keep-alive ... [request] GET /path-to-ws-endpoint HTTP/1.1 Host: localhost:18088 Connection: Upgrade Upgrade: websocket Origin: http://localhost:18088 Sec-WebSocket-Version: 13 Sec-WebSocket-Key: ... Sec-WebSocket-Extensions: ... User-Agent: ... Chrome/73.0.3683.86 ... [response] HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: ... (以降、WebSocketのバイナリデータの送受信 = トンネリング)
この動きは RFC6455 4.1. Client requirements に沿った動きとなっています。
一方、 プロキシをONにした Firefox では通常のHTTP通信と同様に absolute-form で WebSocket のGETリクエストを送信しました。
[request] GET http://localhost:18088/path-to-ws-endpoint HTTP/1.1 Host: localhost:18088 Connection: keep-alive, Upgrade Upgrade: websocket Origin: http://localhost:18088 Sec-WebSocket-Version: 13 Sec-WebSocket-Extensions: ... Sec-WebSocket-Key: ... User-Agent: ... Firefox/66.0 [response] HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: ... (以降、WebSocketのバイナリデータの送受信 = トンネリング)
まとめると HTTPのWebSocketハンドシェイクにおいて、ChromeはRFCに沿ってCONNECTリクエストから開始するが、Firefoxはabsolute-formのGETリクエストから開始する ことが分かりました。
WebSocket対応したときの改修ポイント
- LittleProxy では一般的なHTTPプロキシを想定してリクエスト/レスポンスヘッダーの内容を調整(特定ヘッダーの削除など)をしていたが、それがWebSocket対応に悪影響を与えていました。
- 事前調査という観点から、可能な限り元のHTTPメッセージは改変せず、本来のHTTPメッセージを保存することが求められます。
- → そのため必要最低限度のヘッダー操作のみを残し、それ以外の調整コードは削除しました。
- Firefox/Chrome 双方でのWebSocketハンドシェイクに対応させました。
- 特にChromeでは、CONNECTリクエストの次にTLSパケットがきたらHTTPSとして扱い、GET始まりの平文パケットがきたらWebSocket用のハンドシェイクとして扱うよう処理を分岐する必要がありました。それをLittleProxyの既存のコードフローに組み込むためにどう改造するか、相当悩みました。
- WebSocketへのUpgrade後に Channel Pipeline からHTTP encoder/decoder を取り除いてtunneling状態にするロジックを追加しました。
LittleProxyの内部実装は、Nettyの非同期アーキテクチャに載せるために処理を細かいFutureに分解して連鎖させるようにしていました。 あるFutureが成功したらその中で次のFutureをイベントループのキューに追加するような書き方になっています。 そのため処理の流れが非常に追いづらく、コードの理解に相当時間を取られたのが苦労した点になります。
結果として、いくつかの機能削除 + 新しいListener API を導入することとなり、LittleProxy をベースとした、一部非互換のforkを作ることになりました。
modest-proxy の紹介
LittleProxy をベースに WebSocket に対応し、さらに事前調査観点で細かい改良・調整を行ったforkを「 modest-proxy 」と命名しました。
- LittleProxy よりもHTTP通信に対する影響が「控えめ」であることから「modest」を冠し、さらにすべて小文字とすることでより謙虚さを表す名前にしました。
- 2019-08時点では社内限定のライブラリであるため、公開はしていません。
- Java8でビルドできるよう調整しました。
- APIとしては LittleProxy とほぼ同じですが、一部非互換の変更もあるため、アプリケーションが使用している機能によっては若干の修正が必要になります。
- modest-proxy + MITMを組み込んだHTTPプロキシのサンプルコードを作成し、社内リポジトリにUPしています。
プロキシによる動的な証明書生成技法(BouncyCastle)
HTTPS通信をMITMで復号するには、前述のとおり SSL decrypt/encrypt レイヤーを ChannelPipeline に組み込みます。 ブラウザなどのHTTPクライアントは、SSLのハンドシェイクをHTTPプロキシに対して開始することになります。 この時、HTTPプロキシが適切な証明書を提示しないと、クライアント側では証明書検証のエラーが発生します。
spindle-localproxy では以下の図のように、CONNECTリクエストに含まれるホスト名を Common Name としたサーバ証明書を動的に生成し、それをHTTPプロキシ独自のCA証明書で署名しています。
生成したサーバ証明書はJavaのKeyStore/KeyManagerに登録することで、ハンドシェイク時にクライアントに提示されるようになります。 サーバ証明書の動的な生成とCA証明書による署名は Bouncy Castle を使って実装しました。
なおクライアント側では、HTTPプロキシ独自のCA証明書を信頼できるルート証明書として事前にインポートしておく必要があります。
これをしておかないと、HTTPプロキシが提示したサーバ証明書の検証に失敗します。
Burp でCA証明書をDLするには http://localhost:(Burpのプロキシポート番号)
にアクセスする必要があり、初心者には少々分かりづらい手順となります。
spindle-localproxy では以下のように、HTTP通信ログ一覧のすぐ分かる場所にダウンロードリンクを置いて分かりやすくしてみました。
非同期なHTTP処理 v.s. 同期I/Oのログ保存
spindle-localproxy では Netty ベースのHTTPプロキシを組み込んでいますが、これは非同期I/Oの世界となります。 非同期I/Oの処理で使っているスレッドプール上で、例えばDBへの保存やファイルの同期Read/Write処理を実行するとブロッキングが発生してしまいます。 しかしHTTP通信ログを保存するためには、JDBCを使った同期的なSQL操作や、ファイルの同期的なWrite処理がどうしても必要となります。 (Javaでもファイル処理は非同期にできますが、JDBCについては、使用したバージョンでは非同期操作に対応していませんでした。)
そこで以下の図のように、非同期I/Oの世界からやってくるHTTPメッセージなどのイベント情報を Akka のActorに送るようにしました。
こうすることでHTTPプロキシの処理は非同期I/Oで完結し、Akka が管理するメッセージキューをバッファとして Actor 側はメッセージを1つずつ同期I/Oの世界で処理することが可能となります。つまり同期I/Oの処理をスレッドレベルで分離することで、非同期I/Oをブロックしない構成になりました。
なおメッセージキューのサイズが耐えられるか?ですが、特にデフォルトのまま特別な調整は入れていません。 JVMのヒープが許す限りキューに積み上がることになりますが、事前調査におけるHTTPプロキシにおいては以下のようなワークロードが想定されるため、実用上は問題にならないと判断しました。
- 事前調査でのHTTPプロキシは、診断対象サイト以外の通信はほとんど通らない。
- HTTPプロキシを通すのは、事前調査用のブラウザだけ。メール見たりする業務用のブラウザではHTTPプロキシは通さないことが多い。
- 事前調査ではWebサイトのリンクやフォーム送信を一つずつ人間が辿っていく。この時、通信の様子を目視で確認するため、遷移操作はゆっくりしたスピードになる。
- 大量の画像などでキューが積み上がっても、次の遷移が発生する数秒~数分の間にキューを消化できる。
なお Akka については Java/Scala におけるマルチスレッド処理を改善できるフレームワークとして、数年前からスキャンツール開発に実戦投入しています。 JVM内部のマルチスレッド処理を Actor によって安全に扱えるようになるため、重宝しています。
デスクトップアプリケーションとしての細かい工夫
spindle-localproxy をデスクトップアプリケーションとして組んだ時の、細かい工夫を紹介します。
【1】spindle-localproxy のメインのUIはWebアプリケーションとして作られているため、どうしてもWebアプリのリスニングポートが衝突してしまう可能性があります。 そこで、もし衝突してしまったときに他のポート番号で起動し直せるよう、リスニングポートを変更できるようにしました。
【2】SpringBoot からは slf4j + logback を使ってログ出力をしています。 そのままだと標準出力にログが出力されてしまい、デスクトップアプリケーションからはログが見えず、トラブルシュートが難しくなります。 そこで logback の root ロガーをカスタマイズし、Swingのテキストエリアに出力するようにしました。
【3】Mavenプラグインを使って、ビルド時に各種情報を埋め込んでいます。
参考:
- SpringBoot の Maven プラグインを使って、pom.xml の artifact 名や version 、ビルドタイムスタンプを埋め込み・表示
- Gitのcommit情報を埋め込むMavenプラグインを使って branch 名と commit id を埋め込み・表示
- 依存ライブラリのライセンス情報を集約するMavenプラグインを使って、依存OSSライブラリ名・ライセンス・URLを埋め込み・表示
WebUIの構築 : Sencha Ext JS の紹介
ここまではJava側の主にHTTPプロキシについての紹介でした。 次はユーザが実際に操作するWebUIについて紹介します。
spindle-localproxy ではHTTPの通信ログを大量に表示するため、性能と品質の良いGridコンポーネントが必要です。 そこで、スキャンツール開発で利用実績があるJavaScriptフレームワーク Sencha Ext JS (以下、"ExtJS"と表記) を採用することにしました。
- Sencha Ext JS : https://www.sencha.com/products/extjs/
ExtJS は様々なコンポーネントを提供しています。コンポーネントのデモ/ショーケースもWeb上で公開されているため、どんなコンポーネントが利用できるのか、サンプルコードと一緒に確認できます。
- ExtJSのデモ/ショーケース "KitchenSink" : https://examples.sencha.com/extjs/6.6.0/examples/kitchensink/#all
スキャンツール開発で使っているのは ExtJS バージョン4系ですが、今回は最新の 6.7 を使ってみました。
- ExtJS4での細かいバッドノウハウが解消され、さらに双方向データバインドによるMVVMも使えるようになり、非常に使いやすくなっています。
- スキャンツールで開発したExtJS4版のコードを、わずかな修正でそのまま動かすことができました。開発期間の短縮につながりました。
- それどころか、ExtJS4での細かいバッドノウハウに対応していたコードをごっそり削除することができ、コードがすっきりして可読性が向上しました。
ExtJSで構築したWebUIの例
ExtJSで構築したWebUIをいくつか、スクリーンショットで紹介します。
デザインについては「テーマ」として何種類か用意されています。 ExtJSの提供するコンポーネントについては、基本的に「テーマ」が用意してくれているHTML/CSSデザインをそのまま使えば良く、自分でHTML/CSS と格闘する必要はありません。 もしカスタマイズが必要となっても、CSSについてはSCSSというフレームワークをサポートしています。 SCSSを使うことで生のCSSより書くのが簡単になり、管理も容易になります。
ExtJSのGridコンポーネントのオススメポイント
ExtJSを採用した理由の一つに、Gridコンポーネントがあります。 坂本からの、Gridコンポーネントのオススメポイントを紹介します。
【1】列幅変更、ソート、列の表示/非表示など、Excelを連想させるような機能が組み込み済み。主要ブラウザでの動作保証済み。
【2】フィルタ機能もプラグインとしてすぐに使えます。列で表示しているデータの型に応じて、フィルタタイプをカスタマイズ可能です。
【3】スクロールに高速に追従します。ExtJSのGridはデフォルトで表示範囲のデータしかレンダリングしないため、数千~数万件のレコードでも軽快にスクロールします。
【4】Grid上での編集機能もプラグインとしてすぐに使えます。データの型に応じて入力フォームをカスタマイズ可能です。
ExtJS開発時の工夫
ExtJSを使った開発で工夫した点を紹介します。
- フリー素材を使ってアイコンを補強しました。
- 商用フリーの16x16 PNG アイコンを追加しました : http://www.famfamfam.com/lab/icons/silk/
- → CSS や
<img>
タグで呼び出せるようにしています。
- 開発/デバッグ/本番ビルドとSpringBoot側を連携させました。
- ExtJSは独自のビルドシステムがあり、主に3種類のビルドプロファイルがあります。
- 開発(development) : JSファイルを一つ一つ動的に読み込む。
- デバッグ(testing) : JSファイルを1ファイルに結合する。
- 本番(production) : JSファイルを1ファイルに結合 + minifyしてサイズを縮小する。
- SpringBoot側の "/" コントローラで、実行シーンに応じてどのビルドを読み込むか切り替えるようにしました。
- IDEなど開発環境から起動したときは開発ビルドをロード
- jar/exeから起動したときは本番ビルドをロード
- jar/exeから起動しても、"/debug/index.html" に明示的にアクセスされたらデバッグビルドをロード
- デバッグ/本番ビルドの出力先を SpringBoot の
src/main/resources/static/
以下のディレクトリに設定することで、ビルドファイルをSpringBootのjarファイルに埋め込めるようにしました。
- ExtJSは独自のビルドシステムがあり、主に3種類のビルドプロファイルがあります。
- VSCode + ESLint で必要最低限のstyle checkを組み込みました。
- 未使用変数などを分かりやすく表示してくれて非常に助かりました。
開発環境
開発環境を紹介します。
- Javaバージョン : AdoptOpenJDK 11
- Mavenビルドは HotSpot VM / SpringTools 4 用にはOpenJ9と使い分け
- Spring Tools 4
- SwingUI の作成には Eclipse の WindowBuilder プラグイン を使いました。
- VSCode + ESLint
- リポジトリ : 社内プライベートGitリポジトリ
配布とパッケージング
技術選定のところでも書きましたが、エンジニア以外の人が使うため、デスクトップアプリケーションとして実行ファイルをダブルクリックすれば起動するようにしました。
- SpringBootにより実行可能なjarファイルを生成します。
- それを launch4j でexeファイルに変換し、JREと一緒に配布しています。
- Java11で作ったので、jlinkによりカスタムJREを生成してパッケージングすることも可能です。今回は時間の都合もありそこまではやらず、AdoptOpenJDK の JRE11と一緒に配布することにしました。
launch4jによるexe生成は以下の図のようになります。
- 元になるjarファイルと、生成するexe名を指定します。
- JREの配置場所を、exeからの相対パスで指定します。
- exeを生成したら、JREの配置場所と一緒に置きます。これで、exeをダブルクリックするだけでJavaアプリが起動します。
技術面での今後
技術的な今後の課題として考えているものは、以下があります。
- 品質向上のために、ソースコードのスタイルチェッカーと静的バグ解析ツールを導入したい。
- Checkstyle, SpotBugs, PMD (オプションで SonarQube)
- modest-proxy 内部のリファクタリングと安定性向上
- Futureを細かく分割しているなど、ソースコードの可読性が低いのでなんとかしたい。
- テストコードがあるが、WebSocketなどのバリエーションが不足しているため、強化したい。
- e2eレベルのテストコードがメインで単体レベルのテストコードが不足しているため、リファクタリングしづらい。もっと現実のブラウザアクセスを想定した単体レベルのテストを充実させたい。
- いくつか、現時点では対応しきれなかった既知の不具合の修正
- 新規機能の追加
- Burp の Interceptor 相当で、リクエストの処理を一時停止する機能を追加すると汎用的なプロキシツールになりそう。
まとめ
- 業務上の課題を解決するために、オープンソースソフトウェアをベースとした独自のHTTPプロキシを開発しました。
- 技術的な特徴:
- LittleProxy を WebSocket に対応させ、非同期I/Oと同期I/Oを Akka のメッセージキューで橋渡ししました。
- SpringBootによるWebアプリケーションを、デスクトップアプリケーションとして使えるよう内部構成とパッケージングを工夫しました。
- Sencha Ext JS を用いて、WebUIの使いやすさと開発・保守のしやすさを両立させました。
Webベースの技術でデスクトップアプリケーションを開発 x HTTPプロキシの開発という、なかなか無い組み合わせを体験することができました。 Java11 + ExtJS6 の検証もできたので、今後のスキャンツール開発に活用していきたいと思います。