Validate Helm Charts / Detect changed charts (pull_request) Successful in 11s
Validate Helm Charts / Resource quantities (pull_request) Successful in 15s
Validate Helm Charts / Build keycloak (pull_request) Successful in 4s
Validate Helm Charts / Trivy config keycloak (pull_request) Successful in 17s
Install-test / Install-test keycloak (pull_request) Successful in 3m9s
Validate Helm Charts / Install-test (pull_request) Successful in 3m9s
688 lines
36 KiB
YAML
688 lines
36 KiB
YAML
# Keycloak - an OpenID Connect and SAML identity provider, deployed as a
|
|
# TWO-NODE cluster against an external PostgreSQL.
|
|
#
|
|
# READ THIS FIRST, because three properties shape every value below and none of
|
|
# them is obvious from the outside (Gitea #83).
|
|
#
|
|
# 1. THERE IS NO ConfigMap IN THIS CHART, AND THAT IS NOT AN OMISSION.
|
|
# Keycloak is configured entirely through options - CLI arguments and KC_*
|
|
# environment variables - so no file is rendered, mounted or hashed. The
|
|
# repo's checksum/config rule (#82) therefore has nothing to attach to:
|
|
# there is no ConfigMap whose change could fail to restart the workload.
|
|
# What IS covered is the op:// PATH of the two vault items, through
|
|
# checksum/secret-ref on the pod template. A change INSIDE an item still
|
|
# restarts nothing - Helm never sees it - and NOTES.txt names the manual
|
|
# rollout restart for that case.
|
|
#
|
|
# 2. THE CHART RUNS A BUILD STEP IN AN INIT CONTAINER, AND IT HAS TO.
|
|
# Keycloak distinguishes BUILD options from runtime options. Three of the
|
|
# options this chart needs are build options - verified against the 26.8.0
|
|
# "All configuration" page, where each carries the tool icon:
|
|
# db (default dev-file)
|
|
# health-enabled (default false)
|
|
# metrics-enabled (default false)
|
|
# The published image is augmented with those DEFAULTS. Setting any of them
|
|
# at runtime makes Keycloak re-augment itself at start, and the augmentation
|
|
# rewrites four files INSIDE the install tree:
|
|
# /opt/keycloak/lib/quarkus/{generated-bytecode.jar,
|
|
# transformed-bytecode.jar,quarkus-application.dat,
|
|
# build-system.properties}
|
|
# (read out of the image layer itself, not inferred). Upstream says what
|
|
# that start does, in the container guide: it "detects and applies the build
|
|
# options first", which "significantly increases startup time and CREATES AN
|
|
# IMAGE THAT IS MUTABLE". A mutable image is exactly what
|
|
# readOnlyRootFilesystem: true forbids - so the stock image, probes on
|
|
# /health and a read-only root filesystem are not a combination that can
|
|
# start. And "then do not set db" is no way out: THE PROBES ALONE force the
|
|
# augmentation, because health-enabled is a build option too.
|
|
#
|
|
# So an init container from the SAME image runs `kc.sh build` into an
|
|
# emptyDir that the main container mounts at /opt/keycloak/lib/quarkus, and
|
|
# the main container runs `start --optimized`. No second image to pin (this
|
|
# repository publishes charts, not images), hardening intact, and the
|
|
# augmentation happens once in the init container instead of inside the
|
|
# serving container on every start.
|
|
#
|
|
# THE CONDITION THAT MAKES IT SAFE, and the line a later round will want to
|
|
# "simplify": the two containers must never disagree about the build
|
|
# options, because a disagreement makes the MAIN container re-augment - on
|
|
# a path that is now read-only, i.e. a crash loop. This chart enforces that
|
|
# structurally rather than by care: the build options exist in exactly one
|
|
# place (the keycloak.buildArgs helper, which only the init container uses),
|
|
# the main container passes `start --optimized` and NO build option at all,
|
|
# and the render FAILS if a keycloak.env entry carries a build option's
|
|
# variable name. Do not add a KC_DB, KC_HEALTH_ENABLED, KC_METRICS_ENABLED
|
|
# or KC_FEATURES to the main container "for clarity".
|
|
#
|
|
# 3. NO PVC, AND NO StatefulSet. Keycloak 26 keeps everything in the database,
|
|
# user sessions included (persistent user sessions are the default since 26;
|
|
# volatile sessions are deprecated as of 26.8). There is no state on disk to
|
|
# keep and no stable identity to preserve, so this is a Deployment - the
|
|
# repo's StatefulSet rule is about STATEFUL applications, and here it would
|
|
# be decoration.
|
|
|
|
# Scale the whole chart down without uninstalling it: scaleDown: true renders
|
|
# the Deployment with replicas: 0 and takes precedence over keycloak.replicas.
|
|
# Secrets, Services and Ingresses stay in place; scaleDown: false brings the
|
|
# workload back up.
|
|
#
|
|
# THIS IS ALSO HALF OF THE MINOR-UPGRADE PROCEDURE. Keycloak supports a rolling
|
|
# update only inside a minor line, so a 26.8 -> 26.9 jump is: scaleDown: true,
|
|
# back up configuration and database, change the version, scaleDown: false.
|
|
# NOTES.txt has the sequence and the compatibility check that decides it.
|
|
scaleDown: false
|
|
|
|
secrets:
|
|
# op:// path of the 1Password item carrying the database credentials.
|
|
# REQUIRED - the chart has no default database and Keycloak does not start
|
|
# without one.
|
|
#
|
|
# Must carry the fields:
|
|
# username the database role -> KC_DB_USERNAME
|
|
# password that role's password -> KC_DB_PASSWORD
|
|
#
|
|
# Host, port and database name are NOT in the vault: they are plain values
|
|
# under keycloak.db below, because they are not secret and because putting
|
|
# them in the item would mean editing a vault entry to move a database.
|
|
#
|
|
# THEY ARRIVE AS ENVIRONMENT VARIABLES, NOT AS FILES, and that is forced
|
|
# rather than chosen. Keycloak 26 has no _FILE indirection for an option's
|
|
# value: there is no KC_DB_PASSWORD_FILE and no generic KC_<OPTION>_FILE.
|
|
# Checked against the 26.8.0 "All configuration" page - every KC_*_FILE
|
|
# variable there is a PATH option (certificate, key store, trust store, log
|
|
# file, export file), i.e. "point at this file", never "read this option's
|
|
# value from a file". The file form valkey argues for is simply unavailable
|
|
# here, as it was for garage, for a different reason.
|
|
#
|
|
# The residual price, stated rather than hidden: the password is in the
|
|
# container's process environment, so anyone who can exec into the pod or
|
|
# read its /proc can see it. (Keycloak's own Vault SPI, KC_VAULT=file, is a
|
|
# different and larger mechanism - a directory of secret files with its own
|
|
# naming scheme - and deliberately not used here.)
|
|
databaseSecretRef: null
|
|
|
|
# op:// path of the 1Password item carrying the TEMPORARY bootstrap
|
|
# administrator. Required while keycloak.bootstrapAdmin.enabled is true
|
|
# (the default), which is what a first install needs.
|
|
#
|
|
# Must carry the fields:
|
|
# username -> KC_BOOTSTRAP_ADMIN_USERNAME (upstream default: temp-admin)
|
|
# password -> KC_BOOTSTRAP_ADMIN_PASSWORD
|
|
#
|
|
# TWO THINGS ABOUT THIS ACCOUNT THAT CATCH PEOPLE OUT, both in NOTES.txt as
|
|
# well because they are first-install work:
|
|
# - the variables are named this way only since Keycloak 26. Before that
|
|
# they were KEYCLOAK_ADMIN / KEYCLOAK_ADMIN_PASSWORD, and a values file
|
|
# copied from an older deployment silently creates no administrator at
|
|
# all.
|
|
# - the account is TEMPORARY. Upstream: "A user or service admin account
|
|
# created using one of the methods described below is temporary ... the
|
|
# account should exist only for the duration necessary to perform
|
|
# operations needed to gain permanent and more secure admin access.
|
|
# After that, the account needs to be removed manually." Creating a
|
|
# permanent administrator is therefore a MANDATORY step after the first
|
|
# install, not a recommendation.
|
|
bootstrapAdminSecretRef: null
|
|
|
|
keycloak:
|
|
# TWO, and the reason is the kind of service this is. A mail server may be
|
|
# away briefly because SMTP retries; a blob store has one consumer. An SSO
|
|
# server takes EVERY dependent login down with it, so the second replica is
|
|
# the point of this chart rather than a tuning option.
|
|
#
|
|
# What makes two replicas cheap here: node discovery runs over the database
|
|
# that is there anyway. cache=ispn plus Keycloak's default jdbc-ping stack
|
|
# means no headless Service, no JGroups DNS wiring and no extra option - the
|
|
# nodes register themselves in a table and then talk directly to each other
|
|
# on port 7800. (cache-stack=kubernetes, the DNS_PING variant, is marked
|
|
# deprecated upstream.)
|
|
#
|
|
# What it costs: twice the database connections, which is why the pool sizes
|
|
# below are not upstream's defaults, and the rolling-update restriction -
|
|
# with one replica a minor jump is a restart, with two it is a procedure.
|
|
#
|
|
# An explicit 0 is allowed and is the per-workload way to stop the release;
|
|
# the chart-global scaleDown flag does the same for everything at once.
|
|
replicas: 2
|
|
|
|
db:
|
|
# THE DATABASE VENDOR, AND THE ONE VALUE THAT IS A BUILD OPTION (point 2
|
|
# at the top of this file). It is passed to `kc.sh build` in the init
|
|
# container and never to the serving container. Changing it re-runs the
|
|
# build on the next pod start, which is correct and costs one init-container
|
|
# run - it does NOT migrate any data.
|
|
#
|
|
# "postgres" is the only value this chart is tested against. The others
|
|
# upstream accepts (mariadb, mysql, mssql, oracle) would render, but
|
|
# nothing here or in CI has ever started them. dev-file and dev-mem are
|
|
# rejected by the schema: they are Keycloak's development defaults, and an
|
|
# identity provider on an embedded H2 file is the kind of install that
|
|
# works until the pod moves.
|
|
vendor: postgres
|
|
|
|
# Host of the external PostgreSQL. REQUIRED - there is no sensible default
|
|
# and a wrong one is a pod that waits on a connection it will never get.
|
|
host: null
|
|
port: 5432
|
|
database: keycloak
|
|
|
|
# CONNECTION POOL, AND A DELIBERATE DEPARTURE FROM UPSTREAM'S DEFAULT.
|
|
# db-pool-max-size ships as 100 upstream (verified in the 26.8.0 option
|
|
# table). With two replicas that is 200 connections against a PostgreSQL
|
|
# whose own default max_connections is 100 - so the upstream default does
|
|
# not merely waste connections, it hands out more than a default server
|
|
# has, and the failure arrives under load as "too many clients already"
|
|
# rather than at install time.
|
|
#
|
|
# 20 per replica = 40 in total leaves room for everything else on that
|
|
# server. Raise it together with the server's max_connections, never alone,
|
|
# and remember to multiply by keycloak.replicas when you do.
|
|
#
|
|
# initialSize and minSize have no upstream default; keeping a few warm
|
|
# connections removes the first-request latency after an idle period.
|
|
pool:
|
|
initialSize: 2
|
|
minSize: 2
|
|
maxSize: 20
|
|
|
|
bootstrapAdmin:
|
|
# Render KC_BOOTSTRAP_ADMIN_USERNAME / KC_BOOTSTRAP_ADMIN_PASSWORD from the
|
|
# vault item above. ON, because a fresh install has no way in otherwise:
|
|
# Keycloak "only allows to create the initial admin user from a local
|
|
# network connection", which a container never has.
|
|
#
|
|
# SET THIS TO false ONCE A PERMANENT ADMINISTRATOR EXISTS. While the
|
|
# variables stay set, every start logs a line about the initial
|
|
# administrator not being created, because the master realm already exists
|
|
# and the bootstrap path is skipped. That line is harmless and it is NOT a
|
|
# defect - but it is the kind of message somebody eventually investigates,
|
|
# so switching this off after the first install removes both the noise and
|
|
# a credential the pod no longer needs.
|
|
enabled: true
|
|
|
|
# metrics-enabled, a BUILD option - it goes to `kc.sh build`, so changing it
|
|
# re-runs the init containers.
|
|
#
|
|
# ON, AND NOT FOR THE METRICS. This value is misnamed from our point of view:
|
|
# it also decides whether /health/ready CONTAINS A DATABASE CHECK AT ALL.
|
|
# Upstream's table of available checks has a "Requires Metrics" column, and
|
|
# the Database check - "Returns the status of the database connection pool" -
|
|
# is the one row that says YES. The Cluster, Graceful shutdown and Keycloak
|
|
# Initialized checks say no.
|
|
#
|
|
# So with metrics-enabled=false, a pod whose database is gone still reports
|
|
# Ready and keeps serving errors to every login. That is precisely the shape
|
|
# urlaubsverwaltung cost this repo two years of false confidence (#218, and
|
|
# README guideline 12.3): the probe path was right and the thing behind it
|
|
# contained nothing external. Here the dependency hides behind an option
|
|
# whose name says "observability".
|
|
#
|
|
# The price, which is real and is why the value still exists: /metrics then
|
|
# answers on the management port, UNAUTHENTICATED, carrying realm names,
|
|
# client ids and login counters. That port is ClusterIP-only and in no
|
|
# Ingress (see keycloak.ports.management), so the exposure is in-cluster.
|
|
#
|
|
# SWITCHING THIS OFF IS A DECISION ABOUT WHETHER READINESS CAN FAIL, not
|
|
# about scraping. If you do it, the readiness probe stops being able to
|
|
# detect a database outage, and nothing else in this chart replaces it.
|
|
metrics:
|
|
enabled: true
|
|
|
|
# Additional Keycloak feature flags (--features), e.g. ["organization",
|
|
# "declarative-ui"]. A BUILD option: entries here change what the init
|
|
# container builds, so adding one re-runs the build on the next pod start.
|
|
#
|
|
# Deliberately a list rather than a free-form string: the option is
|
|
# comma-separated, and a string would let two entries arrive as one unknown
|
|
# feature name, which Keycloak rejects at BUILD time - in the init container,
|
|
# where the error is one `kubectl logs -c` away from being invisible.
|
|
features: []
|
|
|
|
cache:
|
|
# --cache-embedded-cluster-name. NULL, i.e. Keycloak's default "ISPN", and
|
|
# the value exists because of a trap rather than a need.
|
|
#
|
|
# THE TRAP: node discovery runs through the database, so - upstream's own
|
|
# words - "clusters that share the same database but should remain separate
|
|
# will merge unless they have distinct cluster names". Two releases of this
|
|
# chart pointed at the SAME database therefore form ONE Infinispan cluster,
|
|
# silently, with no error on either side. If that is your situation, the
|
|
# first thing to check is whether they should be sharing a database at all;
|
|
# separate databases need no cluster name at all.
|
|
#
|
|
# WHY IT IS NOT SET PER RELEASE BY DEFAULT, although that is the obvious
|
|
# fix: the same upstream page warns about the other direction - "As
|
|
# invalidation of caches between clusters with distinct names will not
|
|
# work, this will lead to stale caches and incorrect behavior. Use only
|
|
# together with the stateless feature." That warning is not unambiguous
|
|
# about which configuration it applies to, and a chart default that trades
|
|
# a documented merge for a possibly undocumented invalidation gap is not an
|
|
# improvement. So: the value is here, the trap is written down, and the
|
|
# default is upstream's.
|
|
clusterName: null
|
|
|
|
# THE THREE PORTS, IN ONE PLACE, because two templates have to agree on them:
|
|
# the Deployment renders them as containerPorts and the Services address them
|
|
# BY NAME. The probes also address the management port by name, so renaming
|
|
# one here breaks them.
|
|
ports:
|
|
# The serving port. Behind the Ingress, plain HTTP - see http-enabled in
|
|
# the Deployment.
|
|
http: 8080
|
|
|
|
# THE MANAGEMENT PORT, AND THE ONE THING TO KNOW BEFORE TOUCHING ANY OF
|
|
# THIS: it carries /health, /health/live, /health/ready, /health/started
|
|
# and - with keycloak.metrics.enabled - /metrics, and it carries them
|
|
# WITHOUT AUTHENTICATION. All three probes use it, which is why it exists
|
|
# as a containerPort and as a ClusterIP Service.
|
|
#
|
|
# IT MUST NEVER GET AN INGRESS. There is no value in this chart that would
|
|
# publish it and none should be added: an unauthenticated readiness
|
|
# endpoint on the internet tells an attacker when the database is down, and
|
|
# /metrics tells them the realm and client names. Upstream's alternative
|
|
# (legacy-observability-interface=true, which moves health back onto 8080
|
|
# where the Ingress is) is explicitly "not recommended for security
|
|
# reasons" and this chart does not offer it.
|
|
management: 9000
|
|
|
|
# JGroups. Not served to anything and not in any Service - it is declared
|
|
# so that the manifest says where cluster traffic goes, and so that a
|
|
# NetworkPolicy (none yet in this chart) has a port to name. jdbc-ping
|
|
# discovers peers through the database and then opens DIRECT pod-to-pod
|
|
# connections on this port, so the two replicas need plain L3 reachability
|
|
# between their pod IPs - not a Service, and not a headless one either.
|
|
jgroups: 7800
|
|
|
|
resources:
|
|
# Limits are optional: memory and ephemeral-storage limits default to
|
|
# limitFactor x the request, an explicitly set limit always wins, and a CPU
|
|
# limit is only set when configured explicitly - CPU is compressible, so
|
|
# leaving it unlimited throttles instead of OOM-killing.
|
|
#
|
|
# 700Mi x 3 = 2100Mi, AND THAT NUMBER IS LOAD-BEARING RATHER THAN TIDY.
|
|
# The image sizes the JVM heap RELATIVE to the container limit
|
|
# (-XX:MaxRAMPercentage=70, -XX:InitialRAMPercentage=50 in kc.sh), so the
|
|
# limit is the value the heap is computed from, not a ceiling that is
|
|
# hopefully never reached. Upstream's container guide: "you should always
|
|
# set the memory limit for the container", "at least 750 MB", and "For
|
|
# smaller production-ready deployments, the recommended memory limit is
|
|
# 2 GB". 2100Mi is that recommendation; 70% of it is a ~1470Mi heap.
|
|
#
|
|
# Lowering the request without raising limitFactor shrinks the heap
|
|
# proportionally. That is the connection to keep in mind here - in most
|
|
# charts of this repo the request is about scheduling, in this one it also
|
|
# decides how much memory the application believes it has.
|
|
#
|
|
# The ephemeral-storage request covers the three emptyDirs (the Quarkus
|
|
# build output, /opt/keycloak/data and /tmp) plus container logs. The build
|
|
# output is a few MB; the head room is for the augmentation's temporary
|
|
# files.
|
|
limitFactor: 3
|
|
requests:
|
|
cpu: 500m
|
|
memory: 700Mi
|
|
ephemeral-storage: 256Mi
|
|
|
|
# The two INIT CONTAINERS - the seed step and the Quarkus build. Both use
|
|
# this resources block, because the seed step is a single `cp` whose cost
|
|
# disappears next to the build's and a second block would only be a second
|
|
# thing to keep in step.
|
|
build:
|
|
# Resources for the build init containers. Their own block, because they do
|
|
# something quite different from the serving container: a single-shot
|
|
# Quarkus augmentation, CPU-bound, measured by upstream's own issue reports
|
|
# at roughly 13-23 seconds of "Quarkus augmentation completed in ...".
|
|
#
|
|
# More CPU here shortens every pod start, and the memory is transient - the
|
|
# init containers are gone before the serving container begins, so these
|
|
# requests do not add to the pod's running footprint. They DO count for
|
|
# scheduling, though: a pod's effective request is the maximum of the init
|
|
# containers' and the sum of the containers', so raising cpu above the
|
|
# serving container's request raises what the scheduler needs to find.
|
|
resources:
|
|
limitFactor: 3
|
|
requests:
|
|
cpu: 500m
|
|
memory: 512Mi
|
|
ephemeral-storage: 256Mi
|
|
|
|
# LIVENESS: /health/live on the management port - the endpoint that reports
|
|
# whether this process is alive, and deliberately NOT /health or
|
|
# /health/ready.
|
|
#
|
|
# This is guideline 12.3 in the one shape where upstream has already done the
|
|
# work for us: Keycloak splits the three checks, and /health/live contains no
|
|
# database check, while /health/ready and the aggregate /health do. A
|
|
# liveness probe on either of those restarts every pod of the cluster for as
|
|
# long as the database is away - which cannot fix a database, and which turns
|
|
# a degraded login service into none at all. Same lesson as zipline (README,
|
|
# guideline 12.3); here it is one path away.
|
|
livenessProbe:
|
|
httpGet:
|
|
path: /health/live
|
|
port: management
|
|
periodSeconds: 10
|
|
timeoutSeconds: 5
|
|
failureThreshold: 3
|
|
|
|
# READINESS: /health/ready - which includes the database, and must. A node
|
|
# that cannot reach its database serves errors to every login, so no Service
|
|
# should route to it.
|
|
#
|
|
# AND THAT "INCLUDES THE DATABASE" IS CONDITIONAL, which is the trap worth
|
|
# knowing before anybody tidies up keycloak.metrics.enabled: the database
|
|
# check is only part of the report while metrics-enabled is true (upstream's
|
|
# check table, "Requires Metrics: Yes"). The path alone guarantees nothing.
|
|
readinessProbe:
|
|
httpGet:
|
|
path: /health/ready
|
|
port: management
|
|
periodSeconds: 10
|
|
timeoutSeconds: 5
|
|
failureThreshold: 3
|
|
|
|
# STARTUP: /health/started, Keycloak's own "the server has finished booting"
|
|
# endpoint. It gates liveness and readiness, which is why neither of those
|
|
# carries an initialDelaySeconds.
|
|
#
|
|
# BUDGET = periodSeconds x failureThreshold = 10 x 60 = 600s, and the gap
|
|
# between that and the MEASURED time is deliberate rather than lazy.
|
|
#
|
|
# MEASURED, in the install-test of Gitea PR #87 on the Gitea docker runner
|
|
# (k3s v1.35.5, two replicas, empty database, image pulled during the run):
|
|
# from `helm install` to BOTH pods Ready in 71.8 seconds - the first at
|
|
# ~70.8s, the second one second later, 0 restarts. That figure already
|
|
# contains the image pull, both init containers and the schema creation.
|
|
#
|
|
# Why the budget is still eight times that: everything in the measured run
|
|
# was the cheap case. A first install against a real database creates the
|
|
# whole schema through Liquibase, and with two replicas the second waits on
|
|
# the first's migration lock instead of arriving a second later; a loaded
|
|
# node, a slower disk or a bigger existing schema all push in the same
|
|
# direction. A budget that is too short there produces a CrashLoopBackOff
|
|
# that looks like an application fault and is really a stopwatch - and the
|
|
# cost of the headroom is nothing, because a pod that IS healthy becomes
|
|
# Ready on the first probe either way.
|
|
#
|
|
# The Quarkus augmentation is NOT in this budget - it runs in the init
|
|
# container, which has no probe and no deadline of its own. That is the
|
|
# second thing the init container buys.
|
|
startupProbe:
|
|
httpGet:
|
|
path: /health/started
|
|
port: management
|
|
periodSeconds: 10
|
|
failureThreshold: 60
|
|
timeoutSeconds: 5
|
|
|
|
securityContext:
|
|
pod:
|
|
runAsNonRoot: true
|
|
# uid 1000, NOT this repo's 20000, and that is the image's requirement
|
|
# rather than a free choice: it declares "keycloak:x:1000:0" in
|
|
# /etc/passwd and copies the whole install tree with --chown=1000:0, so
|
|
# 1000 is the OWNER of everything the process reads and executes. Read
|
|
# off the image rather than guessed - `USER 1000` and the chown are in
|
|
# the config blob's history of quay.io/keycloak/keycloak:26.8.0.
|
|
#
|
|
# GROUP 1000 AND NOT 0, WHICH IS WHERE THIS CHART DIVERGES FROM THE
|
|
# IMAGE'S OWN CONVENTION - deliberately, and measured before deciding.
|
|
# Upstream puts the keycloak user in the ROOT GROUP and makes the install
|
|
# tree group-writable (mode 775/664, owner 1000, group 0). That is the
|
|
# OpenShift arbitrary-uid pattern: it lets ANY uid that happens to be in
|
|
# group 0 write there. We do not need it, because we pin the uid that
|
|
# OWNS the files - counted across the image layer, 521 of 522 entries are
|
|
# owned by 1000:0 and every mode grants the owner full access, so uid
|
|
# 1000 gets what it needs through the owner bits whatever its group is.
|
|
# The one remaining entry is /opt itself, which needs only traverse.
|
|
#
|
|
# What that buys: no root primary or supplementary GID in this pod, i.e.
|
|
# no AVD-KSV-0116 finding and no suppression for it. A suppression would
|
|
# have had to cover the whole chart, serving container included, to
|
|
# silence a check that is making a fair point.
|
|
#
|
|
# If this is ever changed back to 0, the reason has to be a path that
|
|
# uid 1000 does NOT own - and there is none in this image today.
|
|
runAsUser: 1000
|
|
runAsGroup: 1000
|
|
fsGroup: 1000
|
|
fsGroupChangePolicy: Always
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
container:
|
|
allowPrivilegeEscalation: false
|
|
# TRUE, and keeping it true is the whole reason this chart has an init
|
|
# container (point 2 at the top). Setting it to false would make the
|
|
# init container unnecessary - and would also mean an identity provider
|
|
# that rewrites its own installation directory at every start. Do not
|
|
# trade one for the other without reading that section.
|
|
readOnlyRootFilesystem: true
|
|
runAsNonRoot: true
|
|
capabilities:
|
|
drop:
|
|
- ALL
|
|
# EMPTY, and it can stay empty: 8080, 9000 and 7800 are all above
|
|
# 1024, and the entrypoint is a shell script calling java - no file
|
|
# capabilities, so no execve-time capability check like stalwart's.
|
|
add: []
|
|
|
|
podDisruptionBudget:
|
|
# A PodDisruptionBudget, the FIRST in this repository - and with two
|
|
# replicas it is the piece that makes the second one worth having. Without
|
|
# it a node drain is free to take both pods at once, and a drain is the
|
|
# most common reason a pod moves: a node upgrade, not a crash.
|
|
enabled: true
|
|
|
|
# minAvailable, not maxUnavailable, because the guarantee we want is about
|
|
# the service and not about the disruption: at least one node answers
|
|
# logins throughout a drain.
|
|
#
|
|
# THE TRAP THIS CHART AVOIDS BY NOT RENDERING THE OBJECT BELOW TWO
|
|
# REPLICAS: minAvailable: 1 with exactly one replica forbids every
|
|
# voluntary eviction, so `kubectl drain` hangs forever on a pod nothing can
|
|
# move - and the message points at the PDB, not at the replica count. The
|
|
# template therefore skips the PDB entirely while the effective replica
|
|
# count is below 2 (keycloak.replicas: 1, or scaleDown: true), and
|
|
# NOTES.txt prints whether it was rendered.
|
|
minAvailable: 1
|
|
|
|
# Additive environment entries for the Keycloak container, rendered through
|
|
# tpl and supporting the value, secretKeyRef and configMapKeyRef forms.
|
|
#
|
|
# ADDITIVE MEANS ADDITIVE: an entry repeating a name the chart already sets
|
|
# does not replace it. At install time both reach the pod and the later one
|
|
# wins; on an EXISTING release the pair collapses to the chart's own value,
|
|
# because Kubernetes merges env by name - and helm upgrade still reports
|
|
# success. So do not use this to override KC_HOSTNAME or the database
|
|
# variables; change the values that produce them.
|
|
#
|
|
# AND ONE CLASS OF ENTRY IS REFUSED AT RENDER TIME: a BUILD option's variable
|
|
# name (KC_DB, KC_HEALTH_ENABLED, KC_METRICS_ENABLED, KC_FEATURES and the
|
|
# rest of the verified list in _helpers.tpl). A build option in the serving
|
|
# container's environment disagrees with what the init container built, and
|
|
# Keycloak answers a disagreement by re-augmenting - onto a read-only path,
|
|
# i.e. a crash loop whose cause is three files away from the error message.
|
|
# The render fails instead, naming the variable. Build options belong in the
|
|
# values that feed the init container.
|
|
#
|
|
# Genuinely additive and useful here: JAVA_OPTS_APPEND, KC_LOG_LEVEL,
|
|
# KC_SPI_* provider settings.
|
|
env: []
|
|
|
|
# ADDITIONAL VOLUMES AND THEIR MOUNTS - the way a file gets into this pod at
|
|
# all (Gitea #88). Deliberately GENERIC rather than a theme-specific key:
|
|
# the same pair serves a custom login theme, an SPI provider JAR and an extra
|
|
# root CA, and the chart does not have to decide how a theme is delivered
|
|
# (ConfigMap, Secret, PVC, an init container of your own filling an
|
|
# emptyDir). Both lists are passed through verbatim, so an entry may use any
|
|
# Kubernetes volume source.
|
|
#
|
|
# THE MOUNTS ARE RENDERED TWICE - ON THE build INIT CONTAINER AND ON THE
|
|
# SERVING CONTAINER - AND THAT IS THE POINT, NOT AN OVERSIGHT. One list, two
|
|
# places, because the two cases are resolved at different times:
|
|
#
|
|
# * The SERVING container is where a THEME is read. Themes are a runtime
|
|
# concern, so a theme mounted there takes effect in the running server
|
|
# even though this chart serves a pre-built installation.
|
|
# * The build INIT container is where `kc.sh build` runs (point 2 at the
|
|
# top of this file), and a PROVIDER is a build-time concern. Upstream, on
|
|
# dropping a provider JAR into providers/: "After that if you are using
|
|
# `--optimized`, you must run the `build` command in order to update the
|
|
# server's provider registry with the implementations from the JAR file."
|
|
# This chart runs `start --optimized`, so a JAR that is mounted only on
|
|
# the serving container is absent while the registry is written: it never
|
|
# gets registered, the file sits there at runtime, and NOTHING reports
|
|
# it - no error, no warning, just an extension that does nothing.
|
|
#
|
|
# Not on the SEED init container: it has a different layout (it only writes
|
|
# the shared volume at /quarkus-build and runs no Keycloak command), so a
|
|
# mount there would be pointless.
|
|
#
|
|
# The side effect of the second place, said out loud rather than discovered:
|
|
# an extra volume is present AT BUILD TIME as well. For a theme that is
|
|
# without consequence. For a Secret it means an init container of this pod
|
|
# sees it too - the same pod either way, but it belongs stated.
|
|
#
|
|
# WHAT THIS PAIR DOES NOT DO: it places files, it does not configure
|
|
# Keycloak. Two cases where that difference is the whole thing:
|
|
#
|
|
# * AN EXTRA ROOT CA IS NOT TRUSTED BY BEING MOUNTED. A file in the pod is
|
|
# in no truststore; Keycloak reads one only from truststore-paths, so the
|
|
# mount has to be accompanied by KC_TRUSTSTORE_PATHS pointing at the
|
|
# mounted DIRECTORY. That is a RUNTIME option (checked against the 26.8.0
|
|
# option table, where it carries no build icon), so keycloak.env above is
|
|
# the right place for it and the build-option guard does not refuse it.
|
|
# * A THEME IS NOT ACTIVATED BY BEING MOUNTED, AND THIS CHART CANNOT
|
|
# ACTIVATE IT. The selection lives PER REALM in Keycloak's database
|
|
# (Realm settings -> Themes -> Login theme). Mounting makes a theme
|
|
# selectable, never selected - so do not go looking for a bug when a
|
|
# freshly mounted theme changes nothing.
|
|
#
|
|
# THE THEME CASE, AND THE ONE MISTAKE EVERYBODY MAKES EXACTLY ONCE:
|
|
#
|
|
# DO NOT MOUNT ON /opt/keycloak/themes. The image's own themes live there
|
|
# (base, keycloak, keycloak.v2) and a volume at that path HIDES ALL THREE -
|
|
# the login page is then broken rather than merely unstyled. Mount on a
|
|
# SUBPATH that does not exist in the image, /opt/keycloak/themes/<name>,
|
|
# which hides nothing. Same mechanism and same class of mistake as the
|
|
# "DELIBERATELY NOT MOUNTED: /opt/keycloak/conf" note in the Deployment,
|
|
# where an emptyDir would hide the cache-ispn.xml that cache=ispn reads.
|
|
#
|
|
# extraVolumes:
|
|
# - name: login-theme
|
|
# configMap:
|
|
# name: my-login-theme
|
|
# # A theme's own layout is <theme>/<type>/theme.properties, and a
|
|
# # ConfigMap key may not contain a slash - so the subdirectory comes
|
|
# # from items[].path, which may.
|
|
# items:
|
|
# - key: theme.properties
|
|
# path: login/theme.properties
|
|
# extraVolumeMounts:
|
|
# - name: login-theme
|
|
# # NOT /opt/keycloak/themes - see above.
|
|
# mountPath: /opt/keycloak/themes/my-theme
|
|
# readOnly: true
|
|
#
|
|
# With that in place a theme.properties carrying
|
|
#
|
|
# parent=keycloak.v2
|
|
# kcLogoIdP-<alias> = fa fa-lock
|
|
#
|
|
# is what puts an icon on an identity provider's login button - Keycloak has
|
|
# no logo field on a provider, so a login theme is the only way to set one.
|
|
#
|
|
# FOUR PROPERTIES OF THE THEME ROUTE THAT COST TIME IF THEY ARE NOT KNOWN:
|
|
#
|
|
# 1. A ConfigMap carries binary content ONLY in binaryData (base64). An SVG
|
|
# is text and fits in data; a PNG does not. And the 1 MiB limit per
|
|
# ConfigMap applies - ample for a theme with SVG icons, not enough for
|
|
# bundled web fonts, which belong in a PVC or an image of your own.
|
|
# 2. THEMES ARE CACHED, and in production that is wanted. It means a theme
|
|
# change takes effect only after a pod restart. Two things add up here:
|
|
# this chart hashes no user-supplied ConfigMap either (it renders none of
|
|
# its own - point 1 at the top of this file), so editing the theme
|
|
# ConfigMap restarts nothing by itself. Use
|
|
# `kubectl rollout restart deploy/<release>-keycloak-deployment`.
|
|
# Do not configure the cache away to avoid this.
|
|
# 3. The realm selection is not ours to make - see above.
|
|
# 4. A CUSTOM LOGIN THEME CAN MAKE THE IDENTITY-PROVIDER BUTTONS DISAPPEAR
|
|
# ENTIRELY when its template does not include the social fragment
|
|
# correctly (reported repeatedly upstream, keycloak/keycloak#42711).
|
|
# After switching a realm's theme, LOOK AT THE LOGIN PAGE - a healthy
|
|
# pod says nothing about whether the buttons are still there.
|
|
extraVolumes: []
|
|
extraVolumeMounts: []
|
|
|
|
ingress:
|
|
# The PUBLIC ingress - the login endpoint, the token endpoints, the account
|
|
# console. On by default, because an identity provider nothing can reach is
|
|
# not an identity provider.
|
|
enabled: true
|
|
|
|
# The public hostname. REQUIRED, AND REQUIRED EVEN WITH ingress.enabled:
|
|
# false - this is guideline 12.1, checked in both directions: the value is
|
|
# read a second time OUTSIDE the Ingress template, as KC_HOSTNAME on the
|
|
# Deployment. Keycloak refuses to start in production mode without a
|
|
# hostname (hostname-strict defaults to true), so gating it away would
|
|
# produce a release that renders cleanly and never starts.
|
|
#
|
|
# IT IS RENDERED AS A FULL URL, https://<domain>, not as a bare host, and
|
|
# that is upstream's requirement rather than a preference: "If hostname-admin
|
|
# is configured, hostname must be set to a URL (not just hostname). Otherwise
|
|
# Keycloak would not know what is the correct frontend URL". The chart
|
|
# derives the URL, so this value stays a plain hostname.
|
|
domain: null
|
|
|
|
# cert-manager cluster issuer. Required while either Ingress is enabled;
|
|
# nothing outside the Ingress templates reads it, so switching both off frees
|
|
# it.
|
|
clusterIssuer: null
|
|
className: nginx
|
|
|
|
# Extra annotations for the public Ingress; merged with the cert-manager
|
|
# annotation the chart always sets.
|
|
annotations: {}
|
|
|
|
admin:
|
|
# THE ADMIN CONSOLE ON A HOSTNAME OF ITS OWN - and read the next paragraph
|
|
# before you count on it for anything.
|
|
#
|
|
# THIS IS NOT AN ACCESS CONTROL. It sets Keycloak's hostname-admin option,
|
|
# which decides WHICH URLs THE SERVER GENERATES - redirects, the CSS and JS
|
|
# the console loads, and the API address the console itself calls. It does
|
|
# not close anything. Upstream, verbatim: "Using the hostname-admin option
|
|
# does not prevent accessing the Administration REST API endpoints via the
|
|
# frontend URL specified by the hostname option. If you want to restrict
|
|
# access to the Administration REST API, you need to do it on the reverse
|
|
# proxy level."
|
|
#
|
|
# So /admin/realms/... stays reachable through ingress.domain, with or
|
|
# without this block, and an IP restriction on the admin host buys exactly
|
|
# nothing on its own. Whoever wants the administration API restricted does
|
|
# it on the reverse proxy in front of the PUBLIC host. This chart renders
|
|
# no such rule: a path rule on /admin that nobody has tested against a
|
|
# running console breaks redirects and asset loading, and a security
|
|
# measure that only looks like one is worse than an absent one.
|
|
#
|
|
# What the second hostname IS good for: the console generates its own URLs
|
|
# for it, so it can live on an internal ingress class or a different DNS
|
|
# zone, and operators reach it by a name that is not the public login name.
|
|
enabled: true
|
|
|
|
# The admin hostname. Required while ingress.admin.enabled is true. Also
|
|
# rendered as a full URL (https://<domain>) for the same upstream reason as
|
|
# ingress.domain.
|
|
domain: null
|
|
|
|
# null falls back to ingress.className. Set it to an internal ingress class
|
|
# to keep the console off the public controller - which is a useful thing
|
|
# to do, and still not the access control the paragraph above is about.
|
|
className: null
|
|
|
|
# Extra annotations for the admin Ingress only; merged with the cert-manager
|
|
# annotation. This is where an IP allow-list would go
|
|
# (nginx.ingress.kubernetes.io/whitelist-source-range) - with the caveat
|
|
# above firmly in mind.
|
|
annotations: {}
|