Envoy Gateway TLS 與安全:把 HTTPS 接起來,別讓 API 裸奔

7 min read

HTTP 跑通只是暖身。只要你的服務不是活在石器時代,正式環境幾乎一定要上 HTTPS。這篇就來把 Envoy Gateway 的 TLS 基礎打好,避免你的 API 還在網路上裸奔。

TLS 在 Gateway 裡是怎麼放的

最常見的做法是:在 Gateway listener 做 TLS termination。

也就是:

  • client 用 HTTPS 打進來
  • Gateway 負責握手、解憑證
  • 後面再把流量轉給 Service

一個基本 HTTPS listener 長這樣:

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: eg
spec:
  gatewayClassName: eg
  listeners:
    - name: http
      protocol: HTTP
      port: 80
    - name: https
      protocol: HTTPS
      port: 443
      tls:
        mode: Terminate
        certificateRefs:
          - kind: Secret
            group: ""
            name: example-cert

這段的重點只有兩個:

  • protocol: HTTPS
  • tls.mode: Terminate

意思就是:TLS 在這裡結束,由 Gateway 來處理憑證。

HTTPS + HTTPRouteTLSRoute 有什麼不同

這一段很重要,因為很多人第一次看到 TLSRoute 會想問:

「不是已經有 HTTPS listener 了嗎?為什麼還要多一個 TLSRoute?」

差別在於 Gateway 有沒有終止 TLS

場景 常見組合 說明
Gateway 終止 TLS,之後再看 HTTP 規則 HTTPS listener + HTTPRoute 最常見網站/API 場景
Gateway 不解密,只根據 SNI 把加密流量轉送出去 TLS listener + TLSRoute TLS passthrough 場景

也就是說:

  • 你想在 Gateway 看 pathheadermethod,通常就得先終止 TLS,然後搭 HTTPRoute
  • 你想保留端到端加密,不在 Gateway 解密,通常才會考慮 TLSRoute

這兩種做法沒有誰高級誰低級,重點是你要先知道自己要的是「終止 TLS 後做 L7 路由」,還是「保留加密只做 SNI 分流」。

先準備憑證 Secret

在測試環境,可以先用自簽憑證。官方範例也是這樣示範:

openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 \
  -subj '/O=example Inc./CN=example.com' \
  -keyout example.com.key \
  -out example.com.crt
 
openssl req -out www.example.com.csr -newkey rsa:2048 -nodes \
  -keyout www.example.com.key \
  -subj "/CN=www.example.com/O=example organization"
 
openssl x509 -req -days 365 -CA example.com.crt -CAkey example.com.key \
  -set_serial 0 -in www.example.com.csr -out www.example.com.crt

把 cert/key 存成 Kubernetes Secret:

kubectl create secret tls example-cert \
  --key=www.example.com.key \
  --cert=www.example.com.crt

之後 Gateway 就能透過 certificateRefs 指到這個 Secret。

⚠️ 自簽憑證只適合測試。正式環境請搭配 cert-manager 或你的既有憑證管理流程,不要把 demo 流程直接搬進 production,這種事跟把泡麵當營養餐一樣,偶爾可以,長期不行。

把 HTTPS listener 加到既有 Gateway

如果你前一篇已經有 eg Gateway,可以直接 patch:

kubectl patch gateway eg --type=json --patch '
  - op: add
    path: /spec/listeners/-
    value:
      name: https
      protocol: HTTPS
      port: 443
      tls:
        mode: Terminate
        certificateRefs:
        - kind: Secret
          group: ""
          name: example-cert
'

確認狀態:

kubectl get gateway/eg -o yaml

如果 listener 正常,接著就可以打 HTTPS 測試:

curl -v -HHost:www.example.com \
  --resolve "www.example.com:443:${GATEWAY_HOST}" \
  --cacert example.com.crt \
  https://www.example.com/get

這條指令很適合本地驗證,尤其在 DNS 還沒正式接好時特別有用。

一個 Gateway 可以掛多個 HTTPS 網域

如果你有多個網域,可以在同一個 Gateway 裡加多個 HTTPS listener。
例如再加一個 foo.example.com

listeners:
  - name: https-main
    protocol: HTTPS
    port: 443
    hostname: www.example.com
    tls:
      mode: Terminate
      certificateRefs:
        - name: example-cert
  - name: https-foo
    protocol: HTTPS
    port: 443
    hostname: foo.example.com
    tls:
      mode: Terminate
      certificateRefs:
        - name: foo-cert

然後記得對應的 HTTPRoute 也要補 hostname,不然入口雖然聽得到,路由還是不知道該往哪裡送。

TLSRoute 的心智模型

如果你要的是 TLS passthrough,TLSRoute 的想法可以先記成這樣:

💡 TLSRoute 在不同 Gateway API CRD 版本中的 apiVersion 可能不同,下面先當作概念示意。實際套用時,請以你叢集已安裝的 Gateway API 版本為準。

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: eg-tls
spec:
  gatewayClassName: eg
  listeners:
    - name: tls
      protocol: TLS
      port: 443
---
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: TLSRoute
metadata:
  name: passthrough-route
spec:
  parentRefs:
    - name: eg-tls
  hostnames:
    - "db.example.com"
  rules:
    - backendRefs:
        - name: db-service
          port: 5432

這類場景通常是:

  • 你只根據 SNI hostname 分流
  • 加密流量一路保留到 backend
  • Gateway 不解析 HTTP path/header

所以 TLSRouteHTTPRoute 最大的差別,不是都叫 route,而是它們看到的流量層級根本不同。

跨 Namespace 憑證引用要注意什麼

官方文件特別提到一個很實用的能力:
Gateway 可以引用其他 namespace 的憑證 Secret,但需要 ReferenceGrant

例如平台團隊把憑證統一放在 envoy-gateway-system,應用 Gateway 在 default,這時就不能直接亂指,必須先開授權。

範例:

apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
  name: allow-default-to-read-cert
  namespace: envoy-gateway-system
spec:
  from:
    - group: gateway.networking.k8s.io
      kind: Gateway
      namespace: default
  to:
    - group: ""
      kind: Secret

這代表:

  • default namespace 的 Gateway
  • 可以合法引用 envoy-gateway-system 裡的 Secret

沒有 ReferenceGrant 時,這種 cross-namespace reference 會被視為無效。這是保護機制,不是 Gateway API 故意刁你。

一句話總結

Envoy Gateway 的 TLS 其實沒有你想像中玄學:準備好憑證 Secret,把 HTTPS listener 設好,再確認 route 與 hostname 對得上,基本上就成了。

💡 初學者常犯的錯不是 TLS 太難,而是三件事沒對齊:hostnamecertificateRefsHTTPRoute hostnames。這三個只要有一個歪掉,流量就會開始演你。

下一步

學會安裝、概念、路由跟 TLS 後,下一篇先來收斂成正式環境比較不容易翻車的做法: 👉 最佳實踐