This is an automated email from the ASF dual-hosted git repository.

adutra pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/polaris.git


The following commit(s) were added to refs/heads/main by this push:
     new 46a2a0311 feat(helm): Add support for external authentication (#2104)
46a2a0311 is described below

commit 46a2a031109612ddabc1c399df65220ca3a2c5d1
Author: Alexandre Dutra <adu...@apache.org>
AuthorDate: Thu Jul 17 23:15:31 2025 +0200

    feat(helm): Add support for external authentication (#2104)
---
 CHANGELOG.md                               |   3 +
 helm/polaris/README.md                     |  33 +++++--
 helm/polaris/ci/authentication-values.yaml |  71 +++++++++++++++
 helm/polaris/ci/fixtures/oidc.yaml         |  26 ++++++
 helm/polaris/ci/fixtures/token-broker.yaml |   1 +
 helm/polaris/templates/_helpers.tpl        | 112 +++++++++++++++++-------
 helm/polaris/templates/configmap.yaml      |  38 ++++++---
 helm/polaris/templates/deployment.yaml     |   3 +-
 helm/polaris/tests/configmap_test.yaml     | 133 +++++++++++++++++++++++++++--
 helm/polaris/tests/deployment_test.yaml    |  65 ++++++++++++++
 helm/polaris/values.yaml                   |  76 +++++++++++++++--
 site/content/in-dev/unreleased/helm.md     |  33 +++++--
 12 files changed, 527 insertions(+), 67 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 36d1f03cc..108eb0004 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -33,6 +33,9 @@ request adding CHANGELOG notes for breaking (!) changes and 
possibly other secti
 
 ### Breaking changes
 
+- Helm chart: the default value of the 
`authentication.tokenBroker.secret.symmetricKey.secretKey` property has changed
+  from `symmetric.pem` to `symmetric.key`.
+
 ### New Features
 
 - Added Catalog configuration for S3 and STS endpoints. This also allows using 
non-AWS S3 implementations.
diff --git a/helm/polaris/README.md b/helm/polaris/README.md
index 05b006974..d0daa190b 100644
--- a/helm/polaris/README.md
+++ b/helm/polaris/README.md
@@ -189,11 +189,13 @@ ct install --namespace polaris --charts ./helm/polaris
 |-----|------|---------|-------------|
 | advancedConfig | object | `{}` | Advanced configuration. You can pass here 
any valid Polaris or Quarkus configuration property. Any property that is 
defined here takes precedence over all the other configuration values generated 
by this chart. Properties can be passed "flattened" or as nested YAML objects 
(see examples below). Note: values should be strings; avoid using numbers, 
booleans, or other types. |
 | affinity | object | `{}` | Affinity and anti-affinity for polaris pods. See 
https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity.
 |
-| authentication | object | 
`{"authenticator":{"type":"default"},"tokenBroker":{"maxTokenGeneration":"PT1H","secret":{"name":null,"privateKey":"private.pem","publicKey":"public.pem","rsaKeyPair":{"privateKey":"private.pem","publicKey":"public.pem"},"secretKey":"symmetric.pem","symmetricKey":{"secretKey":"symmetric.pem"}},"type":"rsa-key-pair"},"tokenService":{"type":"default"}}`
 | Polaris authentication configuration. |
-| authentication.authenticator | object | `{"type":"default"}` | The type of 
authentication to use. Two built-in types are supported: default and test; test 
is not recommended for production. |
-| authentication.tokenBroker | object | 
`{"maxTokenGeneration":"PT1H","secret":{"name":null,"privateKey":"private.pem","publicKey":"public.pem","rsaKeyPair":{"privateKey":"private.pem","publicKey":"public.pem"},"secretKey":"symmetric.pem","symmetricKey":{"secretKey":"symmetric.pem"}},"type":"rsa-key-pair"}`
 | The type of token broker to use. Two built-in types are supported: 
rsa-key-pair and symmetric-key. |
+| authentication | object | 
`{"activeRolesProvider":{"type":"default"},"authenticator":{"type":"default"},"realmOverrides":{},"tokenBroker":{"maxTokenGeneration":"PT1H","secret":{"name":null,"privateKey":"private.pem","publicKey":"public.pem","rsaKeyPair":{"privateKey":"private.pem","publicKey":"public.pem"},"secretKey":"symmetric.pem","symmetricKey":{"secretKey":"symmetric.key"}},"type":"rsa-key-pair"},"tokenService":{"type":"default"},"type":"internal"}`
 | Polaris authentication config [...]
+| authentication.activeRolesProvider | object | `{"type":"default"}` | The 
`ActiveRolesProvider` implementation to use. Only one built-in type is 
supported: default. |
+| authentication.authenticator | object | `{"type":"default"}` | The 
`Authenticator` implementation to use. Only one built-in type is supported: 
default. |
+| authentication.realmOverrides | object | `{}` | Authentication configuration 
overrides per realm. |
+| authentication.tokenBroker | object | 
`{"maxTokenGeneration":"PT1H","secret":{"name":null,"privateKey":"private.pem","publicKey":"public.pem","rsaKeyPair":{"privateKey":"private.pem","publicKey":"public.pem"},"secretKey":"symmetric.pem","symmetricKey":{"secretKey":"symmetric.key"}},"type":"rsa-key-pair"}`
 | The `TokenBroker` implementation to use. Two built-in types are supported: 
rsa-key-pair and symmetric-key. Only relevant when using internal (or mixed) 
authentication. When using ex [...]
 | authentication.tokenBroker.maxTokenGeneration | string | `"PT1H"` | Maximum 
token generation duration (e.g., PT1H for 1 hour). |
-| authentication.tokenBroker.secret | object | 
`{"name":null,"privateKey":"private.pem","publicKey":"public.pem","rsaKeyPair":{"privateKey":"private.pem","publicKey":"public.pem"},"secretKey":"symmetric.pem","symmetricKey":{"secretKey":"symmetric.pem"}}`
 | The secret name to pull the public and private keys, or the symmetric key 
secret from. |
+| authentication.tokenBroker.secret | object | 
`{"name":null,"privateKey":"private.pem","publicKey":"public.pem","rsaKeyPair":{"privateKey":"private.pem","publicKey":"public.pem"},"secretKey":"symmetric.pem","symmetricKey":{"secretKey":"symmetric.key"}}`
 | The secret name to pull the public and private keys, or the symmetric key 
secret from. |
 | authentication.tokenBroker.secret.name | string | `nil` | The name of the 
secret to pull the keys from. If not provided, a key pair will be generated. 
This is not recommended for production. |
 | authentication.tokenBroker.secret.privateKey | string | `"private.pem"` | 
DEPRECATED: Use `authentication.tokenBroker.secret.rsaKeyPair.privateKey` 
instead. Key name inside the secret for the private key |
 | authentication.tokenBroker.secret.publicKey | string | `"public.pem"` | 
DEPRECATED: Use `authentication.tokenBroker.secret.rsaKeyPair.publicKey` 
instead. Key name inside the secret for the public key |
@@ -201,9 +203,10 @@ ct install --namespace polaris --charts ./helm/polaris
 | authentication.tokenBroker.secret.rsaKeyPair.privateKey | string | 
`"private.pem"` | Key name inside the secret for the private key |
 | authentication.tokenBroker.secret.rsaKeyPair.publicKey | string | 
`"public.pem"` | Key name inside the secret for the public key |
 | authentication.tokenBroker.secret.secretKey | string | `"symmetric.pem"` | 
DEPRECATED: Use `authentication.tokenBroker.secret.symmetricKey.secretKey` 
instead. Key name inside the secret for the symmetric key |
-| authentication.tokenBroker.secret.symmetricKey | object | 
`{"secretKey":"symmetric.pem"}` | Optional: configuration specific to symmetric 
key secret. |
-| authentication.tokenBroker.secret.symmetricKey.secretKey | string | 
`"symmetric.pem"` | Key name inside the secret for the symmetric key |
-| authentication.tokenService | object | `{"type":"default"}` | The type of 
token service to use. Two built-in types are supported: default and test; test 
is not recommended for production. |
+| authentication.tokenBroker.secret.symmetricKey | object | 
`{"secretKey":"symmetric.key"}` | Optional: configuration specific to symmetric 
key secret. |
+| authentication.tokenBroker.secret.symmetricKey.secretKey | string | 
`"symmetric.key"` | Key name inside the secret for the symmetric key |
+| authentication.tokenService | object | `{"type":"default"}` | The token 
service (`IcebergRestOAuth2ApiService`) implementation to use. Two built-in 
types are supported: default and disabled. Only relevant when using internal 
(or mixed) authentication. When using external authentication, the token 
service is always disabled. |
+| authentication.type | string | `"internal"` | The type of authentication to 
use. Three built-in types are supported: internal, external, and mixed. |
 | autoscaling.enabled | bool | `false` | Specifies whether automatic 
horizontal scaling should be enabled. Do not enable this when using in-memory 
version store type. |
 | autoscaling.maxReplicas | int | `3` | The maximum number of replicas to 
maintain. |
 | autoscaling.minReplicas | int | `1` | The minimum number of replicas to 
maintain. |
@@ -283,6 +286,22 @@ ct install --namespace polaris --charts ./helm/polaris
 | metrics.enabled | bool | `true` | Specifies whether metrics for the polaris 
server should be enabled. |
 | metrics.tags | object | `{}` | Additional tags (dimensional labels) to add 
to the metrics. |
 | nodeSelector | object | `{}` | Node labels which must match for the polaris 
pod to be scheduled on that node. See 
https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodeselector.
 |
+| oidc | object | 
`{"authServeUrl":null,"client":{"id":"polaris","secret":{"key":"clientSecret","name":null}},"principalMapper":{"idClaimPath":null,"nameClaimPath":null,"type":"default"},"principalRolesMapper":{"filter":null,"mappings":[],"rolesClaimPath":null,"type":"default"}}`
 | Polaris OIDC configuration. Only relevant when at least one realm is 
configured for external (or mixed) authentication. The currently supported 
configuration is for a single, default OIDC tenant. For more comp [...]
+| oidc.authServeUrl | string | `nil` | The authentication server URL. Must be 
provided if at least one realm is configured for external authentication. |
+| oidc.client | object | 
`{"id":"polaris","secret":{"key":"clientSecret","name":null}}` | The client to 
use when authenticating with the authentication server. |
+| oidc.client.id | string | `"polaris"` | The client ID to use when contacting 
the authentication server's introspection endpoint in order to validate tokens. 
|
+| oidc.client.secret | object | `{"key":"clientSecret","name":null}` | The 
secret to pull the client secret from. If no client secret is required, leave 
the secret name unset. |
+| oidc.client.secret.key | string | `"clientSecret"` | The key name inside the 
secret to pull the client secret from. |
+| oidc.client.secret.name | string | `nil` | The name of the secret to pull 
the client secret from. If not provided, the client is assumed to not require a 
client secret when contacting the introspection endpoint. |
+| oidc.principalMapper | object | 
`{"idClaimPath":null,"nameClaimPath":null,"type":"default"}` | Principal 
mapping configuration. |
+| oidc.principalMapper.idClaimPath | string | `nil` | The path to the claim 
that contains the principal ID. Nested paths can be expressed using "/" as a 
separator, e.g. "polaris/principal_id" would look for the "principal_id" field 
inside the "polaris" object in the token claims. Optional. Either this option 
or `nameClaimPath` (or both) must be provided. |
+| oidc.principalMapper.nameClaimPath | string | `nil` | The claim that 
contains the principal name. Nested paths can be expressed using "/" as a 
separator, e.g. "polaris/principal_name" would look for the "principal_name" 
field inside the "polaris" object in the token claims. Optional. Either this 
option or `idClaimPath` (or both) must be provided. |
+| oidc.principalMapper.type | string | `"default"` | The `PrincipalMapper` 
implementation to use. Only one built-in type is supported: default. |
+| oidc.principalRolesMapper | object | 
`{"filter":null,"mappings":[],"rolesClaimPath":null,"type":"default"}` | 
Principal roles mapping configuration. |
+| oidc.principalRolesMapper.filter | string | `nil` | A regular expression 
that matches the role names in the identity. Only roles that match this regex 
will be included in the Polaris-specific roles. |
+| oidc.principalRolesMapper.mappings | list | `[]` | A list of regex mappings 
that will be applied to each role name in the identity. This can be used to 
transform the role names in the identity into role names as expected by 
Polaris. The default ActiveRolesProvider expects the security identity to 
expose role names in the format `POLARIS_ROLE:<role name>`. |
+| oidc.principalRolesMapper.rolesClaimPath | string | `nil` | The path to the 
claim that contains the principal roles. Nested paths can be expressed using 
"/" as a separator, e.g. "polaris/principal_roles" would look for the 
"principal_roles" field inside the "polaris" object in the token claims. If not 
set, Quarkus looks for roles in standard locations. See 
https://quarkus.io/guides/security-oidc-bearer-token-authentication#token-claims-and-security-identity-roles.
 |
+| oidc.principalRolesMapper.type | string | `"default"` | The 
`PrincipalRolesMapper` implementation to use. Only one built-in type is 
supported: default. |
 | persistence | object | 
`{"relationalJdbc":{"secret":{"jdbcUrl":"jdbcUrl","name":null,"password":"password","username":"username"}},"type":"in-memory"}`
 | Polaris persistence configuration. |
 | persistence.relationalJdbc | object | 
`{"secret":{"jdbcUrl":"jdbcUrl","name":null,"password":"password","username":"username"}}`
 | The configuration for the relational-jdbc persistence manager. |
 | persistence.relationalJdbc.secret | object | 
`{"jdbcUrl":"jdbcUrl","name":null,"password":"password","username":"username"}` 
| The secret name to pull the database connection properties from. |
diff --git a/helm/polaris/ci/authentication-values.yaml 
b/helm/polaris/ci/authentication-values.yaml
new file mode 100644
index 000000000..1087c2c44
--- /dev/null
+++ b/helm/polaris/ci/authentication-values.yaml
@@ -0,0 +1,71 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+image:
+  pullPolicy: Never
+
+realmContext:
+  realms:
+    - realm0
+    - realm1
+    - realm2
+    - REALM 3
+
+authentication:
+  type: internal
+  authenticator:
+    type: default
+  activeRolesProvider:
+    type: default
+  tokenService:
+    type: default
+  tokenBroker:
+    type: rsa-key-pair
+    secret:
+      name: polaris-token-broker
+      rsaKeyPair:
+        publicKey: public.pem
+        privateKey: private.pem
+
+  realmOverrides:
+
+    realm1:
+      type: mixed
+      tokenBroker:
+        type: rsa-key-pair
+        secret:
+          name: polaris-token-broker
+
+    realm2:
+      type: external
+
+    "REALM 3":
+      type: internal
+      tokenBroker:
+        type: symmetric-key
+        secret:
+          name: polaris-token-broker
+
+oidc:
+  authServeUrl: https://auth.example.com/realms/polaris
+  client:
+    id: polaris
+    secret:
+      name: polaris-oidc
+      key: client-secret
diff --git a/helm/polaris/ci/fixtures/oidc.yaml 
b/helm/polaris/ci/fixtures/oidc.yaml
new file mode 100644
index 000000000..c266a2306
--- /dev/null
+++ b/helm/polaris/ci/fixtures/oidc.yaml
@@ -0,0 +1,26 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+apiVersion: v1
+kind: Secret
+metadata:
+  name: polaris-oidc
+type: Opaque
+stringData:
+  client-secret: "secret"
diff --git a/helm/polaris/ci/fixtures/token-broker.yaml 
b/helm/polaris/ci/fixtures/token-broker.yaml
index 50399c3d4..39e1fae32 100644
--- a/helm/polaris/ci/fixtures/token-broker.yaml
+++ b/helm/polaris/ci/fixtures/token-broker.yaml
@@ -61,3 +61,4 @@ stringData:
     
wIGRMc6MkZboyVwbi+iUbTkmxGFaTVdZWlXQPFCdm+SNT4jZ1b8dZuIBYvwnpWuO/RDurfgAvmtX
     s/jMUwIDAQAB
     -----END PUBLIC KEY-----
+  symmetric.key: "secret"
diff --git a/helm/polaris/templates/_helpers.tpl 
b/helm/polaris/templates/_helpers.tpl
index d22727a25..3def60b52 100644
--- a/helm/polaris/templates/_helpers.tpl
+++ b/helm/polaris/templates/_helpers.tpl
@@ -150,18 +150,18 @@ line breaks, they will be escaped and a multi-line option 
will be printed.
 {{- $valAsString = $valAsString | nindent 4 | replace "\n" "\\\n" -}}
 {{- end -}}
 {{- end -}}
-{{ print $key "=" $valAsString }}
+{{ print (include "polaris.escapeConfigOptionKey" $key) "=" $valAsString }}
 {{- end -}}
 
 {{/*
-Convert a dict into a string formed by a comma-separated list of key-value 
pairs: key1=value1,key2=value2, ...
+Escapes a property key to be used in a configmap, conforming with the Java 
parsisng rules for
+property files: 
https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Properties.html#load(java.io.Reader)
+- Escapes all backslashes.
+- Escapes all key termination charaters: '=', ':' and whitespace.
 */}}
-{{- define "polaris.dictToString" -}}
-{{- $list := list -}}
-{{- range $k, $v := . -}}
-{{- $list = append $list (printf "%s=%s" $k $v) -}}
-{{- end -}}
-{{ join "," $list }}
+{{- define "polaris.escapeConfigOptionKey" -}}
+{{- $key := . -}}
+{{- $key | replace "\\" "\\\\" | replace "=" "\\=" | replace ":" "\\:" | 
replace " " "\\ " -}}
 {{- end -}}
 
 {{/*
@@ -176,27 +176,10 @@ Prints the config volume definition for deployments and 
jobs.
           items:
             - key: application.properties
               path: application.properties
-      {{- if .Values.authentication.tokenBroker.secret.name }}
-      - secret:
-          name: {{ tpl .Values.authentication.tokenBroker.secret.name . }}
-          items:
-          {{- if eq .Values.authentication.tokenBroker.type "rsa-key-pair" }}
-            {{- /* Backward compatibility for publicKey: new takes precedence 
*/ -}}
-            {{- $publicKey := coalesce 
.Values.authentication.tokenBroker.secret.rsaKeyPair.publicKey 
.Values.authentication.tokenBroker.secret.publicKey }}
-            {{- /* Backward compatibility for privateKey: new takes precedence 
*/ -}}
-            {{- $privateKey := coalesce 
.Values.authentication.tokenBroker.secret.rsaKeyPair.privateKey 
.Values.authentication.tokenBroker.secret.privateKey }}
-            - key: {{ tpl $publicKey . }}
-              path: public.pem
-            - key: {{ tpl $privateKey . }}
-              path: private.pem
-          {{- end }}
-          {{- if eq .Values.authentication.tokenBroker.type "symmetric-key" }}
-            {{- /* Backward compatibility for symmetricKey: new takes 
precedence */ -}}
-            {{- $secretKey := coalesce 
.Values.authentication.tokenBroker.secret.symmetricKey.secretKey 
.Values.authentication.tokenBroker.secret.secretKey }}
-            - key: {{ tpl $secretKey . }}
-              path: symmetric.key
-          {{- end }}
-      {{- end }}
+      {{- include "polaris.configVolumeAuthenticationOptions" (list "" 
.Values.authentication) | nindent 6 }}
+      {{- range $realm, $auth := .Values.authentication.realmOverrides -}}
+      {{- include "polaris.configVolumeAuthenticationOptions" (list $realm 
$auth) | nindent 6 }}
+      {{- end -}}
 {{- end -}}
 
 {{/*
@@ -317,3 +300,74 @@ ports:
     protocol: {{ get $protocols $portName }}
 {{- end }}
 {{- end -}}
+
+{{/*
+Sets the configmap authentication options for a given realm.
+*/}}
+{{- define "polaris.authenticationOptions" -}}
+{{- $realm := index . 0 -}}
+{{- $map := index . 1 -}}
+{{- $auth := index . 2 -}}
+{{- $global := index . 3 -}}
+{{- $prefix := empty $realm | ternary "polaris.authentication" (printf 
"polaris.authentication.\"%s\"" $realm) -}}
+{{- $authType := coalesce $auth.type "internal" -}}
+{{- if and (ne $authType "internal") (ne $authType "mixed") (ne $authType 
"external") -}}
+{{- fail (empty $realm | ternary "authentication.type: invalid authentication 
type" (printf "authentication.realmOverrides.\"%s\".type: invalid 
authentication type" $realm)) -}}
+{{- end -}}
+{{- $_ := set $map (printf "%s.type" $prefix) $authType -}}
+{{- $_ = set $map (printf "%s.authenticator.type" $prefix) (dig 
"authenticator" "type" "default" $auth) -}}
+{{- $_ = set $map (printf "%s.active-roles-provider.type" $prefix) (dig 
"activeRolesProvider" "type" "default" $auth) -}}
+{{- $_ = set $map (printf "%s.active-roles-provider.type" $prefix) (dig 
"activeRolesProvider" "type" "default" $auth) -}}
+{{- if (or (eq $authType "mixed") (eq $authType "internal")) -}}
+{{- $tokenBrokerType := dig "tokenBroker" "type" "rsa-key-pair" $auth -}}
+{{- $_ = set $map (printf "%s.token-service.type" $prefix) (dig "tokenService" 
"type" "default" $auth) -}}
+{{- $_ = set $map (printf "%s.token-broker.type" $prefix) $tokenBrokerType -}}
+{{- $_ = set $map (printf "%s.token-broker.max-token-generation" $prefix) (dig 
"tokenBroker" "maxTokenGeneration" "PT1H" $auth) -}}
+{{- $secretName := dig "tokenBroker" "secret" "name" "" $auth -}}
+{{- if $secretName -}}
+{{- $subpath := empty $realm | ternary "" (printf "%s/" (urlquery $realm)) -}}
+{{- if eq $tokenBrokerType "rsa-key-pair" -}}
+{{- $_ = set $map (printf "%s.token-broker.rsa-key-pair.public-key-file" 
$prefix) (printf "%s/%spublic.pem" $global.Values.image.configDir $subpath ) -}}
+{{- $_ = set $map (printf "%s.token-broker.rsa-key-pair.private-key-file" 
$prefix) (printf "%s/%sprivate.pem" $global.Values.image.configDir $subpath ) 
-}}
+{{- end -}}
+{{- if eq $tokenBrokerType "symmetric-key" -}}
+{{- $_ = set $map (printf "%s.token-broker.symmetric-key.file" $prefix) 
(printf "%s/%ssymmetric.key" $global.Values.image.configDir $subpath ) -}}
+{{- end -}}
+{{- end -}}
+{{- end -}}
+{{- end -}}
+
+{{/*
+Sets authentication options for a given realm in the projected config volume.
+*/}}
+{{- define "polaris.configVolumeAuthenticationOptions" -}}
+{{- $realm := index . 0 -}}
+{{- $auth := index . 1 -}}
+{{- $authType := coalesce $auth.type "internal" -}}
+{{- if (or (eq $authType "mixed") (eq $authType "internal")) }}
+{{- $secretName := dig "tokenBroker" "secret" "name" "" $auth -}}
+{{- if $secretName -}}
+{{- $tokenBrokerType := dig "tokenBroker" "type" "rsa-key-pair" $auth -}}
+{{- $subpath := empty $realm | ternary "" (printf "%s/" (urlquery $realm)) -}}
+- secret:
+    name: {{ tpl $secretName . }}
+    items:
+    {{- if eq $tokenBrokerType "rsa-key-pair" }}
+      {{- /* Backward compatibility for publicKey: new takes precedence */ -}}
+      {{- $publicKey := coalesce (dig "tokenBroker" "secret" "rsaKeyPair" 
"publicKey" "" $auth) (dig "tokenBroker" "secret" "publicKey" "public.pem" 
$auth) }}
+      {{- /* Backward compatibility for privateKey: new takes precedence */ -}}
+      {{- $privateKey := coalesce (dig "tokenBroker" "secret" "rsaKeyPair" 
"privateKey" "" $auth) (dig "tokenBroker" "secret" "privateKey" "private.pem" 
$auth) }}
+      - key: {{ tpl $publicKey . }}
+        path: {{ $subpath }}public.pem
+      - key: {{ tpl $privateKey . }}
+        path: {{ $subpath }}private.pem
+    {{- end }}
+    {{- if eq $tokenBrokerType "symmetric-key" }}
+      {{- /* Backward compatibility for symmetricKey: new takes precedence */ 
-}}
+      {{- $secretKey := coalesce (dig "tokenBroker" "secret" "symmetricKey" 
"secretKey" "" $auth) (dig "tokenBroker" "secret" "secretKey" "symmetric.key" 
$auth) }}
+      - key: {{ tpl $secretKey . }}
+        path: {{ $subpath }}symmetric.key
+    {{- end }}
+{{- end }}
+{{- end }}
+{{- end -}}
diff --git a/helm/polaris/templates/configmap.yaml 
b/helm/polaris/templates/configmap.yaml
index a3ec77441..8d4eef024 100644
--- a/helm/polaris/templates/configmap.yaml
+++ b/helm/polaris/templates/configmap.yaml
@@ -30,6 +30,7 @@ metadata:
 data:
   application.properties: |-
     {{- $map := dict -}}
+    {{- $global := . -}}
 
     {{- /* Realm Context */ -}}
     {{- $_ := set $map "polaris.realm-context.type" .Values.realmContext.type 
-}}
@@ -75,17 +76,33 @@ data:
     {{- end -}}
 
     {{- /* Authentication */ -}}
-    {{- $_ = set $map "polaris.authentication.authenticator.type" 
.Values.authentication.authenticator.type -}}
-    {{- $_ = set $map "polaris.authentication.token-service.type" 
.Values.authentication.tokenService.type -}}
-    {{- $_ = set $map "polaris.authentication.token-broker.type" 
.Values.authentication.tokenBroker.type -}}
-    {{- $_ = set $map 
"polaris.authentication.token-broker.max-token-generation" 
.Values.authentication.tokenBroker.maxTokenGeneration -}}
-    {{- if .Values.authentication.tokenBroker.secret.name -}}
-    {{- if eq .Values.authentication.tokenBroker.type "rsa-key-pair" -}}
-    {{- $_ = set $map 
"polaris.authentication.token-broker.rsa-key-pair.public-key-file" (printf 
"%s/public.pem" .Values.image.configDir ) -}}
-    {{- $_ = set $map 
"polaris.authentication.token-broker.rsa-key-pair.private-key-file" (printf 
"%s/private.pem" .Values.image.configDir ) -}}
+    {{- include "polaris.authenticationOptions" (list "" $map 
.Values.authentication $global) -}}
+    {{- range $realm, $overrides := .Values.authentication.realmOverrides -}}
+    {{- include "polaris.authenticationOptions" (list $realm $map $overrides 
$global) -}}
+    {{- end -}}
+
+    {{- /* OIDC */ -}}
+    {{- if .Values.oidc.authServeUrl -}}
+    {{- $_ = set $map "quarkus.oidc.tenant-enabled" "true" -}}
+    {{- $_ = set $map "quarkus.oidc.auth-server-url" .Values.oidc.authServeUrl 
-}}
+    {{- $_ = set $map "quarkus.oidc.client-id" .Values.oidc.client.id -}}
+    {{- $_ = set $map "polaris.oidc.principal-mapper.type" 
.Values.oidc.principalMapper.type -}}
+    {{- if .Values.oidc.principalMapper.idClaimPath -}}
+    {{- $_ = set $map "polaris.oidc.principal-mapper.id-claim-path" 
.Values.oidc.principalMapper.idClaimPath -}}
+    {{- end -}}
+    {{- if .Values.oidc.principalMapper.nameClaimPath -}}
+    {{- $_ = set $map "polaris.oidc.principal-mapper.name-claim-path" 
.Values.oidc.principalMapper.nameClaimPath -}}
     {{- end -}}
-    {{- if eq .Values.authentication.tokenBroker.type "symmetric-key" -}}
-    {{- $_ = set $map "polaris.authentication.token-broker.symmetric-key.file" 
(printf "%s/symmetric.key" .Values.image.configDir ) -}}
+    {{- $_ = set $map "polaris.oidc.principal-roles-mapper.type" 
.Values.oidc.principalRolesMapper.type -}}
+    {{- if .Values.oidc.principalRolesMapper.rolesClaimPath -}}
+    {{- $_ = set $map "quarkus.oidc.roles.role-claim-path" 
.Values.oidc.principalRolesMapper.rolesClaimPath -}}
+    {{- end -}}
+    {{- if .Values.oidc.principalRolesMapper.filter -}}
+    {{- $_ = set $map "polaris.oidc.principal-roles-mapper.filter" 
.Values.oidc.principalRolesMapper.filter -}}
+    {{- end -}}
+    {{- range $i, $mapping := .Values.oidc.principalRolesMapper.mappings -}}
+    {{- $_ = set $map (printf 
"polaris.oidc.principal-roles-mapper.mappings[%d].regex" $i) $mapping.regex -}}
+    {{- $_ = set $map (printf 
"polaris.oidc.principal-roles-mapper.mappings[%d].replacement" $i) 
$mapping.replacement -}}
     {{- end -}}
     {{- end -}}
 
@@ -195,7 +212,6 @@ data:
     {{- list .Values.advancedConfig "" $map | include 
"polaris.mergeConfigTree" -}}
 
     {{- /* Print the resulting configmap; each configuration option is 
templatized */ -}}
-    {{- $global := . -}}
     {{- range $k, $v := $map }}
     {{ include "polaris.appendConfigOption" (list $k $v $global) }}
     {{- end }}
diff --git a/helm/polaris/templates/deployment.yaml 
b/helm/polaris/templates/deployment.yaml
index 71959b32d..a7cec81a4 100644
--- a/helm/polaris/templates/deployment.yaml
+++ b/helm/polaris/templates/deployment.yaml
@@ -72,7 +72,7 @@ spec:
           {{- end }}
           image: "{{ tpl .Values.image.repository . }}:{{ tpl 
.Values.image.tag . | default .Chart.Version }}"
           imagePullPolicy: {{ tpl .Values.image.pullPolicy . }}
-          {{ if or .Values.storage.secret.name 
.Values.persistence.relationalJdbc.secret.name .Values.extraEnv -}}
+          {{ if or .Values.storage.secret.name 
.Values.persistence.relationalJdbc.secret.name .Values.oidc.client.secret.name 
.Values.extraEnv -}}
           env:
             {{- include "polaris.secretToEnv" (list .Values.storage.secret 
"awsAccessKeyId" "polaris.storage.aws.access-key") | indent 12 -}}
             {{- include "polaris.secretToEnv" (list .Values.storage.secret 
"awsSecretAccessKey" "polaris.storage.aws.secret-key") | indent 12 -}}
@@ -80,6 +80,7 @@ spec:
             {{- include "polaris.secretToEnv" (list 
.Values.persistence.relationalJdbc.secret "username" 
"quarkus.datasource.username") | indent 12 -}}
             {{- include "polaris.secretToEnv" (list 
.Values.persistence.relationalJdbc.secret "password" 
"quarkus.datasource.password") | indent 12 -}}
             {{- include "polaris.secretToEnv" (list 
.Values.persistence.relationalJdbc.secret "jdbcUrl" 
"quarkus.datasource.jdbc.url") | indent 12 -}}
+            {{- include "polaris.secretToEnv" (list .Values.oidc.client.secret 
"key" "quarkus.oidc.credentials.secret") | indent 12 -}}
             {{- if .Values.extraEnv -}}
             {{- tpl (toYaml .Values.extraEnv) . | nindent 12 -}}
             {{- end -}}
diff --git a/helm/polaris/tests/configmap_test.yaml 
b/helm/polaris/tests/configmap_test.yaml
index 3834982a5..0c99c9672 100644
--- a/helm/polaris/tests/configmap_test.yaml
+++ b/helm/polaris/tests/configmap_test.yaml
@@ -79,6 +79,23 @@ tests:
           content:
             app.kubernetes.io/component: polaris
 
+  - it: should escape config option keys
+    set:
+      advancedConfig:
+        "key with spaces": value
+        "key:with:colons": value
+        "key\\with\\backslashes": value
+        "key=with=equals": value
+        "key \"with double quotes\"": value
+        "key 'with single quotes'": value
+    asserts:
+      - matchRegex: { path: 'data["application.properties"]', pattern: 
"key\\\\ with\\\\ spaces=value" }
+      - matchRegex: { path: 'data["application.properties"]', pattern: 
"key\\\\:with\\\\:colons=value" }
+      - matchRegex: { path: 'data["application.properties"]', pattern: 
"key\\\\\\\\with\\\\\\\\backslashes=value" }
+      - matchRegex: { path: 'data["application.properties"]', pattern: 
"key\\\\=with\\\\=equals=value" }
+      - matchRegex: { path: 'data["application.properties"]', pattern: 
"key\\\\ \"with\\\\ double\\\\ quotes\"=value" }
+      - matchRegex: { path: 'data["application.properties"]', pattern: 
"key\\\\ 'with\\\\ single\\\\ quotes'=value" }
+
   - it: should configure realm context
     set:
       realmContext: { type: "custom", realms: [ "realm1", "realm2" ] }
@@ -120,27 +137,101 @@ tests:
     asserts:
       - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.storage.gcp.lifespan=PT2H" }
 
-  - it: should configure authentication with RSA key pair
-    set:
-      authentication: { authenticator: { type: custom }, tokenService: { type: 
custom }, tokenBroker: { type: rsa-key-pair, maxTokenGeneration: PT2H, secret: 
{ name: polaris-auth } } }
+  - it: should configure internal authentication type
+    set: { authentication: { type: internal } }
+    asserts:
+      - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.authentication.type=internal" }
+
+  - it: should configure external authentication type
+    set: { authentication: { type: external } }
+    asserts:
+      - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.authentication.type=external" }
+
+  - it: should configure mixed authentication type
+    set: { authentication: { type: mixed } }
+    asserts:
+      - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.authentication.type=mixed" }
+
+  - it: should fail on invalid authentication type
+    set: { authentication: { type: invalid } }
+    asserts:
+      - failedTemplate:
+          errorMessage: "authentication.type: invalid authentication type"
+
+  - it: should configure default authenticator
+    set: { authentication: { authenticator: { type: default } } }
+    asserts:
+      - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.authentication.authenticator.type=default" }
+
+  - it: should configure custom authenticator
+    set: { authentication: { authenticator: { type: custom } } }
     asserts:
       - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.authentication.authenticator.type=custom" }
+
+  - it: should configure default active roles provider
+    set: { authentication: { activeRolesProvider: { type: default } } }
+    asserts:
+      - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.authentication.active-roles-provider.type=default" }
+
+  - it: should configure custom active roles provider
+    set: { authentication: { activeRolesProvider: { type: custom } } }
+    asserts:
+      - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.authentication.active-roles-provider.type=custom" }
+
+  - it: should configure default token service
+    set: { authentication: { tokenService: { type: default } } }
+    asserts:
+      - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.authentication.token-service.type=default" }
+
+  - it: should configure custom token service
+    set: { authentication: { tokenService: { type: custom } } }
+    asserts:
       - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.authentication.token-service.type=custom" }
+
+  - it: should configure token broker with RSA key pair
+    set:
+      authentication: { tokenBroker: { type: rsa-key-pair, maxTokenGeneration: 
PT2H, secret: { name: polaris-auth } } }
+    asserts:
       - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.authentication.token-broker.type=rsa-key-pair" }
       - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.authentication.token-broker.max-token-generation=PT2H" }
       - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.authentication.token-broker.rsa-key-pair.public-key-file=/deployments/config/public.pem"
 }
       - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.authentication.token-broker.rsa-key-pair.private-key-file=/deployments/config/private.pem"
 }
 
-  - it: should configure authentication with symmetric key
+  - it: should configure token broker with symmetric key
     set:
       authentication: { tokenBroker: { type: symmetric-key, secret: { name: 
polaris-auth } } }
     asserts:
-      - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.authentication.authenticator.type=default" }
-      - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.authentication.token-service.type=default" }
       - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.authentication.token-broker.type=symmetric-key" }
       - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.authentication.token-broker.max-token-generation=PT1H" }
       - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.authentication.token-broker.symmetric-key.file=/deployments/config/symmetric.key"
 }
 
+  - it: should create realm overrides for authentication
+    set:
+      authentication:
+        realmOverrides:
+          realm1: { type: mixed, authenticator: { type: custom1 }, 
activeRolesProvider: { type: custom1 }, tokenBroker: { type: custom1 }, 
tokenService: { type: custom1 } }
+          realm2: { type: external, authenticator: { type: custom2 }, 
activeRolesProvider: { type: custom2 } }
+          "REALM 3": { type: internal, tokenBroker: { type: rsa-key-pair, 
secret: { name: polaris-auth } } }
+    asserts:
+      - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.authentication.\"realm1\".type=mixed" }
+      - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.authentication.\"realm1\".authenticator.type=custom1" }
+      - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.authentication.\"realm1\".active-roles-provider.type=custom1" }
+      - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.authentication.\"realm1\".token-broker.type=custom1" }
+      - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.authentication.\"realm1\".token-service.type=custom1" }
+      - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.authentication.\"realm2\".type=external" }
+      - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.authentication.\"realm2\".authenticator.type=custom2" }
+      - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.authentication.\"realm2\".active-roles-provider.type=custom2" }
+      - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.authentication.\"REALM\\\\ 3\".type=internal" }
+      - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.authentication.\"REALM\\\\ 3\".token-broker.type=rsa-key-pair" }
+      - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.authentication.\"REALM\\\\ 
3\".token-broker.rsa-key-pair.public-key-file=/deployments/config/REALM\\+3/public.pem"
 }
+      - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.authentication.\"REALM\\\\ 
3\".token-broker.rsa-key-pair.private-key-file=/deployments/config/REALM\\+3/private.pem"
 }
+
+  - it: should fail on invalid authentication type in realm override
+    set: { authentication: { realmOverrides: { realm1: { type: invalid } } } }
+    asserts:
+      - failedTemplate:
+          errorMessage: "authentication.realmOverrides.\"realm1\".type: 
invalid authentication type"
+
   - it: should derive HTTP ports from service configuration
     set:
       service: { ports: [ { port: 8080 } ] }
@@ -336,3 +427,33 @@ tests:
     asserts:
       - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.tasks.max-concurrent-tasks=10" }
       - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.tasks.max-queued-tasks=20" }
+
+  - it: should configure OIDC
+    set:
+      oidc:
+        authServeUrl: https://auth.example.com/realms/polaris
+        client:
+          id: polaris
+        principalMapper:
+          type: custom
+          idClaimPath: polaris/principal_id
+          nameClaimPath: polaris/principal_name
+        principalRolesMapper:
+          type: custom
+          rolesClaimPath: polaris/principal_roles
+          filter: role_(.*)
+          mappings:
+          - regex: role_(.*)
+            replacement: PRINCIPAL_ROLE:$1
+    asserts:
+      - matchRegex: { path: 'data["application.properties"]', pattern: 
"quarkus.oidc.tenant-enabled=true" }
+      - matchRegex: { path: 'data["application.properties"]', pattern: 
"quarkus.oidc.auth-server-url=https://auth.example.com/realms/polaris"; }
+      - matchRegex: { path: 'data["application.properties"]', pattern: 
"quarkus.oidc.client-id=polaris" }
+      - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.oidc.principal-mapper.type=custom" }
+      - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.oidc.principal-mapper.id-claim-path=polaris/principal_id" }
+      - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.oidc.principal-mapper.name-claim-path=polaris/principal_name" }
+      - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.oidc.principal-roles-mapper.type=custom" }
+      - matchRegex: { path: 'data["application.properties"]', pattern: 
"quarkus.oidc.roles.role-claim-path=polaris/principal_roles" }
+      - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.oidc.principal-roles-mapper.filter=role_\\(\\.\\*\\)" }
+      - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.oidc.principal-roles-mapper.mappings\\[0\\].regex=role_\\(\\.\\*\\)" }
+      - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.oidc.principal-roles-mapper.mappings\\[0\\].replacement=PRINCIPAL_ROLE:\\$1"
 }
diff --git a/helm/polaris/tests/deployment_test.yaml 
b/helm/polaris/tests/deployment_test.yaml
index 01789c8ff..d80b72b64 100644
--- a/helm/polaris/tests/deployment_test.yaml
+++ b/helm/polaris/tests/deployment_test.yaml
@@ -1091,6 +1091,58 @@ tests:
                       - key: symmetric.pem
                         path: symmetric.key
 
+  - it: should configure config volume with authentication including per-realm 
overrides
+    set:
+      image.configDir: /config/dir
+      authentication:
+        tokenBroker: { type: rsa-key-pair, secret: { name: 
polaris-token-broker-default } }
+        realmOverrides:
+          realm1: { tokenBroker: { type: symmetric-key, secret: { name: 
polaris-token-broker-realm1 } } }
+          "REALM 2": { tokenBroker: { type: rsa-key-pair, secret: { name: 
polaris-token-broker-realm2 } } }
+    asserts:
+      - contains:
+          path: spec.template.spec.containers[0].volumeMounts
+          content:
+            name: config-volume
+            mountPath: /config/dir
+            readOnly: true
+      - contains:
+          path: spec.template.spec.volumes[0].projected.sources
+          content:
+            configMap:
+              name: polaris-release
+              items:
+                - key: application.properties
+                  path: application.properties
+      - contains:
+          path: spec.template.spec.volumes[0].projected.sources
+          content:
+            secret:
+              name: polaris-token-broker-default
+              items:
+                - key: public.pem
+                  path: public.pem
+                - key: private.pem
+                  path: private.pem
+      - contains:
+          path: spec.template.spec.volumes[0].projected.sources
+          content:
+            secret:
+              name: polaris-token-broker-realm1
+              items:
+                - key: symmetric.key
+                  path: realm1/symmetric.key
+      - contains:
+          path: spec.template.spec.volumes[0].projected.sources
+          content:
+            secret:
+              name: polaris-token-broker-realm2
+              items:
+                - key: public.pem
+                  path: REALM+2/public.pem
+                - key: private.pem
+                  path: REALM+2/private.pem
+
   - it: should set relational-jdbc persistence environment variables
     set:
       persistence: { type: "relational-jdbc", relationalJdbc: { secret: { 
name: "polaris-persistence", username: "username", password: "password", 
jdbcUrl: "jdbcUrl" } } }
@@ -1159,3 +1211,16 @@ tests:
           content:
             name: extra-volume
             emptyDir: {}
+
+  - it: should set OIDC client secret
+    set:
+      oidc: { client: { secret: { name: polaris-oidc-secret, key: 
client-secret } } }
+    asserts:
+      - contains:
+          path: spec.template.spec.containers[0].env
+          content:
+            name: quarkus.oidc.credentials.secret
+            valueFrom:
+              secretKeyRef:
+                name: polaris-oidc-secret
+                key: client-secret
diff --git a/helm/polaris/values.yaml b/helm/polaris/values.yaml
index 1db69368a..28ce97de5 100644
--- a/helm/polaris/values.yaml
+++ b/helm/polaris/values.yaml
@@ -560,15 +560,20 @@ storage:
 
 # -- Polaris authentication configuration.
 authentication:
-  # -- The type of authentication to use. Two built-in types are supported: 
default and test;
-  # test is not recommended for production.
+  # -- The type of authentication to use. Three built-in types are supported: 
internal, external, and mixed.
+  type: internal
+  # -- The `Authenticator` implementation to use. Only one built-in type is 
supported: default.
   authenticator:
     type: default
-  # -- The type of token service to use. Two built-in types are supported: 
default and test;
-  # test is not recommended for production.
+  # -- The `ActiveRolesProvider` implementation to use. Only one built-in type 
is supported: default.
+  activeRolesProvider:
+    type: default
+  # -- The token service (`IcebergRestOAuth2ApiService`) implementation to 
use. Two built-in types are supported: default and disabled.
+  # Only relevant when using internal (or mixed) authentication. When using 
external authentication, the token service is always disabled.
   tokenService:
     type: default
-  # -- The type of token broker to use. Two built-in types are supported: 
rsa-key-pair and symmetric-key.
+  # -- The `TokenBroker` implementation to use. Two built-in types are 
supported: rsa-key-pair and symmetric-key.
+  # Only relevant when using internal (or mixed) authentication. When using 
external authentication, the token broker is not used.
   tokenBroker:
     type: rsa-key-pair  # symmetric-key
     # -- Maximum token generation duration (e.g., PT1H for 1 hour).
@@ -596,7 +601,66 @@ authentication:
       # -- Optional: configuration specific to symmetric key secret.
       symmetricKey:
         # -- Key name inside the secret for the symmetric key
-        secretKey: symmetric.pem
+        secretKey: symmetric.key
+  # -- Authentication configuration overrides per realm.
+  realmOverrides: {}
+    # my-realm:
+    #   type: external
+    #   authenticator:
+    #     type: custom
+
+# -- Polaris OIDC configuration. Only relevant when at least one realm is 
configured for external
+# (or mixed) authentication. The currently supported configuration is for a 
single, default OIDC tenant.
+# For more complex scenarios, including OIDC multi-tenancy, you will need to 
provide the relevant
+# configuration using the `advancedConfig` section.
+oidc:
+  # -- The authentication server URL. Must be provided if at least one realm 
is configured for external
+  # authentication.
+  authServeUrl: ~  # https://auth.example.com/realms/polaris
+  # -- The client to use when authenticating with the authentication server.
+  client:
+    # -- The client ID to use when contacting the authentication server's 
introspection endpoint in
+    # order to validate tokens.
+    id: polaris
+    # -- The secret to pull the client secret from. If no client secret is 
required, leave the secret
+    # name unset.
+    secret:
+      # -- The name of the secret to pull the client secret from. If not 
provided, the client is assumed
+      # to not require a client secret when contacting the introspection 
endpoint.
+      name: ~
+      # -- The key name inside the secret to pull the client secret from.
+      key: clientSecret
+  # -- Principal mapping configuration.
+  principalMapper:
+    # -- The `PrincipalMapper` implementation to use. Only one built-in type 
is supported: default.
+    type: default
+    # -- The path to the claim that contains the principal ID. Nested paths 
can be expressed using
+    # "/" as a separator, e.g. "polaris/principal_id" would look for the 
"principal_id" field inside
+    # the "polaris" object in the token claims. Optional. Either this option 
or `nameClaimPath` (or both) must be provided.
+    idClaimPath: ~
+    # -- The claim that contains the principal name. Nested paths can be 
expressed using "/" as a
+    # separator, e.g. "polaris/principal_name" would look for the 
"principal_name" field inside
+    # the "polaris" object in the token claims. Optional. Either this option 
or `idClaimPath` (or both) must be provided.
+    nameClaimPath: ~
+  # -- Principal roles mapping configuration.
+  principalRolesMapper:
+    # -- The `PrincipalRolesMapper` implementation to use. Only one built-in 
type is supported: default.
+    type: default
+    # -- The path to the claim that contains the principal roles. Nested paths 
can be expressed using
+    # "/" as a separator, e.g. "polaris/principal_roles" would look for the 
"principal_roles" field inside
+    # the "polaris" object in the token claims. If not set, Quarkus looks for 
roles in standard locations.
+    # See 
https://quarkus.io/guides/security-oidc-bearer-token-authentication#token-claims-and-security-identity-roles.
+    rolesClaimPath: ~
+    # -- A regular expression that matches the role names in the identity. 
Only roles that match this
+    # regex will be included in the Polaris-specific roles.
+    filter: ~  # ^(?!profile$|email$).*
+    # -- A list of regex mappings that will be applied to each role name in 
the identity. This can
+    # be used to transform the role names in the identity into role names as 
expected by Polaris.
+    # The default ActiveRolesProvider expects the security identity to expose 
role names in the
+    # format `POLARIS_ROLE:<role name>`.
+    mappings: []
+      # - regex: role_(.*)
+      #   replacement: PRINCIPAL_ROLE:$1
 
 # -- Polaris CORS configuration.
 cors:
diff --git a/site/content/in-dev/unreleased/helm.md 
b/site/content/in-dev/unreleased/helm.md
index 05b006974..d0daa190b 100644
--- a/site/content/in-dev/unreleased/helm.md
+++ b/site/content/in-dev/unreleased/helm.md
@@ -189,11 +189,13 @@ ct install --namespace polaris --charts ./helm/polaris
 |-----|------|---------|-------------|
 | advancedConfig | object | `{}` | Advanced configuration. You can pass here 
any valid Polaris or Quarkus configuration property. Any property that is 
defined here takes precedence over all the other configuration values generated 
by this chart. Properties can be passed "flattened" or as nested YAML objects 
(see examples below). Note: values should be strings; avoid using numbers, 
booleans, or other types. |
 | affinity | object | `{}` | Affinity and anti-affinity for polaris pods. See 
https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity.
 |
-| authentication | object | 
`{"authenticator":{"type":"default"},"tokenBroker":{"maxTokenGeneration":"PT1H","secret":{"name":null,"privateKey":"private.pem","publicKey":"public.pem","rsaKeyPair":{"privateKey":"private.pem","publicKey":"public.pem"},"secretKey":"symmetric.pem","symmetricKey":{"secretKey":"symmetric.pem"}},"type":"rsa-key-pair"},"tokenService":{"type":"default"}}`
 | Polaris authentication configuration. |
-| authentication.authenticator | object | `{"type":"default"}` | The type of 
authentication to use. Two built-in types are supported: default and test; test 
is not recommended for production. |
-| authentication.tokenBroker | object | 
`{"maxTokenGeneration":"PT1H","secret":{"name":null,"privateKey":"private.pem","publicKey":"public.pem","rsaKeyPair":{"privateKey":"private.pem","publicKey":"public.pem"},"secretKey":"symmetric.pem","symmetricKey":{"secretKey":"symmetric.pem"}},"type":"rsa-key-pair"}`
 | The type of token broker to use. Two built-in types are supported: 
rsa-key-pair and symmetric-key. |
+| authentication | object | 
`{"activeRolesProvider":{"type":"default"},"authenticator":{"type":"default"},"realmOverrides":{},"tokenBroker":{"maxTokenGeneration":"PT1H","secret":{"name":null,"privateKey":"private.pem","publicKey":"public.pem","rsaKeyPair":{"privateKey":"private.pem","publicKey":"public.pem"},"secretKey":"symmetric.pem","symmetricKey":{"secretKey":"symmetric.key"}},"type":"rsa-key-pair"},"tokenService":{"type":"default"},"type":"internal"}`
 | Polaris authentication config [...]
+| authentication.activeRolesProvider | object | `{"type":"default"}` | The 
`ActiveRolesProvider` implementation to use. Only one built-in type is 
supported: default. |
+| authentication.authenticator | object | `{"type":"default"}` | The 
`Authenticator` implementation to use. Only one built-in type is supported: 
default. |
+| authentication.realmOverrides | object | `{}` | Authentication configuration 
overrides per realm. |
+| authentication.tokenBroker | object | 
`{"maxTokenGeneration":"PT1H","secret":{"name":null,"privateKey":"private.pem","publicKey":"public.pem","rsaKeyPair":{"privateKey":"private.pem","publicKey":"public.pem"},"secretKey":"symmetric.pem","symmetricKey":{"secretKey":"symmetric.key"}},"type":"rsa-key-pair"}`
 | The `TokenBroker` implementation to use. Two built-in types are supported: 
rsa-key-pair and symmetric-key. Only relevant when using internal (or mixed) 
authentication. When using ex [...]
 | authentication.tokenBroker.maxTokenGeneration | string | `"PT1H"` | Maximum 
token generation duration (e.g., PT1H for 1 hour). |
-| authentication.tokenBroker.secret | object | 
`{"name":null,"privateKey":"private.pem","publicKey":"public.pem","rsaKeyPair":{"privateKey":"private.pem","publicKey":"public.pem"},"secretKey":"symmetric.pem","symmetricKey":{"secretKey":"symmetric.pem"}}`
 | The secret name to pull the public and private keys, or the symmetric key 
secret from. |
+| authentication.tokenBroker.secret | object | 
`{"name":null,"privateKey":"private.pem","publicKey":"public.pem","rsaKeyPair":{"privateKey":"private.pem","publicKey":"public.pem"},"secretKey":"symmetric.pem","symmetricKey":{"secretKey":"symmetric.key"}}`
 | The secret name to pull the public and private keys, or the symmetric key 
secret from. |
 | authentication.tokenBroker.secret.name | string | `nil` | The name of the 
secret to pull the keys from. If not provided, a key pair will be generated. 
This is not recommended for production. |
 | authentication.tokenBroker.secret.privateKey | string | `"private.pem"` | 
DEPRECATED: Use `authentication.tokenBroker.secret.rsaKeyPair.privateKey` 
instead. Key name inside the secret for the private key |
 | authentication.tokenBroker.secret.publicKey | string | `"public.pem"` | 
DEPRECATED: Use `authentication.tokenBroker.secret.rsaKeyPair.publicKey` 
instead. Key name inside the secret for the public key |
@@ -201,9 +203,10 @@ ct install --namespace polaris --charts ./helm/polaris
 | authentication.tokenBroker.secret.rsaKeyPair.privateKey | string | 
`"private.pem"` | Key name inside the secret for the private key |
 | authentication.tokenBroker.secret.rsaKeyPair.publicKey | string | 
`"public.pem"` | Key name inside the secret for the public key |
 | authentication.tokenBroker.secret.secretKey | string | `"symmetric.pem"` | 
DEPRECATED: Use `authentication.tokenBroker.secret.symmetricKey.secretKey` 
instead. Key name inside the secret for the symmetric key |
-| authentication.tokenBroker.secret.symmetricKey | object | 
`{"secretKey":"symmetric.pem"}` | Optional: configuration specific to symmetric 
key secret. |
-| authentication.tokenBroker.secret.symmetricKey.secretKey | string | 
`"symmetric.pem"` | Key name inside the secret for the symmetric key |
-| authentication.tokenService | object | `{"type":"default"}` | The type of 
token service to use. Two built-in types are supported: default and test; test 
is not recommended for production. |
+| authentication.tokenBroker.secret.symmetricKey | object | 
`{"secretKey":"symmetric.key"}` | Optional: configuration specific to symmetric 
key secret. |
+| authentication.tokenBroker.secret.symmetricKey.secretKey | string | 
`"symmetric.key"` | Key name inside the secret for the symmetric key |
+| authentication.tokenService | object | `{"type":"default"}` | The token 
service (`IcebergRestOAuth2ApiService`) implementation to use. Two built-in 
types are supported: default and disabled. Only relevant when using internal 
(or mixed) authentication. When using external authentication, the token 
service is always disabled. |
+| authentication.type | string | `"internal"` | The type of authentication to 
use. Three built-in types are supported: internal, external, and mixed. |
 | autoscaling.enabled | bool | `false` | Specifies whether automatic 
horizontal scaling should be enabled. Do not enable this when using in-memory 
version store type. |
 | autoscaling.maxReplicas | int | `3` | The maximum number of replicas to 
maintain. |
 | autoscaling.minReplicas | int | `1` | The minimum number of replicas to 
maintain. |
@@ -283,6 +286,22 @@ ct install --namespace polaris --charts ./helm/polaris
 | metrics.enabled | bool | `true` | Specifies whether metrics for the polaris 
server should be enabled. |
 | metrics.tags | object | `{}` | Additional tags (dimensional labels) to add 
to the metrics. |
 | nodeSelector | object | `{}` | Node labels which must match for the polaris 
pod to be scheduled on that node. See 
https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodeselector.
 |
+| oidc | object | 
`{"authServeUrl":null,"client":{"id":"polaris","secret":{"key":"clientSecret","name":null}},"principalMapper":{"idClaimPath":null,"nameClaimPath":null,"type":"default"},"principalRolesMapper":{"filter":null,"mappings":[],"rolesClaimPath":null,"type":"default"}}`
 | Polaris OIDC configuration. Only relevant when at least one realm is 
configured for external (or mixed) authentication. The currently supported 
configuration is for a single, default OIDC tenant. For more comp [...]
+| oidc.authServeUrl | string | `nil` | The authentication server URL. Must be 
provided if at least one realm is configured for external authentication. |
+| oidc.client | object | 
`{"id":"polaris","secret":{"key":"clientSecret","name":null}}` | The client to 
use when authenticating with the authentication server. |
+| oidc.client.id | string | `"polaris"` | The client ID to use when contacting 
the authentication server's introspection endpoint in order to validate tokens. 
|
+| oidc.client.secret | object | `{"key":"clientSecret","name":null}` | The 
secret to pull the client secret from. If no client secret is required, leave 
the secret name unset. |
+| oidc.client.secret.key | string | `"clientSecret"` | The key name inside the 
secret to pull the client secret from. |
+| oidc.client.secret.name | string | `nil` | The name of the secret to pull 
the client secret from. If not provided, the client is assumed to not require a 
client secret when contacting the introspection endpoint. |
+| oidc.principalMapper | object | 
`{"idClaimPath":null,"nameClaimPath":null,"type":"default"}` | Principal 
mapping configuration. |
+| oidc.principalMapper.idClaimPath | string | `nil` | The path to the claim 
that contains the principal ID. Nested paths can be expressed using "/" as a 
separator, e.g. "polaris/principal_id" would look for the "principal_id" field 
inside the "polaris" object in the token claims. Optional. Either this option 
or `nameClaimPath` (or both) must be provided. |
+| oidc.principalMapper.nameClaimPath | string | `nil` | The claim that 
contains the principal name. Nested paths can be expressed using "/" as a 
separator, e.g. "polaris/principal_name" would look for the "principal_name" 
field inside the "polaris" object in the token claims. Optional. Either this 
option or `idClaimPath` (or both) must be provided. |
+| oidc.principalMapper.type | string | `"default"` | The `PrincipalMapper` 
implementation to use. Only one built-in type is supported: default. |
+| oidc.principalRolesMapper | object | 
`{"filter":null,"mappings":[],"rolesClaimPath":null,"type":"default"}` | 
Principal roles mapping configuration. |
+| oidc.principalRolesMapper.filter | string | `nil` | A regular expression 
that matches the role names in the identity. Only roles that match this regex 
will be included in the Polaris-specific roles. |
+| oidc.principalRolesMapper.mappings | list | `[]` | A list of regex mappings 
that will be applied to each role name in the identity. This can be used to 
transform the role names in the identity into role names as expected by 
Polaris. The default ActiveRolesProvider expects the security identity to 
expose role names in the format `POLARIS_ROLE:<role name>`. |
+| oidc.principalRolesMapper.rolesClaimPath | string | `nil` | The path to the 
claim that contains the principal roles. Nested paths can be expressed using 
"/" as a separator, e.g. "polaris/principal_roles" would look for the 
"principal_roles" field inside the "polaris" object in the token claims. If not 
set, Quarkus looks for roles in standard locations. See 
https://quarkus.io/guides/security-oidc-bearer-token-authentication#token-claims-and-security-identity-roles.
 |
+| oidc.principalRolesMapper.type | string | `"default"` | The 
`PrincipalRolesMapper` implementation to use. Only one built-in type is 
supported: default. |
 | persistence | object | 
`{"relationalJdbc":{"secret":{"jdbcUrl":"jdbcUrl","name":null,"password":"password","username":"username"}},"type":"in-memory"}`
 | Polaris persistence configuration. |
 | persistence.relationalJdbc | object | 
`{"secret":{"jdbcUrl":"jdbcUrl","name":null,"password":"password","username":"username"}}`
 | The configuration for the relational-jdbc persistence manager. |
 | persistence.relationalJdbc.secret | object | 
`{"jdbcUrl":"jdbcUrl","name":null,"password":"password","username":"username"}` 
| The secret name to pull the database connection properties from. |

Reply via email to