keycloak: group 1000, not the image's root group

Checked rather than suppressed after Trivy's AVD-KSV-0116 on the first run:
521 of 522 entries in the image layer are owned by 1000:0 and every mode
grants the owner full access, so uid 1000 needs no group membership to read or
execute anything. Group 0 is upstream's OpenShift arbitrary-uid pattern and
buys nothing once the owning uid is pinned - and leaving it out keeps a root
GID out of the pod without a chart-wide suppression that would also have
covered the serving container.
jklein committed 2026-10-04 17:23:57 +00:00
1 parent c983ae10cd
commit cb70372dd8
1 file changed
+1 -1
+1 -1
@@ -211,7 +211,7 @@ Three practical consequences:
| `keycloak.livenessProbe` | object | `httpGet /health/live` on `management`, period 10s, timeout 5s, 3 failures | **Deliberately not `/health` or `/health/ready`** — see [the probes](#the-three-probes-use-three-different-paths-on-purpose). |
| `keycloak.readinessProbe` | object | `httpGet /health/ready` on `management`, period 10s, timeout 5s, 3 failures | Includes the database, and must. |
| `keycloak.startupProbe` | object | `httpGet /health/started` on `management`, period 10s, 60 failures (= **600s**) | Gates liveness and readiness. The budget covers a **first** start, which creates the whole schema through Liquibase while the second replica waits on the migration lock. The Quarkus augmentation is **not** inside it — that runs in an init container, which has no probe and no deadline. |
| `keycloak.securityContext.pod` | object | non-root **1000**, `runAsGroup` **0**, `fsGroup` `1000`, `fsGroupChangePolicy: Always`, `seccompProfile: RuntimeDefault` | **uid 1000 / gid 0 is the image's requirement, not a free choice**: it declares `keycloak:x:1000:0` in `/etc/passwd`, puts that user in the **root group**, and copies the install tree with `--chown=1000:0`. The mounted build volume has to be writable by the same pair. |
| `keycloak.securityContext.pod` | object | non-root **1000**, `runAsGroup` **1000**, `fsGroup` `1000`, `fsGroupChangePolicy: Always`, `seccompProfile: RuntimeDefault` | **uid 1000 is the image's requirement**: it declares `keycloak:x:1000:0` in `/etc/passwd` and copies the install tree with `--chown=1000:0`, so 1000 **owns** everything the process reads. The **group is 1000 and not the image's own 0**, which is a deliberate divergence: upstream puts the user in the root group and makes the tree group-writable, which is the OpenShift arbitrary-uid pattern — it lets *any* uid in group 0 write. Counted across the image layer, 521 of 522 entries are owned by `1000:0` with every mode granting the owner full access, so pinning the owning uid makes group 0 unnecessary. What that buys: **no root GID in this pod**, hence no `AVD-KSV-0116` finding and no suppression for it — a suppression would have had to cover the serving container too. |
| `keycloak.securityContext.container` | object | `readOnlyRootFilesystem: true`, all caps dropped, no privilege escalation, non-root | Applied to **every** container in the pod, both init containers included. No capability is added and none is needed: 8080, 9000 and 7800 are all above 1024 and the entrypoint is a shell script calling `java`. |
| `keycloak.podDisruptionBudget.enabled` | bool | `true` | |
| `keycloak.podDisruptionBudget.minAvailable` | int | `1` | **Rendered only while the computed replica count is `>= 2`** — see [the PDB trap](#a-pdb-with-one-replica-blocks-every-drain). |