From 6f0445eac588b9b63d9da173fed66f908ca3dbdd Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Sun, 3 Aug 2025 12:11:04 +0200 Subject: [PATCH 1/4] Add getting-started example with external authentication --- LICENSE | 1 + getting-started/README.md | 3 + .../assets/keycloak/iceberg-realm.json | 1988 +++++++++++++++++ .../assets/polaris/create-catalog.sh | 24 +- getting-started/keycloak/README.md | 163 ++ getting-started/keycloak/docker-compose.yml | 97 + getting-started/telemetry/README.md | 2 +- 7 files changed, 2269 insertions(+), 9 deletions(-) create mode 100644 getting-started/assets/keycloak/iceberg-realm.json create mode 100644 getting-started/keycloak/README.md create mode 100644 getting-started/keycloak/docker-compose.yml diff --git a/LICENSE b/LICENSE index 6ed4bcbc0b..7bee761975 100644 --- a/LICENSE +++ b/LICENSE @@ -273,6 +273,7 @@ This product includes code from Project Nessie. * build-logic/src/main/kotlin/Utilities.kt * build-logic/src/main/kotlin/polaris-shadow-jar.gradle.kts * build-logic/src/main/kotlin/polaris-runtime.gradle.kts +* getting-started/assets/keycloak/iceberg-realm.json * tools/config-docs/annotations/src/main/java/org/apache/polaris/docs/ConfigDocs.java * tools/config-docs/generator/src/main/java/org/apache/polaris/docs/generator/DocGenDoclet.java * tools/config-docs/generator/src/main/java/org/apache/polaris/docs/generator/MarkdownFormatter.java diff --git a/getting-started/README.md b/getting-started/README.md index 32a7376509..13155ae6b4 100644 --- a/getting-started/README.md +++ b/getting-started/README.md @@ -40,3 +40,6 @@ this directory. Each example has detailed instructions. database. The realm is bootstrapped with the Polaris Admin tool. This example also creates a `polaris_quickstart` catalog, and offers the ability to run Spark SQL and Trino queries. Finally, it shows how to attach a debugger to the Polaris server. + +- [Keycloak](keycloak): An example that uses Keycloak as an external identity provider (IDP) for + authentication. \ No newline at end of file diff --git a/getting-started/assets/keycloak/iceberg-realm.json b/getting-started/assets/keycloak/iceberg-realm.json new file mode 100644 index 0000000000..5b92eeadbb --- /dev/null +++ b/getting-started/assets/keycloak/iceberg-realm.json @@ -0,0 +1,1988 @@ +{ + "id": "6a483ecb-f66a-4f13-8aed-836936ef6536", + "realm": "iceberg", + "displayName": "Iceberg", + "displayNameHtml": "", + "notBefore": 0, + "defaultSignatureAlgorithm": "RS256", + "revokeRefreshToken": false, + "refreshTokenMaxReuse": 0, + "accessTokenLifespan": 300, + "accessTokenLifespanForImplicitFlow": 900, + "ssoSessionIdleTimeout": 1800, + "ssoSessionMaxLifespan": 36000, + "ssoSessionIdleTimeoutRememberMe": 0, + "ssoSessionMaxLifespanRememberMe": 0, + "offlineSessionIdleTimeout": 2592000, + "offlineSessionMaxLifespanEnabled": false, + "offlineSessionMaxLifespan": 5184000, + "clientSessionIdleTimeout": 0, + "clientSessionMaxLifespan": 0, + "clientOfflineSessionIdleTimeout": 0, + "clientOfflineSessionMaxLifespan": 0, + "accessCodeLifespan": 60, + "accessCodeLifespanUserAction": 300, + "accessCodeLifespanLogin": 1800, + "actionTokenGeneratedByAdminLifespan": 43200, + "actionTokenGeneratedByUserLifespan": 300, + "oauth2DeviceCodeLifespan": 600, + "oauth2DevicePollingInterval": 5, + "enabled": true, + "sslRequired": "external", + "registrationAllowed": false, + "registrationEmailAsUsername": false, + "rememberMe": false, + "verifyEmail": false, + "loginWithEmailAllowed": true, + "duplicateEmailsAllowed": false, + "resetPasswordAllowed": false, + "editUsernameAllowed": false, + "bruteForceProtected": false, + "permanentLockout": false, + "maxTemporaryLockouts": 0, + "maxFailureWaitSeconds": 900, + "minimumQuickLoginWaitSeconds": 60, + "waitIncrementSeconds": 60, + "quickLoginCheckMilliSeconds": 1000, + "maxDeltaTimeSeconds": 43200, + "failureFactor": 30, + "defaultRole": { + "id": "7e3c5dd2-a0dc-474b-8fbb-9616012fdf31", + "name": "default-roles-iceberg", + "description": "${role_default-roles}", + "composite": true, + "clientRole": false, + "containerId": "6a483ecb-f66a-4f13-8aed-836936ef6536" + }, + "requiredCredentials": [ + "password" + ], + "otpPolicyType": "totp", + "otpPolicyAlgorithm": "HmacSHA1", + "otpPolicyInitialCounter": 0, + "otpPolicyDigits": 6, + "otpPolicyLookAheadWindow": 1, + "otpPolicyPeriod": 30, + "otpPolicyCodeReusable": false, + "otpSupportedApplications": [ + "totpAppFreeOTPName", + "totpAppGoogleName", + "totpAppMicrosoftAuthenticatorName" + ], + "localizationTexts": {}, + "webAuthnPolicyRpEntityName": "keycloak", + "webAuthnPolicySignatureAlgorithms": [ + "ES256" + ], + "webAuthnPolicyRpId": "", + "webAuthnPolicyAttestationConveyancePreference": "not specified", + "webAuthnPolicyAuthenticatorAttachment": "not specified", + "webAuthnPolicyRequireResidentKey": "not specified", + "webAuthnPolicyUserVerificationRequirement": "not specified", + "webAuthnPolicyCreateTimeout": 0, + "webAuthnPolicyAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyAcceptableAaguids": [], + "webAuthnPolicyExtraOrigins": [], + "webAuthnPolicyPasswordlessRpEntityName": "keycloak", + "webAuthnPolicyPasswordlessSignatureAlgorithms": [ + "ES256" + ], + "webAuthnPolicyPasswordlessRpId": "", + "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified", + "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified", + "webAuthnPolicyPasswordlessRequireResidentKey": "not specified", + "webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified", + "webAuthnPolicyPasswordlessCreateTimeout": 0, + "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyPasswordlessAcceptableAaguids": [], + "webAuthnPolicyPasswordlessExtraOrigins": [], + "users": [ + { + "id": "91bd4585-d925-47d4-8204-8571e0e5c84b", + "username": "service-account-client1", + "emailVerified": false, + "createdTimestamp": 1716813517228, + "enabled": true, + "totp": false, + "serviceAccountClientId": "client1", + "disableableCredentialTypes": [], + "requiredActions": [], + "notBefore": 0 + } + ], + "scopeMappings": [ + { + "clientScope": "offline_access", + "roles": [ + "offline_access" + ] + } + ], + "clientScopeMappings": { + "account": [ + { + "client": "account-console", + "roles": [ + "manage-account", + "view-groups" + ] + } + ] + }, + "clients": [ + { + "id": "0d9a4aae-bb67-4ed0-be00-0515c1715b29", + "clientId": "account", + "name": "${client_account}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/iceberg/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/realms/iceberg/account/*" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "1c2e9c7e-0603-403a-94de-f60b6b2558a2", + "clientId": "account-console", + "name": "${client_account-console}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/iceberg/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/realms/iceberg/account/*" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+", + "pkce.code.challenge.method": "S256" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "24341d6b-ae72-4c71-9231-ecd49d2e168f", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "946b389f-7249-4145-9a6e-0aa5ef1626d1", + "clientId": "admin-cli", + "name": "${client_admin-cli}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": false, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "1e44e2c8-a02e-49aa-aa2a-04cf83227c55", + "clientId": "broker", + "name": "${client_broker}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": true, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "064f7cf5-86dc-450e-a88a-5d71a266654b", + "clientId": "client1", + "name": "client1", + "description": "", + "rootUrl": "", + "adminUrl": "", + "baseUrl": "", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "s3cr3t", + "redirectUris": [ + "http://localhost*" + ], + "webOrigins": [ + "http://localhost*" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": true, + "authorizationServicesEnabled": true, + "publicClient": false, + "frontchannelLogout": true, + "protocol": "openid-connect", + "attributes": { + "access.token.lifespan": "3600", + "client.secret.creation.time": "1716813517", + "post.logout.redirect.uris": "+", + "oauth2.device.authorization.grant.enabled": "true", + "backchannel.logout.revoke.offline.tokens": "false", + "use.refresh.tokens": "true", + "oidc.ciba.grant.enabled": "false", + "client.use.lightweight.access.token.enabled": "false", + "backchannel.logout.session.required": "true", + "client_credentials.use_refresh_token": "false", + "client.offline.session.idle.timeout": "86400", + "tls.client.certificate.bound.access.tokens": "false", + "require.pushed.authorization.requests": "false", + "acr.loa.map": "{}", + "display.on.consent.screen": "false", + "client.offline.session.max.lifespan": "86400", + "client.session.max.lifespan": "86400", + "token.response.type.bearer.lower-case": "false", + "client.session.idle.timeout": "86400" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "protocolMappers": [ + { + "id": "3390ae1e-0619-4b9c-a342-31b3d6c5ddd7", + "name": "Client Host", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientHost", + "introspection.token.claim": "true", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientHost", + "jsonType.label": "String" + } + }, + { + "id": "principal-id-mapper", + "name": "Principal ID Mapper", + "protocol": "openid-connect", + "protocolMapper": "oidc-hardcoded-claim-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "principal_id", + "claim.value": "0", + "jsonType.label": "long" + } + }, + { + "id": "principal-name-mapper", + "name": "Principal Name Mapper", + "protocol": "openid-connect", + "protocolMapper": "oidc-hardcoded-claim-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "principal_name", + "claim.value": "root", + "jsonType.label": "String" + } + }, + { + "id": "principal-roles-mapper", + "name": "Principal Roles Mapper", + "protocol": "openid-connect", + "protocolMapper": "oidc-hardcoded-claim-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "principal_roles", + "claim.value": "[\"service_admin\", \"catalog_admin\"]", + "jsonType.label": "JSON" + } + }, + { + "id": "9ede87b4-3a39-4fec-add8-9a4b6f2ac3c3", + "name": "Client ID", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "client_id", + "introspection.token.claim": "true", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "client_id", + "jsonType.label": "String" + } + }, + { + "id": "2cec826c-ca00-4778-8fe5-539efa12f225", + "name": "Client IP Address", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientAddress", + "introspection.token.claim": "true", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientAddress", + "jsonType.label": "String" + } + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "catalog", + "offline_access", + "sign", + "microprofile-jwt" + ], + "authorizationSettings": { + "allowRemoteResourceManagement": true, + "policyEnforcementMode": "ENFORCING", + "resources": [ + { + "name": "Default Resource", + "type": "urn:client1:resources:default", + "ownerManagedAccess": false, + "attributes": {}, + "_id": "05110fcd-4ad3-49a9-8891-5d0730f5ca2f", + "uris": [ + "/*" + ] + } + ], + "policies": [ + { + "id": "8acd728b-18cf-4917-9f90-63ba2c6bef52", + "name": "Default Policy", + "description": "A policy that grants access only for users within this realm", + "type": "role", + "logic": "POSITIVE", + "decisionStrategy": "AFFIRMATIVE", + "config": { + "roles": "[{\"id\":\"default-roles-iceberg\",\"required\":false}]" + } + }, + { + "id": "0d65759e-a960-4f48-8054-e20349b82a8d", + "name": "Default Permission", + "description": "A permission that applies to the default resource type", + "type": "resource", + "logic": "POSITIVE", + "decisionStrategy": "UNANIMOUS", + "config": { + "defaultResourceType": "urn:client1:resources:default", + "applyPolicies": "[\"Default Policy\"]" + } + } + ], + "scopes": [], + "decisionStrategy": "UNANIMOUS" + } + }, + { + "id": "4c26b327-7525-48c0-9a0e-699ceeedde03", + "clientId": "realm-management", + "name": "${client_realm-management}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": true, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + } + ], + "clientScopes": [ + { + "id": "fded46f5-09f6-44d7-ab15-1f0da6dafff7", + "name": "profile", + "description": "OpenID Connect built-in scope: profile", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${profileScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "05cef49a-a058-468e-9454-0c465fb6b89c", + "name": "zoneinfo", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "zoneinfo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "zoneinfo", + "jsonType.label": "String" + } + }, + { + "id": "447f4c8f-68c3-4725-be69-2acb0c867588", + "name": "picture", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "picture", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "picture", + "jsonType.label": "String" + } + }, + { + "id": "dc0a7940-6ec4-4ab1-92a9-09e8eef81de8", + "name": "birthdate", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "birthdate", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "birthdate", + "jsonType.label": "String" + } + }, + { + "id": "6e225d00-63aa-448e-acef-d93d0e3e3278", + "name": "gender", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "gender", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "gender", + "jsonType.label": "String" + } + }, + { + "id": "de1d80cc-8682-4df9-a40a-b6ba06d3bcce", + "name": "updated at", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "updatedAt", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "updated_at", + "jsonType.label": "long" + } + }, + { + "id": "7c0c46be-9886-466d-82bb-543be1c40e97", + "name": "full name", + "protocol": "openid-connect", + "protocolMapper": "oidc-full-name-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + }, + { + "id": "6ca38d79-83fc-4fbe-a994-9edd0d5e9f6d", + "name": "username", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "preferred_username", + "jsonType.label": "String" + } + }, + { + "id": "c7a488a0-e5c1-4332-9063-d71a295f409e", + "name": "website", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "website", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "website", + "jsonType.label": "String" + } + }, + { + "id": "fe1737ee-0897-4aa1-9ccb-f199de9523e3", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + }, + { + "id": "57d02dd0-9c8d-48af-bcca-c55f3dd7fb5f", + "name": "family name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "lastName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "family_name", + "jsonType.label": "String" + } + }, + { + "id": "aa5e3cee-f8d8-46bf-8f13-f3cdca89b15d", + "name": "middle name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "middleName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "middle_name", + "jsonType.label": "String" + } + }, + { + "id": "25ae8eb4-0f39-495c-b652-184e2c8b2c47", + "name": "profile", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "profile", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "profile", + "jsonType.label": "String" + } + }, + { + "id": "2b508e53-8e3d-41f0-a650-a7eb59302d28", + "name": "nickname", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "nickname", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "nickname", + "jsonType.label": "String" + } + }, + { + "id": "559ce971-8808-4500-8ed0-92d58b58040a", + "name": "given name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "firstName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "given_name", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "f6cee4ef-a1b4-4662-8c53-d68b96ef03ae", + "name": "sign", + "description": "Iceberg sign scope", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "gui.order": "", + "consent.screen.text": "" + } + }, + { + "id": "a1d49b93-c270-4dd0-9633-616fc0ca56a8", + "name": "role_list", + "description": "SAML role list", + "protocol": "saml", + "attributes": { + "consent.screen.text": "${samlRoleListScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "ea759f8a-88b3-4fb5-984f-0e5c926caedf", + "name": "role list", + "protocol": "saml", + "protocolMapper": "saml-role-list-mapper", + "consentRequired": false, + "config": { + "single": "false", + "attribute.nameformat": "Basic", + "attribute.name": "Role" + } + } + ] + }, + { + "id": "bbc115fe-4de1-4588-b796-cb99fb3c51b0", + "name": "phone", + "description": "OpenID Connect built-in scope: phone", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${phoneScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "8da98a35-a3dc-4cea-892e-102022d0e634", + "name": "phone number verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "phoneNumberVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number_verified", + "jsonType.label": "boolean" + } + }, + { + "id": "19bbdb0c-8107-46e7-a495-eb63a72ca4a1", + "name": "phone number", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "phoneNumber", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "ebacfc45-540c-406c-b858-715c81a0fade", + "name": "microprofile-jwt", + "description": "Microprofile - JWT built-in scope", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "75b09e12-cdf5-4b06-a08a-4f73854080c8", + "name": "upn", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "upn", + "jsonType.label": "String" + } + }, + { + "id": "881e9efb-3562-4b8a-bdc3-12d2f0381f0b", + "name": "groups", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "multivalued": "true", + "userinfo.token.claim": "true", + "user.attribute": "foo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "groups", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "848093ed-6078-460f-bf52-608af61086d5", + "name": "email", + "description": "OpenID Connect built-in scope: email", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${emailScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "f2238310-97cb-40cb-bb46-85e1f00a41e8", + "name": "email verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "emailVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email_verified", + "jsonType.label": "boolean" + } + }, + { + "id": "3593b516-1cd0-4f2d-99a8-f3e8716a3283", + "name": "email", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "email", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "9464ab70-31da-4d22-9584-bd1a425f058e", + "name": "roles", + "description": "OpenID Connect scope for add user roles to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "true", + "consent.screen.text": "${rolesScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "f17f33de-1667-468f-bdc2-dfd32d93625e", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "access.token.claim": "true" + } + }, + { + "id": "e673b280-7633-4983-9cf0-3af2eae6e1a3", + "name": "realm roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "multivalued": "true", + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "realm_access.roles", + "jsonType.label": "String" + } + }, + { + "id": "2be05e6f-e48c-4f03-979d-f16d41b46a7b", + "name": "client roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-client-role-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "multivalued": "true", + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "resource_access.${client_id}.roles", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "460567bd-3fb5-4b38-8ce2-31b4ec9deff4", + "name": "catalog", + "description": "Iceberg catalog scope", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "gui.order": "", + "consent.screen.text": "" + } + }, + { + "id": "22c58b6b-957f-4d39-ac20-3efa612c1b24", + "name": "address", + "description": "OpenID Connect built-in scope: address", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${addressScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "e6fc4071-6c83-43fb-a041-e198e39d6009", + "name": "address", + "protocol": "openid-connect", + "protocolMapper": "oidc-address-mapper", + "consentRequired": false, + "config": { + "user.attribute.formatted": "formatted", + "user.attribute.country": "country", + "introspection.token.claim": "true", + "user.attribute.postal_code": "postal_code", + "userinfo.token.claim": "true", + "user.attribute.street": "street", + "id.token.claim": "true", + "user.attribute.region": "region", + "access.token.claim": "true", + "user.attribute.locality": "locality" + } + } + ] + }, + { + "id": "fc350a9d-c3fe-43dc-9950-875184339cae", + "name": "acr", + "description": "OpenID Connect scope for add acr (authentication context class reference) to the token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "d0fa8528-80b5-4a7c-b23f-85e6b555db14", + "name": "acr loa level", + "protocol": "openid-connect", + "protocolMapper": "oidc-acr-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + } + ] + }, + { + "id": "fe6a977f-f6d6-468c-a35a-66c61c7efd0d", + "name": "offline_access", + "description": "OpenID Connect built-in scope: offline_access", + "protocol": "openid-connect", + "attributes": { + "consent.screen.text": "${offlineAccessScopeConsentText}", + "display.on.consent.screen": "true" + } + }, + { + "id": "5867dc26-b2a1-4322-ab73-e4b71229ddd7", + "name": "web-origins", + "description": "OpenID Connect scope for add allowed web origins to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false", + "consent.screen.text": "" + }, + "protocolMappers": [ + { + "id": "d3651aa4-1491-4d65-afae-7c8ae43ea529", + "name": "allowed web origins", + "protocol": "openid-connect", + "protocolMapper": "oidc-allowed-origins-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "access.token.claim": "true" + } + } + ] + } + ], + "defaultDefaultClientScopes": [ + "role_list", + "profile", + "email", + "roles", + "web-origins", + "acr" + ], + "defaultOptionalClientScopes": [ + "offline_access", + "address", + "phone", + "microprofile-jwt", + "sign", + "catalog" + ], + "browserSecurityHeaders": { + "contentSecurityPolicyReportOnly": "", + "xContentTypeOptions": "nosniff", + "referrerPolicy": "no-referrer", + "xRobotsTag": "none", + "xFrameOptions": "SAMEORIGIN", + "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "xXSSProtection": "1; mode=block", + "strictTransportSecurity": "max-age=31536000; includeSubDomains" + }, + "smtpServer": {}, + "eventsEnabled": false, + "eventsListeners": [ + "jboss-logging" + ], + "enabledEventTypes": [], + "adminEventsEnabled": false, + "adminEventsDetailsEnabled": false, + "identityProviders": [], + "identityProviderMappers": [], + "components": { + "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [ + { + "id": "90f35921-8bb3-4977-9126-f75fda83f55b", + "name": "Trusted Hosts", + "providerId": "trusted-hosts", + "subType": "anonymous", + "subComponents": {}, + "config": { + "host-sending-registration-request-must-match": [ + "true" + ], + "client-uris-must-match": [ + "true" + ] + } + }, + { + "id": "002fe9ac-d89e-411f-bb07-756a7a840aab", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "saml-user-property-mapper", + "saml-role-list-mapper", + "saml-user-attribute-mapper", + "oidc-sha256-pairwise-sub-mapper", + "oidc-usermodel-attribute-mapper", + "oidc-address-mapper", + "oidc-usermodel-property-mapper", + "oidc-full-name-mapper" + ] + } + }, + { + "id": "401c9280-cbb7-4f5a-ba1f-6ea57697c0a6", + "name": "Max Clients Limit", + "providerId": "max-clients", + "subType": "anonymous", + "subComponents": {}, + "config": { + "max-clients": [ + "200" + ] + } + }, + { + "id": "cd3ed07b-5769-4441-bd18-aaedb9672ac8", + "name": "Full Scope Disabled", + "providerId": "scope", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "159f7358-ef91-4b6d-988c-89843716b27e", + "name": "Consent Required", + "providerId": "consent-required", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "bee619ba-ce6b-4c41-bf42-58a837c45515", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allow-default-scopes": [ + "true" + ] + } + }, + { + "id": "4121265a-e132-4273-bdeb-1e4222d4180f", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "saml-user-property-mapper", + "oidc-usermodel-property-mapper", + "oidc-full-name-mapper", + "oidc-sha256-pairwise-sub-mapper", + "oidc-usermodel-attribute-mapper", + "saml-user-attribute-mapper", + "oidc-address-mapper", + "saml-role-list-mapper" + ] + } + }, + { + "id": "9e31b299-5416-49f1-abd7-8aa89a7930ad", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allow-default-scopes": [ + "true" + ] + } + } + ], + "org.keycloak.keys.KeyProvider": [ + { + "id": "ce2453e3-d3fe-47ae-b345-84b30ce29810", + "name": "rsa-enc-generated", + "providerId": "rsa-enc-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ], + "algorithm": [ + "RSA-OAEP" + ] + } + }, + { + "id": "2180a8d4-6dd0-4b54-a2ae-f62f0e45c6c6", + "name": "aes-generated", + "providerId": "aes-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ] + } + }, + { + "id": "59666fdd-2683-4deb-8f11-92263e5e8227", + "name": "rsa-generated", + "providerId": "rsa-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ] + } + }, + { + "id": "43dfab27-197a-49fa-b17f-8e0b0f30171a", + "name": "hmac-generated-hs512", + "providerId": "hmac-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ], + "algorithm": [ + "HS512" + ] + } + } + ] + }, + "internationalizationEnabled": false, + "supportedLocales": [], + "authenticationFlows": [ + { + "id": "fb44ec09-f1ec-40e7-8a81-69bcc50b3766", + "alias": "Account verification options", + "description": "Method with which to verity the existing account", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-email-verification", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Verify Existing Account by Re-authentication", + "userSetupAllowed": false + } + ] + }, + { + "id": "b71e0fe9-467f-47b0-b14e-d2f2041be99d", + "alias": "Browser - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-otp-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "74076643-a0ce-472a-9ec9-f50d25461726", + "alias": "Direct Grant - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "direct-grant-validate-otp", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "0456f940-c9ce-4da2-8b96-ac02b7f9ae73", + "alias": "First broker login - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-otp-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "7e3478de-8bdf-4357-9384-016e181190aa", + "alias": "Handle Existing Account", + "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-confirm-link", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Account verification options", + "userSetupAllowed": false + } + ] + }, + { + "id": "fd88b5ce-83ed-469e-955f-14d19f9f158a", + "alias": "Reset - Conditional OTP", + "description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-otp", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "b2db1f33-491a-4036-b1ac-9c0aed8f810c", + "alias": "User creation or linking", + "description": "Flow for the existing/non-existing user alternatives", + "providerId": "basic-flow", + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorConfig": "create unique user config", + "authenticator": "idp-create-user-if-unique", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Handle Existing Account", + "userSetupAllowed": false + } + ] + }, + { + "id": "5406adf0-e2cf-4625-967b-b0ca059f2b47", + "alias": "Verify Existing Account by Re-authentication", + "description": "Reauthentication of existing account", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-username-password-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "First broker login - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "26c5bdb5-2587-47f2-a58c-fe66478a2110", + "alias": "browser", + "description": "browser based authentication", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-cookie", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-spnego", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "identity-provider-redirector", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 25, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 30, + "autheticatorFlow": true, + "flowAlias": "forms", + "userSetupAllowed": false + } + ] + }, + { + "id": "12ea7d58-4127-4ada-b50d-c028c02a6b9d", + "alias": "clients", + "description": "Base authentication for clients", + "providerId": "client-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "client-secret", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-jwt", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-secret-jwt", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 30, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-x509", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 40, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "7ea61fcb-f9ad-4cb6-a465-c6d81702475d", + "alias": "direct grant", + "description": "OpenID Connect Resource Owner Grant", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "direct-grant-validate-username", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "direct-grant-validate-password", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 30, + "autheticatorFlow": true, + "flowAlias": "Direct Grant - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "7be43319-d292-47db-af94-92cae142e9db", + "alias": "docker auth", + "description": "Used by Docker clients to authenticate against the IDP", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "docker-http-basic-authenticator", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "f954433c-0cf5-4d5f-b9af-6111583f44c9", + "alias": "first broker login", + "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorConfig": "review profile config", + "authenticator": "idp-review-profile", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "User creation or linking", + "userSetupAllowed": false + } + ] + }, + { + "id": "888ed326-5dca-4b25-8236-7c3ee848363c", + "alias": "forms", + "description": "Username, password, otp and other auth forms.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-username-password-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Browser - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "84f212db-b001-482b-a6f2-421422f893cf", + "alias": "registration", + "description": "registration flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-page-form", + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": true, + "flowAlias": "registration form", + "userSetupAllowed": false + } + ] + }, + { + "id": "1e2f5037-5427-4711-b025-2b4f77d441f5", + "alias": "registration form", + "description": "registration form", + "providerId": "form-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-user-creation", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-password-action", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 50, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-recaptcha-action", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 60, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-terms-and-conditions", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 70, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "97952280-0364-42e7-b1e7-24701746a205", + "alias": "reset credentials", + "description": "Reset credentials for a user if they forgot their password or something", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "reset-credentials-choose-user", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-credential-email", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-password", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 30, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 40, + "autheticatorFlow": true, + "flowAlias": "Reset - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "83799b5d-a55f-4c13-84df-835c1890b862", + "alias": "saml ecp", + "description": "SAML ECP Profile Authentication Flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "http-basic-authenticator", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + } + ], + "authenticatorConfig": [ + { + "id": "ff2d017a-da94-44b7-9f88-30f35ef308d5", + "alias": "create unique user config", + "config": { + "require.password.update.after.registration": "false" + } + }, + { + "id": "8d4e64ef-d261-4088-8419-4ab3d35a6ecf", + "alias": "review profile config", + "config": { + "update.profile.on.first.login": "missing" + } + } + ], + "requiredActions": [ + { + "alias": "CONFIGURE_TOTP", + "name": "Configure OTP", + "providerId": "CONFIGURE_TOTP", + "enabled": true, + "defaultAction": false, + "priority": 10, + "config": {} + }, + { + "alias": "TERMS_AND_CONDITIONS", + "name": "Terms and Conditions", + "providerId": "TERMS_AND_CONDITIONS", + "enabled": false, + "defaultAction": false, + "priority": 20, + "config": {} + }, + { + "alias": "UPDATE_PASSWORD", + "name": "Update Password", + "providerId": "UPDATE_PASSWORD", + "enabled": true, + "defaultAction": false, + "priority": 30, + "config": {} + }, + { + "alias": "UPDATE_PROFILE", + "name": "Update Profile", + "providerId": "UPDATE_PROFILE", + "enabled": true, + "defaultAction": false, + "priority": 40, + "config": {} + }, + { + "alias": "VERIFY_EMAIL", + "name": "Verify Email", + "providerId": "VERIFY_EMAIL", + "enabled": true, + "defaultAction": false, + "priority": 50, + "config": {} + }, + { + "alias": "delete_account", + "name": "Delete Account", + "providerId": "delete_account", + "enabled": false, + "defaultAction": false, + "priority": 60, + "config": {} + }, + { + "alias": "webauthn-register", + "name": "Webauthn Register", + "providerId": "webauthn-register", + "enabled": true, + "defaultAction": false, + "priority": 70, + "config": {} + }, + { + "alias": "webauthn-register-passwordless", + "name": "Webauthn Register Passwordless", + "providerId": "webauthn-register-passwordless", + "enabled": true, + "defaultAction": false, + "priority": 80, + "config": {} + }, + { + "alias": "VERIFY_PROFILE", + "name": "Verify Profile", + "providerId": "VERIFY_PROFILE", + "enabled": true, + "defaultAction": false, + "priority": 90, + "config": {} + }, + { + "alias": "delete_credential", + "name": "Delete Credential", + "providerId": "delete_credential", + "enabled": true, + "defaultAction": false, + "priority": 100, + "config": {} + }, + { + "alias": "update_user_locale", + "name": "Update User Locale", + "providerId": "update_user_locale", + "enabled": true, + "defaultAction": false, + "priority": 1000, + "config": {} + } + ], + "browserFlow": "browser", + "registrationFlow": "registration", + "directGrantFlow": "direct grant", + "resetCredentialsFlow": "reset credentials", + "clientAuthenticationFlow": "clients", + "dockerAuthenticationFlow": "docker auth", + "firstBrokerLoginFlow": "first broker login", + "attributes": { + "cibaBackchannelTokenDeliveryMode": "poll", + "cibaAuthRequestedUserHint": "login_hint", + "clientOfflineSessionMaxLifespan": "0", + "oauth2DevicePollingInterval": "5", + "clientSessionIdleTimeout": "0", + "clientOfflineSessionIdleTimeout": "0", + "cibaInterval": "5", + "realmReusableOtpCode": "false", + "cibaExpiresIn": "120", + "oauth2DeviceCodeLifespan": "600", + "parRequestUriLifespan": "60", + "clientSessionMaxLifespan": "0", + "frontendUrl": "", + "acr.loa.map": "{}" + }, + "keycloakVersion": "24.0.4", + "userManagedAccessAllowed": false, + "clientProfiles": { + "profiles": [] + }, + "clientPolicies": { + "policies": [] + } +} diff --git a/getting-started/assets/polaris/create-catalog.sh b/getting-started/assets/polaris/create-catalog.sh index c35a07cc5a..7d67c169c3 100755 --- a/getting-started/assets/polaris/create-catalog.sh +++ b/getting-started/assets/polaris/create-catalog.sh @@ -21,14 +21,21 @@ set -e apk add --no-cache jq -token=$(curl -s http://polaris:8181/api/catalog/v1/oauth/tokens \ - --user ${CLIENT_ID}:${CLIENT_SECRET} \ - -d grant_type=client_credentials \ - -d scope=PRINCIPAL_ROLE:ALL | sed -n 's/.*"access_token":"\([^"]*\)".*/\1/p') +realm=${1:-"POLARIS"} -if [ -z "${token}" ]; then - echo "Failed to obtain access token." - exit 1 +token=${2:-""} + +if [ -z "$token" ]; then + token=$(curl -s http://polaris:8181/api/catalog/v1/oauth/tokens \ + --user ${CLIENT_ID}:${CLIENT_SECRET} \ + -H "Polaris-Realm: $realm" \ + -d grant_type=client_credentials \ + -d scope=PRINCIPAL_ROLE:ALL | jq -r .access_token) + + if [ -z "${token}" ]; then + echo "Failed to obtain access token." + exit 1 + fi fi echo @@ -59,7 +66,7 @@ elif [[ "$STORAGE_TYPE" == "AZURE" ]]; then fi echo -echo Creating a catalog named quickstart_catalog... +echo Creating a catalog named quickstart_catalog in realm $realm... PAYLOAD='{ "catalog": { @@ -78,6 +85,7 @@ echo $PAYLOAD curl -s -H "Authorization: Bearer ${token}" \ -H 'Accept: application/json' \ -H 'Content-Type: application/json' \ + -H "Polaris-Realm: $realm" \ http://polaris:8181/api/management/v1/catalogs \ -d "$PAYLOAD" -v diff --git a/getting-started/keycloak/README.md b/getting-started/keycloak/README.md new file mode 100644 index 0000000000..ecbaa72b43 --- /dev/null +++ b/getting-started/keycloak/README.md @@ -0,0 +1,163 @@ + + +# Getting Started with Apache Polaris, External Authentication and Keycloak + +## Overview + +This example uses Keycloak as the identity provider for Polaris. The "iceberg" realm is automatically created and +configured from the `iceberg-realm.json` file. + +This Keycloak realm contains 1 client definition: `client1:s3cr3t`, and 2 custom scopes: `catalog` and `sign`. It is +also configured to return tokens with the following fixed claims: + +- `principal_id`: the principal ID of the user. It is always set to zero (0) in this example. +- `principal_name`: the principal name of the user. It is always set to "root" in this example. +- `principal_roles`: the principal roles of the user. It is always set to `["server_admin", "catalog_admin"]` in this + example. + +This is obviously not a realistic configuration. In a real-world scenario, you would configure Keycloak to return the +actual principal ID, name and roles of the user. Note that principals must have been created in Polaris beforehand. + +Polaris is configured with 3 realms: + +- `realm-internal`: This is the default realm, and is configured to use the internal authentication only. It accepts + token issues by Polaris itself only. +- `realm-external`: This realm is configured to use an external identity provider (IDP) for authentication only. It + accepts tokens issued by Keycloak only. +- `realm-mixed`: This realm is configured to use both the internal and external authentication. It accepts tokens + issued by both Polaris and Keycloak. + +## Starting the Example + +1. Build the Polaris server image if it's not already present locally: + + ```shell + ./gradlew \ + :polaris-server:assemble \ + :polaris-server:quarkusAppPartsBuild --rerun \ + -Dquarkus.container-image.build=true + ``` + +2. Start the docker compose group by running the following command from the root of the repository: + + ```shell + docker compose -f getting-started/keycloak/docker-compose.yml up + ``` + +## Requesting a Token + +Note: the commands below require `jq` to be installed on your machine. + +### From Polaris + +You can request a token from Polaris for realms `realm-internal` and `realm-mixed`: + +1. Open a terminal and run the following command to request an access token for the `realm-internal` realm: + + ```shell + polaris_token_realm_internal=$(curl -s http://localhost:8181/api/catalog/v1/oauth/tokens \ + --user root:s3cr3t \ + -H 'Polaris-Realm: realm-internal' \ + -d 'grant_type=client_credentials' \ + -d 'scope=PRINCIPAL_ROLE:ALL' | jq -r .access_token) + ``` + + This token is valid only for the `realm-internal` realm. + +2. Open a terminal and run the following command to request an access token for the `realm-mixed` realm: + + ```shell + polaris_token_realm_mixed=$(curl -s http://localhost:8181/api/catalog/v1/oauth/tokens \ + --user root:s3cr3t \ + -H 'Polaris-Realm: realm-mixed' \ + -d 'grant_type=client_credentials' \ + -d 'scope=PRINCIPAL_ROLE:ALL' | jq -r .access_token) + ``` + + This token is valid only for the `realm-mixed` realm. + +### From Keycloak + +You can request a token from Keycloak for the `realm-external` and `realm-mixed` realms: + +1. Open a terminal and run the following command to request an access token from Keycloak: + + ```shell + keycloak_token=$(curl -s http://keycloak:8080/realms/iceberg/protocol/openid-connect/token \ + --resolve keycloak:8080:127.0.0.1 \ + --user client1:s3cr3t \ + -d 'grant_type=client_credentials' \ + -d 'scope=catalog' | jq -r .access_token) + ``` + +Note the `--resolve` option: it is used to send the request with the `Host` header set to `keycloak`. This is necessary +because Keycloak issues tokens with the `iss` claim matching the request's host header; without this, the token would not +be valid when used against Polaris because the `iss` claim would be `127.0.0.1`, but Polaris expects it to be `keycloak`. + +Tokens issued by Keycloak can be used to access Polaris with the `realm-external` or `realm-mixed` realms. Access tokens +are valid for 1 hour. + +You can also access the Keycloak admin console. Open a browser and go to [http://localhost:8080](http://localhost:8080), +then log in with the username `admin` and password `admin` (you can change this in the docker-compose file). + +## Accessing Polaris with the Tokens + +You can access Polaris using the tokens you obtained above. The following examples show how to use the tokens with +`curl`: + +### Using the Polaris Token + +1. Open a terminal and run the following command to list the principal roles in the `realm-internal` realm: + + ```shell + curl -v http://localhost:8181/api/management/v1/catalogs \ + -H "Authorization: Bearer $polaris_token_realm_internal" \ + -H 'Polaris-Realm: realm-internal' \ + -H 'Accept: application/json' + ``` + +2. Open a terminal and run the following command to list the principal roles in the `realm-mixed` realm: + + ```shell + curl -v http://localhost:8181/api/management/v1/catalogs \ + -H "Authorization: Bearer $polaris_token_realm_mixed" \ + -H 'Polaris-Realm: realm-mixed' \ + -H 'Accept: application/json' + ``` + +### Using the Keycloak Token + +1. Open a terminal and run the following command to list the principal roles in the `realm-external` realm: + + ```shell + curl -v http://localhost:8181/api/management/v1/catalogs \ + -H "Authorization: Bearer $keycloak_token" \ + -H 'Polaris-Realm: realm-external' \ + -H 'Accept: application/json' + ``` + +2. Open a terminal and run the following command to list the principal roles in the `realm-mixed` realm: + + ```shell + curl -v http://localhost:8181/api/management/v1/catalogs \ + -H "Authorization: Bearer $keycloak_token" \ + -H 'Polaris-Realm: realm-mixed' \ + -H 'Accept: application/json' + ``` \ No newline at end of file diff --git a/getting-started/keycloak/docker-compose.yml b/getting-started/keycloak/docker-compose.yml new file mode 100644 index 0000000000..4a0f2904e4 --- /dev/null +++ b/getting-started/keycloak/docker-compose.yml @@ -0,0 +1,97 @@ +# +# 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. +# + +services: + + polaris: + image: apache/polaris:latest + ports: + # API port + - "8181:8181" + # Management port (metrics and health checks) + - "8182:8182" + # Optional, allows attaching a debugger to the Polaris JVM + - "5005:5005" + depends_on: + keycloak: + condition: service_healthy + environment: + JAVA_DEBUG: true + JAVA_DEBUG_PORT: "*:5005" + POLARIS_BOOTSTRAP_CREDENTIALS: realm-internal,root,s3cr3t;realm-external,root,s3cr3t;realm-mixed,root,s3cr3t + polaris.realm-context.realms: realm-internal,realm-external,realm-mixed + polaris.authentication.type: internal + polaris.authentication."realm-external".type: external + polaris.authentication."realm-mixed".type: mixed + quarkus.oidc.tenant-enabled: true + quarkus.oidc.auth-server-url: http://keycloak:8080/realms/iceberg + quarkus.oidc.client-id: client1 + quarkus.oidc.roles.role-claim-path: principal_roles + polaris.oidc.principal-mapper.id-claim-path: principal_id + polaris.oidc.principal-mapper.name-claim-path: principal_name + polaris.oidc.principal-roles-mapper.mappings[0].regex: (.+) + polaris.oidc.principal-roles-mapper.mappings[0].replacement: PRINCIPAL_ROLE:$1 + polaris.features."ALLOW_INSECURE_STORAGE_TYPES": "true" + polaris.features."SUPPORTED_CATALOG_STORAGE_TYPES": "[\"FILE\",\"S3\",\"GCS\",\"AZURE\"]" + polaris.readiness.ignore-severe-issues: "true" + healthcheck: + test: ["CMD", "curl", "http://localhost:8182/q/health"] + interval: 2s + timeout: 10s + retries: 10 + start_period: 10s + + polaris-setup: + image: alpine/curl + depends_on: + polaris: + condition: service_healthy + environment: + - CLIENT_ID=root + - CLIENT_SECRET=s3cr3t + volumes: + - ../assets/polaris/:/polaris + entrypoint: | + /bin/sh -c "apk add --no-cache jq && \ + chmod +x /polaris/create-catalog.sh && \ + token=$$(curl http://keycloak:8080/realms/iceberg/protocol/openid-connect/token --user client1:s3cr3t -d 'grant_type=client_credentials' -d 'scope=catalog' | jq -r .access_token) && \ + /polaris/create-catalog.sh realm-internal && \ + /polaris/create-catalog.sh realm-external $$token && \ + /polaris/create-catalog.sh realm-mixed $$token" + + keycloak: + image: quay.io/keycloak/keycloak:26.2.2 + ports: + - "8080:8080" + environment: + KC_BOOTSTRAP_ADMIN_USERNAME: admin + KC_BOOTSTRAP_ADMIN_PASSWORD: admin + volumes: + - ../assets/keycloak/iceberg-realm.json:/opt/keycloak/data/import/iceberg-realm.json + command: [ + "start-dev", + "--import-realm", + "--health-enabled=true" + ] + healthcheck: + test: "exec 3<>/dev/tcp/localhost/9000 && echo -e 'GET /health/ready HTTP/1.1\\r\\nHost: localhost\\r\\nConnection: close\\r\\n\\r\\n' >&3 && cat <&3 | grep -q '200 OK'" + interval: 5s + timeout: 2s + retries: 15 + diff --git a/getting-started/telemetry/README.md b/getting-started/telemetry/README.md index 11533d378d..c1bb392013 100644 --- a/getting-started/telemetry/README.md +++ b/getting-started/telemetry/README.md @@ -49,7 +49,7 @@ This example requires `jq` to be installed on your machine. ``` 4. Then, use the access token in the Authorization header when accessing Polaris; you can also test - the `Polairs-Request-Id` header; you should see it in all logs and traces: + the `Polaris-Request-Id` header; you should see it in all logs and traces: ```shell curl -v 'http://localhost:8181/api/management/v1/principal-roles' \ From a8f656807235c59869aa7e8c460d0f9c6d1da076 Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Mon, 4 Aug 2025 17:04:29 +0200 Subject: [PATCH 2/4] remove scopes --- .../assets/keycloak/iceberg-realm.json | 30 +------------------ 1 file changed, 1 insertion(+), 29 deletions(-) diff --git a/getting-started/assets/keycloak/iceberg-realm.json b/getting-started/assets/keycloak/iceberg-realm.json index 5b92eeadbb..da5c498317 100644 --- a/getting-started/assets/keycloak/iceberg-realm.json +++ b/getting-started/assets/keycloak/iceberg-realm.json @@ -465,9 +465,7 @@ "optionalClientScopes": [ "address", "phone", - "catalog", "offline_access", - "sign", "microprofile-jwt" ], "authorizationSettings": { @@ -790,18 +788,6 @@ } ] }, - { - "id": "f6cee4ef-a1b4-4662-8c53-d68b96ef03ae", - "name": "sign", - "description": "Iceberg sign scope", - "protocol": "openid-connect", - "attributes": { - "include.in.token.scope": "true", - "display.on.consent.screen": "true", - "gui.order": "", - "consent.screen.text": "" - } - }, { "id": "a1d49b93-c270-4dd0-9633-616fc0ca56a8", "name": "role_list", @@ -1015,18 +1001,6 @@ } ] }, - { - "id": "460567bd-3fb5-4b38-8ce2-31b4ec9deff4", - "name": "catalog", - "description": "Iceberg catalog scope", - "protocol": "openid-connect", - "attributes": { - "include.in.token.scope": "true", - "display.on.consent.screen": "true", - "gui.order": "", - "consent.screen.text": "" - } - }, { "id": "22c58b6b-957f-4d39-ac20-3efa612c1b24", "name": "address", @@ -1131,9 +1105,7 @@ "offline_access", "address", "phone", - "microprofile-jwt", - "sign", - "catalog" + "microprofile-jwt" ], "browserSecurityHeaders": { "contentSecurityPolicyReportOnly": "", From 69b8445d760f9322ca8e4c82a3b911b54ac229dc Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Mon, 4 Aug 2025 17:04:37 +0200 Subject: [PATCH 3/4] review --- getting-started/keycloak/README.md | 61 ++++++++++++++++++++++++++---- 1 file changed, 53 insertions(+), 8 deletions(-) diff --git a/getting-started/keycloak/README.md b/getting-started/keycloak/README.md index ecbaa72b43..a244453a65 100644 --- a/getting-started/keycloak/README.md +++ b/getting-started/keycloak/README.md @@ -24,8 +24,8 @@ This example uses Keycloak as the identity provider for Polaris. The "iceberg" realm is automatically created and configured from the `iceberg-realm.json` file. -This Keycloak realm contains 1 client definition: `client1:s3cr3t`, and 2 custom scopes: `catalog` and `sign`. It is -also configured to return tokens with the following fixed claims: +This Keycloak realm contains 1 client definition: `client1:s3cr3t`. It is configured to return tokens with the following +fixed claims: - `principal_id`: the principal ID of the user. It is always set to zero (0) in this example. - `principal_name`: the principal name of the user. It is always set to "root" in this example. @@ -33,7 +33,8 @@ also configured to return tokens with the following fixed claims: example. This is obviously not a realistic configuration. In a real-world scenario, you would configure Keycloak to return the -actual principal ID, name and roles of the user. Note that principals must have been created in Polaris beforehand. +actual principal ID, name and roles of the user. Note that principals and principal roles must have been created in +Polaris beforehand, and the principal ID, name and roles must match the ones returned by Keycloak. Polaris is configured with 3 realms: @@ -44,6 +45,9 @@ Polaris is configured with 3 realms: - `realm-mixed`: This realm is configured to use both the internal and external authentication. It accepts tokens issued by both Polaris and Keycloak. +For more information about how to configure Polaris with external authentication, see the +[Polaris documentation](https://polaris.apache.org/in-dev/unreleased/external-idp/). + ## Starting the Example 1. Build the Polaris server image if it's not already present locally: @@ -93,6 +97,21 @@ You can request a token from Polaris for realms `realm-internal` and `realm-mixe This token is valid only for the `realm-mixed` realm. +Polaris tokens are valid for 1 hour. + +Note: if you request a Polaris token for the `realm-external` realm, it will not work because Polaris won't issue tokens +for this realm: + +```shell +curl -v http://localhost:8181/api/catalog/v1/oauth/tokens \ + --user root:s3cr3t \ + -H 'Polaris-Realm: realm-external' \ + -d 'grant_type=client_credentials' \ + -d 'scope=PRINCIPAL_ROLE:ALL' +``` + +This will return a `501 Not Implemented` error because for this realm, the internal token endpoint has been deactivated. + ### From Keycloak You can request a token from Keycloak for the `realm-external` and `realm-mixed` realms: @@ -103,13 +122,13 @@ You can request a token from Keycloak for the `realm-external` and `realm-mixed` keycloak_token=$(curl -s http://keycloak:8080/realms/iceberg/protocol/openid-connect/token \ --resolve keycloak:8080:127.0.0.1 \ --user client1:s3cr3t \ - -d 'grant_type=client_credentials' \ - -d 'scope=catalog' | jq -r .access_token) + -d 'grant_type=client_credentials' | jq -r .access_token) ``` Note the `--resolve` option: it is used to send the request with the `Host` header set to `keycloak`. This is necessary -because Keycloak issues tokens with the `iss` claim matching the request's host header; without this, the token would not -be valid when used against Polaris because the `iss` claim would be `127.0.0.1`, but Polaris expects it to be `keycloak`. +because Keycloak issues tokens with the `iss` claim matching the request's `Host` header; without this, the token would +not be valid when used against Polaris because the `iss` claim would be `127.0.0.1`, but Polaris expects it to be +`keycloak`, since that's Keycloak's hostname within the Docker network. Tokens issued by Keycloak can be used to access Polaris with the `realm-external` or `realm-mixed` realms. Access tokens are valid for 1 hour. @@ -142,8 +161,23 @@ You can access Polaris using the tokens you obtained above. The following exampl -H 'Accept: application/json' ``` +Note: you cannot mix tokens from different realms. For example, you cannot use a token from the `realm-internal` realm to access +the `realm-mixed` realm: + +```shell +curl -v http://localhost:8181/api/management/v1/catalogs \ + -H "Authorization: Bearer $polaris_token_realm_internal" \ + -H 'Polaris-Realm: realm-mixed' \ + -H 'Accept: application/json' +``` + +This will return a `401 Unauthorized` error because the token is not valid for the `realm-mixed` realm. + ### Using the Keycloak Token +The same Keycloak token can be used to access both the `realm-external` and `realm-mixed` realms, as it is valid for +both (both realms share the same OIDC tenant configuration). + 1. Open a terminal and run the following command to list the principal roles in the `realm-external` realm: ```shell @@ -160,4 +194,15 @@ You can access Polaris using the tokens you obtained above. The following exampl -H "Authorization: Bearer $keycloak_token" \ -H 'Polaris-Realm: realm-mixed' \ -H 'Accept: application/json' - ``` \ No newline at end of file + ``` + +Note: you cannot use a Keycloak token to access the `realm-internal` realm: + +```shell +curl -v http://localhost:8181/api/management/v1/catalogs \ + -H "Authorization: Bearer $keycloak_token" \ + -H 'Polaris-Realm: realm-internal' \ + -H 'Accept: application/json' +``` + +This will return a `401 Unauthorized` error because the token is not valid for the `realm-internal` realm. \ No newline at end of file From 747a247fde14a11ec9dff84b3669099b68e4a6e4 Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Tue, 12 Aug 2025 21:32:24 +0200 Subject: [PATCH 4/4] review --- getting-started/keycloak/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/getting-started/keycloak/README.md b/getting-started/keycloak/README.md index a244453a65..b1553b8c6e 100644 --- a/getting-started/keycloak/README.md +++ b/getting-started/keycloak/README.md @@ -21,8 +21,8 @@ ## Overview -This example uses Keycloak as the identity provider for Polaris. The "iceberg" realm is automatically created and -configured from the `iceberg-realm.json` file. +This example uses Keycloak as an **external** identity provider for Polaris. The "iceberg" realm is automatically +created and configured from the `iceberg-realm.json` file. This Keycloak realm contains 1 client definition: `client1:s3cr3t`. It is configured to return tokens with the following fixed claims: