catalog / quay.io/keycloak/keycloak
quay.io/keycloak/keycloak
validated pin current tags: 26.4
keycloak — minimum Linux capabilities, derived by drop-test against the start-dev invocation (embedded H2 dev database, bootstrap admin via env). ZERO-cap: cap_drop:[ALL] with no cap_add — non-root by construction (the image is USER 1000), unprivileged :8080, all state in the H2 file under the image's data dir. The jellyfin/minio/haproxy over-grant story; here the residual is bounded further by the non-root default. quay.io-hosted (the staleness checker covers it since catalog#40). SCOPE: start-dev (dev H2) — the postgres-backed production invocation is a follow-up (expected same []: keycloak's DB access is a TCP client, no caps). See criteria/quay.io/keycloak/keycloak.md. filesystem: not yet derived. Absence means not tested for this image — not that it cannot run read-only.
Use it
services:
keycloak:
image: quay.io/keycloak/keycloak:26.4
cap_drop: [ALL]
security_opt: ["no-new-privileges:true"]
Dimension: capabilities
cap_drop: ALL (no cap_add)
| tool | container-sec-derive 0.7.0 |
|---|---|
| observer | drop-test |
| validated image | quay.io/keycloak/keycloak@sha256:9409c59bdfb6… |
| validated date | 2026-07-16 |
| confidence | high |
| validated via | drop-test, ci-smoke |
| workload | profiles/workloads/keycloak.sh |
| workload sha256 | 71cf97c6c9fc… |
| ig version | v0.51.0 |
Drop-test evidence
Each candidate removed in turn, the container restarted, and the workload re-verified.
| Removed | Verdict | Observed |
|---|---|---|
CHOWN | removable | correct: admin token + realm create/readback ok, keycloak uid=1000 (non-root) |
DAC_OVERRIDE | removable | correct: admin token + realm create/readback ok, keycloak uid=1000 (non-root) |
FSETID | removable | correct: admin token + realm create/readback ok, keycloak uid=1000 (non-root) |
FOWNER | removable | correct: admin token + realm create/readback ok, keycloak uid=1000 (non-root) |
MKNOD | removable | correct: admin token + realm create/readback ok, keycloak uid=1000 (non-root) |
NET_RAW | removable | correct: admin token + realm create/readback ok, keycloak uid=1000 (non-root) |
SETGID | removable | correct: admin token + realm create/readback ok, keycloak uid=1000 (non-root) |
SETUID | removable | correct: admin token + realm create/readback ok, keycloak uid=1000 (non-root) |
SETFCAP | removable | correct: admin token + realm create/readback ok, keycloak uid=1000 (non-root) |
SETPCAP | removable | correct: admin token + realm create/readback ok, keycloak uid=1000 (non-root) |
NET_BIND_SERVICE | removable | correct: admin token + realm create/readback ok, keycloak uid=1000 (non-root) |
SYS_CHROOT | removable | correct: admin token + realm create/readback ok, keycloak uid=1000 (non-root) |
KILL | removable | correct: admin token + realm create/readback ok, keycloak uid=1000 (non-root) |
AUDIT_WRITE | removable | correct: admin token + realm create/readback ok, keycloak uid=1000 (non-root) |
Recorded invocation (run_config)
The minimum is valid for this invocation; a different user:, volume state, or entrypoint can change it.
| command | start-dev |
|---|---|
| security_opt | no-new-privileges:true |
| env | KC_BOOTSTRAP_ADMIN_USERNAMEKC_BOOTSTRAP_ADMIN_PASSWORD |
Workload script (profiles/workloads/keycloak.sh)
#!/usr/bin/env bash
# Workload exerciser for the keycloak reference image (start-dev, embedded H2).
# Real IdP function: ready -> admin token via the OpenID password grant
# (admin-cli) -> create a realm through the admin API -> read it back. Curl
# sidecar; capture-then-match.
# Required env: KEYCLOAKCONTAINER (target container name or id). Container is
# expected to run with KC_BOOTSTRAP_ADMIN_USERNAME=csdadmin /
# KC_BOOTSTRAP_ADMIN_PASSWORD=CsdProbe-Pw-12345.
set -euo pipefail
: "${KEYCLOAKCONTAINER:?KEYCLOAKCONTAINER must be set}"
C="${KEYCLOAKCONTAINER}"
PROBE="${CSD_PROBE_CURL_IMAGE:-curlimages/curl@sha256:c1fe1679c34d9784c1b0d1e5f62ac0a79fca01fb6377cdd33e90473c6f9f9a69}"
sc() { docker run --rm --network "container:${C}" "$PROBE" "$@"; }
deadline=$((SECONDS + 120))
until [ "$(sc -s -o /dev/null -w '%{http_code}' --max-time 5 http://localhost:8080/realms/master 2>/dev/null)" = 200 ]; do
(( SECONDS >= deadline )) && { echo "keycloak never ready" >&2; exit 1; }
sleep 3
done
tokresp="$(sc -s --max-time 10 -d 'grant_type=password&client_id=admin-cli&username=csdadmin&password=CsdProbe-Pw-12345' http://localhost:8080/realms/master/protocol/openid-connect/token 2>/dev/null)"
tok="$(printf '%s' "$tokresp" | grep -oE '"access_token":"[^"]+' | cut -d'"' -f4)"
[ -n "$tok" ] || { echo "admin token failed" >&2; exit 1; }
code="$(sc -s -o /dev/null -w '%{http_code}' --max-time 15 -X POST -H "Authorization: Bearer $tok" -H 'Content-Type: application/json' -d '{"realm":"csd","enabled":true}' http://localhost:8080/admin/realms 2>/dev/null)"
case "$code" in 201|409) ;; *) echo "realm create failed: HTTP ${code:-none}" >&2; exit 1;; esac
back="$(sc -s --max-time 10 -H "Authorization: Bearer $tok" http://localhost:8080/admin/realms/csd 2>/dev/null)"
case "$back" in *'"realm":"csd"'*) ;; *) echo "realm readback failed" >&2; exit 1;; esac
Dimension: filesystem
Not yet derived — this dimension has not been drop-tested for this image. Absence means not tested, not that a minimum here is infeasible.
Evidence & provenance
- Validation criteria — per-image scenarios and pass criteria
- Profile source in the repository
- Version history — every previously published pin, with the full profile as validated against it
Raw profile YAML
# keycloak — minimum Linux capabilities, derived by drop-test against the
# start-dev invocation (embedded H2 dev database, bootstrap admin via env).
# ZERO-cap: cap_drop:[ALL] with no cap_add — non-root by construction (the
# image is USER 1000), unprivileged :8080, all state in the H2 file under the
# image's data dir. The jellyfin/minio/haproxy over-grant story; here the
# residual is bounded further by the non-root default. quay.io-hosted (the
# staleness checker covers it since catalog#40).
#
# SCOPE: start-dev (dev H2) — the postgres-backed production invocation is a
# follow-up (expected same []: keycloak's DB access is a TCP client, no caps).
# See criteria/quay.io/keycloak/keycloak.md.
#
# filesystem: not yet derived. Absence means not tested for this image —
# not that it cannot run read-only.
schema_version: "1.5"
image: quay.io/keycloak/keycloak
reference_url: https://tmatens.github.io/container-security-profiles/profiles/quay.io/keycloak/keycloak.html
applies_to:
tags: ["26.4"]
status: validated
dimensions:
capabilities:
cap_drop: [ALL]
cap_add: []
derivation:
tool: container-sec-derive
tool_version: "0.7.0"
sidecar_schema_version: "1.5"
observer: drop-test
validated_image: quay.io/keycloak/keycloak@sha256:9409c59bdfb65dbffa20b11e6f18b8abb9281d480c7ca402f51ed3d5977e6007
validated_date: "2026-07-16"
duration_seconds: 400
confidence: high
workload: profiles/workloads/keycloak.sh
workload_sha256: "71cf97c6c9fcb200d588b2f429be16c123ef6d2c0c56f82fc5f0b8c58be0c72c"
validated_via: [drop-test, ci-smoke]
observation_backend:
ig_version: v0.51.0
gadgets: []
run_config:
user: ""
command: ["start-dev"]
entrypoint: ""
network: ""
pid: ""
devices: []
security_opt: ["no-new-privileges:true"]
mounts: []
env: [KC_BOOTSTRAP_ADMIN_USERNAME, KC_BOOTSTRAP_ADMIN_PASSWORD]
drop_test:
checks:
- {removed: CHOWN, required: false, observed: "correct: admin token + realm create/readback ok, keycloak uid=1000 (non-root)"}
- {removed: DAC_OVERRIDE, required: false, observed: "correct: admin token + realm create/readback ok, keycloak uid=1000 (non-root)"}
- {removed: FSETID, required: false, observed: "correct: admin token + realm create/readback ok, keycloak uid=1000 (non-root)"}
- {removed: FOWNER, required: false, observed: "correct: admin token + realm create/readback ok, keycloak uid=1000 (non-root)"}
- {removed: MKNOD, required: false, observed: "correct: admin token + realm create/readback ok, keycloak uid=1000 (non-root)"}
- {removed: NET_RAW, required: false, observed: "correct: admin token + realm create/readback ok, keycloak uid=1000 (non-root)"}
- {removed: SETGID, required: false, observed: "correct: admin token + realm create/readback ok, keycloak uid=1000 (non-root)"}
- {removed: SETUID, required: false, observed: "correct: admin token + realm create/readback ok, keycloak uid=1000 (non-root)"}
- {removed: SETFCAP, required: false, observed: "correct: admin token + realm create/readback ok, keycloak uid=1000 (non-root)"}
- {removed: SETPCAP, required: false, observed: "correct: admin token + realm create/readback ok, keycloak uid=1000 (non-root)"}
- {removed: NET_BIND_SERVICE, required: false, observed: "correct: admin token + realm create/readback ok, keycloak uid=1000 (non-root)"}
- {removed: SYS_CHROOT, required: false, observed: "correct: admin token + realm create/readback ok, keycloak uid=1000 (non-root)"}
- {removed: KILL, required: false, observed: "correct: admin token + realm create/readback ok, keycloak uid=1000 (non-root)"}
- {removed: AUDIT_WRITE, required: false, observed: "correct: admin token + realm create/readback ok, keycloak uid=1000 (non-root)"}