Files
homelab-infra/platform/envoy-gateway/README.md
T
panxiao81 4070b75436
yaml / yaml (pull_request) Successful in 10s
feat(flux): 激活 Envoy Gateway 接管
2026-09-10 09:45:25 +00:00

6.8 KiB
Raw Blame History

Envoy Gateway — LAN ingress + Authelia forward-auth

The cluster's HTTP entry point for services on ad.ddupan.top, and the enforcement point for Authelia authentication. Envoy's LoadBalancer holds 192.168.10.127 (k3s ServiceLB); routing is by Host header.

  • Chart: oci://docker.io/envoyproxy/gateway-helm v1.5.6, ns envoy-gateway-system
  • gateway.yaml — GatewayClass eg + Gateway eg (:80 plaintext, :443 wildcard TLS)
  • Gateway API v1.3.0
  • Replaced Contour on 2026-07-25 (see Why not Contour)

Adding a service

  1. an HTTPRoute with parentRefs → eg / envoy-gateway-system, sectionName: https
  2. an A record in samba_ad_extra_a_records → 192.168.10.127 (../../infrastructure/samba-ad), then ansible-playbook provision-dc.yml --tags dns
  3. optionally a SecurityPolicy for Authelia forward-auth — see ../../apps/netbox

No certificate work: the :443 listener already serves the *.ad.ddupan.top wildcard from ../cert-manager. Worked example: ../../apps/netbox.

Why not Contour

Contour worked as an ingress, but supports only the gRPC Envoy ext_authz protocol — "Only the Envoy GRPC authorization protocol will be supported" (Contour 1.33 docs). Authelia implements the HTTP ExtAuthz filter. The two cannot meet, so Authelia forward-auth was impossible.

That mattered because NetBox has no SSO group→role mapping — its group/superuser mapping is LDAP-only, and the OIDC pipeline can only assign one static group. Without forward-auth the options were "promote every user by hand" or "drop SSO and use LDAP, losing 2FA". Envoy Gateway's SecurityPolicy.extAuth.http removes the dilemma.

Envoy Gateway was chosen over Traefik because everything already built — Gateway, HTTPRoute, wildcard cert, DNS — is Gateway API, so only the GatewayClass and controller changed. Traefik would have meant reverting to IngressRoute + Middleware.

Forward-auth: how it fits together

browser ──▶ Envoy ──(ext_authz HTTP)──▶ Authelia ──▶ 200 + Remote-* headers
                │                             └──▶ 401 ⇒ 302 to auth.ddupan.top
                └──▶ upstream app (headers attached)

Pieces, each in the directory that owns it:

where what
../../apps/netbox/securitypolicy.yaml SecurityPolicy targeting the app's HTTPRoute
../../apps/authelia/referencegrant-extauth.yaml lets a SecurityPolicy in another namespace reference the Authelia Service
../../apps/authelia/values.yaml server.endpoints.authz.ext-authz + an access_control rule
../../apps/netbox/networkpolicy.yaml stops anything bypassing Envoy to reach the app directly

Three details that cost time

  • headersToBackend lives under extAuth.http, not extAuth. One level up the API rejects it: unknown field "spec.extAuth.headersToBackend".
  • backendRefs.port is the SERVICE port, not the container port. The Authelia chart publishes 80 → targetPort http (9091); using 9091 fails with TCP Port 9091 not found on service authelia/authelia.
  • Authelia's session cookie is scoped to ddupan.top, which already covers *.ad.ddupan.top — so SSO works across both without touching the cookie config.

⚠ Trust boundary

Apps behind forward-auth trust Remote-* headers. Two things make that safe and both must remain true:

  1. headersToBackend overrides any client-supplied value ("coexisting headers will be overridden"), so a spoofed Remote-User cannot survive the hop through Envoy.
  2. A NetworkPolicy per app restricts pod ingress to envoy-gateway-system, so nothing in-cluster can bypass Envoy. k3s enforces NetworkPolicy (kube-router), so this is real.

Verified by test: a pod in default sending Remote-User: admin to the app Service gets connection refused, while the same request from envoy-gateway-system is served. Also set failOpen: false — if Authelia is down, refuse traffic rather than admit unauthenticated requests to an app whose auth model is "trust the header".

Install

Flux 接管状态

现有 release 为 OCI chart/app v1.5.6,且没有 user-supplied values。Git 中固定 同一版本并已完成分阶段 Flux HelmRelease 接管。第一阶段确认 Kustomization Ready、 Helm revision 保持为 1,controller/data-plane Pod UID 与入口响应均未变化后,再通过 独立 PR 解除暂停。OCI 类型 HelmRepository 是按需 chart generator,本身不产生 artifact 或 Ready condition;解除暂停后由 HelmRelease 请求并验证 chart。

GatewayClass eg 与 Gateway eg 随本目录 Kustomization 由 Flux 管理。接管期间保持 prune: false;Gateway API 与 Envoy Gateway CRD 使用 CreateReplace,延续现有 Helm 所有权且绝不通过删除 CRD 迁移。Gateway 的三个 certificateRefs 最初属于 kubectl-client-side-apply;首次 reconcile 已由 Flux 默认 SSA Override 接管这些在 Git 中声明且值相同的字段,没有改变 listener spec。不得用 force: true,它用于 不可变字段失败时删除重建资源。以下命令保留为 break-glass 手工恢复流程。

# The Gateway API CRDs may already be owned by another tool's field manager (Contour's
# quickstart used client-side apply), which makes Helm fail with
#   "conflict with kubectl-client-side-apply: .spec.versions".
# Transfer ownership WITHOUT deleting the CRDs (deleting them would delete every
# Gateway and HTTPRoute):
helm pull oci://docker.io/envoyproxy/gateway-helm --version v1.5.6 --untar
kubectl apply --server-side --force-conflicts --field-manager=helm \
  -f gateway-helm/crds/gatewayapi-crds.yaml

helm upgrade --install envoy-gateway oci://docker.io/envoyproxy/gateway-helm \
  --version v1.5.6 -n envoy-gateway-system --create-namespace

kubectl apply -f envoy-gateway/gateway.yaml

Only one LoadBalancer can hold :80/:443 — k3s ServiceLB uses hostPorts, so a second one's svclb-* DaemonSet sits at 0/1 and the Gateway stays PROGRAMMED: False until the previous controller's Service is gone.

Verify

kubectl -n envoy-gateway-system get gateway eg          # PROGRAMMED=True, ADDRESS 192.168.10.127
kubectl -n envoy-gateway-system get pods                # envoy-... 2/2
kubectl -n <ns> get securitypolicy                      # Accepted=True
curl -s -o /dev/null -w '%{http_code}\n' http://192.168.10.127/    # 404 = serving, no route matched

ss -lntp shows nothing on :80/:443 even when healthy — ServiceLB forwards via iptables/NodePort rather than binding. Test with curl, not ss.

Outstanding

Contour source manifests were removed from the clean baseline. Live leftovers may still exist and must be inventoried before deletion; do not infer live state from the archive cleanup. The legacy Git history retains the retired manifests.