最近咱們生產環境升級了 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 (envoyauthority
) 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 thehttpproxy
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.com
。app
只要全部*.example.com
主機名都由同一偵聽器/過濾器鏈提供服務,這在Envoy中就不成問題,由於路由是基於每一個請求而不是每一個鏈接進行的。框架
可是,若是www.example.com
(帶有*.example.com
證書)由一個偵聽器/過濾器鏈提供服務,而app.example.com
由另外一個偵聽器/過濾器鏈提供服務,則存在問題,由於鏈接是在鏈接的整個生命週期內都鎖在單個偵聽器/過濾器鏈上,若是首先創建了與www.example.com
的鏈接,則對app.example.com
的請求將使用www.example.com
的配置在同一鏈接上合併,而後轉發到錯誤的後端。測試
很惋惜,目前envoy並無修復。不過社區給出了兩種解決方案。
*.example.com
,則將不起做用),或者將421錯誤定向的請求響應發送給請求用於在其餘偵聽器/過濾器鏈上配置的主機名(但這須要全部已配置主機名的全局列表)。對於第一種方案,大體三種思路:
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的完全修復。