ALBとECS間がHTTP通信のときのRailsのforce_sslの挙動

SSL終端がALBのとき、Railsのforce_sslがどのように動作するのか疑問に思ったので調べてみました。

こちらの記事を大いに参考にさせていただきました。

ALB + RailsでSSLを有効にする

force_ssl における動作

ALBにhttpsでリクエストするとALBがX-Forwarded-Proto: https ヘッダを追加してRailsにリクエストしてくれます。

HTTP ヘッダーと Application Load Balancer - Elastic Load Balancing

そのヘッダを用いてRackの内部ではSSL判定がなされています。

https://github.com/rack/rack/blob/3-0-stable/lib/rack/request.rb#L410

      def ssl?
        scheme == 'https' || scheme == 'wss'
      end

https://github.com/rack/rack/blob/3-0-stable/lib/rack/request.rb#L249

      def scheme
        if get_header(HTTPS) == 'on'
          'https'
        elsif get_header(HTTP_X_FORWARDED_SSL) == 'on'
          'https'
        elsif forwarded_scheme
          forwarded_scheme
        else
          get_header(RACK_URL_SCHEME)
        end
      end

https://github.com/rack/rack/blob/3-0-stable/lib/rack/request.rb#L713

      def forwarded_scheme
        forwarded_priority.each do |type|
          case type
          when :forwarded
            if (forwarded_proto = get_http_forwarded(:proto)) &&
               (scheme = allowed_scheme(forwarded_proto.last))
              return scheme
            end
          when :x_forwarded
            x_forwarded_proto_priority.each do |x_type|
              if header = FORWARDED_SCHEME_HEADERS[x_type]
                split_header(get_header(header)).reverse_each do |scheme|
                  if allowed_scheme(scheme)
                    return scheme
                  end
                end
              end
            end
          end
        end

        nil
      end

ヘルスチェックでリダイレクトを回避する

ALB + RailsでSSLを有効にする

にもあるように、HTTPで通信かつステータス200で成功判定している場合はforce_sslを有効化するとリダイレクトしてしまうので、 ELB-HealthCheckerをUAに含む場合リダイレクトしない設定をすると良いようです。

request specをどう書くべきか

下の記事のように、ActionDispatch::Integration::RequestHelpersのヘルパーメソッドをオーバーライドするといいみたいです。

Request Specでデフォルトのヘッダーを設定する | Ver.1.0

オプションは他にもあるので、下記のようにpathとheader以外は可変長引数にした方がいいです。

module RequestSpecHelper
  [:get, :post, :patch, :put, :delete, :head].each do |name|
    define_method(name) do |path, headers: {}, **args|
      super(path, headers: headers.merge({ "X-Forwarded-Proto": "https" }), **args)
    end
  end
end

大抵のケースは上記でカバーできる一方で、Specが特定条件下で使用しているCookieJarがSecure Cookieをサポートしていないため、Secure Cookieが絡むテストが一部NGになることがあります。このようなケースを考慮するとテスト環境ではforce_ssl: false にした方が無難そうです。

Signed cookies not available in controller specs - rspec-rails 3.5.0, rails 5.0 · Issue #1658 · rspec/rspec-rails · GitHub