Files
helm-charts/keycloak/values.yaml
T
jklein 4c1cff1744 feat(keycloak): new chart for Keycloak (1.0.0+up26.8.0)
Two replicas with cache=ispn, clustered through Keycloak's default jdbc-ping
stack: discovery runs over the PostgreSQL that is there anyway, so no headless
Service and no JGroups wiring. Deployment rather than StatefulSet - Keycloak 26
keeps everything in the database, user sessions included.

The chart's defining property is that it has TWO INIT CONTAINERS and needs
both. db, health-enabled and metrics-enabled are Keycloak BUILD options
(verified in the 26.8.0 option table), the published image is augmented with
their defaults, and supplying different ones at runtime makes Keycloak rewrite
/opt/keycloak/lib/quarkus at start - which readOnlyRootFilesystem: true forbids
and which upstream describes as a start that "creates an image that is
mutable". Not setting db is no way out: the probes alone force it, because
health-enabled is a build option too. An empty emptyDir over that path fails as
well, because QuarkusEntryPoint loads quarkus/quarkus-application.dat before it
dispatches any command, `build` included. So one init container seeds the
directory into a volume, the next runs `kc.sh build` into it, and the serving
container runs `start --optimized` - every container keeping the full hardening
baseline, with no documented deviation to carry.

Build options therefore exist in exactly one place, and the asymmetry is
enforced rather than trusted: the serving container passes no build option at
all, and a build option's variable name in keycloak.env fails the render with a
message naming it.

Hostname handling is the security-relevant part. KC_HOSTNAME is derived from
ingress.domain as a full URL (which upstream requires alongside hostname-admin),
hostname-strict stays at its default true, and proxy-headers is set rather than
optional. The admin hostname is NOT presented as an access control: upstream
states that hostname-admin "does not prevent accessing the Administration REST
API endpoints via the frontend URL specified by the hostname option", so the
chart renders no /admin deny rule and values.yaml, NOTES.txt and the wiki all
say in the same words that restricting that API is reverse-proxy work.

The management port 9000 is a containerPort and a ClusterIP Service for
in-cluster scraping, and is in no Ingress: it serves /health/* and optionally
/metrics without authentication. Liveness uses /health/live and deliberately
not /health or /health/ready - those include the database, and a liveness probe
on them would restart the whole cluster for as long as the database is away.

Also in this change:
- First PodDisruptionBudget in this repository, which makes it a convention
  (README section 11): minAvailable as a value, and rendered only while the
  computed replica count is >= 2, because minAvailable: 1 with one replica
  blocks every node drain forever with a message that names the PDB rather
  than the replica count.
- First quay.io image, so quay.io joins the trusted registries in
  ci/trivy/data/ksv0125.yaml - configured rather than suppressed, so
  AVD-KSV-0125 keeps being able to say something.
- Renovate block with NO version constraint, which is the counter-case to
  garage and stalwart and is measured rather than assumed: docker versioning
  already excludes the -N rebuild tags, the floating minors and latest/nightly.
  Three dry-run fixtures are recorded at the block, and against the committed
  config 26.7.4 offers exactly 26.7.5 (patch) and 26.8.0 (minor).
- No automerge exception, by decision (Gitea #83): the global automerge applies,
  and the minor-upgrade procedure plus `kc.sh update-compatibility` is
  documented in NOTES.txt instead.
- Two upstream defaults deviated from, each with its reason at the value:
  db-pool-max-size 20 instead of 100 (two replicas at 100 ask a default
  PostgreSQL for twice its max_connections), and uid 1000 / gid 0 instead of
  this repo's 20000/1000, because the image declares keycloak:x:1000:0 and
  chowns its install tree to that pair.

Part of #83
2026-10-04 18:00:20 +00:00

536 lines
27 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
# Expose /metrics on the management port (metrics-enabled, a BUILD option -
# it goes to `kc.sh build`, so changing it re-runs the init container).
#
# OFF, and the reason is the port rather than the data: the management
# interface is UNAUTHENTICATED in this chart's configuration, and switching
# metrics on adds Keycloak's full metric set - realm names, client ids, login
# and token counters - to a surface nothing authenticates. Switch it on
# together with whatever scrapes it, and read
# keycloak.ports.management below first.
metrics:
enabled: false
# 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 DOES include the database, and must. A
# node that cannot reach its database serves errors to every login, so no
# Service should route to it.
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 it is
# generous on purpose. What has to fit inside it is not only the JVM and
# Infinispan: on a FIRST install Keycloak creates its entire schema through
# Liquibase, and with two replicas starting at once the second waits on the
# first's migration lock. A budget that is too short there produces a
# CrashLoopBackOff that looks like an application fault and is really a
# stopwatch.
#
# 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
# 1000 AND GROUP 0, which is not this repo's 20000/1000 default and not a
# free choice: the published image declares "keycloak:x:1000:0" in
# /etc/passwd, adds that user to the ROOT GROUP in /etc/group, and copies
# the whole install tree with --chown=1000:0. Everything under
# /opt/keycloak is therefore owned by 1000:0 and group-writable, and the
# emptyDir mounted over /opt/keycloak/lib/quarkus has to be writable by
# the same pair or the build writes nothing.
#
# 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.
runAsUser: 1000
runAsGroup: 0
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: []
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: {}