House conventions port and valid listener names for wildcard certificates #1

Merged
kacerr merged 11 commits from feat/house-conventions into main 2026-10-08 20:48:32 +02:00
2 changed files with 120 additions and 0 deletions
Showing only changes of commit 6698eab1d8 - Show all commits
@@ -0,0 +1,9 @@
# Execution: Sanitize listener names derived from certificate DNS names
Plan: `docs/plans/2026-10-08-2046-listener-name-sanitize.md`
- [x] Step 0 — Save and commit the plan
- [ ] Step 1 — Regression tests first
- [ ] Step 2 — Fix
- [ ] Step 3 — Docs
- [ ] Step 4 — Ship
@@ -0,0 +1,111 @@
# Plan: Sanitize listener names derived from certificate DNS names
**Created:** 2026-10-08 20:46 CEST
## Context
The operator in the home-lab cluster fails every reconcile of
`kube-system/wildcard-home-hrajfrisbee`:
```
spec.listeners[4].name: Invalid value: "auto-wildcard-home-hrajfrisbee-*-home-hrajfrisbee-cz":
should match '^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$'
```
`listenerNameFor` (`internal/gateway/patch.go:137`) only replaces dots in the DNS
name, so `*.home.hrajfrisbee.cz` puts a `*` into the listener name. Gateway API
listener names are `SectionName`: lowercase alphanumerics, `-` and `.`, each
dot-separated label starting and ending alphanumeric, max 253 chars. Because the
patch replaces the whole listeners array, this one name blocks every other
certificate on `cilium-gateway` too.
Outcome: any DNS name cert-manager accepts yields a valid listener name, and names
that are valid today do not change (no listener churn on existing Gateways).
Lands on `feat/house-conventions` (decided), in the same MR as the conventions port.
## Design
Only the DNS-name part is rewritten; the listener `Hostname` keeps the original
value (`*.home.hrajfrisbee.cz` is a valid Gateway hostname).
`listenerNameFor(prefix, certName, dnsName)` in `internal/gateway/patch.go`:
1. Sanitize the DNS name with a new unexported `sanitizeDNSName`:
- lowercase;
- a leading `*` label becomes `wildcard` (`*.example.com` → `wildcard-example-com`);
- `.` becomes `-` (as today);
- any other character outside `[a-z0-9-]` becomes `-`;
- trim leading and trailing `-`.
2. Name is `prefix + certName + "-" + sanitized`, as today.
3. If longer than 253: cut to 244, trim trailing `-` and `.`, append `-` plus the
first 8 hex chars of the SHA-256 of the untruncated name. This replaces the
current blind `name[:253]`, which can end in `-` (invalid) and makes long names
collide.
Result for the failing cert:
`auto-wildcard-home-hrajfrisbee-wildcard-home-hrajfrisbee-cz`.
Not changed here (stay on the backlog): collisions between distinct inputs that
sanitize to the same name (e.g. `*.x.com` and `wildcard.x.com` in one cert, or the
same cert name in two namespaces), uppercase in the listener `Hostname`, and the
other items in `docs/reviews/`.
## Steps
### Step 0 — Save and commit the plan
Copy to `docs/plans/2026-10-08-2046-listener-name-sanitize.md`, create the paired
execution log, commit alone.
### Step 1 — Regression tests first
In `internal/gateway/patch_test.go`, extend `TestListenerNameFor_sanitizesDots`
(rename to `TestListenerNameFor_validSectionName`) with cases, each asserting the
exact name and that it matches the `SectionName` pattern and length:
- `*.home.hrajfrisbee.cz` with cert `wildcard-home-hrajfrisbee` (the production case);
- uppercase DNS name;
- a character outside the allowed set;
- a name over 253 chars: result is ≤ 253, valid, ends with the hash suffix, and two
long inputs sharing a 253-char prefix produce different names;
- existing plain names unchanged (already in the table).
Add a wildcard case to `TestBuildListenersFromCertificates_names` and assert in
`_listenerFields` style that the hostname stays `*.example.com`. Run and confirm the
new cases fail before the fix.
### Step 2 — Fix
Implement the design in `internal/gateway/patch.go` (stdlib only: `strings`,
`crypto/sha256`, `encoding/hex`). Update the function comment.
### Step 3 — Docs
- `CLAUDE.md`: Gotchas — drop the wildcard half of "a single bad listener blocks the
whole Gateway" (collisions remain); Architecture — describe the name as sanitized.
- `docs/flows.md` §7 and §10 item 7: wildcard and truncation rows updated.
- `docs/reviews/…-house-conventions.md` is not touched (it predates this).
### Step 4 — Ship
`make check` and `go test -race ./...`, then `/ship` (pushes
`feat/house-conventions`) and open the MR with `tea pr create`. Tick Step 7 of the
conventions execution log in the same commit.
## Verification
```bash
go test -race -run 'TestListenerNameFor|TestBuildListeners' -v ./internal/gateway/
make check
```
Live, after you merge the MR:
1. `/release` → tag `v0.1.0`; CI builds and moves `:latest`.
2. `kubectl -n gateway-cert-operator-system rollout restart deploy/gateway-cert-operator`
(the Deployment pins `:latest`, so it does not roll on its own).
3. The "Reconciler error" for `wildcard-home-hrajfrisbee` stops, and
`kubectl -n kube-system get gateway cilium-gateway -o jsonpath='{.spec.listeners[*].name}'`
lists `auto-wildcard-home-hrajfrisbee-wildcard-home-hrajfrisbee-cz`.
4. Then the CHANGELOG entries for both the conventions port and this fix.