備忘録、はじめました。

作業したこと忘れないようにメモっておきます。

Nginx (reverse proxy) + Phoenix でschemeを強制的にhttpsにする

memo: たどり着くのに時間が掛かったで残しておくことにした.

課題

  • oauth認証ueberauthのライブラリを使っていた
  • callback urlを動的に生成させるときに Plug.Connschemeを参照している
  • backend側のサーバーはhttpで動かしたい
  • それだと Plug.Connのschemeはhttpなので、https のcallback_urlを生成できない

解決方法

Plug.SSLのプラグを使って強制的にhttpsschemeを変更しました.

以下、phoenix側のconfigの設定です.

config :my_web, MyWeb.Endpoint,
  url: [host: "localhost", scheme: "https", port: 443],
  force_ssl: [
    host: nil,
    rewrite_on: [:x_forwarded_proto]
  ]

上記のように :force_ssl の設定を追加します. (参考はここ Using SSL – Phoenix v1.3.0-rc.2.)

Forcing requests to use SSL:

In many cases, you’ll want to force all incoming requests to use SSL by redirecting http to https. This can be accomplished by setting the :force_ssl option in your endpoint. It expects a list of options which are forwarded to Plug.SSL. By default it sets the “strict-transport-security” header in https requests, forcing browsers to always use https. If an unsafe request (http) is sent, it redirects to the https version using the :host specified in the :url configuration.

記述を読むと、endpointのconfigの:force_sslの設定がPlug.SSLのオプションになるようです.

また、nginx側は以下の設定が必要です.

        ...

        location / {

            ...

            proxy_set_header X-Forwarded-Proto $scheme; # ここの一行が必要

            proxy_pass http://backend/;
        }

        ...

これは、Plug.SSLの仕様で :rewrite_on に設定したヘッダー名がrewriteされるためです.

:rewrite_on - rewrites the scheme to https based on the given headers

なので、そのヘッダーをproxy_set_headerしています.

(apacheなどの他のproxyサーバーでも同様な設定を入れれば期待する挙動になると思います.)

Plug.Connの確認

%Plug.Conn{
  adapter: {Plug.Adapters.Cowboy.Conn, :...},
  assigns: %{},
  before_send: [#Function<0.116269836/1 in Plug.CSRFProtection.call/2>,
   #Function<4.117387578/1 in Phoenix.Controller.fetch_flash/2>,
   #Function<0.58261320/1 in Plug.Session.before_send/2>,
   #Function<1.112466771/1 in Plug.Logger.call/2>,
   #Function<0.61641163/1 in Phoenix.LiveReloader.before_send_inject_reloader/2>],
  body_params: %{},
  cookies: %{
    "_my_web_session_id" => "***"
  },
  halted: false,
  host: "localhost",
  method: "GET",
  owner: #PID<0.467.0>,
  params: %{},
  path_info: [],
  path_params: %{},
  port: 80,
  private: %{
    ***.Router => {[], %{}},
    :phoenix_action => :index,
    :phoenix_controller => ***.PageController,
    :phoenix_endpoint => ***.Endpoint,
    :phoenix_flash => %{},
    :phoenix_format => "html",
    :phoenix_layout => {***.LayoutView, :app},
    :phoenix_pipelines => [:browser],
    :phoenix_router => ***.Router,
    :phoenix_view => ***.PageView,
    :plug_session => %{"_csrf_token" => "gnjVVSwCXhyfv4pu4rBfVA=="},
    :plug_session_fetch => :done
  },
  query_params: %{},
  query_string: "",
  remote_ip: {172, 21, 0, 6},
  req_cookies: %{
    "_my_web_session_id" => "***"
  },
  req_headers: [
    {"host", "localhost"},
    {"x-forwarded-for", "172.21.0.1"},
    {"x-real-ip", "172.21.0.1"},
    {"x-forwarded-proto", "https"},
    {"cache-control", "max-age=0"},
    {"upgrade-insecure-requests", "1"},
    {"user-agent",
     "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36"},
    {"accept",
     "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8"},
    {"referer", "https://localhost/"},
    {"accept-encoding", "gzip, deflate, br"},
    {"accept-language", "ja-JP,ja;q=0.9,en-US;q=0.8,en;q=0.7"},
    {"cookie",
     "_my_web_session_id=***"}
  ],
  request_path: "/",
  resp_body: nil,
  resp_cookies: %{},
  resp_headers: [
    {"cache-control", "max-age=0, private, must-revalidate"},
    {"x-frame-options", "SAMEORIGIN"},
    {"x-xss-protection", "1; mode=block"},
    {"x-content-type-options", "nosniff"},
    {"x-download-options", "noopen"},
    {"x-permitted-cross-domain-policies", "none"}
  ],
  scheme: :https,
  script_name: [],
  secret_key_base: :...,
  state: :unset,
  status: nil
}

scheme: の部分が https であることが確認できました.

参考