Compare commits

...
Author SHA1 Message Date
jakman85 7be693bf37 Merge pull request 'chore(deps): Update quay.io/oauth2-proxy/oauth2-proxy Docker tag to v7.15.6' (#136) from renovate/quay.io-oauth2-proxy-oauth2-proxy-7.x into main
gate / gate (push) Canceled after 1m32s
gate / conformance (push) Canceled after 0s
Reviewed-on: #136
Reviewed-by: Keith Jackson <[email protected]>
2026-11-08 06:45:31 +00:00
jakman85 9e9a3ea8cc Merge pull request 'fix(keycloak): one owner for the keycloak ServiceMonitor (26.8 operator 409 loop blocks Flux)' (#148) from fix/keycloak-servicemonitor-owner into main
gate / gate (push) Successful in 3m0s
gate / conformance (push) Skipped
Reviewed-on: #148
2026-11-08 06:37:13 +00:00
jakman85andClaude Opus 5.5 35ba875380 fix(keycloak): one owner for the keycloak ServiceMonitor (26.8 operator 409 loop blocks Flux)
gate / gate (pull_request) Successful in 3m2s
gate / conformance (pull_request) Skipped
The 26.x Keycloak operator creates its own ServiceMonitor named after the
CR (keycloak/keycloak) when metrics are enabled; spec.serviceMonitor.enabled
defaults true. That collides with the Flux-owned platform/keycloak/
servicemonitor.yaml: the operator's PATCH 409s, it exhausts retries, the CR
stays Ready=False/HasErrors=True, and platform-config (wait: true) stalls
every downstream Kustomization.

Keep the repo's ServiceMonitor (conformance L0/L2 and the dashboard are
written against it; the operator's scrapes the same port 9000 /metrics) and
set spec.serviceMonitor.enabled: false.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-08 00:25:45 -06:00
jakman85andClaude Opus 5.5 3d7ecbcfe9 fix(conformance): regenerate L1 render goldens for oauth2-proxy v7.15.6
gate / gate (pull_request) Successful in 3m0s
gate / conformance (pull_request) Skipped
PR #136 bumps the renderer's OAUTH2_PROXY_IMAGE; the container-workload
and egress-class (vpn + internet) goldens still pinned v7.8.1. Regenerated
with the check scripts' own invocation; each diff is the image tag only.
Also updates the crafty README's current-pin row.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 23:58:29 -06:00
renovate-bot 893c9dee4f chore(deps): Update quay.io/oauth2-proxy/oauth2-proxy Docker tag to v7.15.6
gate / gate (pull_request) Failing after 2m57s
gate / conformance (pull_request) Has been skipped
2026-11-07 08:18:51 +00:00
15 changed files with 136 additions and 11 deletions

No files matched your search

+1 -1
View File
@@ -85,7 +85,7 @@ spec:
- name: config
mountPath: /config
- name: oauth2-proxy
image: quay.io/oauth2-proxy/oauth2-proxy:v7.8.1
image: quay.io/oauth2-proxy/oauth2-proxy:v7.15.6
imagePullPolicy: IfNotPresent
args:
- --http-address=0.0.0.0:4180
+1 -1
View File
@@ -90,7 +90,7 @@ spec:
- -c
- "# Assert the declared adlists in gravity.db. Idempotent: steady state writes nothing and\n# does NOT run a gravity rebuild.\nset -u\nGRAVITY=/etc/pihole/gravity.db\n\n# THE DECLARED SET \u2014 one URL per line, the source of truth for what PiHole blocks.\n# To add a list: add a line here, commit, let Flux reconcile. Nothing else to do.\nDECLARED='https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts'\n\n# On a fresh PVC, FTL creates gravity.db a moment after the container starts. Wait, bounded,\n# and NEVER block or fail the start: a missing DB here must not crashloop DNS for the LAN.\ni=0\nwhile [ ! -s \"$GRAVITY\" ] && [ \"$i\" -lt 90 ]; do sleep 2; i=$(expr $i + 1); done\nif [ ! -s \"$GRAVITY\" ]; then\n echo \"pihole-adlists: gravity.db absent after 180s - leaving adlists untouched\" >&2\n exit 0\nfi\n\nsql() { pihole-FTL sqlite3 \"$GRAVITY\" \"$1\"; }\nCHANGED=0\n\n# 1) Every declared URL must be present AND enabled.\necho \"$DECLARED\" | while IFS= read -r url; do\n [ -n \"$url\" ] || continue\n printf '%s\\n' \"$url\"\ndone > /tmp/declared.txt\nwhile IFS= read -r url; do\n [ -n \"$url\" ] || continue\n # Single quotes inside a URL would break this SQL; adlist URLs do not contain them.\n have=$(sql \"select count(*) from adlist where address='$url';\")\n if [ \"$have\" = \"0\" ]; then\n echo \"pihole-adlists: adding $url\"\n sql \"insert into adlist (address,enabled,comment) values ('$url',1,'declared in catalog/pihole.yaml');\"\n CHANGED=1\n else\n en=$(sql \"select enabled from adlist where address='$url';\")\n if [ \"$en\" != \"1\" ]; then\n echo \"pihole-adlists: re-enabling $url\"\n sql \"update adlist set enabled=1 where address='$url';\"\n CHANGED=1\n fi\n fi\ndone < /tmp/declared.txt\n\n# 2) Anything enabled in the DB but NOT declared gets disabled (never deleted - see above).\nsql \"select address from adlist where enabled=1;\" | while IFS= read -r addr; do\n [ -n \"$addr\" ] || continue\n if ! grep -qxF \"$addr\" /tmp/declared.txt; then\n echo \"pihole-adlists: disabling undeclared $addr\"\n sql \"update adlist set enabled=0 where address='$addr';\"\n echo changed >> /tmp/adlist-changed\n fi\ndone\n[ -s /tmp/adlist-changed ] && CHANGED=1\nrm -f /tmp/adlist-changed /tmp/declared.txt\n\n# 3) Rebuild gravity ONLY if the declared set actually moved.\nif [ \"$CHANGED\" = \"1\" ]; then\n echo \"pihole-adlists: declared set changed - running gravity update\"\n pihole -g || echo \"pihole-adlists: gravity update failed; existing gravity.db retained\" >&2\nelse\n echo \"pihole-adlists: already matches the declared set - nothing to do\"\nfi\nexit 0\n"
- name: oauth2-proxy
image: quay.io/oauth2-proxy/oauth2-proxy:v7.8.1
image: quay.io/oauth2-proxy/oauth2-proxy:v7.15.6
imagePullPolicy: IfNotPresent
args:
- --http-address=0.0.0.0:4180
+1 -1
View File
@@ -156,7 +156,7 @@ spec:
, \"[Preferences]\"\n\ndef matches(raw):\n \"\"\"True iff the stored digest is one OUR password produces (salt is read from it).\"\"\"\n v = raw.strip().strip('\"')\n if v.startswith(\"@ByteArray(\") and v.endswith(\")\"):\n v = v[len(\"@ByteArray(\"):-1]\n try:\n salt_b64, digest_b64 = v.split(\":\", 1)\n salt = base64.b64decode(salt_b64, validate=True)\n digest = base64.b64decode(digest_b64, validate=True)\n except Exception:\n return False\n if not salt or not digest:\n return False\n return hmac.compare_digest(\n hashlib.pbkdf2_hmac(\"sha512\", pw.encode(), salt, ITERATIONS, len(digest)), digest)\n\ntry:\n lines = open(conf, encoding=\"utf-8\", errors=\"replace\").read().splitlines()\nexcept FileNotFoundError:\n lines = []\n\ndef value_of(key):\n for i, line in enumerate(lines):\n if line.startswith(key + \"=\"):\n return i, line.split(\"=\", 1)[1]\n return -1, None\n\nui, uval = value_of(UKEY)\npi, pval = value_of(PKEY)\nif uval == user and pval is not None and matches(pval):\n raise SystemExit(0) # converged - leave the file (and its salt) alone\n\nsalt = os.urandom(SALT_LEN)\ndigest = hashlib.pbkdf2_hmac(\"sha512\", pw.encode(), salt, ITERATIONS, DIGEST_LEN)\nstored = base64.b64encode(salt).decode() + \":\" + base64.b64encode(digest).decode()\ndesired = [UKEY + \"=\" + user, PKEY + '=\"@ByteArray(' + stored + ')\"']\n\nout = [line for i, line in enumerate(lines) if i not in (ui, pi)]\nif SECTION in out:\n at = out.index(SECTION) + 1\nelse:\n if out and out[-1].strip():\n out.append(\"\")\n out.append(SECTION)\n at = len(out)\nout[at:at] = desired\n\ntry:\n os.makedirs(os.path.dirname(conf) or \".\", exist_ok=True)\n tmp = conf + \".webui-cred.new\"\n with open(tmp, \"w\", encoding=\"utf-8\") as fh:\n fh.write(\"\\n\".join(out) + \"\\n\")\n fh.flush()\n os.fsync(fh.fileno())\n # This hook runs as ROOT; the service runs as PUID:PGID. A root-owned 0600 conf would\n # cost the app the ability to save its own settings, so carry the ownership over.\n try:\n st = os.stat(conf)\n os.chown(tmp, st.st_uid, st.st_gid)\n os.chmod(tmp, stat.S_IMODE(st.st_mode))\n except FileNotFoundError:\n os.chown(tmp, int(os.environ.get(\"PUID\", \"1000\")),\n int(os.environ.get(\"PGID\", \"1000\")))\n os.chmod(tmp, 0o664)\n os.replace(tmp, conf) # atomic: the app never reads a half-written conf\nexcept OSError as exc:\n sys.stderr.write(\"[qbt-webui-cred] cannot write %s: %s\\n\" % (conf, exc))\n raise SystemExit(1)\nraise SystemExit(10)\nPY\n rc=$?\n\n if [ \"$rc\" = 0 ]; then\n log \"conf already holds the durable credential for '$QBT_WEBUI_USERNAME' (no write)\"\n elif [ \"$rc\" = 10 ]; then\n log \"asserted WebUI Username + Password_PBKDF2 into $CONF\"\n # postStart runs CONCURRENTLY with the s6 init chain, so qbittorrent-nox may ALREADY\n # have read the old, credential-less conf and be serving a random temporary password.\n # Restart the SUPERVISED SERVICE (not the pod) so it re-reads what was just written.\n # Nothing to do when the service has not started yet - it will read the new file.\n # Its own counter, NOT the shared `waited` API budget: sharing one counter is how the\n # 2026-09-13 failure spent the entire verification budget before the first login and\n # then fired attempts 2 and 3 with no wait left at all.\n sv=0\n while [ \"$sv\" -lt 60 ] && [ \"$(s6-svstat -u \"$SVC\" 2>/dev/null)\" != \"true\" ]; do\n sv=$((sv + 1)); sleep 1\n done\n if [ \"$(s6-svstat -u \"$SVC\" 2>/dev/null)\" = \"true\" ]; then\n log \"qbittorrent-nox was already running - restarting it to load the credential\"\n s6-svc -r \"$SVC\" || log \"s6-svc -r failed (continuing to verify anyway)\"\n sleep 3\n fi\n else\n log \"FAILED to write the credential into $CONF (helper exit $rc). The WebUI will keep\" >&2\n log \"minting a random password\
\ every start and BOTH arrs will break. Fix and restart.\" >&2\n exit 0\n fi\n\n # (3) GROUND TRUTH: the WebUI itself must accept it. Wait for the API to answer at ALL\n # (shared ~120s budget across attempts), then spend exactly ONE login per attempt -\n # at most 3 rejected logins, which stays under qBittorrent's default ban threshold.\n #\n # \"THE API NEVER ANSWERED\" IS A DIFFERENT FAULT FROM \"THE PASSWORD WAS REJECTED\", and\n # conflating them is what made this hook lie once already (see the PORT comment). An\n # unreachable WebUI is reported as unreachable, and the hook stops rather than spending\n # its remaining login attempts to produce two more identical wrong diagnoses.\n api_ok=0\n while [ \"$waited\" -lt 120 ]; do\n code=$(curl -s -o /dev/null -m 3 -w '%{http_code}' \"http://127.0.0.1:$PORT/\" 2>/dev/null)\n if [ -n \"$code\" ] && [ \"$code\" != \"000\" ]; then api_ok=1; break; fi\n waited=$((waited + 1)); sleep 1\n done\n if [ \"$api_ok\" != 1 ]; then\n log \"the WebUI never answered on 127.0.0.1:$PORT within the wait budget, so the\" >&2\n log \"credential in $CONF is UNVERIFIED. This is NOT a rejected password - it was\" >&2\n log \"never submitted. Do not rotate the credential on the strength of this line.\" >&2\n exit 0\n fi\n body=$(curl -sS -m 10 \\\n --data-urlencode \"username=$QBT_WEBUI_USERNAME\" \\\n --data-urlencode \"password=$QBT_WEBUI_PASSWORD\" \\\n \"http://127.0.0.1:$PORT/api/v2/auth/login\" 2>/dev/null)\n if [ \"$body\" = \"Ok.\" ]; then\n log \"VERIFIED: the WebUI accepts the durable credential for '$QBT_WEBUI_USERNAME'\"\n exit 0\n fi\n log \"WebUI rejected the durable credential (login said '$body')\" >&2\n # A rejection when the conf was ALREADY converged (rc=0, nothing written) can only mean\n # the running qbittorrent-nox is serving something it read from an EARLIER conf, so\n # rewriting the same bytes on the next attempt would change nothing. The remedy is to\n # make the process RE-READ the file, so restart the supervised service and let the next\n # attempt re-verify. Without this the retry loop could never change its own outcome.\n if [ \"$(s6-svstat -u \"$SVC\" 2>/dev/null)\" = \"true\" ]; then\n log \"restarting qbittorrent-nox so it re-reads $CONF, then re-verifying\" >&2\n s6-svc -r \"$SVC\" || log \"s6-svc -r failed (continuing to verify anyway)\" >&2\n sleep 3\n fi\ndone\n\nlog \"GAVE UP after $attempt attempts: the WebUI does not accept the credential from git.\" >&2\n# A postStart hook MUST exit 0 - a non-zero exit makes the kubelet KILL the container\n# (FailedPostStartHook -> CrashLoopBackOff), turning a credential fault into an outage.\n# Both failure paths above therefore log LOUDLY and exit 0; the `up` SLI still watches the\n# app, and the arrs' own download-client health check surfaces a broken credential.\nexit 0\n"
- name: oauth2-proxy
image: quay.io/oauth2-proxy/oauth2-proxy:v7.8.1
image: quay.io/oauth2-proxy/oauth2-proxy:v7.15.6
imagePullPolicy: IfNotPresent
args:
- --http-address=0.0.0.0:4180
+1 -1
View File
@@ -138,7 +138,7 @@ spec:
- name: media
mountPath: /media
- name: oauth2-proxy
image: quay.io/oauth2-proxy/oauth2-proxy:v7.8.1
image: quay.io/oauth2-proxy/oauth2-proxy:v7.15.6
imagePullPolicy: IfNotPresent
args:
- --http-address=0.0.0.0:4180
+1 -1
View File
@@ -138,7 +138,7 @@ spec:
- name: media
mountPath: /media
- name: oauth2-proxy
image: quay.io/oauth2-proxy/oauth2-proxy:v7.8.1
image: quay.io/oauth2-proxy/oauth2-proxy:v7.15.6
imagePullPolicy: IfNotPresent
args:
- --http-address=0.0.0.0:4180
+1 -1
View File
@@ -129,7 +129,7 @@ AUTH_MODES = ("oidc", "forward-auth", "native", "none")
# the sidecar authenticates the browser against Keycloak (homelab realm, the app's auth.groups)
# and only THEN proxies to the co-located app UI at http://127.0.0.1:<uiport>. The gate is IN
# the data path (fail-closed: no valid session in an authorized group => no upstream; R-IDP-02).
OAUTH2_PROXY_IMAGE = "quay.io/oauth2-proxy/oauth2-proxy:v7.8.1"
OAUTH2_PROXY_IMAGE = "quay.io/oauth2-proxy/oauth2-proxy:v7.15.6"
OAUTH2_PROXY_PORT = 4180
KEYCLOAK_ISSUER = f"https://keycloak.{ZONE}/realms/homelab"
@@ -62,7 +62,7 @@ spec:
ports:
- containerPort: 8080
- name: oauth2-proxy
image: quay.io/oauth2-proxy/oauth2-proxy:v7.8.1
image: quay.io/oauth2-proxy/oauth2-proxy:v7.15.6
imagePullPolicy: IfNotPresent
args:
- --http-address=0.0.0.0:4180
@@ -85,7 +85,7 @@ spec:
- name: config
mountPath: /data/config
- name: oauth2-proxy
image: quay.io/oauth2-proxy/oauth2-proxy:v7.8.1
image: quay.io/oauth2-proxy/oauth2-proxy:v7.15.6
imagePullPolicy: IfNotPresent
args:
- --http-address=0.0.0.0:4180
@@ -85,7 +85,7 @@ spec:
- name: config
mountPath: /data/config
- name: oauth2-proxy
image: quay.io/oauth2-proxy/oauth2-proxy:v7.8.1
image: quay.io/oauth2-proxy/oauth2-proxy:v7.15.6
imagePullPolicy: IfNotPresent
args:
- --http-address=0.0.0.0:4180
@@ -0,0 +1,60 @@
---
date: 2026-11-08
branch: fix/keycloak-servicemonitor-owner
title: One owner for the keycloak ServiceMonitor — disable the 26.8 operator's (409 loop stalled platform-config)
---
## What changed
- `platform/keycloak/keycloak-instance.yaml`: added `spec.serviceMonitor.enabled: false`.
The 26.x Keycloak operator creates its own ServiceMonitor, named after the CR (`keycloak/keycloak`),
whenever `metrics-enabled=true` and the ServiceMonitor CRD exists. `serviceMonitor.enabled` defaults
to `true` in the 26.8.2 CRD, both in v2alpha1 and in v2beta1 (v2beta1 is now the storage version).
That is the same name and namespace as the repo's Flux-owned `platform/keycloak/servicemonitor.yaml`.
After the 26.0.6 -> 26.8.2 bump (#121/#144) the operator's PATCH against it returned
`409 Conflict` and it ran out of retries. The CR then sat at `Ready=False`/`HasErrors=True`, and
`platform-config` (`wait: true`, which health-checks every object it applies, this CR included)
blocked everything that `dependsOn` it: velero, apps-vms, apps-wave-0 (and so waves 1/2 and
pm-agent), homepage, oauth2-proxy, headlamp, conformance, vpn-ingress, crafty, runbooks.
- `platform/keycloak/servicemonitor.yaml`: a comment saying it is the sole owner.
- **Why ours is kept:** these are written against it: the L0 `servicemonitor-ports-resolve`
allow-list entry `platform/keycloak/servicemonitor.yaml:keycloak`, the L2 `keycloak-monitored`
chainsaw test, the README's ownership table, and the dashboard's `up{namespace="keycloak"}`.
The operator's version (upstream `KeycloakServiceMonitorDependentResource` @26.8.2) scrapes the same
target: port name `management` (9000 on `keycloak-service`), path `/metrics`, interval 30s,
selector = the operator's instance labels. It offers nothing ours lacks, so having two owners
would add a fight and gain nothing.
## What I verified
- Live, read-only, `k3s-prod` ~06:25Z: the CRD schema field `spec.serviceMonitor.enabled`
(`default: true`) exists in both served versions. The live `keycloak` ServiceMonitor has exactly
**one** managedFields manager, `kustomize-controller` (Apply, 2026-09-05), **no ownerReferences**,
and labels `kustomize.toolkit.fluxcd.io/name=platform-config`, `release=kube-prometheus-stack`. So
the operator's apply never landed, and nothing on the object ties it to the Keycloak CR.
- The ServiceMonitor will not be deleted when the operator is switched off: the operator's dependent
informer is filtered on `Constants.DEFAULT_LABELS_AS_STRING`
(`app.kubernetes.io/managed-by=keycloak-operator`). Our object does not carry that label and has
no ownerReference, so the operator cannot see it as its secondary and cannot delete or GC it.
This conclusion comes from reading the code. It was not observed.
- The other operator dependents in ns `keycloak` do not collide with anything: `keycloak-service`,
`keycloak-discovery`, the StatefulSet `keycloak`, `keycloak-initial-admin`, and
`keycloak-network-policy` (owner Keycloak, no Flux label). The repo declares none of these names.
It only *references* `keycloak-service` by name (HTTPRoute, reconcilers). There are no PDBs in the
namespace, and the Ingress is already `enabled: false`. The only NetworkPolicy in the namespace is
the operator's. The repo has no Keycloak NetworkPolicy, and no Kyverno-generated default-deny is
present there. NetworkPolicies add up, so the operator's allow-policy cannot fight another owner.
Left at its default.
- Local gate: see the PR body. It records the rc and stages.
## What I did NOT verify
- That the CR reaches `Ready=True`/`HasErrors=False` after merge. The status also says
"Performing Keycloak upgrade, scaling down the deployment". I am inferring that this half clears
once the workflow stops erroring. It is not proven.
- That the operator stops issuing the PATCH once `enabled: false` is applied. Watch the operator
logs for 409.
- That `platform-config` and its downstream Kustomizations go Ready on the new revision, and that
Prometheus keeps exactly one `serviceMonitor/keycloak/keycloak/0` pool with targets up.
- The operator's actual label set for `DEFAULT_LABELS_AS_STRING`. I took it from the observed labels
on operator-created objects, not from `Constants.java`.
+48
View File
@@ -0,0 +1,48 @@
---
date: 2026-11-08
branch: rework/136
title: "PR #136 rework: regenerate L1 render goldens for the oauth2-proxy v7.15.6 sidecar bump"
---
## What changed
- Renovate PR #136 bumps `OAUTH2_PROXY_IMAGE` in `catalog/render.py` (and the generated
`apps/wave-*` / `platform/crafty` pins) from `v7.8.1` to `v7.15.6`. Its gate failed at L1
because three byte-for-byte render goldens still carried the old tag:
- `conformance/tests/l1-rendering/container-workload-render/golden/expected.yaml`
- `conformance/tests/l1-rendering/egress-class-render/golden/egress-vpn.expected.yaml`
- `conformance/tests/l1-rendering/egress-class-render/golden/egress-internet.expected.yaml`
(CI only reported the vpn one because `render-check.sh` stops at the first failing diff;
the internet golden had drifted too.)
- Regenerated all three in the pinned toolbox with the exact invocation the check scripts use:
`python3 catalog/render.py <fixture> > <golden>`. The diff in each is **one line**:
`image: quay.io/oauth2-proxy/oauth2-proxy:v7.8.1` -> `...:v7.15.6`. Nothing else moved.
- `platform/crafty/README.md`: the "Sidecar" row described the current pin; updated to v7.15.6.
- Deliberately NOT changed (they record history, not the current pin):
- `catalog/render.py` comments / assertion messages saying a flag was "confirmed present in the
PINNED v7.8.1 binary" or that `--api-routes` "does not exist in v7.8.1". Rewriting them to
v7.15.6 would assert a `--help` verification nobody has done against v7.15.6.
- `conformance/tests/l0-static/oauth2-proxy-api-routes-per-app/check.sh`, the schema
description, `runbooks/oauth2-proxy-session.md`, `docs/plans/...` — same reasoning.
- `platform/oauth2-proxy/auth-visibility-alerts.promtool-test.yaml` — v7.8.1 appears in
synthetic kube-state-metrics series (test input data), not as a pin.
- `.gitea/workflows/renovate.yaml` comment — a dated measurement of the pre-Renovate state.
- Branch was 55 commits behind `origin/main`, but main touches neither `catalog/render.py` nor
the L1 goldens, and the merge is conflict-free; no merge of main was needed for the gate.
## What I verified
- The golden diff is exactly the image tag in each of the three files (`git diff -U0`).
- No remaining `oauth2-proxy:v7.8.1` in any rendered/generated/golden file (`git grep`).
- Local toolbox gate result: see the PR #136 comment posted with this push (rc and per-stage
results are quoted there and in the worker report).
## What I did NOT verify
- That the v7.15.6 sidecar actually starts and authenticates in the cluster with the rendered
args. Earlier research said no flag used here was removed/renamed between 7.8.1 and 7.15.6;
I did not re-run `oauth2-proxy --help` against the v7.15.6 image.
- L2/L3 conformance (does not run in CI: `CLUSTER_AVAILABLE` unset).
- `approval-policy`: `conformance/**` is a sensitive path, so this PR is CI-red until the
operator posts `/approve-override <reason>` as a PR review. The local gate cannot evaluate it.
- Whether Renovate keeps rebasing this branch now that it carries a non-Renovate commit.
+1 -1
View File
@@ -16,7 +16,7 @@ Cilium L2 LoadBalancer (game protocols authenticate players themselves).
|---|---|
| Image | `registry.gitlab.com/crafty-controller/crafty-4:4.10.8` (current stable; bare-semver tags, no `v`) |
| Web UI | HTTPS **self-signed** on `:8443` (reached only via the sidecar / same-pod) |
| Sidecar | `quay.io/oauth2-proxy/oauth2-proxy:v7.8.1`, reverse-proxy on `:4180` |
| Sidecar | `quay.io/oauth2-proxy/oauth2-proxy:v7.15.6`, reverse-proxy on `:4180` |
| Data | ONE Longhorn RWO PVC `crafty-data` (20Gi), mounted 5× by subPath |
| PVC subPaths | `servers`→`/crafty/servers`, `backups`→`/crafty/backups`, `logs`→`/crafty/logs`, `import`→`/crafty/import`, `config`→`/crafty/app/config` |
| Identity | `runAsUser:1000 runAsGroup:0 fsGroup:0 runAsNonRoot:true` (see below) |
+1 -1
View File
@@ -130,7 +130,7 @@ spec:
- name: oauth2-proxy
# quay.io/oauth2-proxy/oauth2-proxy — same authenticator family as platform/oauth2-proxy, but
# here in per-app REVERSE-PROXY mode (--upstream is the co-located Crafty UI, not static://202).
image: quay.io/oauth2-proxy/oauth2-proxy:v7.8.1
image: quay.io/oauth2-proxy/oauth2-proxy:v7.15.6
imagePullPolicy: IfNotPresent
args:
- --http-address=0.0.0.0:4180
+14
View File
@@ -38,6 +38,20 @@ spec:
ingress:
enabled: false # exposed via Gateway API HTTPRoute (httproute.yaml), not the
# operator's built-in Ingress
# --- ONE OWNER for the `keycloak` ServiceMonitor: this repo (servicemonitor.yaml), not the operator.
# Since 26.x the operator creates its OWN ServiceMonitor, named after this CR (`keycloak`, i.e.
# the SAME name/namespace as ours), whenever metrics-enabled=true and the CRD exists —
# spec.serviceMonitor.enabled defaults TRUE. On 2026-11-08, right after the 26.0.6 -> 26.8.2
# bump, its server-side apply onto our Flux-owned object failed `409 Conflict`, the operator
# exhausted retries, the CR sat Ready=False/HasErrors=True, and `platform-config` (wait: true,
# so it health-checks this CR) stalled every Kustomization downstream of it. Ours is kept
# because the L0 servicemonitor-ports-resolve and L2 keycloak-monitored conformance tests and
# the dashboard's up{namespace="keycloak"} are written against it; the operator's would scrape
# the same thing (management port 9000, /metrics, 30s). Do NOT flip this to true without
# deleting servicemonitor.yaml in the same change — two owners of one object is a fight
# (cf. the default-deny NetworkPolicy drift loop).
serviceMonitor:
enabled: false
# --- Hardening + observability options (docs/15). Brute-force detection is a REALM setting
# (bruteForceProtected in the realm import); these enable health + metrics endpoints. ---
additionalOptions:
+3
View File
@@ -8,6 +8,9 @@
# /metrics) once metrics-enabled/health-enabled are set (see keycloak-instance.yaml additionalOptions).
# We select the operator-created keycloak-service (which selects the Keycloak pods) and scrape the pods'
# management port by number via targetPort.
# SOLE OWNER: the operator's own ServiceMonitor (same name `keycloak`) is switched OFF in
# keycloak-instance.yaml (spec.serviceMonitor.enabled: false) — 26.x defaults it ON and its apply
# 409-looped against this object on 2026-11-08. Keep exactly one of the two.
# TODO(WS-SEC, bring-up): confirm the management port number/name the operator exposes (9000 default)
# and adjust targetPort if the pinned operator changes it. The KeycloakDown / KeycloakDatabaseDown
# alerts (platform/monitoring/alerts.yaml) key on kube-state-metrics and fire regardless of this SM.