読者です 読者をやめる 読者になる 読者になる

リア充爆発日記

You don't even know what ria-ju really is.

オレオレ証明書でsslをaction単位で、かつnginx + unicorn + rails

ステージング環境とかで需要があるかもしれない、ぶっちゃけ証明書がオレオレかどうかはあまり関係のない話。

アプリの対応

全体的にSSLにするならconfig/enviroments/あたりにconfig.force_ssl = trueしておけばいいのだけど、action単位でforce_sslしたい場合はこんな感じで対応する。

controllers/application_controller.rb

  def force_ssl(options = {})
    host = options.delete(:host)
    unless request.ssl? or Rails.env.development?
      redirect_options = {protocol: 'https://', status: :moved_permanently}
      redirect_options.merge!(host: host) if host
      flash.keep
      redirect_to redirect_options and return
    else
      true
    end
  end

設定例

class UsersController < ApplicationController
  before_filter :force_ssl, only: [:new, :create]

こんな感じ。

証明書準備

オレオレでやる場合は、適当なディレクトリで

openssl genrsa -des3 2048 > server.key
openssl rsa -in server.key -out server.key
openssl req -new -key server.key > server.csr
openssl x509 -in server.csr -days 3650 -req -signkey server.key > server.crt

パスフレーズはテキトウで、質問コーナーも全部enterで。

nginxに設定

もともとhttpで動いていた設定にsslの設定を追加するという前提。

upstream your_app {
  server unix:/var/www/your-web/shared/unicorn.sock;
}

server {
  listen 80;
  listen 443 ssl;
  server_name example.com;

  ssl_certificate /etc/nginx/ssl/server.crt;
  ssl_certificate_key /etc/nginx/ssl/server.key;

  root /var/www/your-web/public;
  access_log /var/www/your-web/current/log/access.log;
  error_log /var/www/your-web/current/log/error.log;
  location ~ ^(/assets/|/audios/|/system/) {
    gzip_static on;

    root /var/www/your-web/current/public;
    expires 1y;
    add_header Cache-Control public;

    add_header ETag "";
    break;
  }

  location / {
    proxy_set_header X-Forwarded-Proto $scheme;
    if (-f $request_filename) { break; }
    # ファイルが存在しなければunicornにproxyする
    proxy_set_header X-Real-IP  $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_pass http://your_app;
  }

}

足したのは

~snip~
  listen 443 ssl;
~snip~
  ssl_certificate /etc/nginx/ssl/server.crt;
  ssl_certificate_key /etc/nginx/ssl/server.key;
~snip~
  location / {
    proxy_set_header X-Forwarded-Proto $scheme;
~snip~

httpとsslの設定を混ぜて設定する場合は最後のproxy_set_headerが必要。これがないとrailsはリクエストがssl-terminatedなやつかどうか判断つかなくて無限リダイレクトに突入するから。
http://stackoverflow.com/questions/9448168/why-am-i-getting-infinite-redirect-loop-with-force-ssl-in-my-rails-app


追記)
これだと一回SSLのページに行ってあと、どこにいってもSSLの状態がキープされてしまい、だったら全部SSLでもいいんじゃね?という状態になる。なのでトップページなど、SSLでアクセスしてほしくないページを逆にHTTPに矯正するようにするforce_httpも作った。うーん。でもなんかアレな感じがするなぁ。

  def force_http(options = {})
    host = options.delete(:host)
    if request.ssl?
      redirect_options = {protocol: 'http://'}
      redirect_options.merge!(host: host) if host
      flash.keep
      redirect_to redirect_options and return
    else
      true
    end
  end