From 65cde79ff08d3dcaa4e8bda52b324a0a38bbbe85 Mon Sep 17 00:00:00 2001 From: Rulin Xing Date: Thu, 3 Apr 2025 06:12:53 +0000 Subject: [PATCH 1/3] Move PolarisAuthenticationParameters to a top-level property according to the latest spec --- .../connection/ConnectionConfigInfoDpo.java | 47 +++++++++++++++++-- .../IcebergRestConnectionConfigInfoDpo.java | 20 +++----- .../OAuthClientCredentialsParametersDpo.java | 1 + .../polaris/core/entity/CatalogEntity.java | 26 ++-------- 4 files changed, 55 insertions(+), 39 deletions(-) diff --git a/polaris-core/src/main/java/org/apache/polaris/core/connection/ConnectionConfigInfoDpo.java b/polaris-core/src/main/java/org/apache/polaris/core/connection/ConnectionConfigInfoDpo.java index 69a0dfedbc..d4bd48942d 100644 --- a/polaris-core/src/main/java/org/apache/polaris/core/connection/ConnectionConfigInfoDpo.java +++ b/polaris-core/src/main/java/org/apache/polaris/core/connection/ConnectionConfigInfoDpo.java @@ -29,8 +29,11 @@ import java.net.MalformedURLException; import java.net.URI; import java.net.URL; +import java.util.Map; import org.apache.polaris.core.PolarisDiagnostics; import org.apache.polaris.core.admin.model.ConnectionConfigInfo; +import org.apache.polaris.core.admin.model.IcebergRestConnectionConfigInfo; +import org.apache.polaris.core.secrets.UserSecretReference; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -47,17 +50,26 @@ public abstract class ConnectionConfigInfoDpo implements IcebergCatalogPropertie // The URI of the remote catalog private final String uri; + // The authentication parameters for the connection + private final AuthenticationParametersDpo authenticationParameters; + public ConnectionConfigInfoDpo( @JsonProperty(value = "connectionType", required = true) @Nonnull ConnectionType connectionType, - @JsonProperty(value = "uri", required = true) @Nonnull String uri) { - this(connectionType, uri, true); + @JsonProperty(value = "uri", required = true) @Nonnull String uri, + @JsonProperty(value = "authenticationParameters", required = true) @Nonnull + AuthenticationParametersDpo authenticationParameters) { + this(connectionType, uri, authenticationParameters, true); } protected ConnectionConfigInfoDpo( - ConnectionType connectionType, String uri, boolean validateUri) { + @Nonnull ConnectionType connectionType, + @Nonnull String uri, + @Nonnull AuthenticationParametersDpo authenticationParameters, + boolean validateUri) { this.connectionType = connectionType; this.uri = uri; + this.authenticationParameters = authenticationParameters; if (validateUri) { validateUri(uri); } @@ -71,6 +83,10 @@ public String getUri() { return uri; } + public AuthenticationParametersDpo getAuthenticationParameters() { + return authenticationParameters; + } + private static final ObjectMapper DEFAULT_MAPPER; static { @@ -108,5 +124,30 @@ protected void validateUri(String uri) { } } + public static ConnectionConfigInfoDpo fromConnectionConfigInfoModelWithSecrets( + ConnectionConfigInfo connectionConfigurationModel, + Map secretReferences) { + ConnectionConfigInfoDpo config = null; + switch (connectionConfigurationModel.getConnectionType()) { + case ICEBERG_REST: + IcebergRestConnectionConfigInfo icebergRestConfigModel = + (IcebergRestConnectionConfigInfo) connectionConfigurationModel; + AuthenticationParametersDpo authenticationParameters = + AuthenticationParametersDpo.fromAuthenticationParametersModelWithSecrets( + icebergRestConfigModel.getAuthenticationParameters(), secretReferences); + config = + new IcebergRestConnectionConfigInfoDpo( + ConnectionType.ICEBERG_REST, + icebergRestConfigModel.getUri(), + authenticationParameters, + icebergRestConfigModel.getRemoteCatalogName()); + break; + default: + throw new IllegalStateException( + "Unsupported connection type: " + connectionConfigurationModel.getConnectionType()); + } + return config; + } + public abstract ConnectionConfigInfo asConnectionConfigInfoModel(); } diff --git a/polaris-core/src/main/java/org/apache/polaris/core/connection/IcebergRestConnectionConfigInfoDpo.java b/polaris-core/src/main/java/org/apache/polaris/core/connection/IcebergRestConnectionConfigInfoDpo.java index 690ac6d1d1..b138370726 100644 --- a/polaris-core/src/main/java/org/apache/polaris/core/connection/IcebergRestConnectionConfigInfoDpo.java +++ b/polaris-core/src/main/java/org/apache/polaris/core/connection/IcebergRestConnectionConfigInfoDpo.java @@ -34,29 +34,22 @@ public class IcebergRestConnectionConfigInfoDpo extends ConnectionConfigInfoDpo private final String remoteCatalogName; - private final AuthenticationParametersDpo authenticationParameters; - public IcebergRestConnectionConfigInfoDpo( @JsonProperty(value = "connectionType", required = true) @Nonnull ConnectionType connectionType, @JsonProperty(value = "uri", required = true) @Nonnull String uri, + @JsonProperty(value = "authenticationParameters", required = true) @Nonnull + AuthenticationParametersDpo authenticationParameters, @JsonProperty(value = "remoteCatalogName", required = false) @Nullable - String remoteCatalogName, - @JsonProperty(value = "authenticationParameters", required = false) @Nonnull - AuthenticationParametersDpo authenticationParameters) { - super(connectionType, uri); + String remoteCatalogName) { + super(connectionType, uri, authenticationParameters); this.remoteCatalogName = remoteCatalogName; - this.authenticationParameters = authenticationParameters; } public String getRemoteCatalogName() { return remoteCatalogName; } - public AuthenticationParametersDpo getAuthenticationParameters() { - return authenticationParameters; - } - @Override public @Nonnull Map asIcebergCatalogProperties( UserSecretsManager secretsManager) { @@ -65,7 +58,7 @@ public AuthenticationParametersDpo getAuthenticationParameters() { if (getRemoteCatalogName() != null) { properties.put(CatalogProperties.WAREHOUSE_LOCATION, getRemoteCatalogName()); } - properties.putAll(authenticationParameters.asIcebergCatalogProperties(secretsManager)); + properties.putAll(getAuthenticationParameters().asIcebergCatalogProperties(secretsManager)); return properties; } @@ -75,7 +68,8 @@ public ConnectionConfigInfo asConnectionConfigInfoModel() { .setConnectionType(ConnectionConfigInfo.ConnectionTypeEnum.ICEBERG_REST) .setUri(getUri()) .setRemoteCatalogName(getRemoteCatalogName()) - .setAuthenticationParameters(authenticationParameters.asAuthenticationParametersModel()) + .setAuthenticationParameters( + getAuthenticationParameters().asAuthenticationParametersModel()) .build(); } diff --git a/polaris-core/src/main/java/org/apache/polaris/core/connection/OAuthClientCredentialsParametersDpo.java b/polaris-core/src/main/java/org/apache/polaris/core/connection/OAuthClientCredentialsParametersDpo.java index f0e9f1ddd5..e80a8467d3 100644 --- a/polaris-core/src/main/java/org/apache/polaris/core/connection/OAuthClientCredentialsParametersDpo.java +++ b/polaris-core/src/main/java/org/apache/polaris/core/connection/OAuthClientCredentialsParametersDpo.java @@ -94,6 +94,7 @@ public OAuthClientCredentialsParametersDpo( Objects.requireNonNullElse(scopes, List.of(OAuth2Properties.CATALOG_SCOPE))); } + @JsonIgnore private @Nonnull String getCredentialAsConcatenatedString(UserSecretsManager secretsManager) { String clientSecret = secretsManager.readSecret(getClientSecretReference()); return COLON_JOINER.join(clientId, clientSecret); diff --git a/polaris-core/src/main/java/org/apache/polaris/core/entity/CatalogEntity.java b/polaris-core/src/main/java/org/apache/polaris/core/entity/CatalogEntity.java index a6a80c19e3..ff4ce894a0 100644 --- a/polaris-core/src/main/java/org/apache/polaris/core/entity/CatalogEntity.java +++ b/polaris-core/src/main/java/org/apache/polaris/core/entity/CatalogEntity.java @@ -39,14 +39,10 @@ import org.apache.polaris.core.admin.model.ExternalCatalog; import org.apache.polaris.core.admin.model.FileStorageConfigInfo; import org.apache.polaris.core.admin.model.GcpStorageConfigInfo; -import org.apache.polaris.core.admin.model.IcebergRestConnectionConfigInfo; import org.apache.polaris.core.admin.model.PolarisCatalog; import org.apache.polaris.core.admin.model.StorageConfigInfo; import org.apache.polaris.core.config.BehaviorChangeConfiguration; -import org.apache.polaris.core.connection.AuthenticationParametersDpo; import org.apache.polaris.core.connection.ConnectionConfigInfoDpo; -import org.apache.polaris.core.connection.ConnectionType; -import org.apache.polaris.core.connection.IcebergRestConnectionConfigInfoDpo; import org.apache.polaris.core.secrets.UserSecretReference; import org.apache.polaris.core.storage.FileStorageConfigurationInfo; import org.apache.polaris.core.storage.PolarisStorageConfigurationInfo; @@ -325,25 +321,9 @@ public Builder setConnectionConfigurationInfoWithSecrets( ConnectionConfigInfo connectionConfigurationModel, Map secretReferences) { if (connectionConfigurationModel != null) { - ConnectionConfigInfoDpo config; - switch (connectionConfigurationModel.getConnectionType()) { - case ICEBERG_REST: - IcebergRestConnectionConfigInfo icebergRestConfigModel = - (IcebergRestConnectionConfigInfo) connectionConfigurationModel; - AuthenticationParametersDpo authenticationParameters = - AuthenticationParametersDpo.fromAuthenticationParametersModelWithSecrets( - icebergRestConfigModel.getAuthenticationParameters(), secretReferences); - config = - new IcebergRestConnectionConfigInfoDpo( - ConnectionType.ICEBERG_REST, - icebergRestConfigModel.getUri(), - icebergRestConfigModel.getRemoteCatalogName(), - authenticationParameters); - break; - default: - throw new IllegalStateException( - "Unsupported connection type: " + connectionConfigurationModel.getConnectionType()); - } + ConnectionConfigInfoDpo config = + ConnectionConfigInfoDpo.fromConnectionConfigInfoModelWithSecrets( + connectionConfigurationModel, secretReferences); internalProperties.put( PolarisEntityConstants.getConnectionConfigInfoPropertyName(), config.serialize()); } From 5edb23e6595421a14455bc563bcdbeebea4c169b Mon Sep 17 00:00:00 2001 From: Rulin Xing Date: Fri, 4 Apr 2025 03:23:00 +0000 Subject: [PATCH 2/3] Create a Factory for UserSecretsManager --- .../secrets/UnsafeInMemorySecretsManager.java | 9 +++-- .../core/secrets/UserSecretsManager.java | 9 +++-- .../secrets/UserSecretsManagerFactory.java | 31 ++++++++++++++ .../main/resources/application-it.properties | 2 + .../src/main/resources/application.properties | 2 + .../quarkus/config/QuarkusProducers.java | 17 ++++++++ .../QuarkusSecretsManagerConfiguration.java | 33 +++++++++++++++ .../admin/PolarisAdminServiceAuthzTest.java | 1 + .../quarkus/admin/PolarisAuthzTestBase.java | 16 +++++++- .../catalog/GenericTableCatalogTest.java | 6 +++ .../IcebergCatalogHandlerAuthzTest.java | 4 ++ .../quarkus/catalog/IcebergCatalogTest.java | 6 +++ .../catalog/IcebergCatalogViewTest.java | 6 +++ .../service/admin/PolarisAdminService.java | 7 ++-- .../service/admin/PolarisServiceImpl.java | 14 ++++++- .../iceberg/IcebergCatalogAdapter.java | 5 +++ .../iceberg/IcebergCatalogHandler.java | 7 ++-- .../PolarisCallContextCatalogFactory.java | 4 ++ .../UnsafeInMemorySecretsManagerFactory.java | 40 +++++++++++++++++++ .../apache/polaris/service/TestServices.java | 20 +++++++++- 20 files changed, 222 insertions(+), 17 deletions(-) create mode 100644 polaris-core/src/main/java/org/apache/polaris/core/secrets/UserSecretsManagerFactory.java create mode 100644 quarkus/service/src/main/java/org/apache/polaris/service/quarkus/secrets/QuarkusSecretsManagerConfiguration.java create mode 100644 service/common/src/main/java/org/apache/polaris/service/secrets/UnsafeInMemorySecretsManagerFactory.java diff --git a/polaris-core/src/main/java/org/apache/polaris/core/secrets/UnsafeInMemorySecretsManager.java b/polaris-core/src/main/java/org/apache/polaris/core/secrets/UnsafeInMemorySecretsManager.java index d5cdfa0c3f..fcf255ee31 100644 --- a/polaris-core/src/main/java/org/apache/polaris/core/secrets/UnsafeInMemorySecretsManager.java +++ b/polaris-core/src/main/java/org/apache/polaris/core/secrets/UnsafeInMemorySecretsManager.java @@ -18,6 +18,7 @@ */ package org.apache.polaris.core.secrets; +import jakarta.annotation.Nonnull; import java.io.UnsupportedEncodingException; import java.security.SecureRandom; import java.util.Base64; @@ -46,7 +47,8 @@ public class UnsafeInMemorySecretsManager implements UserSecretsManager { /** {@inheritDoc} */ @Override - public UserSecretReference writeSecret(String secret, PolarisEntity forEntity) { + @Nonnull + public UserSecretReference writeSecret(@Nonnull String secret, @Nonnull PolarisEntity forEntity) { // For illustrative purposes and to exercise the control flow of requiring both the stored // secret as well as the secretReferencePayload to recover the original secret, we'll use // basic XOR encryption and store the randomly generated key in the reference payload. @@ -107,7 +109,8 @@ public UserSecretReference writeSecret(String secret, PolarisEntity forEntity) { /** {@inheritDoc} */ @Override - public String readSecret(UserSecretReference secretReference) { + @Nonnull + public String readSecret(@Nonnull UserSecretReference secretReference) { // TODO: Precondition checks and/or wire in PolarisDiagnostics String encryptedSecretCipherTextBase64 = rawSecretStore.get(secretReference.getUrn()); if (encryptedSecretCipherTextBase64 == null) { @@ -148,7 +151,7 @@ public String readSecret(UserSecretReference secretReference) { /** {@inheritDoc} */ @Override - public void deleteSecret(UserSecretReference secretReference) { + public void deleteSecret(@Nonnull UserSecretReference secretReference) { rawSecretStore.remove(secretReference.getUrn()); } } diff --git a/polaris-core/src/main/java/org/apache/polaris/core/secrets/UserSecretsManager.java b/polaris-core/src/main/java/org/apache/polaris/core/secrets/UserSecretsManager.java index 1dfd11d213..8df656e4ee 100644 --- a/polaris-core/src/main/java/org/apache/polaris/core/secrets/UserSecretsManager.java +++ b/polaris-core/src/main/java/org/apache/polaris/core/secrets/UserSecretsManager.java @@ -18,6 +18,7 @@ */ package org.apache.polaris.core.secrets; +import jakarta.annotation.Nonnull; import org.apache.polaris.core.entity.PolarisEntity; /** @@ -42,7 +43,8 @@ public interface UserSecretsManager { * @return A reference object that can be used to retrieve the secret which is safe to store in * its entirety within a persisted PolarisEntity */ - UserSecretReference writeSecret(String secret, PolarisEntity forEntity); + @Nonnull + UserSecretReference writeSecret(@Nonnull String secret, @Nonnull PolarisEntity forEntity); /** * Retrieve a secret using the {@code secretReference}. @@ -50,12 +52,13 @@ public interface UserSecretsManager { * @param secretReference Identifier and any associated payload used for retrieving the secret * @return The stored secret, or null if it no longer exists */ - String readSecret(UserSecretReference secretReference); + @Nonnull + String readSecret(@Nonnull UserSecretReference secretReference); /** * Delete a stored secret * * @param secretReference Identifier and any associated payload used for retrieving the secret */ - void deleteSecret(UserSecretReference secretReference); + void deleteSecret(@Nonnull UserSecretReference secretReference); } diff --git a/polaris-core/src/main/java/org/apache/polaris/core/secrets/UserSecretsManagerFactory.java b/polaris-core/src/main/java/org/apache/polaris/core/secrets/UserSecretsManagerFactory.java new file mode 100644 index 0000000000..a8ee2b7b44 --- /dev/null +++ b/polaris-core/src/main/java/org/apache/polaris/core/secrets/UserSecretsManagerFactory.java @@ -0,0 +1,31 @@ +/* + * 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. + */ +package org.apache.polaris.core.secrets; + +import org.apache.polaris.core.context.RealmContext; + +/** + * Factory for creating {@link UserSecretsManager} instances. + * + *

Each {@link UserSecretsManager} instance is associated with a {@link RealmContext} and is + * responsible for managing the secrets for the user in that realm. + */ +public interface UserSecretsManagerFactory { + UserSecretsManager getOrCreateUserSecretsManager(RealmContext realmContext); +} diff --git a/quarkus/defaults/src/main/resources/application-it.properties b/quarkus/defaults/src/main/resources/application-it.properties index 5e110071de..906d13c1ed 100644 --- a/quarkus/defaults/src/main/resources/application-it.properties +++ b/quarkus/defaults/src/main/resources/application-it.properties @@ -29,6 +29,8 @@ quarkus.management.port=0 # polaris.persistence.type=in-memory-atomic polaris.persistence.type=in-memory +polaris.secrets-manager.type=in-memory + polaris.features.defaults."ALLOW_EXTERNAL_CATALOG_CREDENTIAL_VENDING"=false polaris.features.defaults."ALLOW_EXTERNAL_METADATA_FILE_LOCATION"=false polaris.features.defaults."ALLOW_OVERLAPPING_CATALOG_URLS"=true diff --git a/quarkus/defaults/src/main/resources/application.properties b/quarkus/defaults/src/main/resources/application.properties index 0cd79eadae..d6f579910b 100644 --- a/quarkus/defaults/src/main/resources/application.properties +++ b/quarkus/defaults/src/main/resources/application.properties @@ -99,6 +99,8 @@ polaris.features.defaults."SUPPORTED_CATALOG_STORAGE_TYPES"=["S3","GCS","AZURE", # polaris.persistence.type=in-memory-atomic polaris.persistence.type=in-memory +polaris.secrets-manager.type=in-memory + polaris.file-io.type=default polaris.log.request-id-header-name=Polaris-Request-Id diff --git a/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/config/QuarkusProducers.java b/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/config/QuarkusProducers.java index 16b7439791..e4c34c4652 100644 --- a/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/config/QuarkusProducers.java +++ b/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/config/QuarkusProducers.java @@ -45,6 +45,8 @@ import org.apache.polaris.core.persistence.MetaStoreManagerFactory; import org.apache.polaris.core.persistence.PolarisEntityManager; import org.apache.polaris.core.persistence.PolarisMetaStoreManager; +import org.apache.polaris.core.secrets.UserSecretsManager; +import org.apache.polaris.core.secrets.UserSecretsManagerFactory; import org.apache.polaris.core.storage.cache.StorageCredentialCache; import org.apache.polaris.service.auth.ActiveRolesProvider; import org.apache.polaris.service.auth.Authenticator; @@ -62,6 +64,7 @@ import org.apache.polaris.service.quarkus.persistence.QuarkusPersistenceConfiguration; import org.apache.polaris.service.quarkus.ratelimiter.QuarkusRateLimiterFilterConfiguration; import org.apache.polaris.service.quarkus.ratelimiter.QuarkusTokenBucketConfiguration; +import org.apache.polaris.service.quarkus.secrets.QuarkusSecretsManagerConfiguration; import org.apache.polaris.service.ratelimiter.RateLimiter; import org.apache.polaris.service.ratelimiter.TokenBucketFactory; import org.apache.polaris.service.task.TaskHandlerConfiguration; @@ -150,6 +153,13 @@ public MetaStoreManagerFactory metaStoreManagerFactory( return metaStoreManagerFactories.select(Identifier.Literal.of(config.type())).get(); } + @Produces + public UserSecretsManagerFactory userSecretsManagerFactory( + QuarkusSecretsManagerConfiguration config, + @Any Instance userSecretsManagerFactories) { + return userSecretsManagerFactories.select(Identifier.Literal.of(config.type())).get(); + } + /** * Eagerly initialize the in-memory default realm on startup, so that users can check the * credentials printed to stdout immediately. @@ -218,6 +228,13 @@ public PolarisMetaStoreManager polarisMetaStoreManager( return metaStoreManagerFactory.getOrCreateMetaStoreManager(realmContext); } + @Produces + @RequestScoped + public UserSecretsManager userSecretsManager( + RealmContext realmContext, UserSecretsManagerFactory userSecretsManagerFactory) { + return userSecretsManagerFactory.getOrCreateUserSecretsManager(realmContext); + } + @Produces @RequestScoped public BasePersistence polarisMetaStoreSession( diff --git a/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/secrets/QuarkusSecretsManagerConfiguration.java b/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/secrets/QuarkusSecretsManagerConfiguration.java new file mode 100644 index 0000000000..a472dc681f --- /dev/null +++ b/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/secrets/QuarkusSecretsManagerConfiguration.java @@ -0,0 +1,33 @@ +/* + * 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. + */ +package org.apache.polaris.service.quarkus.secrets; + +import io.quarkus.runtime.annotations.StaticInitSafe; +import io.smallrye.config.ConfigMapping; + +@StaticInitSafe +@ConfigMapping(prefix = "polaris.secrets-manager") +public interface QuarkusSecretsManagerConfiguration { + + /** + * The type of the UserSecretsManagerFactory to use. This is the {@link + * org.apache.polaris.core.secrets.UserSecretsManagerFactory} identifier. + */ + String type(); +} diff --git a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/admin/PolarisAdminServiceAuthzTest.java b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/admin/PolarisAdminServiceAuthzTest.java index 7199f4fa7e..55b2d9f341 100644 --- a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/admin/PolarisAdminServiceAuthzTest.java +++ b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/admin/PolarisAdminServiceAuthzTest.java @@ -53,6 +53,7 @@ private PolarisAdminService newTestAdminService(Set activatedPrincipalRo callContext, entityManager, metaStoreManager, + userSecretsManager, securityContext(authenticatedPrincipal, activatedPrincipalRoles), polarisAuthorizer); } diff --git a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/admin/PolarisAuthzTestBase.java b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/admin/PolarisAuthzTestBase.java index d908752812..4b0037eed0 100644 --- a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/admin/PolarisAuthzTestBase.java +++ b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/admin/PolarisAuthzTestBase.java @@ -75,6 +75,8 @@ import org.apache.polaris.core.persistence.dao.entity.EntityResult; import org.apache.polaris.core.persistence.resolver.PolarisResolutionManifest; import org.apache.polaris.core.persistence.transactional.TransactionalPersistence; +import org.apache.polaris.core.secrets.UserSecretsManager; +import org.apache.polaris.core.secrets.UserSecretsManagerFactory; import org.apache.polaris.service.admin.PolarisAdminService; import org.apache.polaris.service.catalog.PolarisPassthroughResolutionView; import org.apache.polaris.service.catalog.generic.GenericTableCatalog; @@ -176,6 +178,7 @@ public Map getConfigOverrides() { @Inject protected MetaStoreManagerFactory managerFactory; @Inject protected RealmEntityManagerFactory realmEntityManagerFactory; @Inject protected CallContextCatalogFactory callContextCatalogFactory; + @Inject protected UserSecretsManagerFactory userSecretsManagerFactory; @Inject protected PolarisDiagnostics diagServices; @Inject protected Clock clock; @Inject protected FileIOFactory fileIOFactory; @@ -185,6 +188,7 @@ public Map getConfigOverrides() { protected PolarisAdminService adminService; protected PolarisEntityManager entityManager; protected PolarisMetaStoreManager metaStoreManager; + protected UserSecretsManager userSecretsManager; protected TransactionalPersistence metaStoreSession; protected PolarisBaseEntity catalogEntity; protected PrincipalEntity principalEntity; @@ -205,6 +209,7 @@ public static void setUpMocks() { public void before(TestInfo testInfo) { RealmContext realmContext = testInfo::getDisplayName; metaStoreManager = managerFactory.getOrCreateMetaStoreManager(realmContext); + userSecretsManager = userSecretsManagerFactory.getOrCreateUserSecretsManager(realmContext); Map configMap = Map.of( @@ -248,6 +253,7 @@ public void before(TestInfo testInfo) { callContext, entityManager, metaStoreManager, + userSecretsManager, securityContext(authenticatedRoot, Set.of()), polarisAuthorizer); @@ -465,16 +471,22 @@ public static class TestPolarisCallContextCatalogFactory extends PolarisCallContextCatalogFactory { public TestPolarisCallContextCatalogFactory() { - super(null, null, null, null); + super(null, null, null, null, null); } @Inject public TestPolarisCallContextCatalogFactory( RealmEntityManagerFactory entityManagerFactory, MetaStoreManagerFactory metaStoreManagerFactory, + UserSecretsManagerFactory userSecretsManagerFactory, TaskExecutor taskExecutor, FileIOFactory fileIOFactory) { - super(entityManagerFactory, metaStoreManagerFactory, taskExecutor, fileIOFactory); + super( + entityManagerFactory, + metaStoreManagerFactory, + userSecretsManagerFactory, + taskExecutor, + fileIOFactory); } @Override diff --git a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/catalog/GenericTableCatalogTest.java b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/catalog/GenericTableCatalogTest.java index 311c239902..462cc7bb73 100644 --- a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/catalog/GenericTableCatalogTest.java +++ b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/catalog/GenericTableCatalogTest.java @@ -67,6 +67,8 @@ import org.apache.polaris.core.persistence.dao.entity.BaseResult; import org.apache.polaris.core.persistence.dao.entity.PrincipalSecretsResult; import org.apache.polaris.core.persistence.transactional.TransactionalPersistence; +import org.apache.polaris.core.secrets.UserSecretsManager; +import org.apache.polaris.core.secrets.UserSecretsManagerFactory; import org.apache.polaris.core.storage.PolarisStorageIntegration; import org.apache.polaris.core.storage.PolarisStorageIntegrationProvider; import org.apache.polaris.core.storage.aws.AwsCredentialsStorageIntegration; @@ -119,6 +121,7 @@ public Map getConfigOverrides() { public static final String SESSION_TOKEN = "session_token"; @Inject MetaStoreManagerFactory managerFactory; + @Inject UserSecretsManagerFactory userSecretsManagerFactory; @Inject PolarisConfigurationStore configurationStore; @Inject PolarisStorageIntegrationProvider storageIntegrationProvider; @Inject PolarisDiagnostics diagServices; @@ -129,6 +132,7 @@ public Map getConfigOverrides() { private AwsStorageConfigInfo storageConfigModel; private String realmName; private PolarisMetaStoreManager metaStoreManager; + private UserSecretsManager userSecretsManager; private PolarisCallContext polarisContext; private PolarisAdminService adminService; private PolarisEntityManager entityManager; @@ -158,6 +162,7 @@ public void before(TestInfo testInfo) { testInfo.getTestMethod().map(Method::getName).orElse("test"), System.nanoTime()); RealmContext realmContext = () -> realmName; metaStoreManager = managerFactory.getOrCreateMetaStoreManager(realmContext); + userSecretsManager = userSecretsManagerFactory.getOrCreateUserSecretsManager(realmContext); polarisContext = new PolarisCallContext( managerFactory.getOrCreateSessionSupplier(realmContext).get(), @@ -192,6 +197,7 @@ public void before(TestInfo testInfo) { callContext, entityManager, metaStoreManager, + userSecretsManager, securityContext, new PolarisAuthorizerImpl(new PolarisConfigurationStore() {})); diff --git a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/catalog/IcebergCatalogHandlerAuthzTest.java b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/catalog/IcebergCatalogHandlerAuthzTest.java index b8742404b6..11a938f316 100644 --- a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/catalog/IcebergCatalogHandlerAuthzTest.java +++ b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/catalog/IcebergCatalogHandlerAuthzTest.java @@ -111,6 +111,7 @@ private IcebergCatalogHandler newWrapper( callContext, entityManager, metaStoreManager, + userSecretsManager, securityContext(authenticatedPrincipal, activatedPrincipalRoles), factory, catalogName, @@ -249,6 +250,7 @@ public void testInsufficientPermissionsPriorToSecretRotation() { callContext, entityManager, metaStoreManager, + userSecretsManager, securityContext(authenticatedPrincipal, Set.of(PRINCIPAL_ROLE1, PRINCIPAL_ROLE2)), callContextCatalogFactory, CATALOG_NAME, @@ -281,6 +283,7 @@ public void testInsufficientPermissionsPriorToSecretRotation() { callContext, entityManager, metaStoreManager, + userSecretsManager, securityContext(authenticatedPrincipal1, Set.of(PRINCIPAL_ROLE1, PRINCIPAL_ROLE2)), callContextCatalogFactory, CATALOG_NAME, @@ -1802,6 +1805,7 @@ public PolarisEntityManager getOrCreateEntityManager(RealmContext realmContext) } }, managerFactory, + userSecretsManagerFactory, Mockito.mock(), new DefaultFileIOFactory( realmEntityManagerFactory, managerFactory, new PolarisConfigurationStore() {})) { diff --git a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/catalog/IcebergCatalogTest.java b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/catalog/IcebergCatalogTest.java index eedc9a588d..690a335a2b 100644 --- a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/catalog/IcebergCatalogTest.java +++ b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/catalog/IcebergCatalogTest.java @@ -99,6 +99,8 @@ import org.apache.polaris.core.persistence.dao.entity.EntityResult; import org.apache.polaris.core.persistence.dao.entity.PrincipalSecretsResult; import org.apache.polaris.core.persistence.transactional.TransactionalPersistence; +import org.apache.polaris.core.secrets.UserSecretsManager; +import org.apache.polaris.core.secrets.UserSecretsManagerFactory; import org.apache.polaris.core.storage.PolarisCredentialProperty; import org.apache.polaris.core.storage.PolarisStorageActions; import org.apache.polaris.core.storage.PolarisStorageIntegration; @@ -173,12 +175,14 @@ public Map getConfigOverrides() { @Inject MetaStoreManagerFactory managerFactory; @Inject PolarisConfigurationStore configurationStore; @Inject PolarisStorageIntegrationProvider storageIntegrationProvider; + @Inject UserSecretsManagerFactory userSecretsManagerFactory; @Inject PolarisDiagnostics diagServices; private IcebergCatalog catalog; private CallContext callContext; private String realmName; private PolarisMetaStoreManager metaStoreManager; + private UserSecretsManager userSecretsManager; private PolarisCallContext polarisContext; private PolarisAdminService adminService; private PolarisEntityManager entityManager; @@ -205,6 +209,7 @@ public void before(TestInfo testInfo) { testInfo.getTestMethod().map(Method::getName).orElse("test"), System.nanoTime()); RealmContext realmContext = () -> realmName; metaStoreManager = managerFactory.getOrCreateMetaStoreManager(realmContext); + userSecretsManager = userSecretsManagerFactory.getOrCreateUserSecretsManager(realmContext); polarisContext = new PolarisCallContext( managerFactory.getOrCreateSessionSupplier(realmContext).get(), @@ -240,6 +245,7 @@ public void before(TestInfo testInfo) { callContext, entityManager, metaStoreManager, + userSecretsManager, securityContext, new PolarisAuthorizerImpl(new PolarisConfigurationStore() {})); diff --git a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/catalog/IcebergCatalogViewTest.java b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/catalog/IcebergCatalogViewTest.java index fa51a021d8..8d7708dd73 100644 --- a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/catalog/IcebergCatalogViewTest.java +++ b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/catalog/IcebergCatalogViewTest.java @@ -58,6 +58,8 @@ import org.apache.polaris.core.persistence.PolarisEntityManager; import org.apache.polaris.core.persistence.PolarisMetaStoreManager; import org.apache.polaris.core.persistence.cache.EntityCache; +import org.apache.polaris.core.secrets.UserSecretsManager; +import org.apache.polaris.core.secrets.UserSecretsManagerFactory; import org.apache.polaris.core.storage.cache.StorageCredentialCache; import org.apache.polaris.service.admin.PolarisAdminService; import org.apache.polaris.service.catalog.PolarisPassthroughResolutionView; @@ -98,6 +100,7 @@ public Map getConfigOverrides() { public static final String CATALOG_NAME = "polaris-catalog"; @Inject MetaStoreManagerFactory managerFactory; + @Inject UserSecretsManagerFactory userSecretsManagerFactory; @Inject PolarisConfigurationStore configurationStore; @Inject PolarisDiagnostics diagServices; @@ -105,6 +108,7 @@ public Map getConfigOverrides() { private String realmName; private PolarisMetaStoreManager metaStoreManager; + private UserSecretsManager userSecretsManager; private PolarisCallContext polarisContext; @BeforeAll @@ -131,6 +135,7 @@ public void before(TestInfo testInfo) { RealmContext realmContext = () -> realmName; metaStoreManager = managerFactory.getOrCreateMetaStoreManager(realmContext); + userSecretsManager = userSecretsManagerFactory.getOrCreateUserSecretsManager(realmContext); polarisContext = new PolarisCallContext( managerFactory.getOrCreateSessionSupplier(realmContext).get(), @@ -167,6 +172,7 @@ public void before(TestInfo testInfo) { callContext, entityManager, metaStoreManager, + userSecretsManager, securityContext, new PolarisAuthorizerImpl(new PolarisConfigurationStore() {})); adminService.createCatalog( diff --git a/service/common/src/main/java/org/apache/polaris/service/admin/PolarisAdminService.java b/service/common/src/main/java/org/apache/polaris/service/admin/PolarisAdminService.java index 893df5237c..133249a0f6 100644 --- a/service/common/src/main/java/org/apache/polaris/service/admin/PolarisAdminService.java +++ b/service/common/src/main/java/org/apache/polaris/service/admin/PolarisAdminService.java @@ -100,7 +100,6 @@ import org.apache.polaris.core.persistence.resolver.PolarisResolutionManifest; import org.apache.polaris.core.persistence.resolver.ResolverPath; import org.apache.polaris.core.persistence.resolver.ResolverStatus; -import org.apache.polaris.core.secrets.UnsafeInMemorySecretsManager; import org.apache.polaris.core.secrets.UserSecretReference; import org.apache.polaris.core.secrets.UserSecretsManager; import org.apache.polaris.core.storage.PolarisStorageConfigurationInfo; @@ -129,6 +128,7 @@ public class PolarisAdminService { private final AuthenticatedPolarisPrincipal authenticatedPrincipal; private final PolarisAuthorizer authorizer; private final PolarisMetaStoreManager metaStoreManager; + private final UserSecretsManager userSecretsManager; // Initialized in the authorize methods. private PolarisResolutionManifest resolutionManifest = null; @@ -137,6 +137,7 @@ public PolarisAdminService( @NotNull CallContext callContext, @NotNull PolarisEntityManager entityManager, @NotNull PolarisMetaStoreManager metaStoreManager, + @NotNull UserSecretsManager userSecretsManager, @NotNull SecurityContext securityContext, @NotNull PolarisAuthorizer authorizer) { this.callContext = callContext; @@ -154,6 +155,7 @@ public PolarisAdminService( this.authenticatedPrincipal = (AuthenticatedPolarisPrincipal) securityContext.getUserPrincipal(); this.authorizer = authorizer; + this.userSecretsManager = userSecretsManager; } private PolarisCallContext getCurrentPolarisContext() { @@ -161,8 +163,7 @@ private PolarisCallContext getCurrentPolarisContext() { } private UserSecretsManager getUserSecretsManager() { - // TODO: Wire into appropriate factories and/or contexts. - return UnsafeInMemorySecretsManager.GLOBAL_INSTANCE; + return userSecretsManager; } private Optional findCatalogByName(String name) { diff --git a/service/common/src/main/java/org/apache/polaris/service/admin/PolarisServiceImpl.java b/service/common/src/main/java/org/apache/polaris/service/admin/PolarisServiceImpl.java index b47d50208f..c2c53c17b2 100644 --- a/service/common/src/main/java/org/apache/polaris/service/admin/PolarisServiceImpl.java +++ b/service/common/src/main/java/org/apache/polaris/service/admin/PolarisServiceImpl.java @@ -68,6 +68,8 @@ import org.apache.polaris.core.persistence.MetaStoreManagerFactory; import org.apache.polaris.core.persistence.PolarisEntityManager; import org.apache.polaris.core.persistence.PolarisMetaStoreManager; +import org.apache.polaris.core.secrets.UserSecretsManager; +import org.apache.polaris.core.secrets.UserSecretsManagerFactory; import org.apache.polaris.service.admin.api.PolarisCatalogsApiService; import org.apache.polaris.service.admin.api.PolarisPrincipalRolesApiService; import org.apache.polaris.service.admin.api.PolarisPrincipalsApiService; @@ -85,16 +87,19 @@ public class PolarisServiceImpl private final RealmEntityManagerFactory entityManagerFactory; private final PolarisAuthorizer polarisAuthorizer; private final MetaStoreManagerFactory metaStoreManagerFactory; + private final UserSecretsManagerFactory userSecretsManagerFactory; private final CallContext callContext; @Inject public PolarisServiceImpl( RealmEntityManagerFactory entityManagerFactory, MetaStoreManagerFactory metaStoreManagerFactory, + UserSecretsManagerFactory userSecretsManagerFactory, PolarisAuthorizer polarisAuthorizer, CallContext callContext) { this.entityManagerFactory = entityManagerFactory; this.metaStoreManagerFactory = metaStoreManagerFactory; + this.userSecretsManagerFactory = userSecretsManagerFactory; this.polarisAuthorizer = polarisAuthorizer; this.callContext = callContext; // FIXME: This is a hack to set the current context for downstream calls. @@ -113,8 +118,15 @@ private PolarisAdminService newAdminService( entityManagerFactory.getOrCreateEntityManager(realmContext); PolarisMetaStoreManager metaStoreManager = metaStoreManagerFactory.getOrCreateMetaStoreManager(realmContext); + UserSecretsManager userSecretsManager = + userSecretsManagerFactory.getOrCreateUserSecretsManager(realmContext); return new PolarisAdminService( - callContext, entityManager, metaStoreManager, securityContext, polarisAuthorizer); + callContext, + entityManager, + metaStoreManager, + userSecretsManager, + securityContext, + polarisAuthorizer); } /** From PolarisCatalogsApiService */ diff --git a/service/common/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogAdapter.java b/service/common/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogAdapter.java index ccb6b44f57..21316536a0 100644 --- a/service/common/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogAdapter.java +++ b/service/common/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogAdapter.java @@ -67,6 +67,7 @@ import org.apache.polaris.core.persistence.ResolvedPolarisEntity; import org.apache.polaris.core.persistence.resolver.Resolver; import org.apache.polaris.core.persistence.resolver.ResolverStatus; +import org.apache.polaris.core.secrets.UserSecretsManager; import org.apache.polaris.service.catalog.AccessDelegationMode; import org.apache.polaris.service.catalog.CatalogPrefixParser; import org.apache.polaris.service.catalog.api.IcebergRestCatalogApiService; @@ -132,6 +133,7 @@ public class IcebergCatalogAdapter private final CallContextCatalogFactory catalogFactory; private final PolarisEntityManager entityManager; private final PolarisMetaStoreManager metaStoreManager; + private final UserSecretsManager userSecretsManager; private final PolarisAuthorizer polarisAuthorizer; private final CatalogPrefixParser prefixParser; @@ -142,6 +144,7 @@ public IcebergCatalogAdapter( CallContextCatalogFactory catalogFactory, PolarisEntityManager entityManager, PolarisMetaStoreManager metaStoreManager, + UserSecretsManager userSecretsManager, PolarisAuthorizer polarisAuthorizer, CatalogPrefixParser prefixParser) { this.realmContext = realmContext; @@ -149,6 +152,7 @@ public IcebergCatalogAdapter( this.catalogFactory = catalogFactory; this.entityManager = entityManager; this.metaStoreManager = metaStoreManager; + this.userSecretsManager = userSecretsManager; this.polarisAuthorizer = polarisAuthorizer; this.prefixParser = prefixParser; @@ -188,6 +192,7 @@ private IcebergCatalogHandler newHandlerWrapper( callContext, entityManager, metaStoreManager, + userSecretsManager, securityContext, catalogFactory, catalogName, diff --git a/service/common/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogHandler.java b/service/common/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogHandler.java index 63413ebd98..e8f46e15ae 100644 --- a/service/common/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogHandler.java +++ b/service/common/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogHandler.java @@ -86,7 +86,6 @@ import org.apache.polaris.core.persistence.TransactionWorkspaceMetaStoreManager; import org.apache.polaris.core.persistence.dao.entity.EntitiesResult; import org.apache.polaris.core.persistence.dao.entity.EntityWithPath; -import org.apache.polaris.core.secrets.UnsafeInMemorySecretsManager; import org.apache.polaris.core.secrets.UserSecretsManager; import org.apache.polaris.core.storage.PolarisStorageActions; import org.apache.polaris.service.catalog.SupportsNotifications; @@ -117,6 +116,7 @@ public class IcebergCatalogHandler extends CatalogHandler implements AutoCloseab private static final Logger LOGGER = LoggerFactory.getLogger(IcebergCatalogHandler.class); private final PolarisMetaStoreManager metaStoreManager; + private final UserSecretsManager userSecretsManager; private final CallContextCatalogFactory catalogFactory; // Catalog instance will be initialized after authorizing resolver successfully resolves @@ -129,12 +129,14 @@ public IcebergCatalogHandler( CallContext callContext, PolarisEntityManager entityManager, PolarisMetaStoreManager metaStoreManager, + UserSecretsManager userSecretsManager, SecurityContext securityContext, CallContextCatalogFactory catalogFactory, String catalogName, PolarisAuthorizer authorizer) { super(callContext, entityManager, securityContext, catalogName, authorizer); this.metaStoreManager = metaStoreManager; + this.userSecretsManager = userSecretsManager; this.catalogFactory = catalogFactory; } @@ -160,8 +162,7 @@ public static boolean isCreate(UpdateTableRequest request) { } private UserSecretsManager getUserSecretsManager() { - // TODO: Wire into appropriate factories and/or contexts. - return UnsafeInMemorySecretsManager.GLOBAL_INSTANCE; + return userSecretsManager; } @Override diff --git a/service/common/src/main/java/org/apache/polaris/service/context/PolarisCallContextCatalogFactory.java b/service/common/src/main/java/org/apache/polaris/service/context/PolarisCallContextCatalogFactory.java index 2332e9a00d..7ffd72c448 100644 --- a/service/common/src/main/java/org/apache/polaris/service/context/PolarisCallContextCatalogFactory.java +++ b/service/common/src/main/java/org/apache/polaris/service/context/PolarisCallContextCatalogFactory.java @@ -34,6 +34,7 @@ import org.apache.polaris.core.persistence.MetaStoreManagerFactory; import org.apache.polaris.core.persistence.PolarisEntityManager; import org.apache.polaris.core.persistence.resolver.PolarisResolutionManifest; +import org.apache.polaris.core.secrets.UserSecretsManagerFactory; import org.apache.polaris.service.catalog.iceberg.IcebergCatalog; import org.apache.polaris.service.catalog.io.FileIOFactory; import org.apache.polaris.service.config.RealmEntityManagerFactory; @@ -53,15 +54,18 @@ public class PolarisCallContextCatalogFactory implements CallContextCatalogFacto private final TaskExecutor taskExecutor; private final FileIOFactory fileIOFactory; private final MetaStoreManagerFactory metaStoreManagerFactory; + private final UserSecretsManagerFactory userSecretsManagerFactory; @Inject public PolarisCallContextCatalogFactory( RealmEntityManagerFactory entityManagerFactory, MetaStoreManagerFactory metaStoreManagerFactory, + UserSecretsManagerFactory userSecretsManagerFactory, TaskExecutor taskExecutor, FileIOFactory fileIOFactory) { this.entityManagerFactory = entityManagerFactory; this.metaStoreManagerFactory = metaStoreManagerFactory; + this.userSecretsManagerFactory = userSecretsManagerFactory; this.taskExecutor = taskExecutor; this.fileIOFactory = fileIOFactory; } diff --git a/service/common/src/main/java/org/apache/polaris/service/secrets/UnsafeInMemorySecretsManagerFactory.java b/service/common/src/main/java/org/apache/polaris/service/secrets/UnsafeInMemorySecretsManagerFactory.java new file mode 100644 index 0000000000..2962708ed5 --- /dev/null +++ b/service/common/src/main/java/org/apache/polaris/service/secrets/UnsafeInMemorySecretsManagerFactory.java @@ -0,0 +1,40 @@ +/* + * 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. + */ +package org.apache.polaris.service.secrets; + +import io.smallrye.common.annotation.Identifier; +import jakarta.enterprise.context.ApplicationScoped; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import org.apache.polaris.core.context.RealmContext; +import org.apache.polaris.core.secrets.UnsafeInMemorySecretsManager; +import org.apache.polaris.core.secrets.UserSecretsManager; +import org.apache.polaris.core.secrets.UserSecretsManagerFactory; + +@ApplicationScoped +@Identifier("in-memory") +public class UnsafeInMemorySecretsManagerFactory implements UserSecretsManagerFactory { + private final Map cachedEntityManagers = new ConcurrentHashMap<>(); + + @Override + public UserSecretsManager getOrCreateUserSecretsManager(RealmContext realmContext) { + return cachedEntityManagers.computeIfAbsent( + realmContext.getRealmIdentifier(), key -> new UnsafeInMemorySecretsManager()); + } +} diff --git a/service/common/src/testFixtures/java/org/apache/polaris/service/TestServices.java b/service/common/src/testFixtures/java/org/apache/polaris/service/TestServices.java index c763af661c..3e8eded344 100644 --- a/service/common/src/testFixtures/java/org/apache/polaris/service/TestServices.java +++ b/service/common/src/testFixtures/java/org/apache/polaris/service/TestServices.java @@ -42,6 +42,8 @@ import org.apache.polaris.core.persistence.PolarisMetaStoreManager; import org.apache.polaris.core.persistence.dao.entity.CreatePrincipalResult; import org.apache.polaris.core.persistence.transactional.TransactionalPersistence; +import org.apache.polaris.core.secrets.UserSecretsManager; +import org.apache.polaris.core.secrets.UserSecretsManagerFactory; import org.apache.polaris.service.admin.PolarisServiceImpl; import org.apache.polaris.service.admin.api.PolarisCatalogsApi; import org.apache.polaris.service.catalog.DefaultCatalogPrefixParser; @@ -55,6 +57,7 @@ import org.apache.polaris.service.context.CallContextCatalogFactory; import org.apache.polaris.service.context.PolarisCallContextCatalogFactory; import org.apache.polaris.service.persistence.InMemoryPolarisMetaStoreManagerFactory; +import org.apache.polaris.service.secrets.UnsafeInMemorySecretsManagerFactory; import org.apache.polaris.service.storage.PolarisStorageIntegrationProviderImpl; import org.apache.polaris.service.task.TaskExecutor; import org.assertj.core.util.TriFunction; @@ -131,11 +134,15 @@ public TestServices build() { storageIntegrationProvider, polarisDiagnostics); RealmEntityManagerFactory realmEntityManagerFactory = new RealmEntityManagerFactory(metaStoreManagerFactory) {}; + UserSecretsManagerFactory userSecretsManagerFactory = + new UnsafeInMemorySecretsManagerFactory(); PolarisEntityManager entityManager = realmEntityManagerFactory.getOrCreateEntityManager(realmContext); PolarisMetaStoreManager metaStoreManager = metaStoreManagerFactory.getOrCreateMetaStoreManager(realmContext); + UserSecretsManager userSecretsManager = + userSecretsManagerFactory.getOrCreateUserSecretsManager(realmContext); TransactionalPersistence metaStoreSession = metaStoreManagerFactory.getOrCreateSessionSupplier(realmContext).get(); CallContext callContext = @@ -168,7 +175,11 @@ public Map contextVariables() { CallContextCatalogFactory callContextFactory = new PolarisCallContextCatalogFactory( - realmEntityManagerFactory, metaStoreManagerFactory, taskExecutor, fileIOFactory); + realmEntityManagerFactory, + metaStoreManagerFactory, + userSecretsManagerFactory, + taskExecutor, + fileIOFactory); IcebergRestCatalogApiService service = new IcebergCatalogAdapter( @@ -177,6 +188,7 @@ public Map contextVariables() { callContextFactory, entityManager, metaStoreManager, + userSecretsManager, authorizer, new DefaultCatalogPrefixParser()); @@ -220,7 +232,11 @@ public String getAuthenticationScheme() { PolarisCatalogsApi catalogsApi = new PolarisCatalogsApi( new PolarisServiceImpl( - realmEntityManagerFactory, metaStoreManagerFactory, authorizer, callContext)); + realmEntityManagerFactory, + metaStoreManagerFactory, + userSecretsManagerFactory, + authorizer, + callContext)); return new TestServices( catalogsApi, From 8d974a993cd2cd465a6bef705a57b1a587a50574 Mon Sep 17 00:00:00 2001 From: Rulin Xing Date: Fri, 4 Apr 2025 06:02:35 +0000 Subject: [PATCH 3/3] Fix a typo in UnsafeInMemorySecretsManagerFactory --- .../service/secrets/UnsafeInMemorySecretsManagerFactory.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/service/common/src/main/java/org/apache/polaris/service/secrets/UnsafeInMemorySecretsManagerFactory.java b/service/common/src/main/java/org/apache/polaris/service/secrets/UnsafeInMemorySecretsManagerFactory.java index 2962708ed5..fb11b9acd7 100644 --- a/service/common/src/main/java/org/apache/polaris/service/secrets/UnsafeInMemorySecretsManagerFactory.java +++ b/service/common/src/main/java/org/apache/polaris/service/secrets/UnsafeInMemorySecretsManagerFactory.java @@ -30,11 +30,11 @@ @ApplicationScoped @Identifier("in-memory") public class UnsafeInMemorySecretsManagerFactory implements UserSecretsManagerFactory { - private final Map cachedEntityManagers = new ConcurrentHashMap<>(); + private final Map cachedSecretsManagers = new ConcurrentHashMap<>(); @Override public UserSecretsManager getOrCreateUserSecretsManager(RealmContext realmContext) { - return cachedEntityManagers.computeIfAbsent( + return cachedSecretsManagers.computeIfAbsent( realmContext.getRealmIdentifier(), key -> new UnsafeInMemorySecretsManager()); } }