Skip to content

Commit 0e7be85

Browse files
committed
Move some logic to ServiceIdentityConfiguration
1 parent 46f9690 commit 0e7be85

File tree

8 files changed

+103
-77
lines changed

8 files changed

+103
-77
lines changed

polaris-core/src/main/java/org/apache/polaris/core/identity/registry/ServiceIdentityRegistry.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public interface ServiceIdentityRegistry {
4444
* @param serviceIdentityType The type of service identity (e.g., AWS_IAM).
4545
* @return A new {@link ServiceIdentityInfoDpo} representing the discovered service identity.
4646
*/
47-
ServiceIdentityInfoDpo discoverServiceIdentity(ServiceIdentityType serviceIdentityType);
47+
Optional<ServiceIdentityInfoDpo> discoverServiceIdentity(ServiceIdentityType serviceIdentityType);
4848

4949
/**
5050
* Resolves the given service identity by retrieving the actual credential or secret referenced by

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ polaris.realm-context.require-header=false
113113

114114
polaris.features."ENFORCE_PRINCIPAL_CREDENTIAL_ROTATION_REQUIRED_CHECKING"=false
115115
polaris.features."SUPPORTED_CATALOG_STORAGE_TYPES"=["S3","GCS","AZURE"]
116-
polaris.features."ENABLE_CATALOG_FEDERATION"=true
116+
# polaris.features."ENABLE_CATALOG_FEDERATION"=true
117117
polaris.features."SUPPORTED_CATALOG_CONNECTION_TYPES"=["ICEBERG_REST"]
118118

119119
# realm overrides
@@ -200,7 +200,7 @@ polaris.oidc.principal-roles-mapper.type=default
200200

201201
# Polaris Service Identity Config
202202
# Default identity (can be overridden in per realm)
203-
polaris.service-identity.aws-iam.iam-arn=arn:aws:iam::123456789012:user/polaris-iam-user
203+
# polaris.service-identity.aws-iam.iam-arn=arn:aws:iam::123456789012:user/polaris-iam-user
204204
# polaris.service-identity.aws-iam.access-key-id=accessKeyId
205205
# polaris.service-identity.aws-iam.secret-access-key=secretAccessKey
206206
# polaris.service-identity.aws-iam.session-token=sessionToken

runtime/service/src/main/java/org/apache/polaris/service/admin/PolarisAdminService.java

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -784,17 +784,31 @@ public PolarisEntity createCatalog(CreateCatalogRequest catalogRequest) {
784784
"Implicit authentication based catalog federation is not supported.");
785785
}
786786

787-
ServiceIdentityInfoDpo serviceIdentityInfo = null;
787+
// Discover service identity if needed for the authentication type.
788+
Optional<ServiceIdentityInfoDpo> serviceIdentityInfoDpoOptional = Optional.empty();
788789
if (connectionConfigInfo.getAuthenticationParameters().getAuthenticationType()
789790
== AuthenticationParameters.AuthenticationTypeEnum.SIGV4) {
790-
serviceIdentityInfo =
791+
serviceIdentityInfoDpoOptional =
791792
serviceIdentityRegistry.discoverServiceIdentity(ServiceIdentityType.AWS_IAM);
793+
if (serviceIdentityInfoDpoOptional.isEmpty()) {
794+
throw new IllegalStateException(
795+
String.format(
796+
"Cannot create Catalog %s. Failed to discover %s service identity for %s authentication",
797+
entity.getName(),
798+
ServiceIdentityType.AWS_IAM.name(),
799+
connectionConfigInfo
800+
.getAuthenticationParameters()
801+
.getAuthenticationType()
802+
.name()));
803+
}
792804
}
793805

794806
entity =
795807
new CatalogEntity.Builder(entity)
796808
.setConnectionConfigInfoDpoWithSecrets(
797-
connectionConfigInfo, processedSecretReferences, serviceIdentityInfo)
809+
connectionConfigInfo,
810+
processedSecretReferences,
811+
serviceIdentityInfoDpoOptional.orElse(null))
798812
.build();
799813
}
800814
}

runtime/service/src/main/java/org/apache/polaris/service/identity/AwsIamServiceIdentityConfiguration.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,14 @@
2121

2222
import jakarta.annotation.Nonnull;
2323
import java.util.Optional;
24+
import org.apache.polaris.core.identity.ServiceIdentityType;
2425
import org.apache.polaris.core.identity.resolved.ResolvedAwsIamServiceIdentity;
25-
import software.amazon.awssdk.auth.credentials.*;
26+
import org.apache.polaris.service.identity.registry.DefaultServiceIdentityRegistry;
27+
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
28+
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
29+
import software.amazon.awssdk.auth.credentials.AwsSessionCredentials;
30+
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
31+
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
2632

2733
/**
2834
* Configuration for an AWS IAM service identity used by Polaris to access AWS services.
@@ -61,11 +67,16 @@ public interface AwsIamServiceIdentityConfiguration extends ResolvableServiceIde
6167
* @return the resolved identity, or an empty optional if the ARN is missing
6268
*/
6369
@Override
64-
default Optional<ResolvedAwsIamServiceIdentity> resolve() {
70+
default Optional<ResolvedAwsIamServiceIdentity> resolve(@Nonnull String realmIdentifier) {
6571
if (iamArn() == null) {
6672
return Optional.empty();
6773
} else {
68-
return Optional.of(new ResolvedAwsIamServiceIdentity(iamArn(), awsCredentialsProvider()));
74+
return Optional.of(
75+
new ResolvedAwsIamServiceIdentity(
76+
DefaultServiceIdentityRegistry.buildIdentityInfoReference(
77+
realmIdentifier, ServiceIdentityType.AWS_IAM),
78+
iamArn(),
79+
awsCredentialsProvider()));
6980
}
7081
}
7182

runtime/service/src/main/java/org/apache/polaris/service/identity/ResolvableServiceIdentityConfiguration.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
package org.apache.polaris.service.identity;
2121

22+
import jakarta.annotation.Nonnull;
2223
import java.util.Optional;
2324
import org.apache.polaris.core.identity.resolved.ResolvedServiceIdentity;
2425

@@ -37,5 +38,7 @@ public interface ResolvableServiceIdentityConfiguration {
3738
* @return an optional resolved service identity, or empty if resolution fails or is not
3839
* configured
3940
*/
40-
Optional<? extends ResolvedServiceIdentity> resolve();
41+
default Optional<? extends ResolvedServiceIdentity> resolve(@Nonnull String realmIdentifier) {
42+
return Optional.empty();
43+
}
4144
}

runtime/service/src/main/java/org/apache/polaris/service/identity/ServiceIdentityConfiguration.java

Lines changed: 25 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,11 @@
2323
import io.smallrye.config.WithDefaults;
2424
import io.smallrye.config.WithParentName;
2525
import io.smallrye.config.WithUnnamedKey;
26+
import java.util.List;
2627
import java.util.Map;
28+
import java.util.Optional;
2729
import org.apache.polaris.core.context.RealmContext;
30+
import org.apache.polaris.core.identity.resolved.ResolvedServiceIdentity;
2831

2932
/**
3033
* Represents the service identity configuration for one or more realms.
@@ -54,48 +57,38 @@ public interface ServiceIdentityConfiguration {
5457
Map<String, RealmServiceIdentityConfiguration> realms();
5558

5659
/**
57-
* Returns the service identity configuration for the given {@link RealmContext}. Falls back to
58-
* the default if the realm is not explicitly configured.
59-
*
60-
* @param realmContext the realm context
61-
* @return the matching or default realm configuration
60+
* Resolves the actual realm configuration entry (identifier + config) to use for the given
61+
* context. Falls back to the default if the specified realm is not configured.
6262
*/
63-
default RealmServiceIdentityConfiguration forRealm(RealmContext realmContext) {
63+
default RealmConfigEntry forRealm(RealmContext realmContext) {
6464
return forRealm(realmContext.getRealmIdentifier());
6565
}
6666

6767
/**
68-
* Returns the service identity configuration for the given realm identifier. Falls back to the
69-
* default if the realm is not explicitly configured.
70-
*
71-
* @param realmIdentifier the identifier of the realm
72-
* @return the matching or default realm configuration
68+
* Resolves the actual realm configuration entry (identifier + config) for the given realm
69+
* identifier. Falls back to the default if the specified realm is not configured.
7370
*/
74-
default RealmServiceIdentityConfiguration forRealm(String realmIdentifier) {
75-
return realms().containsKey(realmIdentifier)
76-
? realms().get(realmIdentifier)
77-
: realms().get(DEFAULT_REALM_KEY);
71+
default RealmConfigEntry forRealm(String realmIdentifier) {
72+
String resolvedRealmIdentifier =
73+
realms().containsKey(realmIdentifier) ? realmIdentifier : DEFAULT_REALM_KEY;
74+
return new RealmConfigEntry(resolvedRealmIdentifier, realms().get(resolvedRealmIdentifier));
7875
}
7976

8077
/**
81-
* Returns the actual key of the service identity configuration to use for the given {@link
82-
* RealmContext}, falling back to the default if the specified realm is not configured.
83-
*
84-
* @param realmContext the realm context
85-
* @return the actual realm identifier to use
78+
* Resolves and returns the list of {@link ResolvedServiceIdentity} objects for the given realm.
8679
*/
87-
default String resolveRealm(RealmContext realmContext) {
88-
return resolveRealm(realmContext.getRealmIdentifier());
89-
}
80+
default List<? extends ResolvedServiceIdentity> resolveServiceIdentities(
81+
RealmContext realmContext) {
82+
RealmConfigEntry entry = forRealm(realmContext);
9083

91-
/**
92-
* Returns the actual key of the service identity configuration to use for the given realm
93-
* identifier, falling back to the default if the specified realm is not configured.
94-
*
95-
* @param realmIdentifier the identifier of the realm
96-
* @return the actual realm identifier to use
97-
*/
98-
default String resolveRealm(String realmIdentifier) {
99-
return realms().containsKey(realmIdentifier) ? realmIdentifier : DEFAULT_REALM_KEY;
84+
return entry.config().serviceIdentityConfigurations().stream()
85+
.map(
86+
resolvableServiceIdentityConfiguration ->
87+
resolvableServiceIdentityConfiguration.resolve(entry.realm()))
88+
.flatMap(Optional::stream)
89+
.toList();
10090
}
91+
92+
/** A pairing of a resolved realm identifier and its associated configuration. */
93+
record RealmConfigEntry(String realm, RealmServiceIdentityConfiguration config) {}
10194
}

runtime/service/src/main/java/org/apache/polaris/service/identity/registry/DefaultServiceIdentityRegistry.java

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,6 @@
3131
import org.apache.polaris.core.identity.registry.ServiceIdentityRegistry;
3232
import org.apache.polaris.core.identity.resolved.ResolvedServiceIdentity;
3333
import org.apache.polaris.core.secrets.ServiceSecretReference;
34-
import org.apache.polaris.service.identity.RealmServiceIdentityConfiguration;
35-
import org.apache.polaris.service.identity.ResolvableServiceIdentityConfiguration;
3634
import org.apache.polaris.service.identity.ServiceIdentityConfiguration;
3735

3836
/**
@@ -80,20 +78,8 @@ public DefaultServiceIdentityRegistry(
8078
@Inject
8179
public DefaultServiceIdentityRegistry(
8280
RealmContext realmContext, ServiceIdentityConfiguration serviceIdentityConfiguration) {
83-
String serviceIdentityConfigKey = serviceIdentityConfiguration.resolveRealm(realmContext);
84-
RealmServiceIdentityConfiguration realmServiceIdentityConfiguration =
85-
serviceIdentityConfiguration.forRealm(realmContext);
86-
8781
this.resolvedServiceIdentities =
88-
realmServiceIdentityConfiguration.serviceIdentityConfigurations().stream()
89-
.map(ResolvableServiceIdentityConfiguration::resolve)
90-
.flatMap(Optional::stream)
91-
.peek(
92-
// Set the identity info reference for each resolved identity
93-
identity ->
94-
identity.setIdentityInfoReference(
95-
buildIdentityInfoReference(
96-
serviceIdentityConfigKey, identity.getIdentityType())))
82+
serviceIdentityConfiguration.resolveServiceIdentities(realmContext).stream()
9783
.collect(
9884
// Collect to an EnumMap, grouping by ServiceIdentityType
9985
Collectors.toMap(
@@ -111,14 +97,14 @@ public DefaultServiceIdentityRegistry(
11197
}
11298

11399
@Override
114-
public ServiceIdentityInfoDpo discoverServiceIdentity(ServiceIdentityType serviceIdentityType) {
100+
public Optional<ServiceIdentityInfoDpo> discoverServiceIdentity(
101+
ServiceIdentityType serviceIdentityType) {
115102
ResolvedServiceIdentity resolvedServiceIdentity =
116103
resolvedServiceIdentities.get(serviceIdentityType);
117104
if (resolvedServiceIdentity == null) {
118-
throw new IllegalArgumentException(
119-
"Service identity type not supported: " + serviceIdentityType);
105+
return Optional.empty();
120106
}
121-
return resolvedServiceIdentity.asServiceIdentityInfoDpo();
107+
return Optional.of(resolvedServiceIdentity.asServiceIdentityInfoDpo());
122108
}
123109

124110
@Override
@@ -147,7 +133,7 @@ public EnumMap<ServiceIdentityType, ResolvedServiceIdentity> getResolvedServiceI
147133
* @param type the service identity type
148134
* @return the constructed service secret reference
149135
*/
150-
private ServiceSecretReference buildIdentityInfoReference(
136+
public static ServiceSecretReference buildIdentityInfoReference(
151137
String realm, ServiceIdentityType type) {
152138
// urn:polaris-service-secret:default-identity-registry:<realm>:<type>
153139
return new ServiceSecretReference(

runtime/service/src/test/java/org/apache/polaris/service/identity/registry/DefaultServiceIdentityRegistryTest.java

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@
3737
import org.assertj.core.api.Assertions;
3838
import org.junit.jupiter.api.Test;
3939
import org.mockito.Mockito;
40+
import software.amazon.awssdk.auth.credentials.AwsSessionCredentials;
41+
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
42+
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
4043

4144
@QuarkusTest
4245
@TestProfile(DefaultServiceIdentityRegistryTest.Profile.class)
@@ -77,8 +80,10 @@ void testServiceIdentityConfiguration() {
7780
.isEqualTo(2);
7881

7982
// Check the default realm configuration
80-
RealmServiceIdentityConfiguration defaultConfig =
83+
ServiceIdentityConfiguration.RealmConfigEntry defaultConfigEntry =
8184
serviceIdentityConfiguration.forRealm(DEFAULT_REALM_KEY);
85+
Assertions.assertThat(defaultConfigEntry.realm()).isEqualTo(DEFAULT_REALM_KEY);
86+
RealmServiceIdentityConfiguration defaultConfig = defaultConfigEntry.config();
8287
Assertions.assertThat(defaultConfig.awsIamServiceIdentity().isPresent()).isTrue();
8388
Assertions.assertThat(defaultConfig.awsIamServiceIdentity().get().iamArn())
8489
.isEqualTo("arn:aws:iam::123456789012:user/polaris-default-iam-user");
@@ -87,8 +92,10 @@ void testServiceIdentityConfiguration() {
8792
Assertions.assertThat(defaultConfig.awsIamServiceIdentity().get().sessionToken()).isEmpty();
8893

8994
// Check the my-realm configuration
90-
RealmServiceIdentityConfiguration myRealmConfig =
95+
ServiceIdentityConfiguration.RealmConfigEntry myRealmConfigEntry =
9196
serviceIdentityConfiguration.forRealm(MY_REALM_KEY);
97+
Assertions.assertThat(myRealmConfigEntry.realm()).isEqualTo(MY_REALM_KEY);
98+
RealmServiceIdentityConfiguration myRealmConfig = myRealmConfigEntry.config();
9299
Assertions.assertThat(myRealmConfig.awsIamServiceIdentity().isPresent()).isTrue();
93100
Assertions.assertThat(myRealmConfig.awsIamServiceIdentity().get().iamArn())
94101
.isEqualTo("arn:aws:iam::123456789012:user/polaris-iam-user");
@@ -100,8 +107,10 @@ void testServiceIdentityConfiguration() {
100107
.isEqualTo(Optional.of("session-token"));
101108

102109
// Check the unexisting realm configuration
103-
RealmServiceIdentityConfiguration otherConfig =
110+
ServiceIdentityConfiguration.RealmConfigEntry otherConfigEntry =
104111
serviceIdentityConfiguration.forRealm("other-realm");
112+
Assertions.assertThat(otherConfigEntry.realm()).isEqualTo(DEFAULT_REALM_KEY);
113+
RealmServiceIdentityConfiguration otherConfig = otherConfigEntry.config();
105114
Assertions.assertThat(otherConfig.awsIamServiceIdentity().isPresent()).isTrue();
106115
Assertions.assertThat(otherConfig.awsIamServiceIdentity().get().iamArn())
107116
.isEqualTo("arn:aws:iam::123456789012:user/polaris-default-iam-user");
@@ -131,9 +140,10 @@ void testRealmServiceIdentityConfigToResolvedServiceIdentity() {
131140
.isEqualTo(
132141
new ServiceSecretReference(
133142
"urn:polaris-secret:default-identity-registry:system:default:AWS_IAM", Map.of()));
134-
Assertions.assertThat(resolvedAwsIamServiceIdentity.getAccessKeyId()).isNull();
135-
Assertions.assertThat(resolvedAwsIamServiceIdentity.getSecretAccessKey()).isNull();
136-
Assertions.assertThat(resolvedAwsIamServiceIdentity.getSessionToken()).isNull();
143+
Assertions.assertThat(
144+
resolvedAwsIamServiceIdentity.getAwsCredentialsProvider()
145+
instanceof DefaultCredentialsProvider)
146+
.isTrue();
137147

138148
// Check the my-realm
139149
Mockito.when(realmContext.getRealmIdentifier()).thenReturn(MY_REALM_KEY);
@@ -153,12 +163,20 @@ void testRealmServiceIdentityConfigToResolvedServiceIdentity() {
153163
.isEqualTo(
154164
new ServiceSecretReference(
155165
"urn:polaris-secret:default-identity-registry:my-realm:AWS_IAM", Map.of()));
156-
Assertions.assertThat(resolvedAwsIamServiceIdentity.getAccessKeyId())
157-
.isEqualTo("access-key-id");
158-
Assertions.assertThat(resolvedAwsIamServiceIdentity.getSecretAccessKey())
159-
.isEqualTo("secret-access-key");
160-
Assertions.assertThat(resolvedAwsIamServiceIdentity.getSessionToken())
161-
.isEqualTo("session-token");
166+
Assertions.assertThat(
167+
resolvedAwsIamServiceIdentity.getAwsCredentialsProvider()
168+
instanceof StaticCredentialsProvider)
169+
.isTrue();
170+
StaticCredentialsProvider staticCredentialsProvider =
171+
(StaticCredentialsProvider) resolvedAwsIamServiceIdentity.getAwsCredentialsProvider();
172+
Assertions.assertThat(
173+
staticCredentialsProvider.resolveCredentials() instanceof AwsSessionCredentials)
174+
.isTrue();
175+
AwsSessionCredentials awsSessionCredentials =
176+
(AwsSessionCredentials) staticCredentialsProvider.resolveCredentials();
177+
Assertions.assertThat(awsSessionCredentials.accessKeyId()).isEqualTo("access-key-id");
178+
Assertions.assertThat(awsSessionCredentials.secretAccessKey()).isEqualTo("secret-access-key");
179+
Assertions.assertThat(awsSessionCredentials.sessionToken()).isEqualTo("session-token");
162180

163181
// Check the other realm which does not exist in the configuration, should fallback to default
164182
Mockito.when(realmContext.getRealmIdentifier()).thenReturn("other-realm");
@@ -177,8 +195,9 @@ void testRealmServiceIdentityConfigToResolvedServiceIdentity() {
177195
.isEqualTo(
178196
new ServiceSecretReference(
179197
"urn:polaris-secret:default-identity-registry:system:default:AWS_IAM", Map.of()));
180-
Assertions.assertThat(resolvedAwsIamServiceIdentity.getAccessKeyId()).isNull();
181-
Assertions.assertThat(resolvedAwsIamServiceIdentity.getSecretAccessKey()).isNull();
182-
Assertions.assertThat(resolvedAwsIamServiceIdentity.getSessionToken()).isNull();
198+
Assertions.assertThat(
199+
resolvedAwsIamServiceIdentity.getAwsCredentialsProvider()
200+
instanceof DefaultCredentialsProvider)
201+
.isTrue();
183202
}
184203
}

0 commit comments

Comments
 (0)