記contour偶発404問題調査


最近、デルの本番環境はcontourから1.4.0にアップグレードされ、ユーザーは偶発的な404の問題を反映しています.
簡単なテストの結果、ブラウザを介してhttpを有効にしたWebサイトにアクセスするだけで404が偶発的に発生します.
私たちのプロジェクトhttpproxyは以下の通りです.
apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
  name: hawkeye-grafana
  namespace: sgt
spec:
  virtualhost:
    fqdn: hawkeye.xx.me
    tls:
      secretName: https-xx-me-new
  routes:
    - conditions:
      - prefix: /
      services:
        - name: hawkeye-grafana
          port: 80

envoyのaccesslogを表示すると、次のように表示されます.
[2020-04-26T22:28:27.120Z] "GET / HTTP/2" 404 NR 0 0 0 - "10.107.8.251" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.122 Safari/537.36" "45fc064a-3124-47f2-b49e-cffb6de031e9" "hawkeye.xx.me" "-"

SNI関連と考えられる.
このとき万能のgithubで検索してみます.やはりもう他の人が穴を踏んだ.
I've found a similar behavior after upgrading as well. It appears to be related to http2 connection coalescing. The SNI (envoy authority ) will not match the requested host name and Envoy will 404 the connection similar to how it behaves in
#1493 . So far I've only seen it impact users on Mozilla/Firefox which fits since it appears to have the most aggressive connection coalescing from what I've read. Pretty certain this is due to
#2381 , but given the
Envoy CVE it probably shouldn't be reverted until Envoy comes up with a fix.
I was able to work around it by issuing separate certs for each virtualhost and updating the httpproxy to use the cert for that virtualhost instead of using a wildcard that covered them all.
本質的にenvoyの1つのバグ(Envoy does not adhere to HTTP/2 RFC 7540)によるものである.
基本的な原理は、ブラウザがHTTP/2接続を非常に多重化することである.ブラウザがwww.example.comとの接続を開き、TLS握手中に*.example.comの証明書を表示すると、ホスト名が同じIP(一部のブラウザーはそれに関心を持たない)と解析される限り、すべての要求が*.example.comにルーティングされる.
すべての*.example.comホスト名が同じリスナー/フィルタチェーンによってサービスされる限り、ルーティングは各接続ではなく各要求に基づいて行われるので、Envoyでは問題ありません.
しかし、www.example.com(*.example.com証明書付き)が1つのリスナー/フィルタチェーンによってサービスを提供し、app.example.comが別のリスナー/フィルタチェーンによってサービスを提供する場合、接続は接続のライフサイクル全体にわたって1つのリスナー/フィルタチェーンにロックされているため、www.example.comとの接続が最初に確立された場合、app.example.comに対する要求はwww.example.comの構成を使用して同じ接続に統合されるため、問題がある.その後、エラーのバックエンドに転送します.
残念ながらenvoyは修復されていません.しかし、コミュニティは2つのソリューションを提供しています.
  • の1つの解決策は、421が誤って指向化された要求応答を、所与のリスナー/フィルタチェーン上で構成されていないホスト名要求に送信するか(ただし、*.example.comが構成されている場合は機能しない)、または421が誤って指向化された要求応答を、他のリスナー/フィルタチェーン上で構成するために要求されたホスト名に送信するか(ただし、これは、すべての構成されたホスト名のグローバルリストが必要である).
  • の別の解決策は、HTTP/2 ORIGINフレームワーク(RFC 8336)を使用して、所与のリスナー/フィルタチェーン上で許可されたホスト名をブロードキャストすることである(ただし、グローバルリストも必要であり、少数のクライアントのみがこの拡張子をサポートする).

  • 第1の案については、大体3つの考え方があります.
  • RBACフィルタDENYにHTTP応答コードを指定できたらどうしますか?その後、HCMが構成された管理サーバは、そのHCM上で許可されたサーバ名にRBACポリシーを追加し、DENY上で421を生成することができる.
  • 管理サーバは、LuaフィルタにおいてSNIサーバ名チェックをプログラムすることができ、一致しなければ421を生成する.
  • は、許容可能なSNIサーバ名を使用して構成できる専用フィルタを追加する.

  • contourはenvoyをデータ層として選択し、この問題を避けることができず、チームは最終的に第2の考え方を選んで解決した.
    luaを用いてTLSエラーリクエストのフィルタを実現した.
    具体的なコードは以下の通りです.
    func FilterMisdirectedRequests(fqdn string) *http.HttpFilter {
        code := `
    function envoy_on_request(request_handle)
        local headers = request_handle:headers()
        local host = headers:get(":authority")
        if host ~= "%s" then
        request_handle:respond({
            [":status"] = "421",
            },
            ""
        )
        end
    end
    `
    
        return &http.HttpFilter{
            Name: "envoy.filters.http.lua",
            ConfigType: &http.HttpFilter_TypedConfig{
                TypedConfig: protobuf.MustMarshalAny(&lua.Lua{
                    InlineCode: fmt.Sprintf(code, fqdn),
                }),
            },
        }
    }

    TLSルーティングは、一意の仮想ホスト名に専用です.ただし、ワイルドカード証明書を使用すると、元のホスト名が一致しなくても、ブラウザはサーバ接続を積極的に統合して再利用します.これは、各TLS仮想ホストが1つのホストにルーティングされているだけであるため、404のエラー応答が発生する.
    仮想ホストのFQDNに一致しない場合、421を生成することによって、このような動作がユーザに漏れることを回避する.この場合、ブラウザを使用してリクエストが処理されていないことを理解し、新しい接続に再送信する必要があります.
    もちろんこれは一時的な案にすぎず、本当の解決にはenvoyの徹底的な修復が必要だ.