Skip to content

Commit 6b76c39

Browse files
committed
Support for external identity providers
1 parent 11999f9 commit 6b76c39

File tree

58 files changed

+2991
-434
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+2991
-434
lines changed

polaris-core/src/main/java/org/apache/polaris/core/auth/AuthenticatedPolarisPrincipal.java

+9-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public class AuthenticatedPolarisPrincipal implements java.security.Principal {
3535
public AuthenticatedPolarisPrincipal(
3636
@Nonnull PolarisEntity principalEntity, @Nonnull Set<String> activatedPrincipalRoles) {
3737
this.principalEntity = principalEntity;
38-
this.activatedPrincipalRoleNames = activatedPrincipalRoles;
38+
this.activatedPrincipalRoleNames = Set.copyOf(activatedPrincipalRoles);
3939
this.activatedPrincipalRoles = null;
4040
}
4141

@@ -48,6 +48,13 @@ public PolarisEntity getPrincipalEntity() {
4848
return principalEntity;
4949
}
5050

51+
/**
52+
* Returns the set of activated principal role names. Activated role names are the roles that were
53+
* explicitly requested by the client when authenticating, through JWT claims or other means.
54+
*
55+
* <p>By convention, this method returns an empty set when the principal is requesting all
56+
* available principal roles.
57+
*/
5158
public Set<String> getActivatedPrincipalRoleNames() {
5259
return activatedPrincipalRoleNames;
5360
}
@@ -56,6 +63,7 @@ public List<PrincipalRoleEntity> getActivatedPrincipalRoles() {
5663
return activatedPrincipalRoles;
5764
}
5865

66+
/** FIXME this method makes this class mutable, which seems risky */
5967
public void setActivatedPrincipalRoles(List<PrincipalRoleEntity> activatedPrincipalRoles) {
6068
this.activatedPrincipalRoles = activatedPrincipalRoles;
6169
}

polaris-core/src/main/java/org/apache/polaris/core/config/ProductionReadinessCheck.java

+4
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ static ProductionReadinessCheck of(Error... errors) {
3232
return ImmutableProductionReadinessCheck.builder().addErrors(errors).build();
3333
}
3434

35+
static ProductionReadinessCheck of(Iterable<? extends Error> errors) {
36+
return ImmutableProductionReadinessCheck.builder().addAllErrors(errors).build();
37+
}
38+
3539
default boolean ready() {
3640
return getErrors().isEmpty();
3741
}

quarkus/defaults/build.gradle.kts

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ dependencies {
3535
compileOnly("io.quarkus:quarkus-smallrye-health")
3636
compileOnly("io.quarkus:quarkus-micrometer")
3737
compileOnly("io.quarkus:quarkus-micrometer-registry-prometheus")
38+
compileOnly("io.quarkus:quarkus-oidc")
3839
compileOnly("io.quarkus:quarkus-opentelemetry")
3940
compileOnly("io.quarkus:quarkus-smallrye-context-propagation")
4041
}

quarkus/defaults/src/main/resources/application.properties

+41
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ quarkus.http.compress-media-types=application/json,text/html,text/plain
3030
quarkus.management.enabled=true
3131
quarkus.micrometer.enabled=true
3232
quarkus.micrometer.export.prometheus.enabled=true
33+
quarkus.oidc.enabled=true
3334
quarkus.otel.enabled=true
3435

3536
# ---- Runtime Configuration ----
@@ -74,6 +75,18 @@ quarkus.log.category."io.smallrye.config".level=INFO
7475
quarkus.management.port=8182
7576
quarkus.management.test-port=0
7677

78+
# OIDC settings. These settings are required only when using external authentication providers.
79+
# See https://quarkus.io/guides/security-oidc-configuration-properties-reference
80+
# Default tenant (disabled by default, set this to true if you use external authentication)
81+
quarkus.oidc.tenant-enabled=false
82+
# quarkus.oidc.auth-server-url=https://auth.example.com/realms/polaris
83+
# quarkus.oidc.client-id=polaris
84+
# Roles mapping; see https://quarkus.io/guides/security-oidc-bearer-token-authentication#token-claims-and-security-identity-roles
85+
# quarkus.oidc.roles.role-claim-path=realm/groups
86+
# Named tenants:
87+
# quarkus.oidc.idp1.auth-server-url=https://auth.example.com/realms/polaris2
88+
# quarkus.oidc.idp1.client-id=polaris2
89+
7790
# quarkus.otel.sdk.disabled is set to `true` by default to avoid spuriour errors about
7891
# trace collector connections being impossible to establish. This setting can be enabled
7992
# at runtime after configuring other OTel properties for proper trace data collection.
@@ -126,7 +139,14 @@ polaris.rate-limiter.token-bucket.window=PT10S
126139

127140
polaris.active-roles-provider.type=default
128141

142+
# Polaris authentication settings
143+
polaris.authentication.type=internal
129144
polaris.authentication.authenticator.type=default
145+
# Per-realm overrides:
146+
# polaris.authentication.realm1.type=external
147+
# polaris.authentication.realm1.authenticator.type=custom
148+
149+
# Options effective when using internal auth (can be overridden in per realm):
130150
polaris.authentication.token-service.type=default
131151
polaris.authentication.token-broker.type=rsa-key-pair
132152
polaris.authentication.token-broker.max-token-generation=PT1H
@@ -135,6 +155,27 @@ polaris.authentication.token-broker.max-token-generation=PT1H
135155
# polaris.authentication.token-broker.symmetric-key.secret=secret
136156
# polaris.authentication.token-broker.symmetric-key.file=/tmp/symmetric.key
137157

158+
# OIDC Principals mapping
159+
polaris.oidc.principal-mapper.type=default
160+
# polaris.oidc.principal-mapper.id-claim-path=sub
161+
# polaris.oidc.principal-mapper.name-claim-path=preferred_username
162+
# Per-tenant overrides:
163+
# polaris.oidc.idp1.principal-mapper.id-claim-path=polaris/principal_id
164+
# polaris.oidc.idp1.principal-mapper.name-claim-path=polaris/principal_name
165+
166+
# OIDC Principal roles mapping
167+
polaris.oidc.principal-roles-mapper.type=default
168+
# Principal role mapping is done through quarkus.oidc.roles.role-claim-path
169+
# The properties below define how the roles mapped by Quarkus are converted to Polaris roles
170+
# polaris.oidc.principal-roles-mapper.filter=PRINCIPAL_ROLE:.*
171+
# polaris.oidc.principal-roles-mapper.mappings[0].regex=PRINCIPAL_ROLE:(.*)
172+
# polaris.oidc.principal-roles-mapper.mappings[0].replacement=PRINCIPAL_ROLE:$1
173+
# Per-tenant overrides:
174+
# polaris.oidc.idp1.principal-roles-mapper.type=custom
175+
# polaris.oidc.idp1.principal-roles-mapper.filter=POLARIS_ROLE:.*
176+
# polaris.oidc.idp1.principal-roles-mapper.mappings[0].regex=POLARIS_ROLE:(.*)
177+
# polaris.oidc.idp1.principal-roles-mapper.mappings[0].replacement=POLARIS_ROLE:$1
178+
138179
# If the following properties are unset, the default credential provider chain will be used
139180
# polaris.storage.aws.access-key=accessKey
140181
# polaris.storage.aws.secret-key=secretKey

quarkus/service/build.gradle.kts

+2
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ dependencies {
5555
implementation("io.quarkus:quarkus-opentelemetry")
5656
implementation("io.quarkus:quarkus-security")
5757
implementation("io.quarkus:quarkus-smallrye-context-propagation")
58+
implementation("io.quarkus:quarkus-security")
59+
implementation("io.quarkus:quarkus-oidc")
5860

5961
implementation(libs.jakarta.enterprise.cdi.api)
6062
implementation(libs.jakarta.inject.api)

quarkus/service/src/main/java/org/apache/polaris/service/quarkus/auth/ActiveRolesAugmentor.java

+16-7
Original file line numberDiff line numberDiff line change
@@ -32,33 +32,42 @@
3232

3333
/**
3434
* A custom {@link SecurityIdentityAugmentor} that adds active roles to the {@link
35-
* SecurityIdentity}. This is used to augment the identity with active roles after authentication.
35+
* SecurityIdentity}. This is used to augment the identity with valid active roles after
36+
* authentication.
3637
*/
3738
@ApplicationScoped
3839
public class ActiveRolesAugmentor implements SecurityIdentityAugmentor {
3940

41+
// must run after AuthenticatingAugmentor
42+
public static final int PRIORITY = AuthenticatingAugmentor.PRIORITY - 1;
43+
4044
@Inject ActiveRolesProvider activeRolesProvider;
4145

46+
@Override
47+
public int priority() {
48+
return PRIORITY;
49+
}
50+
4251
@Override
4352
public Uni<SecurityIdentity> augment(
4453
SecurityIdentity identity, AuthenticationRequestContext context) {
4554
if (identity.isAnonymous()) {
4655
return Uni.createFrom().item(identity);
4756
}
48-
return context.runBlocking(() -> augmentWithActiveRoles(identity));
57+
return context.runBlocking(() -> validateActiveRoles(identity));
4958
}
5059

51-
private SecurityIdentity augmentWithActiveRoles(SecurityIdentity identity) {
52-
AuthenticatedPolarisPrincipal polarisPrincipal =
53-
identity.getPrincipal(AuthenticatedPolarisPrincipal.class);
54-
if (polarisPrincipal == null) {
60+
private SecurityIdentity validateActiveRoles(SecurityIdentity identity) {
61+
if (!(identity.getPrincipal() instanceof AuthenticatedPolarisPrincipal)) {
5562
throw new AuthenticationFailedException("No Polaris principal found");
5663
}
64+
AuthenticatedPolarisPrincipal polarisPrincipal =
65+
identity.getPrincipal(AuthenticatedPolarisPrincipal.class);
5766
Set<String> validRoleNames = activeRolesProvider.getActiveRoles(polarisPrincipal);
5867
return QuarkusSecurityIdentity.builder()
5968
.setAnonymous(false)
6069
.setPrincipal(polarisPrincipal)
61-
.addRoles(validRoleNames)
70+
.addRoles(validRoleNames) // replace the current roles with valid ones
6271
.addCredentials(identity.getCredentials())
6372
.addAttributes(identity.getAttributes())
6473
.addPermissionChecker(identity::checkPermission)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.polaris.service.quarkus.auth;
20+
21+
import io.quarkus.security.AuthenticationFailedException;
22+
import io.quarkus.security.identity.AuthenticationRequestContext;
23+
import io.quarkus.security.identity.SecurityIdentity;
24+
import io.quarkus.security.identity.SecurityIdentityAugmentor;
25+
import io.quarkus.security.runtime.QuarkusSecurityIdentity;
26+
import io.smallrye.mutiny.Uni;
27+
import jakarta.enterprise.context.ApplicationScoped;
28+
import jakarta.inject.Inject;
29+
import org.apache.iceberg.exceptions.NotAuthorizedException;
30+
import org.apache.polaris.core.auth.AuthenticatedPolarisPrincipal;
31+
import org.apache.polaris.service.auth.Authenticator;
32+
import org.apache.polaris.service.auth.PrincipalCredential;
33+
34+
/**
35+
* A custom {@link SecurityIdentityAugmentor} that, after Quarkus OIDC or Internal Auth extracted
36+
* and validated the principal credentials, augments the {@link SecurityIdentity} by authenticating
37+
* the principal and setting an {@link AuthenticatedPolarisPrincipal} as the identity's principal.
38+
*/
39+
@ApplicationScoped
40+
public class AuthenticatingAugmentor implements SecurityIdentityAugmentor {
41+
42+
public static final int PRIORITY = 100;
43+
44+
@Inject Authenticator<PrincipalCredential, AuthenticatedPolarisPrincipal> authenticator;
45+
46+
@Override
47+
public int priority() {
48+
return PRIORITY;
49+
}
50+
51+
@Override
52+
public Uni<SecurityIdentity> augment(
53+
SecurityIdentity identity, AuthenticationRequestContext context) {
54+
if (identity.isAnonymous()) {
55+
return Uni.createFrom().item(identity);
56+
}
57+
PrincipalCredential credential = extractTokenCredential(identity);
58+
return context.runBlocking(() -> authenticatePolarisPrincipal(identity, credential));
59+
}
60+
61+
private PrincipalCredential extractTokenCredential(SecurityIdentity identity) {
62+
QuarkusPrincipalCredential credential =
63+
identity.getCredential(QuarkusPrincipalCredential.class);
64+
if (credential == null) {
65+
throw new AuthenticationFailedException("No token credential available");
66+
}
67+
return credential;
68+
}
69+
70+
private SecurityIdentity authenticatePolarisPrincipal(
71+
SecurityIdentity identity, PrincipalCredential credential) {
72+
try {
73+
AuthenticatedPolarisPrincipal polarisPrincipal =
74+
authenticator
75+
.authenticate(credential)
76+
.orElseThrow(() -> new NotAuthorizedException("Authentication failed"));
77+
return QuarkusSecurityIdentity.builder(identity).setPrincipal(polarisPrincipal).build();
78+
} catch (RuntimeException e) {
79+
throw new AuthenticationFailedException(e);
80+
}
81+
}
82+
}

quarkus/service/src/main/java/org/apache/polaris/service/quarkus/auth/PolarisIdentityProvider.java

-80
This file was deleted.

quarkus/service/src/main/java/org/apache/polaris/service/quarkus/auth/QuarkusAuthenticationConfiguration.java

+12-31
Original file line numberDiff line numberDiff line change
@@ -19,44 +19,25 @@
1919
package org.apache.polaris.service.quarkus.auth;
2020

2121
import io.smallrye.config.ConfigMapping;
22+
import io.smallrye.config.WithDefaults;
23+
import io.smallrye.config.WithParentName;
24+
import io.smallrye.config.WithUnnamedKey;
25+
import java.util.Map;
26+
import org.apache.polaris.core.context.RealmContext;
2227
import org.apache.polaris.service.auth.AuthenticationConfiguration;
2328

2429
@ConfigMapping(prefix = "polaris.authentication")
2530
public interface QuarkusAuthenticationConfiguration extends AuthenticationConfiguration {
2631

32+
@WithParentName
33+
@WithUnnamedKey(DEFAULT_REALM_KEY)
34+
@WithDefaults
2735
@Override
28-
QuarkusAuthenticatorConfiguration authenticator();
36+
Map<String, QuarkusAuthenticationRealmConfiguration> realms();
2937

3038
@Override
31-
QuarkusTokenServiceConfiguration tokenService();
32-
33-
@Override
34-
QuarkusTokenBrokerConfiguration tokenBroker();
35-
36-
interface QuarkusAuthenticatorConfiguration extends AuthenticatorConfiguration {
37-
38-
/**
39-
* The type of the authenticator. Must be a registered {@link
40-
* org.apache.polaris.service.auth.Authenticator} identifier.
41-
*/
42-
String type();
43-
}
44-
45-
interface QuarkusTokenServiceConfiguration extends TokenServiceConfiguration {
46-
47-
/**
48-
* The type of the OAuth2 service. Must be a registered {@link
49-
* org.apache.polaris.service.catalog.api.IcebergRestOAuth2ApiService} identifier.
50-
*/
51-
String type();
52-
}
53-
54-
interface QuarkusTokenBrokerConfiguration extends TokenBrokerConfiguration {
55-
56-
/**
57-
* The type of the token broker factory. Must be a registered {@link
58-
* org.apache.polaris.service.auth.TokenBrokerFactory} identifier.
59-
*/
60-
String type();
39+
default QuarkusAuthenticationRealmConfiguration forRealm(RealmContext realmContext) {
40+
return (QuarkusAuthenticationRealmConfiguration)
41+
AuthenticationConfiguration.super.forRealm(realmContext);
6142
}
6243
}

0 commit comments

Comments
 (0)