diary

I like Hatena Star with a text selection.

2021-06-09

ピアノレッスンだった。愛の挨拶、まずは左手を重点的に練習しよう、ということで左手をずっと練習してたらちょっと弾けるような気がしてきた。


Railsとetagについて調べていたのを軽く社内ブログにまとめたのを雑にアウトプットしておく。

etagとは

うちのRails appではetagを付与はしているけど、HTMLを返すときは意味がなくなっている。

多分etagを付与しているのはRack::ETagさん。 https://github.com/rack/rack/blob/d0c6efc666ede26768f33935f00530629690369a/lib/rack/etag.rb

これはresponse bodyのSHA256をetagとしている。

Rails appのCSRFトークンはリクエストのたびにランダム。これはBREACH攻撃によってCSRFトークンを盗み出されないようにするため。

BREACH攻撃については https://blog.ohgaki.net/breach-attack-explained-why-and-how を読んだ。 雑に説明すると、暗号化の圧縮効率を利用した攻撃。任意のテキストをレスポンスに含めることができて、そのレスポンス(暗号化されている)を入手できれば攻撃が成立する。盗み取りたいデータ(ここではCSRFトークン)が毎回固定ならば、テキストをランダムに付与してレスポンスを得て、そのレスポンスの圧縮後のサイズが一番小さいテキストがCSRFトークンに一致するらしい。

CSRFがどうランダムになっているのかは https://techracho.bpsinc.jp/hachi8833/2017_10_23/46891 を読んだ。Base64(nonce + (token XOR nonce))CSRFトークンとして渡す、という話っぽい。

脱線した。 CSRFトークンがランダムなので、response bodyのSHA256は当然毎回異なる。そのため、CSRFトークンが埋め込まれたHTMLは絶対にetagがマッチすることはない。

あとKibelaではHypernovaのIDもランダムに生成されていたのでそれも障害になっていた。

Railsはcontrollerからetagを渡すようなメソッドも用意しているのだけど、どう考えてもアクション1つが依存するキャッシュキーをすべて正確に抜き出すのはだるい。そしてそのだるさを頑張ったとしても、スキップできるのはresponse body分の通信帯域とviewを組み立てるコストぐらいで、割に合わなさそうな感じがしている。

# application_record.rb
class ApplicationRecord < ActiveRecord::Base
  after_commit -> { Rails.cache.write 'global-etag', Time.zone.now.to_f }
end

# application_controller.rb
class ApplicationController < ActionController::Base
  before_action -> {
    etag = Rails.cache.fetch('global-etag') { Time.zone.now.to_f }
    fresh_when etag: etag
  }
end

キャッシュキーを考えるのは大変すぎるので、DBのレコードが1つでも更新されたらパージされるキャッシュキーを作る、というのも思いついた。 まあこれも色々考えると大変そうだから、実際にproductionに入れるのはつらいかなあ。Rails.cacheの更新頻度が大丈夫なのかとか、まあ色々ある。