Upgrading to rc.8 / rc.9¶
What you must change, and what will behave differently, when you move a HelixIAM install from v1.0.0-rc.7 to v1.0.0-rc.8 or v1.0.0-rc.9.
Which release has what
Both are released: v1.0.0-rc.8, and v1.0.0-rc.9 (2026-10-13), which moves the server to Spring Boot 4.1 (Spring Framework 7, Spring Security 7.1) and adds the experimental EU Digital Identity Wallet sign-in and a complete outbound-request (SSRF / DNS-rebinding) hardening. Items marked rc.9 arrived with rc.9. If you upgrade from rc.7 or rc.8 to rc.9, apply every item on this page — and read item 13 first: the rc.9 rollout must not run old and new pods side by side. The sources are the BREAKING checklist at the top of CHANGES-1.0.md and docs/UPGRADING.md in the product repository.
Before you upgrade: checklist¶
Work through this list before you roll out. Each item links to its detail below.
| # | Do this | Applies from |
|---|---|---|
| 1 | Behind any TLS-terminating proxy, ingress, load balancer or mesh, set HELIX_TRUSTED_PROXIES |
rc.8 |
| 2 | List every other host name clients use in HELIX_ALLOWED_HOSTS |
rc.8 |
| 3 | Check clients that reach the server on another port than the base URL's (port mappings) | rc.9 |
| 4 | Make sure your proxy appends to X-Forwarded-For: X-Real-IP is ignored |
rc.9 |
| 5 | If you are adding DB_ENCRYPTION to an install that ran without it, plan the plaintext migration window |
rc.8 (enforced at runtime from rc.9) |
| 6 | Read the first start's log for the legacy re-encryption WARN | rc.9 |
| 7 | Read the generated admin password from the password file, not the log | rc.9 |
| 8 | Check that each eID connection's minimum level of assurance is one the server knows | rc.8 |
| 9 | Classic DigiD now requests the Logius context classes | rc.9 |
| 10 | Stop reading identity-provider secrets back from the admin API (sync scripts, Terraform) | rc.9 |
| 11 | Expect in-flight federated logins to fail once during the rollout | rc.9 |
| 12 | Give the server a database or Redis (no in-memory fallback) | rc.9 |
| 13 | Spring Boot 4: rename FACTOR_… roles, back up, upgrade with no old and new pods side by side (Recreate or scale to 0); know the rollback |
rc.9 |
| 14 | If you set Spring properties directly, rename them | rc.9 |
| 15 | Allow an internal or loopback SMTP relay (realm or global) in HELIX_EGRESS_ALLOWED_PRIVATE_HOSTS, and odd ports in HELIX_MESSAGING_SMTP_ALLOWED_PORTS |
rc.9 |
| 16 | Allow an internal LDAP directory in HELIX_FEDERATION_LDAP_ALLOWED_HOSTS |
rc.9 |
| 17 | Rename helix.outbound.fetch.login.* if you tuned it |
rc.9 |
| 18 | Give every private_key_jwt client an https JWKS URL |
rc.9 |
| 19 | Use /agent/delegation/token, not actor_token on /oauth2/token |
rc.9 |
| 20 | Expect wallet issuer certificates to be checked with HARD_FAIL by default |
rc.9 |
1. Trusted proxies are now required behind a proxy¶
What changed. The server honours forwarded headers only from the proxies you list. X-Forwarded-For (the client IP) and Forwarded / X-Forwarded-Proto / -Host / -Port / -Prefix (the scheme, host and OIDC issuer) are applied only when the TCP peer is in HELIX_TRUSTED_PROXIES; from any other peer they are removed. Before rc.8 they were honoured from any client, so a single request could steer the issuer in the discovery document or claim any client IP for the rate limiter and the audit log.
What you must do. Behind an ingress controller, an external load balancer, a Gateway API route, a service mesh or the console's nginx, set the addresses the proxy connects from:
| Setting | Value |
|---|---|
| Environment | HELIX_TRUSTED_PROXIES (property helix.security.trusted-proxies) |
| Helm | config.trustedProxies |
| Format | comma-separated IP addresses or CIDR ranges, IPv4 or IPv6 |
| Default | empty: no proxy is trusted |
# Helm
helm upgrade helixiam deploy/helm/helixiam --reuse-values \
--set 'config.trustedProxies={10.42.0.0/16}'
If you skip it. Behind a TLS-terminating proxy the server sees plain HTTP: the request-derived issuer and redirects become http://, so relying parties reject the tokens' iss. Every request also appears to come from the proxy, so all clients share one sign-in rate-limit bucket and the audit log records the proxy's address. The server logs an ERROR, at most once a minute, when an https IDP_BASE_URL is reached over plain HTTP, and the Helm install notes print a warning whenever config.trustedProxies is empty.
Never list addresses clients can connect from directly
A listed address is believed when it says who the client is. Only list your own proxies.
2. The server answers only for its own host names¶
What changed. Outside the dev profile, a request whose Host (after trusted forwarded-header processing) is not the host of IDP_BASE_URL, the host of SP_BASE_URL, or a host listed in HELIX_ALLOWED_HOSTS gets 400, before any issuer or URL is derived from it. Before rc.8, a server reached directly answered Host: evil.example with the issuer http://evil.example/realms/….
What you must do. List every other host name that clients use to reach the server, for example the in-cluster service name that relying parties call for tokens or JWKS:
| Setting | Value |
|---|---|
| Environment | HELIX_ALLOWED_HOSTS (property helix.security.allowed-hosts) |
| Helm | config.allowedHosts |
| Format | comma-separated; name accepts any port, name:port (or [v6]:port) only that port |
| Default | empty: only the hosts of IDP_BASE_URL and SP_BASE_URL |
# Helm
helm upgrade helixiam deploy/helm/helixiam --reuse-values \
--set 'config.allowedHosts={helixiam.identity.svc}'
Probes keep working: the connection's own address (kubelet probes send the pod IP), localhost from a loopback peer (Docker HEALTHCHECK) and /actuator/health/** stay reachable.
3. The Host port counts too¶
rc.9. In rc.8 the host allow-list ignored the port. From rc.9, outside dev:
- a host of
IDP_BASE_URLorSP_BASE_URLis accepted only on that URL's port (its explicit port, or 443/80 for https/http); - a
Hostheader without a port is accepted; - a
HELIX_ALLOWED_HOSTSentry accepts any port unless it is writtenhost:port(or[v6]:port); - a port outside 1–65535 gets
400; - the probe exemptions (the connection's own address,
localhostfrom a loopback peer) apply only on the port the connection arrived on, or the scheme's default port;/actuator/health/**still answers on any host and port.
What you must do. A client that reaches the server by its base URL's host on another port, for example through a Docker port mapping, needs that host:port in HELIX_ALLOWED_HOSTS. Otherwise it gets 400.
# IDP_BASE_URL=http://localhost:8080, but a tool calls http://localhost:18080
HELIX_ALLOWED_HOSTS=localhost:18080
4. X-Real-IP is no longer read¶
rc.9. The client IP comes only from X-Forwarded-For, and only from a trusted proxy (read from the right; the first address that is not a trusted proxy is the client). X-Real-IP is never read, not even from a trusted proxy: a trusted hop that neither set nor stripped it (an L4 load balancer, an ingress with forwarded headers off) let a client choose its own address for rate limits and the audit log.
What you must do. Make sure every trusted proxy appends to X-Forwarded-For (nginx $proxy_add_x_forwarded_for; ingress-nginx and cloud load balancers do). A proxy that sets only X-Real-IP now makes every client appear as the proxy.
5. Plaintext secret rows: a time-boxed migration window¶
What changed (rc.8). A secret column (TOTP and HOTP secrets, realm signing keys, client and webhook secrets, SCIM tokens, messaging-provider secrets, SAML relying-party certificates) whose stored value cannot be decrypted used to be returned as stored. It now fails the read. A deployment that ran without DB_ENCRYPTION and then sets it may hold plaintext rows; reading them is an explicit, loud and time-boxed migration mode:
| Variable | Default | Meaning |
|---|---|---|
HELIX_DB_ENCRYPTION_MIGRATE_PLAINTEXT |
false |
true reads undecryptable secret columns as stored (a WARN per read, naming entity and column), encrypts them on their next write, and logs an ERROR banner at startup and every hour |
HELIX_DB_ENCRYPTION_MIGRATE_UNTIL |
empty | the last day of the window, yyyy-MM-dd (UTC, inclusive); outside dev the server refuses to start in migration mode unless this is a date at most 30 days ahead |
rc.9: the end date is also enforced while the server runs. After that day, a plaintext read fails closed and logs an ERROR naming the date (rc.8 only checked it at startup).
DB_ENCRYPTION=<your 32-hex-character key>
HELIX_DB_ENCRYPTION_MIGRATE_PLAINTEXT=true
HELIX_DB_ENCRYPTION_MIGRATE_UNTIL=2026-11-10
Unset both once the rows are rewritten. HELIX_ALLOW_PLAINTEXT_SECRETS again only means "accept running without a key", and an empty DB_ENCRYPTION now means "no key" (it used to fail startup with "Empty key").
The mode also hides a changed key
While it is on, a value written under a different DB_ENCRYPTION key is read as stored too. That is why it is loud and time-boxed. Never use it to paper over a key change.
6. Legacy-encrypted secrets are re-encrypted at startup¶
rc.9. At every startup, values of every encrypted column still in the legacy AES/ECB format (realm keys, TOTP and HOTP secrets, client secrets, SCIM tokens, webhook secrets, identity-provider and messaging secrets, SAML relying-party certificates, verifier keys, wallet peppers) are re-encrypted to AES-GCM. The job is idempotent and logs table, column and row id only. Rows it cannot read are skipped and named in a production-readiness WARN.
After that run the legacy format is no longer read: a remaining legacy value fails closed, unless you set:
| Variable | Default | Meaning |
|---|---|---|
HELIX_DB_ENCRYPTION_ALLOW_LEGACY |
false |
true keeps reading the legacy format after the startup run |
Each column is migrated in its own transaction. A column that fails with a database error is logged at ERROR by table and column, reported as a readiness WARNING, and counted in the gauge helix_db_encryption_legacy_failed_columns; the legacy fallback then stays on until a later start migrates it.
What you must do. Usually nothing: the first start rewrites the legacy rows. Check that start's log for the readiness WARN. Only if the WARN names rows, or you attach a database restored from an older backup to a running server, set HELIX_DB_ENCRYPTION_ALLOW_LEGACY=true (or restart, which re-runs the job) while you investigate.
7. The generated admin password is no longer logged¶
rc.9. When HELIX_ADMIN_PASSWORD is unset, the first boot still generates a strong random password for the master-realm admin, but it is never logged. It is written with mode 0600 to HELIX_ADMIN_PASSWORD_FILE, and only the path is logged.
| Variable | Default |
|---|---|
HELIX_ADMIN_PASSWORD_FILE |
$TMPDIR/helixiam-admin-password (/tmp/helixiam-admin-password in the container image) |
Read it from where the server ran, then sign in and change it:
# Docker Compose (after removing HELIX_ADMIN_PASSWORD from the compose file)
docker compose exec server cat /tmp/helixiam-admin-password
# Helm: /tmp is an emptyDir, so read it before that pod restarts.
# With several replicas, exec into the pod whose log says it generated the password.
kubectl exec deploy/helixiam -- cat /tmp/helixiam-admin-password
HELIX_ADMIN_USERNAME, HELIX_ADMIN_PASSWORD and HELIX_ADMIN_PASSWORD_FILE are declared in application.properties, so they also work as command-line arguments (--HELIX_ADMIN_PASSWORD=…). For any non-local install, set HELIX_ADMIN_PASSWORD explicitly (Helm: the admin-password key of secrets.existingSecret).
8. Unknown eID minimum levels are refused¶
What changed (rc.8). An eID connection (DigiD, eHerkenning, eIDAS) whose minimumLoa was not a level the server knows accepted every asserted level. An unrecognised non-blank minimum is now never met (fail closed); a blank minimum still means "no minimum". The keywords the console stores are now mapped to the scheme's ladder, and eHerkenning EH2+ (loa2plus) ranks above EH2 and below EH3.
An unknown minimum is refused:
- when the connection is saved through the admin API:
400withfieldErrors.minimumLoa; - when a realm is imported: the import reports it as a failed import with the field error (rc.9; rc.8 saved it and skipped it with a WARN at load);
- when a stored connection is built: it is skipped with a warning (a connection in
helix.federation.eidfails startup).
What you must do. Check every eID connection's minimum against the accepted values. A connection with a value outside that list stops working after the upgrade.
9. Classic DigiD requests the Logius context classes¶
rc.9. The classic DigiD AuthnRequest used to ask for the urn:nl-eid-gdi:1.0:LoA:* URN, which a DigiD endpoint does not know. Following Logius' Koppelvlakspecificatie DigiD SAML Authenticatie v3.5, a DigiD keyword is now requested as the SAML 2.0 context class of that level, with RequestedAuthnContext Comparison="minimum":
| DigiD level | Keywords | Requested class (urn:oasis:names:tc:SAML:2.0:ac:classes:…) |
|---|---|---|
| Basis | loa1, basis |
PasswordProtectedTransport |
| Midden | loa2, midden |
MobileTwoFactorContract |
| Substantieel | loa3, substantieel |
Smartcard |
| Hoog | loa4, hoog |
SmartcardPKI |
The asserted class is ranked on the same ladder (rc.8 already ranked these classes when comparing). A minimum stored as an urn:nl-eid-gdi:1.0:LoA:* URN is still compared as stored but requested as the context class of the same level, and brokers that assert those URNs keep working. No eID scheme sends an nl-eid-gdi URN in the request any more.
What you must do. Nothing for the console keywords. If you worked around the old behaviour by storing the oasis class by hand as an "Other (custom)" minimum, that is no longer needed. A broker that requires the nl-eid-gdi URN in the AuthnRequest is not supported as a DigiD connection. See DigiD.
10. Identity-provider secrets are write-only in the admin API¶
rc.9 — API change. GET /admin/realms/{realm}/identity-providers (list and by alias) no longer returns secret values: the OIDC clientSecret, the eID SP private keys (spDecryptionPrivateKey, spSigningPrivateKey), the LDAP bind password, and the wallet's keys. Each one comes back as a flag instead:
On a write (POST or PUT):
| You send | Effect |
|---|---|
the key absent, null or blank |
the stored secret is kept |
| a value | the stored secret is replaced |
"clear<Key>": "true" (e.g. "clearClientSecret": "true") and no value |
the stored secret is removed |
| a value and its clear flag | 400 |
Secrets are now encrypted at rest (Flyway V75 / V75.1), and realm exports mask them.
What you must do. Any script or tool that reads a secret back must stop doing so. A tool that compares the returned secret with its desired value (a sync script, a Terraform provider) will always see a difference. The bundled Terraform provider is not affected. At rc.9 it had no identity-provider resource; its helix_wallet_identity_provider resource, added after rc.10, never reads a secret back: access_certificate_private_key keeps the value in state, and removing it from the configuration clears it on the server. A third-party or home-grown provider that manages identity providers is affected.
11. In-flight federated and wallet logins fail once during the rollout¶
rc.9. The broker's per-login session state (state, nonce, SAML request id, browser binding, wallet transaction) is now keyed by realm and alias, so logins in two realms that use the same alias no longer clobber each other. The session keys were renamed as a result.
What to expect. A federated (OIDC, SAML, eID) or wallet login, or a wallet link in the account console, that starts on the old version and finishes on the new one ends with error=federation (or a refused link). Starting it again works. This is transient and affects no data. Roll out outside peak hours if that matters to you.
12. The wallet transaction store needs the database or Redis¶
rc.9. The EU Digital Identity Wallet keeps each login's in-flight state in a transaction store shared by all replicas: PostgreSQL (wallet_transaction, Flyway V79) or Redis, chosen from your session and token stores. The in-memory store is used only when you ask for it with HELIX_WALLET_TRANSACTION_STORE=memory (development and tests, one replica). A server with neither Redis nor a database that was not asked for memory refuses to start instead of falling back silently.
What you must do. Nothing for a normal install, which always has PostgreSQL. See Transaction store.
13. Spring Boot 4: do not run old and new pods side by side¶
rc.9. The server moved from Spring Boot 3.5 (rc.8) to Spring Boot 4.1 (Spring Security 7.1). It reads everything the earlier release stored — sessions, authorization codes, refresh tokens, device codes, consents — in PostgreSQL and in Redis. The reverse does not hold: the earlier release cannot read what rc.9 writes or rewrites. Every new sign-in carries Spring Security 7's sign-in factor (FactorGrantedAuthority), a class the earlier release does not have.
| Written by rc.9 | Read by an earlier pod, PostgreSQL store (queue) |
Read by an earlier pod, Redis store |
|---|---|---|
| a browser session (any sign-in) | dropped (HTTP session dropped WARN): the user is signed out |
HTTP 500 on every request with that cookie, until the session expires or is deleted |
| an OAuth2 authorization rc.9 created or rewrote (sign-in, code exchange, refresh, revocation, consent, device-flow step) | treated as absent: refresh fails (invalid_grant), introspection says active: false |
HTTP 500 for that token until it expires or is deleted |
Access tokens are JWTs and stay valid for their lifetime on either version. The stores: PostgreSQL tables helix_authorization, helix_authorization_token and helix_http_session (HELIX_SESSION_STORE=queue); Redis keys spring:session:* (sessions, the default store) and helix:authz:* (tokens with HELIX_TOKEN_STORE=redis).
1. Rename roles named FACTOR_…. The prefix (any case) is now reserved for Spring Security's sign-in factors (FACTOR_PASSWORD marks every password login). A role with such a name is ignored — it grants nothing in admin RBAC and never appears in a token — and the admin API and realm import refuse to create one (400). List them; an empty result means nothing to do:
Run this query with psql "$DATABASE_URL":
SELECT 'realm role' AS kind, tenant_id AS realm, name AS role, NULL AS holder
FROM user_roles WHERE name ILIKE 'factor\_%'
UNION ALL
SELECT 'client role', realm_id, name, client_id
FROM client_role WHERE name ILIKE 'factor\_%'
UNION ALL
SELECT 'service-account role', realm_id, role_name, client_id
FROM client_service_account_role WHERE role_name ILIKE 'factor\_%'
UNION ALL
SELECT 'agent role', realm_id, roles, name
FROM agent_identity WHERE roles ~* '(^|[,[:space:]/])factor_'
ORDER BY 1, 2, 3;
It covers realm, client, service-account and agent roles. Rename any it lists (create the new role, move its users, grants and mappings, delete the old one) before upgrading.
2. Back up PostgreSQL, the token store included (a pg_dump of the whole database covers it), and Redis if you use it (redis-cli BGSAVE, then copy dump.rdb).
3. Upgrade without mixing versions. A rolling update, or scaling to one replica, still runs old and new side by side. Pick one:
- Helm, Recreate — every old pod stops before a new one starts (the server is unavailable for one start-up):
helm upgrade helixiam deploy/helm/helixiam --reuse-values --server-side=false \
--set image.tag=<new version> --set strategy.type=Recreate
--server-side=false is needed once, with Helm 4: the rc.8 chart never rendered spec.strategy, so under Helm 4's default server-side apply the rollingUpdate block Kubernetes defaulted would survive and the API server would reject the Deployment. With Helm 3 leave the flag out. From rc.9 the chart renders the strategy in full (the new value strategy), so later changes need no flag.
- Or scale to zero first, then upgrade (the upgrade restores replicaCount):
kubectl -n <namespace> scale deploy/helixiam --replicas=0
kubectl -n <namespace> wait --for=delete pod -l app.kubernetes.io/name=helixiam --timeout=180s
helm upgrade helixiam deploy/helm/helixiam --reuse-values --set image.tag=<new version>
4. Check readiness (/actuator/health/readiness), one sign-in, and one refresh of a token issued before the upgrade. Later releases can go back to rolling updates: --set strategy.type=RollingUpdate.
Rolling back to rc.8 signs users out and drops every authorization rc.9 wrote or rewrote. The rc.8 chart has no strategy value, so helm rollback alone would mix the versions. Instead:
- Scale to zero and wait until every rc.9 pod is gone (the two
kubectlcommands above). - Delete what rc.8 cannot read from Redis (PostgreSQL stores: nothing to delete): sessions
spring:session:*, and withHELIX_TOKEN_STORE=redistokenshelix:authz:*(every client then needs a new sign-in orclient_credentialstoken). Otherwise rc.8 answers HTTP 500 for them until they expire. The script uses SCAN and UNLINK:
# REDIS_HOST, REDIS_PORT; REDIS_PASSWORD (requirepass) or REDIS_USER + REDIS_PASSWORD (ACL);
# REDIS_TLS=1 + REDIS_CACERT (+ REDIS_CERT, REDIS_KEY); REDIS_CLUSTER=1 for a cluster.
REDIS_HOST=redis.example.internal REDIS_PASSWORD=... deploy/scripts/redis-delete-keys.sh 'spring:session:*'
REDIS_HOST=redis.example.internal REDIS_PASSWORD=... deploy/scripts/redis-delete-keys.sh 'helix:authz:*'
# or inside the Redis pod (no local redis-cli needed):
kubectl -n <namespace> exec -i deploy/redis -- sh -s 'spring:session:*' < deploy/scripts/redis-delete-keys.sh
helm rollback helixiam <revision before the upgrade> (helm history helixiam lists it).
4. Check the pods are back; if the replica count stayed at 0, kubectl -n <namespace> scale deploy/helixiam --replicas=<n>.
Restoring the backup instead undoes every other change made since the upgrade (users, clients, settings, audit log): use it only if nothing else changed.
14. Renamed Spring properties¶
rc.9. Only if you set Spring properties directly (environment variables SPRING_* / MANAGEMENT_*, or a mounted application.properties). The HELIX_* settings and the Helm chart are unchanged.
| Spring Boot 3.5 (rc.8) | Spring Boot 4 (rc.9) |
|---|---|
server.error.* (e.g. SERVER_ERROR_INCLUDE_MESSAGE) |
spring.web.error.* (SPRING_WEB_ERROR_INCLUDE_MESSAGE); the old keys are ignored |
spring.session.redis.* |
spring.session.data.redis.* |
spring.jackson.* (the application's JSON mapper) |
spring.jackson2.* (spring.jackson.* now configures Boot's Jackson 3 mapper only) |
spring.dao.exceptiontranslation.enabled |
spring.persistence.exceptiontranslation.enabled |
This row is reversed again as of rc.11. rc.9/rc.10 (above) ran the application's own mapper on Jackson 2 under
spring.jackson2.*, withspring.jackson.*configuring only Boot'sJsonMapper. rc.11 moves the application itself onto Jackson 3:spring-boot-jackson2is removed, andspring.jackson.*once again configures the application's mapper (as well as Boot's). There is nospring.jackson2.*property any more. See Upgrading from rc.10 to rc.11 for the full detail, including the new 500-level nesting limit and the unchanged 20 MB string limit.
15. SMTP relays pass the egress guard¶
rc.9. Until rc.8 the SMTP driver connected to any host and port a realm admin typed, loopback and cluster-internal addresses included. Now every relay — a realm's SMTP provider and the operator's global relay (helix.notification.smtp.*, used when a realm has no email provider) — passes the outbound guard: the name is resolved once, the connection goes to the checked address, and TLS (SMTPS or STARTTLS) still verifies the configured host name.
| Setting | Helm | Default | Meaning |
|---|---|---|---|
HELIX_EGRESS_ALLOWED_PRIVATE_HOSTS (helix.egress.allowed-private-hosts) |
config.egressAllowedPrivateHosts |
empty | private or loopback hosts outbound calls may reach; exact host names or IP literals, optionally host:port |
HELIX_MESSAGING_SMTP_ALLOWED_PORTS (helix.messaging.smtp.allowed-ports) |
— (config.extraEnv) |
empty | SMTP ports allowed besides 25, 465, 587 and 2525; ports or ranges, e.g. 1025,8025-8030 |
What you must do. List an in-cluster or localhost relay, preferably as host:port so only that port opens:
HELIX_EGRESS_ALLOWED_PRIVATE_HOSTS=mailer.mail.svc.cluster.local:587
# a relay on a non-standard port also needs:
HELIX_MESSAGING_SMTP_ALLOWED_PORTS=1025
Until then sends fail with result CONFIGURATION ("not allowed by the egress policy"). Both are deployment settings: a realm admin cannot widen them. In Helm, also allow the port in networkPolicy.extraEgressPorts.
16. LDAP directories pass the egress guard¶
rc.9. An LDAP federation provider used to let JNDI connect to any URL a realm admin typed. Now only one ldap:// or ldaps:// URL is accepted, the host is resolved once and both binds connect to the checked address, LDAPS verifies the certificate against the configured name, and referrals are ignored. A private or loopback directory — the usual Active Directory setup — must be listed:
| Setting | Helm | Default | Format |
|---|---|---|---|
HELIX_FEDERATION_LDAP_ALLOWED_HOSTS (helix.federation.ldap.allowed-hosts) |
config.ldapAllowedHosts |
empty (public directories only) | host names (ldap.corp, ldap.corp:636) or CIDR ranges (10.20.0.0/16) |
helm upgrade helixiam deploy/helm/helixiam --reuse-values --set 'config.ldapAllowedHosts={ldap.corp,10.20.0.0/16}'
Until then LDAP logins for that provider fail (logged as refused by the egress policy). Metadata and link-local addresses stay refused even inside a listed range. Also allow 389/636 in networkPolicy.extraEgressPorts.
17. Outbound fetch limits¶
rc.9. Server-initiated calls run in feature groups, each with a total limit and a per-realm share, so one realm's slow endpoints cannot delay another realm or any login:
| Group | Used by | helix.outbound.fetch.<group>.max-concurrent |
.per-realm-max |
|---|---|---|---|
login-admin |
OIDC broker, eID, workload identity | 32 | 8 |
login-client |
client-assertion JWKS, FAPI request-object JWKS | 32 | 8 |
otp |
OTP delivery | 32 | 8 |
outbound |
webhooks, SCIM, logout | 32 | 8 |
ldap |
LDAP binds (one 15 s deadline per bind sequence) | 32 | 8 |
wallet |
wallet status lists and issuer revocation | 16 | = max-concurrent |
What you must do. Only if you tuned it: helix.outbound.fetch.login.* is split into login-admin and login-client. The old login names are still read as login-admin; helix.wallet.fetch.max-concurrent is still read as helix.outbound.fetch.wallet.max-concurrent. Webhook and SCIM queues also hold at most 1,000 pending deliveries per realm and helix.outbound.deliveries.max-pending (20,000) in total.
18. Client JWKS URLs must be https¶
rc.9. A private_key_jwt client's JWKS URL is chosen by whoever registers the client, so the token endpoint now fetches it through the outbound guard, without redirects.
- Admin API, console and realm-import client writes need an
httpsjwksUrl(http://localhost/http://127.0.0.1only indev). A realm import reports such a client as a failedclientsentry (jwksUrl: The JWKS URL must be an https URL.). - Flyway
V80rewrites a DCR-stored lowercaseprivate_key_jwtauth method toPRIVATE_KEY_JWT. The auth method is matched case-insensitively; the admin API and realm import refuse unknown values. Dynamic Client Registration accepts onlyclient_secret_basic,private_key_jwtandnone, and requires an httpsjwks_urithe guard allows. - A client stored earlier with an
httpJWKS URL can no longer authenticate outsidedev: the token endpoint refuses to fetch it (invalid_client). At startup the server logs a WARN (ClientAuthMethodAudit) for every such client, and for everyprivate_key_jwtclient without a JWKS URL. Change their JWKS URL to https before upgrading.
19. actor_token is refused on /oauth2/token¶
rc.9. A token exchange on /oauth2/token with actor_token or actor_token_type is refused with invalid_request. Plain token exchange (subject_token only) still works. HelixIAM's supported on-behalf-of delegation is /agent/delegation/token — see Agent authorization.
20. Wallet issuer revocation defaults to HARD_FAIL¶
rc.9. An EU Digital Identity Wallet connection without an explicit revocationChecking now checks issuer certificates with HARD_FAIL (was SOFT_FAIL): an issuer certificate whose CRL / OCSP status cannot be determined — including one that names no CRL or OCSP endpoint — is refused. For an issuer that publishes neither, set revocationChecking to SOFT_FAIL (console: Credentials → Issuer certificate revocation).
Related changes you may notice¶
These need no action but change behaviour you may see:
- Federated logins get a new session id at completion and again after a post-login step-up (OIDC, SAML, eID, wallet).
- MFA after a federated sign-in. The post-broker second factor is now enforced for users with MFA enabled, and in realms that require MFA, a user who has not enrolled is sent to
/mfa/enableafter a federated, password, flow-engine or/flowsign-in instead of getting a full session. - Console-created identity providers load in every realm (
HELIX_FEDERATION_STORE_ENABLED, defaulttrue). With several replicas, an identity-provider change reaches every replica withinhelix.federation.store.poll-interval-seconds(default 10). - Broker start errors send the browser to
/login?error=federationinstead of an error page. - eID artifact binding. The back-channel
ArtifactResponseof classic DigiD must carry a valid signature by the IdP's metadata signing key; an unsigned response, or any response when the connection has no IdP signing certificate, is refused. - ID tokens carry
acrfor brokered eID logins whose provider asserted a recognised level, and for wallet logins. - APNs push goes only to
api.push.apple.com/api.sandbox.push.apple.com(HTTP/2, no redirects); a providerhostnaming anything else is a configuration error. - Outbound calls are pinned (rc.9): webhooks, SCIM, HTTP email/SMS/push, logout, OIDC broker and workload-identity JWKS, eID artifact resolution, SAML metadata import, wallet status lists and CRL/OCSP connect to the address the guard checked (no DNS rebinding), follow no redirects, and IPv6 forms that embed an IPv4 address are judged by that address.
- Helm (rc.9): new values
strategy(rendered in full; default Kubernetes' RollingUpdate 25% / 25%) andenableServiceLinks(defaultfalse, so a Service namedredisno longer injectsREDIS_PORT=tcp://…).
See also¶
- Upgrades & migrations — the general rolling-upgrade procedure
- Security hardening — the production checklist, including proxies and host names
- Configuration — every variable on this page, with defaults
- Install — Helm values
config.trustedProxies,config.allowedHosts,config.ldapAllowedHosts,strategy