6.8 KiB
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-helmv1.5.6, nsenvoy-gateway-system gateway.yaml—GatewayClass eg+Gateway eg(:80plaintext,:443wildcard TLS)- Gateway API v1.3.0
- Replaced Contour on 2026-07-25 (see Why not Contour)
Adding a service
- an
HTTPRoutewithparentRefs→eg/envoy-gateway-system,sectionName: https - an A record in
samba_ad_extra_a_records→192.168.10.127(../../infrastructure/samba-ad), thenansible-playbook provision-dc.yml --tags dns - optionally a
SecurityPolicyfor 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
headersToBackendlives underextAuth.http, notextAuth. One level up the API rejects it:unknown field "spec.extAuth.headersToBackend".backendRefs.portis the SERVICE port, not the container port. The Authelia chart publishes80 → targetPort http (9091); using9091fails withTCP 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:
headersToBackendoverrides any client-supplied value ("coexisting headers will be overridden"), so a spoofedRemote-Usercannot survive the hop through Envoy.- A
NetworkPolicyper app restricts pod ingress toenvoy-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.