記contour 偶發404問題排查

最近咱們生產環境升級了 contour 到1.4.0版本,用戶反映偶發404問題。git

通過簡單測試,只在經過瀏覽器訪問啓用了https的網站上會偶發404。github

咱們的一個項目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相關。api

此時萬能的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的一個bug(Envoy does not adhere to HTTP/2 RFC 7540)致使的。服務器

大體原理是:瀏覽器會很是積極地複用HTTP/2鏈接:當瀏覽器打開與www.example.com的鏈接並在TLS握手期間顯示*.example.com的證書時,它將在全部狀況下從新使用此鏈接,只要主機名解析爲相同的IP(某些瀏覽器甚至不關心它),全部的請求都被路由到*.example.comapp

只要全部*.example.com主機名都由同一偵聽器/過濾器鏈提供服務,這在Envoy中就不成問題,由於路由是基於每一個請求而不是每一個鏈接進行的。框架

可是,若是www.example.com(帶有*.example.com證書)由一個偵聽器/過濾器鏈提供服務,而app.example.com由另外一個偵聽器/過濾器鏈提供服務,則存在問題,由於鏈接是在鏈接的整個生命週期內都鎖在單個偵聽器/過濾器鏈上,若是首先創建了與www.example.com的鏈接,則對app.example.com的請求將使用www.example.com的配置在同一鏈接上合併,而後轉發到錯誤的後端。測試

很惋惜,目前envoy並無修復。不過社區給出了兩種解決方案。

  • 一種解決方案是將421錯誤定向的請求響應發送給未在給定偵聽器/過濾器鏈上配置的主機名請求(可是若是配置了*.example.com,則將不起做用),或者將421錯誤定向的請求響應發送給請求用於在其餘偵聽器/過濾器鏈上配置的主機名(但這須要全部已配置主機名的全局列表)。
  • 另外一種解決方案是使用HTTP/2 ORIGIN框架(RFC8336)在給定的偵聽器/過濾器鏈上廣播容許的主機名(但這也須要全局列表,而且只有少數客戶端支持此擴展名)。

對於第一種方案,大體三種思路:

  • 若是您能夠爲RBAC過濾器DENY指定HTTP響應代碼,該怎麼辦?而後,配置了HCM的管理服務器能夠爲其在該HCM上容許的服務器名稱添加RBAC策略,並在DENY上生成421。
  • 管理服務器能夠在Lua過濾器中對SNI服務器名稱檢查進行編程,若是不匹配則生成421。
  • 添加可使用可接受的SNI服務器名稱配置的專用過濾器。

contour 選擇envoy做爲數據層,避不開該問題,團隊最終選擇了第二種思路來解決。

利用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路由專用於惟一的虛擬主機名。可是,若是使用通配符證書,即便完整的原始主機名並不匹配,瀏覽器也會積極合併並重用服務器鏈接。這就會發生404的錯誤響應,由於每一個TLS虛擬主機只有一個路由到一個主機上。

若是不匹配虛擬主機的FQDN,經過生成421來避免這種行爲泄露給用戶。在這種狀況下,應該使用瀏覽器瞭解該請求未獲得處理,而後將其從新發送到新的鏈接。

固然這只是一個臨時方案,真正的解決還須要envoy的完全修復。

相關文章
相關標籤/搜索