2018-03-13(火) [長年日記]
■ (自分で書いた)誤ったHTTPヘッダのせいで苦労した話
わさますのコミュニケーション基盤であるmassr、いまはHerokuで動かしているんだけど、長年の運用でMongoDBの容量も増えてきてコスト的にも厳しい感じになってきたので、VPSでも借りて引っ越そうという算段をしている。とりあえずGCE(Google Compute Engine)の無料枠でためそうかという流れに。
Dockerを使ってmassr本体とMongoDB、Memcachedはそれぞれ別のコンテナに入れ、reverse proxyとしてフロントに立てたnginxにhttpsをさばいてもらうという、(たぶん)昨今ではオーソドックスな構成。
で、普通に会話するくらいのところまでは問題なく動いたんだけど、フォームを経由しないLikeなんかの動作が403を返して失敗する。使い慣れないDocker上でいろいろ調べると、Rack::Csrfが403を返している。CSRFトークンが渡ってきてない。
ブラウザ上のjQuery*1はちゃんとHTTPヘッダにトークンを乗せている。でもmassrには渡ってきてない。てことは途中のnginxが怪しい?
調査過程はすっ飛ばして、結論から言えば犯人はこれである:
- xhr.setRequestHeader('X_CSRF_TOKEN', token); + xhr.setRequestHeader('X-CSRF-TOKEN', token);
こんなHTTPヘッダ、nginxが取り次いでくれるわけがない。おそらくはる~~~か昔、jQueryでAjaxをやるにあたって参考にした(いまはもう見つけられない古い)サイトからのコピペが原因だ。まぁ、HTTPヘッダなのに「_」区切りになってる時点で気づくべきだったのだ。自分が悪い。
これがなんで今まで動いていたかというと、まず(これは想像だけど)Herokuはこの手のおかしなHTTPヘッダもアプリまでちゃんと運んでくれている。で、さらに、Rack::Csrfにこんなコードがある:
def self.rackified_header "HTTP_#{@@header.gsub('-', '_').upcase}" end
このミドルウェアでは内部的に「_」を区切りに使うので、HTTPヘッダの区切りを強制的に「-」から「_」に変えているんだな。で、結果として誤ったヘッダはそのまま(変換されることなく)内部表現として扱われると。
よかれと思って書かれたコードの連鎖で本来の問題が隠れてしまうの、つらいですね……。
*1 設計が古いのでいまだにjQueryを使っている。React移植用のブランチは基本機能が動いたあたりでしばらく止まったままだ(白目)。