Rails
nginx
Security
cookie

Railsエンジニアが安心して新年を迎える方法

More than 3 years have passed since last update.

この記事は ドワンゴ Advent Calendar 2014 20日目の記事です

新しい年を迎える準備

もうあと10日もしたら新年です。
みなさん新年を迎える準備はできているでしょうか?
ん? その前に大事なイベント? コミケのことかな???
正月の準備といえば、家を大掃除し、しめ飾りや門松を飾り、餅を撞き、おせちを作り、色々ありますが、
これはすべて神様を迎え入れるための準備なんですね。
年神様という、元日に各家に訪れ、一年の実りと幸せをもたらしてくれるありがたい神様です。
年神様を心から歓迎し、失礼ないようおもてなしすれば、次の一年の幸福と繁栄とマネタイズは約束されたも同然です。

さて、今年は、Railsエンジニアが年神様をお迎えするにあたって、
その前に供物を捧げ荒ぶる現世神の怒りを鎮めなければなりません。
すなわち、今年のうちに secure属性がついたcookie を現世神にお供えしなければならないのです。

secure属性とは

cookieはHTTP Headerにつけて送られるので、https通信ならばcookieも暗号化されます。
逆に言うと、httpで通信してしまうとcookieの中身が暗号化されずにネットワークに流れてしまうということです。
これでは中間者攻撃されていたらcookieの中身が漏れてしまいますね。
いくらhttpをhttpsにリダイレクトしていても、最初のhttpリクエストにcookieが乗ってしまったらその段階でアウトです。
これを防ぐために、cookieにはsecure属性というのがあり、secure属性のついたcookieはhttps接続の時にしか送信されないようになります。
認証情報などの重要なcookieはsecure属性を付けないといけません。

Railsのセッション

Railsで直接cookieを使うことはほとんど無いと思いますが、
セッション情報はCookieに保存されるのでsecure化が必須です。
https://sp.book.nicovideo.jp/ の場合は _umaru_session というcookieがそうです。
Railsのデフォルトはすべてのセッション情報がCookieに暗号化されて格納されます。

ちなみにうまるちゃんアニメ化が決定しました。すでに最高の一年が約束されていますね。きりえちゃんが好きです。

Railsのセッションをsecure化するのはとても簡単。

config/initializers/session_store.rbsession_store メソッドを呼んでいるところに

secure: true

のオプションを追加してやるだけです。
実際には

secure: Rails.env.production?

を追加するのが一番いいでしょう。

secureオプションを有効にすると

  • _hoge_sessionのcookieがsecure属性になる
  • httpで接続した場合は_hoge_sessionのcookieが書かれない

ようになります。
httpで接続したらセッションは効かないし、新たに発行されることもない安全設計です。
もうみんなフルhttpsですよね?問題ないですよね?

なんだこれだけか!簡単だな!!!
軽率にデプロイするとたぶん死にます

NginxとリバースプロキシとSSL

その原因はNginxとの通信。
大抵の人はRailsアプリで直接接続を受けず、Nginxをフロントに置きリバースプロキシにしていると思います。
port指定であれUnixソケット経由であれ、そのままではNginxはhttp接続でRailsアプリにアクセスします。
Nginxまではhttpsで繋いでも、肝心のNginxがhttpで繋いでしまうので、Railsアプリは「httpだ! cookieなんて出してやんねえ!!」となっていまします。
なんとかしてRailsアプリにhttpsであることを伝えなければなりません。

で、Railsのソースを辿って行くと、Rackがhttpsを判定していることがわかります。

rack/request.rb

def scheme
  if @env['HTTPS'] == 'on'
    'https'
  elsif @env['HTTP_X_FORWARDED_SSL'] == 'on'
    'https'
  elsif @env['HTTP_X_FORWARDED_SCHEME']
    @env['HTTP_X_FORWARDED_SCHEME']
  elsif @env['HTTP_X_FORWARDED_PROTO']
    @env['HTTP_X_FORWARDED_PROTO'].split(',')[0]
  else
    @env["rack.url_scheme"]
  end
end

Nginxの設定でこの中のどれかをproxy_set_headerで付けてやりましょう。
ここまで設定してデプロイすると、https接続時にだけSet-Cookiesecure設定されたcookieが送られてくるのが見れるはずです。一旦確認しておきましょう。
http接続時でもsecureなcookieがSet-CookieされていたらNginxの設定が間違っています。

http接続をhttpsにリダイレクト

conf/environments/production.rbforce_ssl = trueを設定すればhttpをリダイレクトしてくれます。
でもどうせNginxを噛ますなら、Nginxでリダイレクトしちゃいましょう。

server {
        listen 80;
        server_name example.com;
        return 301 https://$host$request_uri;
}

これでhttp接続が全部httpsにリダイレクトされます。
ここまで設定できたらこんにちわ〜o(^^)oされることはたぶん無いでしょう・・・
安心して新年を迎えることができると思います。クッキー☆

おわりに

この時期になればコミケの原稿も終わってんだろと高をくくっていたら全く終わる気配を見せません。
マジヤバイです。
新年の準備どころじゃないです。
とりあえず売り子を手伝ってくれる美少女(自称可)を募集しています。
3日目く22-bです。