This is an automated email from the ASF dual-hosted git repository.
honahx pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/polaris.git
The following commit(s) were added to refs/heads/main by this push:
new 6b957ec14 [Catalog Federation] Add Connection Credential Vendors for
Other Auth Types (#2782)
6b957ec14 is described below
commit 6b957ec14516ab764783833c4e5c40ba58fe3864
Author: Rulin Xing <[email protected]>
AuthorDate: Tue Oct 14 13:00:24 2025 -0700
[Catalog Federation] Add Connection Credential Vendors for Other Auth Types
(#2782)
Add Connection Credential Vendors for Other Auth Types
This change is a prerequisite for enabling connection credential caching.
By making PolarisCredentialManager the central entry point for obtaining
connection credentials, we can introduce caching cleanly and manage all
credential flows in a consistent way.
---
.../hadoop/HadoopFederatedCatalogFactory.java | 8 +-
.../hive/HiveFederatedCatalogFactory.java | 8 +-
.../core/catalog/ExternalCatalogFactory.java | 13 +--
.../BearerAuthenticationParametersDpo.java | 11 --
.../ImplicitAuthenticationParametersDpo.java | 5 +-
.../OAuthClientCredentialsParametersDpo.java | 11 +-
.../SigV4AuthenticationParametersDpo.java | 5 +-
.../hadoop/HadoopConnectionConfigInfoDpo.java | 9 +-
.../hive/HiveConnectionConfigInfoDpo.java | 8 +-
.../iceberg/IcebergCatalogPropertiesProvider.java | 7 +-
.../IcebergRestConnectionConfigInfoDpo.java | 9 +-
.../connection/CatalogAccessProperty.java | 43 ++++++--
.../connection/ConnectionCredentials.java | 6 +-
.../service/catalog/common/CatalogHandler.java | 8 --
.../generic/GenericTableCatalogAdapter.java | 5 -
.../generic/GenericTableCatalogHandler.java | 5 +-
.../catalog/iceberg/IcebergCatalogAdapter.java | 5 -
.../catalog/iceberg/IcebergCatalogHandler.java | 8 +-
.../iceberg/IcebergRESTExternalCatalogFactory.java | 9 +-
.../catalog/policy/PolicyCatalogAdapter.java | 5 -
.../catalog/policy/PolicyCatalogHandler.java | 3 -
.../service/config/ProductionReadinessChecks.java | 62 +++++++++++
.../credentials/CredentialVendorPriorities.java | 26 ++---
.../service/credentials/connection/AuthType.java | 2 +-
.../BearerConnectionCredentialVendor.java | 85 +++++++++++++++
.../ImplicitConnectionCredentialVendor.java | 68 ++++++++++++
.../connection/OAuthClientCredentialVendor.java | 92 ++++++++++++++++
.../SigV4ConnectionCredentialVendor.java | 30 +++---
...PolarisGenericTableCatalogHandlerAuthzTest.java | 1 -
.../iceberg/IcebergCatalogHandlerAuthzTest.java | 4 -
...ebergCatalogHandlerFineGrainedDisabledTest.java | 1 -
.../policy/PolicyCatalogHandlerAuthzTest.java | 1 -
.../BearerConnectionCredentialVendorTest.java | 109 +++++++++++++++++++
.../ImplicitConnectionCredentialVendorTest.java | 74 +++++++++++++
.../OAuthClientCredentialVendorTest.java | 116 +++++++++++++++++++++
.../org/apache/polaris/service/TestServices.java | 1 -
36 files changed, 708 insertions(+), 155 deletions(-)
diff --git
a/extensions/federation/hadoop/src/main/java/org/apache/polaris/extensions/federation/hadoop/HadoopFederatedCatalogFactory.java
b/extensions/federation/hadoop/src/main/java/org/apache/polaris/extensions/federation/hadoop/HadoopFederatedCatalogFactory.java
index 95bd26f9d..b2cc24ec1 100644
---
a/extensions/federation/hadoop/src/main/java/org/apache/polaris/extensions/federation/hadoop/HadoopFederatedCatalogFactory.java
+++
b/extensions/federation/hadoop/src/main/java/org/apache/polaris/extensions/federation/hadoop/HadoopFederatedCatalogFactory.java
@@ -31,7 +31,6 @@ import
org.apache.polaris.core.connection.ConnectionConfigInfoDpo;
import org.apache.polaris.core.connection.ConnectionType;
import org.apache.polaris.core.connection.hadoop.HadoopConnectionConfigInfoDpo;
import org.apache.polaris.core.credentials.PolarisCredentialManager;
-import org.apache.polaris.core.secrets.UserSecretsManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -44,7 +43,6 @@ public class HadoopFederatedCatalogFactory implements
ExternalCatalogFactory {
@Override
public Catalog createCatalog(
ConnectionConfigInfoDpo connectionConfigInfoDpo,
- UserSecretsManager userSecretsManager,
PolarisCredentialManager polarisCredentialManager) {
// Currently, Polaris supports Hadoop federation only via IMPLICIT
authentication.
// Hence, prior to initializing the configuration, ensure that the catalog
uses
@@ -59,15 +57,13 @@ public class HadoopFederatedCatalogFactory implements
ExternalCatalogFactory {
String warehouse = ((HadoopConnectionConfigInfoDpo)
connectionConfigInfoDpo).getWarehouse();
HadoopCatalog hadoopCatalog = new HadoopCatalog(conf, warehouse);
hadoopCatalog.initialize(
- warehouse,
- connectionConfigInfoDpo.asIcebergCatalogProperties(
- userSecretsManager, polarisCredentialManager));
+ warehouse,
connectionConfigInfoDpo.asIcebergCatalogProperties(polarisCredentialManager));
return hadoopCatalog;
}
@Override
public GenericTableCatalog createGenericCatalog(
- ConnectionConfigInfoDpo connectionConfig, UserSecretsManager
userSecretsManager) {
+ ConnectionConfigInfoDpo connectionConfig, PolarisCredentialManager
polarisCredentialManager) {
// TODO implement
throw new UnsupportedOperationException(
"Generic table federation to this catalog is not supported.");
diff --git
a/extensions/federation/hive/src/main/java/org/apache/polaris/extensions/federation/hive/HiveFederatedCatalogFactory.java
b/extensions/federation/hive/src/main/java/org/apache/polaris/extensions/federation/hive/HiveFederatedCatalogFactory.java
index 0f88acf09..939bc5384 100644
---
a/extensions/federation/hive/src/main/java/org/apache/polaris/extensions/federation/hive/HiveFederatedCatalogFactory.java
+++
b/extensions/federation/hive/src/main/java/org/apache/polaris/extensions/federation/hive/HiveFederatedCatalogFactory.java
@@ -30,7 +30,6 @@ import
org.apache.polaris.core.connection.ConnectionConfigInfoDpo;
import org.apache.polaris.core.connection.ConnectionType;
import org.apache.polaris.core.connection.hive.HiveConnectionConfigInfoDpo;
import org.apache.polaris.core.credentials.PolarisCredentialManager;
-import org.apache.polaris.core.secrets.UserSecretsManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -43,7 +42,6 @@ public class HiveFederatedCatalogFactory implements
ExternalCatalogFactory {
@Override
public Catalog createCatalog(
ConnectionConfigInfoDpo connectionConfigInfoDpo,
- UserSecretsManager userSecretsManager,
PolarisCredentialManager polarisCredentialManager) {
// Currently, Polaris supports Hive federation only via IMPLICIT
authentication.
// Hence, prior to initializing the configuration, ensure that the catalog
uses
@@ -72,15 +70,13 @@ public class HiveFederatedCatalogFactory implements
ExternalCatalogFactory {
// Kerberos instances are not suitable because Kerberos ties a single
identity to the server.
HiveCatalog hiveCatalog = new HiveCatalog();
hiveCatalog.initialize(
- warehouse,
- connectionConfigInfoDpo.asIcebergCatalogProperties(
- userSecretsManager, polarisCredentialManager));
+ warehouse,
connectionConfigInfoDpo.asIcebergCatalogProperties(polarisCredentialManager));
return hiveCatalog;
}
@Override
public GenericTableCatalog createGenericCatalog(
- ConnectionConfigInfoDpo connectionConfig, UserSecretsManager
userSecretsManager) {
+ ConnectionConfigInfoDpo connectionConfig, PolarisCredentialManager
polarisCredentialManager) {
// TODO implement
throw new UnsupportedOperationException(
"Generic table federation to this catalog is not supported.");
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/catalog/ExternalCatalogFactory.java
b/polaris-core/src/main/java/org/apache/polaris/core/catalog/ExternalCatalogFactory.java
index 035563b52..6253b8809 100644
---
a/polaris-core/src/main/java/org/apache/polaris/core/catalog/ExternalCatalogFactory.java
+++
b/polaris-core/src/main/java/org/apache/polaris/core/catalog/ExternalCatalogFactory.java
@@ -21,7 +21,6 @@ package org.apache.polaris.core.catalog;
import org.apache.iceberg.catalog.Catalog;
import org.apache.polaris.core.connection.ConnectionConfigInfoDpo;
import org.apache.polaris.core.credentials.PolarisCredentialManager;
-import org.apache.polaris.core.secrets.UserSecretsManager;
/**
* Factory interface for creating external catalog handles based on connection
configuration.
@@ -35,25 +34,23 @@ public interface ExternalCatalogFactory {
* Creates a catalog handle for the given connection configuration.
*
* @param connectionConfig the connection configuration
- * @param userSecretsManager the user secrets manager for handling
user-provided credentials
- * @param polarisCredentialManager the credential manager for generating
temporary credentials
+ * @param polarisCredentialManager the credential manager for generating
connection credentials
* that Polaris uses to access external systems
* @return the initialized catalog
* @throws IllegalStateException if the connection configuration is invalid
*/
Catalog createCatalog(
- ConnectionConfigInfoDpo connectionConfig,
- UserSecretsManager userSecretsManager,
- PolarisCredentialManager polarisCredentialManager);
+ ConnectionConfigInfoDpo connectionConfig, PolarisCredentialManager
polarisCredentialManager);
/**
* Creates a generic table catalog for the given connection configuration.
*
* @param connectionConfig the connection configuration
- * @param userSecretsManager the user secrets manager for handling
credentials
+ * @param polarisCredentialManager the credential manager for generating
connection credentials
+ * that Polaris uses to access external systems
* @return the initialized catalog
* @throws IllegalStateException if the connection configuration is invalid
*/
GenericTableCatalog createGenericCatalog(
- ConnectionConfigInfoDpo connectionConfig, UserSecretsManager
userSecretsManager);
+ ConnectionConfigInfoDpo connectionConfig, PolarisCredentialManager
polarisCredentialManager);
}
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/connection/BearerAuthenticationParametersDpo.java
b/polaris-core/src/main/java/org/apache/polaris/core/connection/BearerAuthenticationParametersDpo.java
index c11502015..2d06b22cf 100644
---
a/polaris-core/src/main/java/org/apache/polaris/core/connection/BearerAuthenticationParametersDpo.java
+++
b/polaris-core/src/main/java/org/apache/polaris/core/connection/BearerAuthenticationParametersDpo.java
@@ -21,13 +21,9 @@ package org.apache.polaris.core.connection;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.MoreObjects;
import jakarta.annotation.Nonnull;
-import java.util.Map;
-import org.apache.iceberg.rest.auth.OAuth2Properties;
import org.apache.polaris.core.admin.model.AuthenticationParameters;
import org.apache.polaris.core.admin.model.BearerAuthenticationParameters;
-import org.apache.polaris.core.credentials.PolarisCredentialManager;
import org.apache.polaris.core.secrets.SecretReference;
-import org.apache.polaris.core.secrets.UserSecretsManager;
/**
* The internal persistence-object counterpart to
BearerAuthenticationParameters defined in the API
@@ -49,13 +45,6 @@ public class BearerAuthenticationParametersDpo extends
AuthenticationParametersD
return bearerTokenReference;
}
- @Override
- public @Nonnull Map<String, String> asIcebergCatalogProperties(
- UserSecretsManager secretsManager, PolarisCredentialManager
credentialManager) {
- String bearerToken = secretsManager.readSecret(getBearerTokenReference());
- return Map.of(OAuth2Properties.TOKEN, bearerToken);
- }
-
@Override
public @Nonnull AuthenticationParameters asAuthenticationParametersModel() {
return BearerAuthenticationParameters.builder()
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/connection/ImplicitAuthenticationParametersDpo.java
b/polaris-core/src/main/java/org/apache/polaris/core/connection/ImplicitAuthenticationParametersDpo.java
index 113872429..371c9766b 100644
---
a/polaris-core/src/main/java/org/apache/polaris/core/connection/ImplicitAuthenticationParametersDpo.java
+++
b/polaris-core/src/main/java/org/apache/polaris/core/connection/ImplicitAuthenticationParametersDpo.java
@@ -24,7 +24,6 @@ import java.util.Map;
import org.apache.polaris.core.admin.model.AuthenticationParameters;
import org.apache.polaris.core.admin.model.ImplicitAuthenticationParameters;
import org.apache.polaris.core.credentials.PolarisCredentialManager;
-import org.apache.polaris.core.secrets.UserSecretsManager;
/**
* The internal persistence-object counterpart to
ImplicitAuthenticationParameters defined in the
@@ -38,7 +37,9 @@ public class ImplicitAuthenticationParametersDpo extends
AuthenticationParameter
@Override
public @Nonnull Map<String, String> asIcebergCatalogProperties(
- UserSecretsManager secretsManager, PolarisCredentialManager
credentialManager) {
+ PolarisCredentialManager credentialManager) {
+ // Return only metadata properties - credentials are handled by
ConnectionCredentialVendor
+ // Implicit auth has no metadata properties
return Map.of();
}
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 9c1293624..7b855496b 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
@@ -37,7 +37,6 @@ import
org.apache.polaris.core.admin.model.AuthenticationParameters;
import org.apache.polaris.core.admin.model.OAuthClientCredentialsParameters;
import org.apache.polaris.core.credentials.PolarisCredentialManager;
import org.apache.polaris.core.secrets.SecretReference;
-import org.apache.polaris.core.secrets.UserSecretsManager;
/**
* The internal persistence-object counterpart to
OAuthClientCredentialsParameters defined in the
@@ -97,20 +96,14 @@ public class OAuthClientCredentialsParametersDpo extends
AuthenticationParameter
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);
- }
-
@Override
public @Nonnull Map<String, String> asIcebergCatalogProperties(
- UserSecretsManager secretsManager, PolarisCredentialManager
credentialManager) {
+ PolarisCredentialManager credentialManager) {
+ // Return only metadata properties - credentials are handled by
ConnectionCredentialVendor
HashMap<String, String> properties = new HashMap<>();
if (getTokenUri() != null) {
properties.put(OAuth2Properties.OAUTH2_SERVER_URI, getTokenUri());
}
- properties.put(OAuth2Properties.CREDENTIAL,
getCredentialAsConcatenatedString(secretsManager));
properties.put(OAuth2Properties.SCOPE, getScopesAsString());
return properties;
}
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/connection/SigV4AuthenticationParametersDpo.java
b/polaris-core/src/main/java/org/apache/polaris/core/connection/SigV4AuthenticationParametersDpo.java
index 061245110..16dfd6e58 100644
---
a/polaris-core/src/main/java/org/apache/polaris/core/connection/SigV4AuthenticationParametersDpo.java
+++
b/polaris-core/src/main/java/org/apache/polaris/core/connection/SigV4AuthenticationParametersDpo.java
@@ -29,7 +29,6 @@ import org.apache.iceberg.rest.auth.AuthProperties;
import org.apache.polaris.core.admin.model.AuthenticationParameters;
import org.apache.polaris.core.admin.model.SigV4AuthenticationParameters;
import org.apache.polaris.core.credentials.PolarisCredentialManager;
-import org.apache.polaris.core.secrets.UserSecretsManager;
/**
* The internal persistence-object counterpart to
SigV4AuthenticationParameters defined in the API
@@ -95,14 +94,14 @@ public class SigV4AuthenticationParametersDpo extends
AuthenticationParametersDp
@Nonnull
@Override
public Map<String, String> asIcebergCatalogProperties(
- UserSecretsManager secretsManager, PolarisCredentialManager
credentialManager) {
+ PolarisCredentialManager credentialManager) {
+ // Return only metadata properties - credentials are handled by
ConnectionCredentialVendor
ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
builder.put(AuthProperties.AUTH_TYPE, AuthProperties.AUTH_TYPE_SIGV4);
builder.put(AwsProperties.REST_SIGNER_REGION, getSigningRegion());
if (getSigningName() != null) {
builder.put(AwsProperties.REST_SIGNING_NAME, getSigningName());
}
- // Connection credentials are handled by ConnectionConfigInfoDpo
return builder.build();
}
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/connection/hadoop/HadoopConnectionConfigInfoDpo.java
b/polaris-core/src/main/java/org/apache/polaris/core/connection/hadoop/HadoopConnectionConfigInfoDpo.java
index 26be6ad83..accbc7be1 100644
---
a/polaris-core/src/main/java/org/apache/polaris/core/connection/hadoop/HadoopConnectionConfigInfoDpo.java
+++
b/polaris-core/src/main/java/org/apache/polaris/core/connection/hadoop/HadoopConnectionConfigInfoDpo.java
@@ -35,7 +35,6 @@ import
org.apache.polaris.core.credentials.PolarisCredentialManager;
import org.apache.polaris.core.credentials.connection.ConnectionCredentials;
import org.apache.polaris.core.identity.dpo.ServiceIdentityInfoDpo;
import org.apache.polaris.core.identity.provider.ServiceIdentityProvider;
-import org.apache.polaris.core.secrets.UserSecretsManager;
/**
* The internal persistence-object counterpart to {@link
@@ -73,16 +72,14 @@ public class HadoopConnectionConfigInfoDpo extends
ConnectionConfigInfoDpo {
@Override
public @Nonnull Map<String, String> asIcebergCatalogProperties(
- UserSecretsManager secretsManager, PolarisCredentialManager
credentialManager) {
+ PolarisCredentialManager credentialManager) {
HashMap<String, String> properties = new HashMap<>();
properties.put(CatalogProperties.URI, getUri());
if (getWarehouse() != null) {
properties.put(CatalogProperties.WAREHOUSE_LOCATION, getWarehouse());
}
- // Add authentication-specific properties
- properties.putAll(
- getAuthenticationParameters()
- .asIcebergCatalogProperties(secretsManager, credentialManager));
+ // Add authentication-specific metadata (non-credential properties)
+
properties.putAll(getAuthenticationParameters().asIcebergCatalogProperties(credentialManager));
// Add connection credentials from Polaris credential manager
ConnectionCredentials connectionCredentials =
credentialManager.getConnectionCredentials(this);
properties.putAll(connectionCredentials.credentials());
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/connection/hive/HiveConnectionConfigInfoDpo.java
b/polaris-core/src/main/java/org/apache/polaris/core/connection/hive/HiveConnectionConfigInfoDpo.java
index d4a7e2b3d..51619216e 100644
---
a/polaris-core/src/main/java/org/apache/polaris/core/connection/hive/HiveConnectionConfigInfoDpo.java
+++
b/polaris-core/src/main/java/org/apache/polaris/core/connection/hive/HiveConnectionConfigInfoDpo.java
@@ -35,7 +35,6 @@ import
org.apache.polaris.core.credentials.PolarisCredentialManager;
import org.apache.polaris.core.credentials.connection.ConnectionCredentials;
import org.apache.polaris.core.identity.dpo.ServiceIdentityInfoDpo;
import org.apache.polaris.core.identity.provider.ServiceIdentityProvider;
-import org.apache.polaris.core.secrets.UserSecretsManager;
/**
* The internal persistence-object counterpart to {@link
@@ -72,17 +71,16 @@ public class HiveConnectionConfigInfoDpo extends
ConnectionConfigInfoDpo {
@Override
public @Nonnull Map<String, String> asIcebergCatalogProperties(
- UserSecretsManager secretsManager, PolarisCredentialManager
polarisCredentialManager) {
+ PolarisCredentialManager polarisCredentialManager) {
HashMap<String, String> properties = new HashMap<>();
properties.put(CatalogProperties.URI, getUri());
if (getWarehouse() != null) {
properties.put(CatalogProperties.WAREHOUSE_LOCATION, getWarehouse());
}
if (getAuthenticationParameters() != null) {
- // Add authentication-specific properties
+ // Add authentication-specific metadata (non-credential properties)
properties.putAll(
- getAuthenticationParameters()
- .asIcebergCatalogProperties(secretsManager,
polarisCredentialManager));
+
getAuthenticationParameters().asIcebergCatalogProperties(polarisCredentialManager));
// Add connection credentials from Polaris credential manager
ConnectionCredentials connectionCredentials =
polarisCredentialManager.getConnectionCredentials(this);
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/connection/iceberg/IcebergCatalogPropertiesProvider.java
b/polaris-core/src/main/java/org/apache/polaris/core/connection/iceberg/IcebergCatalogPropertiesProvider.java
index e17218f25..c07ceaabb 100644
---
a/polaris-core/src/main/java/org/apache/polaris/core/connection/iceberg/IcebergCatalogPropertiesProvider.java
+++
b/polaris-core/src/main/java/org/apache/polaris/core/connection/iceberg/IcebergCatalogPropertiesProvider.java
@@ -21,7 +21,6 @@ package org.apache.polaris.core.connection.iceberg;
import jakarta.annotation.Nonnull;
import java.util.Map;
import org.apache.polaris.core.credentials.PolarisCredentialManager;
-import org.apache.polaris.core.secrets.UserSecretsManager;
/**
* Configuration wrappers which ultimately translate their contents into
Iceberg properties and
@@ -31,6 +30,8 @@ import org.apache.polaris.core.secrets.UserSecretsManager;
*/
public interface IcebergCatalogPropertiesProvider {
@Nonnull
- Map<String, String> asIcebergCatalogProperties(
- UserSecretsManager secretsManager, PolarisCredentialManager
credentialManager);
+ default Map<String, String> asIcebergCatalogProperties(
+ PolarisCredentialManager credentialManager) {
+ return Map.of();
+ }
}
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/connection/iceberg/IcebergRestConnectionConfigInfoDpo.java
b/polaris-core/src/main/java/org/apache/polaris/core/connection/iceberg/IcebergRestConnectionConfigInfoDpo.java
index 43f5c8a92..56e3144fd 100644
---
a/polaris-core/src/main/java/org/apache/polaris/core/connection/iceberg/IcebergRestConnectionConfigInfoDpo.java
+++
b/polaris-core/src/main/java/org/apache/polaris/core/connection/iceberg/IcebergRestConnectionConfigInfoDpo.java
@@ -35,7 +35,6 @@ import
org.apache.polaris.core.credentials.PolarisCredentialManager;
import org.apache.polaris.core.credentials.connection.ConnectionCredentials;
import org.apache.polaris.core.identity.dpo.ServiceIdentityInfoDpo;
import org.apache.polaris.core.identity.provider.ServiceIdentityProvider;
-import org.apache.polaris.core.secrets.UserSecretsManager;
/**
* The internal persistence-object counterpart to
IcebergRestConnectionConfigInfo defined in the API
@@ -65,16 +64,14 @@ public class IcebergRestConnectionConfigInfoDpo extends
ConnectionConfigInfoDpo
@Override
public @Nonnull Map<String, String> asIcebergCatalogProperties(
- UserSecretsManager secretsManager, PolarisCredentialManager
credentialManager) {
+ PolarisCredentialManager credentialManager) {
HashMap<String, String> properties = new HashMap<>();
properties.put(CatalogProperties.URI, getUri());
if (getRemoteCatalogName() != null) {
properties.put(CatalogProperties.WAREHOUSE_LOCATION,
getRemoteCatalogName());
}
- // Add authentication-specific properties
- properties.putAll(
- getAuthenticationParameters()
- .asIcebergCatalogProperties(secretsManager, credentialManager));
+ // Add authentication-specific metadata (non-credential properties)
+
properties.putAll(getAuthenticationParameters().asIcebergCatalogProperties(credentialManager));
// Add connection credentials from Polaris credential manager
ConnectionCredentials connectionCredentials =
credentialManager.getConnectionCredentials(this);
properties.putAll(connectionCredentials.credentials());
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/credentials/connection/CatalogAccessProperty.java
b/polaris-core/src/main/java/org/apache/polaris/core/credentials/connection/CatalogAccessProperty.java
index cb42f7e32..a683e61bf 100644
---
a/polaris-core/src/main/java/org/apache/polaris/core/credentials/connection/CatalogAccessProperty.java
+++
b/polaris-core/src/main/java/org/apache/polaris/core/credentials/connection/CatalogAccessProperty.java
@@ -20,6 +20,7 @@
package org.apache.polaris.core.credentials.connection;
import org.apache.iceberg.aws.AwsProperties;
+import org.apache.iceberg.rest.auth.OAuth2Properties;
/**
* A subset of Iceberg catalog properties recognized by Polaris.
@@ -28,28 +29,54 @@ import org.apache.iceberg.aws.AwsProperties;
* Catalog service.
*/
public enum CatalogAccessProperty {
+ // OAuth
+ OAUTH2_CREDENTIAL(String.class, OAuth2Properties.CREDENTIAL, "the OAuth2
credential", true),
+
+ // Bearer
+ BEARER_TOKEN(String.class, OAuth2Properties.TOKEN, "the bearer token", true),
+
+ // SigV4
AWS_ACCESS_KEY_ID(String.class, AwsProperties.REST_ACCESS_KEY_ID, "the aws
access key id", true),
AWS_SECRET_ACCESS_KEY(
- String.class, AwsProperties.REST_SECRET_ACCESS_KEY, "the aws access key
secret", true),
- AWS_SESSION_TOKEN(
- String.class, AwsProperties.REST_SESSION_TOKEN, "the aws scoped access
token", true),
- EXPIRATION_TIME(
+ String.class, AwsProperties.REST_SECRET_ACCESS_KEY, "the aws secret
access key", true),
+ AWS_SESSION_TOKEN(String.class, AwsProperties.REST_SESSION_TOKEN, "the aws
session token", true),
+ AWS_SESSION_TOKEN_EXPIRES_AT_MS(
+ Long.class,
+ "rest.session-token-expires-at-ms",
+ "the time the aws session token expires, in milliseconds",
+ false,
+ true),
+
+ // Metadata
+ EXPIRES_AT_MS(
Long.class,
- "expiration-time",
- "the expiration time for the access token, in milliseconds",
- false);
+ "rest.expires-at-ms",
+ "the expiration time for the access token or the credential, in
milliseconds",
+ false,
+ true);
private final Class valueType;
private final String propertyName;
private final String description;
private final boolean isCredential;
+ private final boolean isExpirationTimestamp;
CatalogAccessProperty(
Class valueType, String propertyName, String description, boolean
isCredential) {
+ this(valueType, propertyName, description, isCredential, false);
+ }
+
+ CatalogAccessProperty(
+ Class valueType,
+ String propertyName,
+ String description,
+ boolean isCredential,
+ boolean isExpirationTimestamp) {
this.valueType = valueType;
this.propertyName = propertyName;
this.description = description;
this.isCredential = isCredential;
+ this.isExpirationTimestamp = isExpirationTimestamp;
}
public String getPropertyName() {
@@ -61,6 +88,6 @@ public enum CatalogAccessProperty {
}
public boolean isExpirationTimestamp() {
- return this == EXPIRATION_TIME;
+ return isExpirationTimestamp;
}
}
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/credentials/connection/ConnectionCredentials.java
b/polaris-core/src/main/java/org/apache/polaris/core/credentials/connection/ConnectionCredentials.java
index d9e5e549c..bc8cd3e95 100644
---
a/polaris-core/src/main/java/org/apache/polaris/core/credentials/connection/ConnectionCredentials.java
+++
b/polaris-core/src/main/java/org/apache/polaris/core/credentials/connection/ConnectionCredentials.java
@@ -71,8 +71,10 @@ public interface ConnectionCredentials {
default Builder put(CatalogAccessProperty key, String value) {
if (key.isExpirationTimestamp()) {
expiresAt(Instant.ofEpochMilli(Long.parseLong(value)));
- } else if (key.isCredential()) {
- putCredential(key.getPropertyName(), value);
+ }
+
+ if (key.isCredential()) {
+ return putCredential(key.getPropertyName(), value);
}
return this;
}
diff --git
a/runtime/service/src/main/java/org/apache/polaris/service/catalog/common/CatalogHandler.java
b/runtime/service/src/main/java/org/apache/polaris/service/catalog/common/CatalogHandler.java
index 6cfd16321..4be7a55f5 100644
---
a/runtime/service/src/main/java/org/apache/polaris/service/catalog/common/CatalogHandler.java
+++
b/runtime/service/src/main/java/org/apache/polaris/service/catalog/common/CatalogHandler.java
@@ -48,7 +48,6 @@ import
org.apache.polaris.core.persistence.resolver.PolarisResolutionManifest;
import org.apache.polaris.core.persistence.resolver.ResolutionManifestFactory;
import org.apache.polaris.core.persistence.resolver.ResolverPath;
import org.apache.polaris.core.persistence.resolver.ResolverStatus;
-import org.apache.polaris.core.secrets.UserSecretsManager;
import org.apache.polaris.service.types.PolicyIdentifier;
/**
@@ -64,7 +63,6 @@ public abstract class CatalogHandler {
protected final ResolutionManifestFactory resolutionManifestFactory;
protected final String catalogName;
protected final PolarisAuthorizer authorizer;
- protected final UserSecretsManager userSecretsManager;
protected final PolarisCredentialManager credentialManager;
protected final Instance<ExternalCatalogFactory> externalCatalogFactories;
@@ -81,7 +79,6 @@ public abstract class CatalogHandler {
SecurityContext securityContext,
String catalogName,
PolarisAuthorizer authorizer,
- UserSecretsManager userSecretsManager,
PolarisCredentialManager credentialManager,
Instance<ExternalCatalogFactory> externalCatalogFactories) {
this.diagnostics = diagnostics;
@@ -98,15 +95,10 @@ public abstract class CatalogHandler {
this.securityContext = securityContext;
this.polarisPrincipal = (PolarisPrincipal)
securityContext.getUserPrincipal();
this.authorizer = authorizer;
- this.userSecretsManager = userSecretsManager;
this.credentialManager = credentialManager;
this.externalCatalogFactories = externalCatalogFactories;
}
- protected UserSecretsManager getUserSecretsManager() {
- return userSecretsManager;
- }
-
protected PolarisCredentialManager getPolarisCredentialManager() {
return credentialManager;
}
diff --git
a/runtime/service/src/main/java/org/apache/polaris/service/catalog/generic/GenericTableCatalogAdapter.java
b/runtime/service/src/main/java/org/apache/polaris/service/catalog/generic/GenericTableCatalogAdapter.java
index ae16db5ba..5cf634aed 100644
---
a/runtime/service/src/main/java/org/apache/polaris/service/catalog/generic/GenericTableCatalogAdapter.java
+++
b/runtime/service/src/main/java/org/apache/polaris/service/catalog/generic/GenericTableCatalogAdapter.java
@@ -35,7 +35,6 @@ import org.apache.polaris.core.context.RealmContext;
import org.apache.polaris.core.credentials.PolarisCredentialManager;
import org.apache.polaris.core.persistence.PolarisMetaStoreManager;
import org.apache.polaris.core.persistence.resolver.ResolutionManifestFactory;
-import org.apache.polaris.core.secrets.UserSecretsManager;
import org.apache.polaris.service.catalog.CatalogPrefixParser;
import
org.apache.polaris.service.catalog.api.PolarisCatalogGenericTableApiService;
import org.apache.polaris.service.catalog.common.CatalogAdapter;
@@ -61,7 +60,6 @@ public class GenericTableCatalogAdapter
private final PolarisAuthorizer polarisAuthorizer;
private final ReservedProperties reservedProperties;
private final CatalogPrefixParser prefixParser;
- private final UserSecretsManager userSecretsManager;
private final PolarisCredentialManager polarisCredentialManager;
private final Instance<ExternalCatalogFactory> externalCatalogFactories;
@@ -75,7 +73,6 @@ public class GenericTableCatalogAdapter
PolarisAuthorizer polarisAuthorizer,
CatalogPrefixParser prefixParser,
ReservedProperties reservedProperties,
- UserSecretsManager userSecretsManager,
PolarisCredentialManager polarisCredentialManager,
@Any Instance<ExternalCatalogFactory> externalCatalogFactories) {
this.diagnostics = diagnostics;
@@ -87,7 +84,6 @@ public class GenericTableCatalogAdapter
this.polarisAuthorizer = polarisAuthorizer;
this.prefixParser = prefixParser;
this.reservedProperties = reservedProperties;
- this.userSecretsManager = userSecretsManager;
this.polarisCredentialManager = polarisCredentialManager;
this.externalCatalogFactories = externalCatalogFactories;
}
@@ -106,7 +102,6 @@ public class GenericTableCatalogAdapter
securityContext,
prefixParser.prefixToCatalogName(realmContext, prefix),
polarisAuthorizer,
- userSecretsManager,
polarisCredentialManager,
externalCatalogFactories);
}
diff --git
a/runtime/service/src/main/java/org/apache/polaris/service/catalog/generic/GenericTableCatalogHandler.java
b/runtime/service/src/main/java/org/apache/polaris/service/catalog/generic/GenericTableCatalogHandler.java
index 4564e117f..55d52070f 100644
---
a/runtime/service/src/main/java/org/apache/polaris/service/catalog/generic/GenericTableCatalogHandler.java
+++
b/runtime/service/src/main/java/org/apache/polaris/service/catalog/generic/GenericTableCatalogHandler.java
@@ -40,7 +40,6 @@ import org.apache.polaris.core.entity.PolarisEntitySubType;
import org.apache.polaris.core.entity.table.GenericTableEntity;
import org.apache.polaris.core.persistence.PolarisMetaStoreManager;
import org.apache.polaris.core.persistence.resolver.ResolutionManifestFactory;
-import org.apache.polaris.core.secrets.UserSecretsManager;
import org.apache.polaris.service.catalog.common.CatalogHandler;
import org.apache.polaris.service.types.GenericTable;
import org.apache.polaris.service.types.ListGenericTablesResponse;
@@ -63,7 +62,6 @@ public class GenericTableCatalogHandler extends
CatalogHandler {
SecurityContext securityContext,
String catalogName,
PolarisAuthorizer authorizer,
- UserSecretsManager userSecretsManager,
PolarisCredentialManager polarisCredentialManager,
Instance<ExternalCatalogFactory> externalCatalogFactories) {
super(
@@ -73,7 +71,6 @@ public class GenericTableCatalogHandler extends
CatalogHandler {
securityContext,
catalogName,
authorizer,
- userSecretsManager,
polarisCredentialManager,
externalCatalogFactories);
this.metaStoreManager = metaStoreManager;
@@ -104,7 +101,7 @@ public class GenericTableCatalogHandler extends
CatalogHandler {
federatedCatalog =
externalCatalogFactory
.get()
- .createGenericCatalog(connectionConfigInfoDpo,
getUserSecretsManager());
+ .createGenericCatalog(connectionConfigInfoDpo,
getPolarisCredentialManager());
} else {
throw new UnsupportedOperationException(
"External catalog factory for type '" + connectionType + "' is
unavailable.");
diff --git
a/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogAdapter.java
b/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogAdapter.java
index 0f622cb0b..90813a221 100644
---
a/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogAdapter.java
+++
b/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogAdapter.java
@@ -77,7 +77,6 @@ import
org.apache.polaris.core.persistence.resolver.ResolverFactory;
import org.apache.polaris.core.persistence.resolver.ResolverStatus;
import org.apache.polaris.core.rest.PolarisEndpoints;
import org.apache.polaris.core.rest.PolarisResourcePaths;
-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;
@@ -149,7 +148,6 @@ public class IcebergCatalogAdapter
private final ResolutionManifestFactory resolutionManifestFactory;
private final ResolverFactory resolverFactory;
private final PolarisMetaStoreManager metaStoreManager;
- private final UserSecretsManager userSecretsManager;
private final PolarisCredentialManager credentialManager;
private final PolarisAuthorizer polarisAuthorizer;
private final CatalogPrefixParser prefixParser;
@@ -168,7 +166,6 @@ public class IcebergCatalogAdapter
ResolverFactory resolverFactory,
ResolutionManifestFactory resolutionManifestFactory,
PolarisMetaStoreManager metaStoreManager,
- UserSecretsManager userSecretsManager,
PolarisCredentialManager credentialManager,
PolarisAuthorizer polarisAuthorizer,
CatalogPrefixParser prefixParser,
@@ -185,7 +182,6 @@ public class IcebergCatalogAdapter
this.resolutionManifestFactory = resolutionManifestFactory;
this.resolverFactory = resolverFactory;
this.metaStoreManager = metaStoreManager;
- this.userSecretsManager = userSecretsManager;
this.credentialManager = credentialManager;
this.polarisAuthorizer = polarisAuthorizer;
this.prefixParser = prefixParser;
@@ -225,7 +221,6 @@ public class IcebergCatalogAdapter
callContext,
resolutionManifestFactory,
metaStoreManager,
- userSecretsManager,
credentialManager,
securityContext,
catalogFactory,
diff --git
a/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogHandler.java
b/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogHandler.java
index 07af147d7..c3b92971a 100644
---
a/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogHandler.java
+++
b/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogHandler.java
@@ -99,7 +99,6 @@ import
org.apache.polaris.core.persistence.dao.entity.EntityWithPath;
import org.apache.polaris.core.persistence.pagination.Page;
import org.apache.polaris.core.persistence.pagination.PageToken;
import org.apache.polaris.core.persistence.resolver.ResolutionManifestFactory;
-import org.apache.polaris.core.secrets.UserSecretsManager;
import org.apache.polaris.core.storage.AccessConfig;
import org.apache.polaris.core.storage.PolarisStorageActions;
import org.apache.polaris.core.storage.StorageUtil;
@@ -156,7 +155,6 @@ public class IcebergCatalogHandler extends CatalogHandler
implements AutoCloseab
CallContext callContext,
ResolutionManifestFactory resolutionManifestFactory,
PolarisMetaStoreManager metaStoreManager,
- UserSecretsManager userSecretsManager,
PolarisCredentialManager credentialManager,
SecurityContext securityContext,
CallContextCatalogFactory catalogFactory,
@@ -174,7 +172,6 @@ public class IcebergCatalogHandler extends CatalogHandler
implements AutoCloseab
securityContext,
catalogName,
authorizer,
- userSecretsManager,
credentialManager,
externalCatalogFactories);
this.metaStoreManager = metaStoreManager;
@@ -258,10 +255,7 @@ public class IcebergCatalogHandler extends CatalogHandler
implements AutoCloseab
federatedCatalog =
externalCatalogFactory
.get()
- .createCatalog(
- connectionConfigInfoDpo,
- getUserSecretsManager(),
- getPolarisCredentialManager());
+ .createCatalog(connectionConfigInfoDpo,
getPolarisCredentialManager());
} else {
throw new UnsupportedOperationException(
"External catalog factory for type '" + connectionType + "' is
unavailable.");
diff --git
a/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergRESTExternalCatalogFactory.java
b/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergRESTExternalCatalogFactory.java
index 7167e382e..de12bed4c 100644
---
a/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergRESTExternalCatalogFactory.java
+++
b/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergRESTExternalCatalogFactory.java
@@ -30,7 +30,6 @@ import
org.apache.polaris.core.connection.ConnectionConfigInfoDpo;
import org.apache.polaris.core.connection.ConnectionType;
import
org.apache.polaris.core.connection.iceberg.IcebergRestConnectionConfigInfoDpo;
import org.apache.polaris.core.credentials.PolarisCredentialManager;
-import org.apache.polaris.core.secrets.UserSecretsManager;
/** Factory class for creating an Iceberg REST catalog handle based on
connection configuration. */
@ApplicationScoped
@@ -39,9 +38,7 @@ public class IcebergRESTExternalCatalogFactory implements
ExternalCatalogFactory
@Override
public Catalog createCatalog(
- ConnectionConfigInfoDpo connectionConfig,
- UserSecretsManager userSecretsManager,
- PolarisCredentialManager polarisCredentialManager) {
+ ConnectionConfigInfoDpo connectionConfig, PolarisCredentialManager
polarisCredentialManager) {
if (!(connectionConfig instanceof IcebergRestConnectionConfigInfoDpo
icebergConfig)) {
throw new IllegalArgumentException(
"Expected IcebergRestConnectionConfigInfoDpo but got: "
@@ -59,14 +56,14 @@ public class IcebergRESTExternalCatalogFactory implements
ExternalCatalogFactory
federatedCatalog.initialize(
icebergConfig.getRemoteCatalogName(),
- connectionConfig.asIcebergCatalogProperties(userSecretsManager,
polarisCredentialManager));
+ connectionConfig.asIcebergCatalogProperties(polarisCredentialManager));
return federatedCatalog;
}
@Override
public GenericTableCatalog createGenericCatalog(
- ConnectionConfigInfoDpo connectionConfig, UserSecretsManager
userSecretsManager) {
+ ConnectionConfigInfoDpo connectionConfig, PolarisCredentialManager
polarisCredentialManager) {
// TODO implement
throw new UnsupportedOperationException(
"Generic table federation to this catalog is not supported.");
diff --git
a/runtime/service/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogAdapter.java
b/runtime/service/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogAdapter.java
index 545848065..aa5e75fee 100644
---
a/runtime/service/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogAdapter.java
+++
b/runtime/service/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogAdapter.java
@@ -37,7 +37,6 @@ import
org.apache.polaris.core.credentials.PolarisCredentialManager;
import org.apache.polaris.core.persistence.PolarisMetaStoreManager;
import org.apache.polaris.core.persistence.resolver.ResolutionManifestFactory;
import org.apache.polaris.core.policy.PolicyType;
-import org.apache.polaris.core.secrets.UserSecretsManager;
import org.apache.polaris.service.catalog.CatalogPrefixParser;
import org.apache.polaris.service.catalog.api.PolarisCatalogPolicyApiService;
import org.apache.polaris.service.catalog.common.CatalogAdapter;
@@ -64,7 +63,6 @@ public class PolicyCatalogAdapter implements
PolarisCatalogPolicyApiService, Cat
private final PolarisMetaStoreManager metaStoreManager;
private final PolarisAuthorizer polarisAuthorizer;
private final CatalogPrefixParser prefixParser;
- private final UserSecretsManager userSecretsManager;
private final PolarisCredentialManager polarisCredentialManager;
private final Instance<ExternalCatalogFactory> externalCatalogFactories;
@@ -77,7 +75,6 @@ public class PolicyCatalogAdapter implements
PolarisCatalogPolicyApiService, Cat
PolarisMetaStoreManager metaStoreManager,
PolarisAuthorizer polarisAuthorizer,
CatalogPrefixParser prefixParser,
- UserSecretsManager userSecretsManager,
PolarisCredentialManager polarisCredentialManager,
@Any Instance<ExternalCatalogFactory> externalCatalogFactories) {
this.diagnostics = diagnostics;
@@ -88,7 +85,6 @@ public class PolicyCatalogAdapter implements
PolarisCatalogPolicyApiService, Cat
this.metaStoreManager = metaStoreManager;
this.polarisAuthorizer = polarisAuthorizer;
this.prefixParser = prefixParser;
- this.userSecretsManager = userSecretsManager;
this.polarisCredentialManager = polarisCredentialManager;
this.externalCatalogFactories = externalCatalogFactories;
}
@@ -106,7 +102,6 @@ public class PolicyCatalogAdapter implements
PolarisCatalogPolicyApiService, Cat
securityContext,
prefixParser.prefixToCatalogName(realmContext, prefix),
polarisAuthorizer,
- userSecretsManager,
polarisCredentialManager,
externalCatalogFactories);
}
diff --git
a/runtime/service/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogHandler.java
b/runtime/service/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogHandler.java
index 0c92c816f..504b0a8d3 100644
---
a/runtime/service/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogHandler.java
+++
b/runtime/service/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogHandler.java
@@ -46,7 +46,6 @@ import
org.apache.polaris.core.persistence.resolver.ResolverPath;
import org.apache.polaris.core.persistence.resolver.ResolverStatus;
import org.apache.polaris.core.policy.PolicyType;
import org.apache.polaris.core.policy.exceptions.NoSuchPolicyException;
-import org.apache.polaris.core.secrets.UserSecretsManager;
import org.apache.polaris.service.catalog.common.CatalogHandler;
import org.apache.polaris.service.types.AttachPolicyRequest;
import org.apache.polaris.service.types.CreatePolicyRequest;
@@ -72,7 +71,6 @@ public class PolicyCatalogHandler extends CatalogHandler {
SecurityContext securityContext,
String catalogName,
PolarisAuthorizer authorizer,
- UserSecretsManager userSecretsManager,
PolarisCredentialManager polarisCredentialManager,
Instance<ExternalCatalogFactory> externalCatalogFactories) {
super(
@@ -82,7 +80,6 @@ public class PolicyCatalogHandler extends CatalogHandler {
securityContext,
catalogName,
authorizer,
- userSecretsManager,
polarisCredentialManager,
externalCatalogFactories);
this.metaStoreManager = metaStoreManager;
diff --git
a/runtime/service/src/main/java/org/apache/polaris/service/config/ProductionReadinessChecks.java
b/runtime/service/src/main/java/org/apache/polaris/service/config/ProductionReadinessChecks.java
index 1c9cedd4c..51a1e1008 100644
---
a/runtime/service/src/main/java/org/apache/polaris/service/config/ProductionReadinessChecks.java
+++
b/runtime/service/src/main/java/org/apache/polaris/service/config/ProductionReadinessChecks.java
@@ -33,6 +33,7 @@ import java.util.List;
import org.apache.polaris.core.config.FeatureConfiguration;
import org.apache.polaris.core.config.ProductionReadinessCheck;
import org.apache.polaris.core.config.ProductionReadinessCheck.Error;
+import
org.apache.polaris.core.credentials.connection.ConnectionCredentialVendor;
import org.apache.polaris.core.persistence.MetaStoreManagerFactory;
import org.apache.polaris.service.auth.AuthenticationConfiguration;
import
org.apache.polaris.service.auth.AuthenticationRealmConfiguration.TokenBrokerConfiguration.RSAKeyPairConfiguration;
@@ -42,6 +43,7 @@ import
org.apache.polaris.service.catalog.validation.IcebergPropertiesValidation
import org.apache.polaris.service.context.DefaultRealmContextResolver;
import org.apache.polaris.service.context.RealmContextResolver;
import org.apache.polaris.service.context.TestRealmContextResolver;
+import org.apache.polaris.service.credentials.connection.AuthType;
import org.apache.polaris.service.events.listeners.PolarisEventListener;
import org.apache.polaris.service.events.listeners.TestPolarisEventListener;
import org.apache.polaris.service.metrics.MetricsConfiguration;
@@ -335,4 +337,64 @@ public class ProductionReadinessChecks {
? ProductionReadinessCheck.OK
: ProductionReadinessCheck.of(errors.toArray(new Error[0]));
}
+
+ @Produces
+ @SuppressWarnings("unchecked")
+ public ProductionReadinessCheck checkConnectionCredentialVendors(
+ Instance<ConnectionCredentialVendor> credentialVendors,
+ FeaturesConfiguration featureConfiguration) {
+ var mapper = new ObjectMapper();
+ var defaults = featureConfiguration.parseDefaults(mapper);
+ var realmOverrides = featureConfiguration.parseRealmOverrides(mapper);
+
+ var federationKey = FeatureConfiguration.ENABLE_CATALOG_FEDERATION.key();
+ var authTypesKey =
FeatureConfiguration.SUPPORTED_EXTERNAL_CATALOG_AUTHENTICATION_TYPES.key();
+ var federationEnabled =
+ Boolean.parseBoolean(defaults.getOrDefault(federationKey,
false).toString());
+ var defaultAuthTypes = (List<String>) defaults.getOrDefault(authTypesKey,
List.of());
+
+ var allAuthTypes = new java.util.HashSet<String>();
+ if (federationEnabled) allAuthTypes.addAll(defaultAuthTypes);
+
+ realmOverrides.forEach(
+ (id, overrides) -> {
+ if (Boolean.parseBoolean(
+ overrides.getOrDefault(federationKey,
federationEnabled).toString())) {
+ allAuthTypes.addAll(
+ (List<String>)
+ (overrides.containsKey(authTypesKey)
+ ? overrides.get(authTypesKey)
+ : defaultAuthTypes));
+ }
+ });
+
+ var errors = new ArrayList<Error>();
+ for (var name : allAuthTypes) {
+ try {
+ var type =
org.apache.polaris.core.connection.AuthenticationType.valueOf(name);
+ if
(credentialVendors.select(AuthType.Literal.of(type)).isUnsatisfied()) {
+ errors.add(
+ Error.ofSevere(
+ format(
+ "Catalog federation is enabled but no
ConnectionCredentialVendor found for "
+ + "authentication type '%s'. External catalog
connections using this "
+ + "authentication type will fail.",
+ type),
+ format("polaris.features.\"%s\"", authTypesKey)));
+ }
+ } catch (IllegalArgumentException e) {
+ errors.add(
+ Error.ofSevere(
+ format(
+ "Invalid authentication type '%s' in
SUPPORTED_EXTERNAL_CATALOG_AUTHENTICATION_TYPES "
+ + "configuration.",
+ name),
+ format("polaris.features.\"%s\"", authTypesKey)));
+ }
+ }
+
+ return errors.isEmpty()
+ ? ProductionReadinessCheck.OK
+ : ProductionReadinessCheck.of(errors.toArray(new Error[0]));
+ }
}
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/connection/iceberg/IcebergCatalogPropertiesProvider.java
b/runtime/service/src/main/java/org/apache/polaris/service/credentials/CredentialVendorPriorities.java
similarity index 50%
copy from
polaris-core/src/main/java/org/apache/polaris/core/connection/iceberg/IcebergCatalogPropertiesProvider.java
copy to
runtime/service/src/main/java/org/apache/polaris/service/credentials/CredentialVendorPriorities.java
index e17218f25..2325a90a4 100644
---
a/polaris-core/src/main/java/org/apache/polaris/core/connection/iceberg/IcebergCatalogPropertiesProvider.java
+++
b/runtime/service/src/main/java/org/apache/polaris/service/credentials/CredentialVendorPriorities.java
@@ -16,21 +16,21 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.polaris.core.connection.iceberg;
+package org.apache.polaris.service.credentials;
-import jakarta.annotation.Nonnull;
-import java.util.Map;
-import org.apache.polaris.core.credentials.PolarisCredentialManager;
-import org.apache.polaris.core.secrets.UserSecretsManager;
+import
org.apache.polaris.core.credentials.connection.ConnectionCredentialVendor;
/**
- * Configuration wrappers which ultimately translate their contents into
Iceberg properties and
- * which may hold other nested configuration wrapper objects implement this
interface to allow
- * delegating type-specific configuration translation logic to subclasses
instead of needing to
- * expose the internals of deeply nested configuration objects to a visitor
class.
+ * Priority constants for credential vendor implementations. e.g., {@link
+ * ConnectionCredentialVendor}
+ *
+ * <p>Higher priority values are selected first when multiple vendors support
the same
+ * authentication or storage type. Default built-in implementations use {@code
DEFAULT}. Custom
+ * implementations should use a higher priority value to override defaults.
*/
-public interface IcebergCatalogPropertiesProvider {
- @Nonnull
- Map<String, String> asIcebergCatalogProperties(
- UserSecretsManager secretsManager, PolarisCredentialManager
credentialManager);
+public final class CredentialVendorPriorities {
+ /** Priority for default built-in vendor implementations. */
+ public static final int DEFAULT = 100;
+
+ private CredentialVendorPriorities() {}
}
diff --git
a/runtime/service/src/main/java/org/apache/polaris/service/credentials/connection/AuthType.java
b/runtime/service/src/main/java/org/apache/polaris/service/credentials/connection/AuthType.java
index ada9825bf..832858463 100644
---
a/runtime/service/src/main/java/org/apache/polaris/service/credentials/connection/AuthType.java
+++
b/runtime/service/src/main/java/org/apache/polaris/service/credentials/connection/AuthType.java
@@ -39,7 +39,7 @@ import
org.apache.polaris.core.credentials.connection.ConnectionCredentialVendor
* <pre>{@code
* @ApplicationScoped
* @AuthType(AuthenticationType.SIGV4)
- * @Priority(100)
+ * @Priority(CredentialVendorPriorities.DEFAULT)
* public class SigV4ConnectionCredentialVendor implements
ConnectionCredentialVendor {
* // AWS STS AssumeRole logic for SigV4 authentication
* }
diff --git
a/runtime/service/src/main/java/org/apache/polaris/service/credentials/connection/BearerConnectionCredentialVendor.java
b/runtime/service/src/main/java/org/apache/polaris/service/credentials/connection/BearerConnectionCredentialVendor.java
new file mode 100644
index 000000000..75e1551f7
--- /dev/null
+++
b/runtime/service/src/main/java/org/apache/polaris/service/credentials/connection/BearerConnectionCredentialVendor.java
@@ -0,0 +1,85 @@
+/*
+ * 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.credentials.connection;
+
+import com.google.common.base.Preconditions;
+import jakarta.annotation.Nonnull;
+import jakarta.annotation.Priority;
+import jakarta.enterprise.context.RequestScoped;
+import jakarta.inject.Inject;
+import org.apache.polaris.core.connection.AuthenticationType;
+import org.apache.polaris.core.connection.BearerAuthenticationParametersDpo;
+import org.apache.polaris.core.connection.ConnectionConfigInfoDpo;
+import org.apache.polaris.core.credentials.connection.CatalogAccessProperty;
+import
org.apache.polaris.core.credentials.connection.ConnectionCredentialVendor;
+import org.apache.polaris.core.credentials.connection.ConnectionCredentials;
+import org.apache.polaris.core.secrets.UserSecretsManager;
+import org.apache.polaris.service.credentials.CredentialVendorPriorities;
+
+/**
+ * Connection credential vendor for Bearer token authentication.
+ *
+ * <p>This vendor handles Bearer token authentication by reading the bearer
token from the secrets
+ * manager and providing it for connecting to external catalogs.
+ *
+ * <p>The vendor provides only the bearer token. When connecting to the remote
catalog, Iceberg SDK
+ * will use this token as-is in HTTP Authorization headers. Bearer tokens
typically have a limited
+ * lifetime and should be refreshed by the user when they expire.
+ *
+ * <p>This is the default implementation with {@code
@Priority(CredentialVendorPriorities.DEFAULT)}.
+ * Custom implementations can override this by providing a higher priority
value.
+ */
+@RequestScoped
+@AuthType(AuthenticationType.BEARER)
+@Priority(CredentialVendorPriorities.DEFAULT)
+public class BearerConnectionCredentialVendor implements
ConnectionCredentialVendor {
+
+ private final UserSecretsManager secretsManager;
+
+ @Inject
+ public BearerConnectionCredentialVendor(UserSecretsManager secretsManager) {
+ this.secretsManager = secretsManager;
+ }
+
+ @Override
+ public @Nonnull ConnectionCredentials getConnectionCredentials(
+ @Nonnull ConnectionConfigInfoDpo connectionConfig) {
+
+ // Validate authentication parameters type
+ Preconditions.checkArgument(
+ connectionConfig.getAuthenticationParameters() instanceof
BearerAuthenticationParametersDpo,
+ "Expected BearerAuthenticationParametersDpo, got: %s",
+ connectionConfig.getAuthenticationParameters().getClass().getName());
+
+ BearerAuthenticationParametersDpo bearerParams =
+ (BearerAuthenticationParametersDpo)
connectionConfig.getAuthenticationParameters();
+
+ // Read the bearer token from secrets manager
+ String bearerToken =
secretsManager.readSecret(bearerParams.getBearerTokenReference());
+
+ // Return the bearer token with expiration
+ // Bearer tokens don't expire from Polaris's perspective - set expiration
to Long.MAX_VALUE
+ // to indicate infinite validity. The token itself may have an expiration,
but that's managed
+ // by the token issuer, not Polaris.
+ return ConnectionCredentials.builder()
+ .put(CatalogAccessProperty.BEARER_TOKEN, bearerToken)
+ .put(CatalogAccessProperty.EXPIRES_AT_MS,
String.valueOf(Long.MAX_VALUE))
+ .build();
+ }
+}
diff --git
a/runtime/service/src/main/java/org/apache/polaris/service/credentials/connection/ImplicitConnectionCredentialVendor.java
b/runtime/service/src/main/java/org/apache/polaris/service/credentials/connection/ImplicitConnectionCredentialVendor.java
new file mode 100644
index 000000000..ecef5f5bf
--- /dev/null
+++
b/runtime/service/src/main/java/org/apache/polaris/service/credentials/connection/ImplicitConnectionCredentialVendor.java
@@ -0,0 +1,68 @@
+/*
+ * 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.credentials.connection;
+
+import com.google.common.base.Preconditions;
+import jakarta.annotation.Nonnull;
+import jakarta.annotation.Priority;
+import jakarta.enterprise.context.RequestScoped;
+import org.apache.polaris.core.connection.AuthenticationType;
+import org.apache.polaris.core.connection.ConnectionConfigInfoDpo;
+import org.apache.polaris.core.connection.ImplicitAuthenticationParametersDpo;
+import org.apache.polaris.core.credentials.connection.CatalogAccessProperty;
+import
org.apache.polaris.core.credentials.connection.ConnectionCredentialVendor;
+import org.apache.polaris.core.credentials.connection.ConnectionCredentials;
+import org.apache.polaris.service.credentials.CredentialVendorPriorities;
+
+/**
+ * Connection credential vendor for Implicit (no authentication) type.
+ *
+ * <p>This vendor handles implicit authentication where no credentials are
required to connect to
+ * external catalogs.
+ *
+ * <p>The vendor provides no credentials. When connecting to the remote
catalog, Iceberg SDK will
+ * not send any authentication headers or credentials. This is typically used
for publicly
+ * accessible catalogs or when authentication is handled externally.
+ *
+ * <p>This is the default implementation with {@code
@Priority(CredentialVendorPriorities.DEFAULT)}.
+ * Custom implementations can override this by providing a higher priority
value.
+ */
+@RequestScoped
+@AuthType(AuthenticationType.IMPLICIT)
+@Priority(CredentialVendorPriorities.DEFAULT)
+public class ImplicitConnectionCredentialVendor implements
ConnectionCredentialVendor {
+
+ @Override
+ public @Nonnull ConnectionCredentials getConnectionCredentials(
+ @Nonnull ConnectionConfigInfoDpo connectionConfig) {
+
+ // Validate authentication parameters type
+ Preconditions.checkArgument(
+ connectionConfig.getAuthenticationParameters()
+ instanceof ImplicitAuthenticationParametersDpo,
+ "Expected ImplicitAuthenticationParametersDpo, got: %s",
+ connectionConfig.getAuthenticationParameters().getClass().getName());
+
+ // Return empty credentials for implicit (no authentication) type with
expiration
+ // Set expiration to Long.MAX_VALUE to indicate infinite validity
+ return ConnectionCredentials.builder()
+ .put(CatalogAccessProperty.EXPIRES_AT_MS,
String.valueOf(Long.MAX_VALUE))
+ .build();
+ }
+}
diff --git
a/runtime/service/src/main/java/org/apache/polaris/service/credentials/connection/OAuthClientCredentialVendor.java
b/runtime/service/src/main/java/org/apache/polaris/service/credentials/connection/OAuthClientCredentialVendor.java
new file mode 100644
index 000000000..993613259
--- /dev/null
+++
b/runtime/service/src/main/java/org/apache/polaris/service/credentials/connection/OAuthClientCredentialVendor.java
@@ -0,0 +1,92 @@
+/*
+ * 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.credentials.connection;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Preconditions;
+import jakarta.annotation.Nonnull;
+import jakarta.annotation.Priority;
+import jakarta.enterprise.context.RequestScoped;
+import jakarta.inject.Inject;
+import org.apache.polaris.core.connection.AuthenticationType;
+import org.apache.polaris.core.connection.ConnectionConfigInfoDpo;
+import org.apache.polaris.core.connection.OAuthClientCredentialsParametersDpo;
+import org.apache.polaris.core.credentials.connection.CatalogAccessProperty;
+import
org.apache.polaris.core.credentials.connection.ConnectionCredentialVendor;
+import org.apache.polaris.core.credentials.connection.ConnectionCredentials;
+import org.apache.polaris.core.secrets.UserSecretsManager;
+import org.apache.polaris.service.credentials.CredentialVendorPriorities;
+
+/**
+ * Connection credential vendor for OAuth 2.0 Client Credentials
authentication.
+ *
+ * <p>This vendor handles OAuth 2.0 client credentials flow by reading the
client secret from the
+ * secrets manager and formatting it as an OAuth credential for connecting to
external catalogs.
+ *
+ * <p>The vendor provides only the OAuth credential (formatted as
"clientId:clientSecret"). When
+ * connecting to the remote catalog, Iceberg SDK will use this credential to
fetch OAuth tokens
+ * automatically.
+ *
+ * <p>This is the default implementation with {@code
@Priority(CredentialVendorPriorities.DEFAULT)}.
+ * Custom implementations can override this by providing a higher priority
value.
+ */
+@RequestScoped
+@AuthType(AuthenticationType.OAUTH)
+@Priority(CredentialVendorPriorities.DEFAULT)
+public class OAuthClientCredentialVendor implements ConnectionCredentialVendor
{
+
+ private static final Joiner COLON_JOINER = Joiner.on(":");
+
+ private final UserSecretsManager secretsManager;
+
+ @Inject
+ public OAuthClientCredentialVendor(UserSecretsManager secretsManager) {
+ this.secretsManager = secretsManager;
+ }
+
+ @Override
+ public @Nonnull ConnectionCredentials getConnectionCredentials(
+ @Nonnull ConnectionConfigInfoDpo connectionConfig) {
+
+ // Validate authentication parameters type
+ Preconditions.checkArgument(
+ connectionConfig.getAuthenticationParameters()
+ instanceof OAuthClientCredentialsParametersDpo,
+ "Expected OAuthClientCredentialsParametersDpo, got: %s",
+ connectionConfig.getAuthenticationParameters().getClass().getName());
+
+ OAuthClientCredentialsParametersDpo oauthParams =
+ (OAuthClientCredentialsParametersDpo)
connectionConfig.getAuthenticationParameters();
+
+ // Read the client secret from secrets manager
+ String clientSecret =
secretsManager.readSecret(oauthParams.getClientSecretReference());
+
+ // Format credential as "clientId:clientSecret"
+ String credential = COLON_JOINER.join(oauthParams.getClientId(),
clientSecret);
+
+ // Return the OAuth credential with expiration
+ // OAuth credentials don't expire from Polaris's perspective - set
expiration to Long.MAX_VALUE
+ // to indicate infinite validity. If the credential expires, users need to
update the catalog
+ // entity to rotate the credential.
+ return ConnectionCredentials.builder()
+ .put(CatalogAccessProperty.OAUTH2_CREDENTIAL, credential)
+ .put(CatalogAccessProperty.EXPIRES_AT_MS,
String.valueOf(Long.MAX_VALUE))
+ .build();
+ }
+}
diff --git
a/runtime/service/src/main/java/org/apache/polaris/service/credentials/connection/SigV4ConnectionCredentialVendor.java
b/runtime/service/src/main/java/org/apache/polaris/service/credentials/connection/SigV4ConnectionCredentialVendor.java
index d85d318cf..e0e230e4b 100644
---
a/runtime/service/src/main/java/org/apache/polaris/service/credentials/connection/SigV4ConnectionCredentialVendor.java
+++
b/runtime/service/src/main/java/org/apache/polaris/service/credentials/connection/SigV4ConnectionCredentialVendor.java
@@ -22,7 +22,7 @@ import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import jakarta.annotation.Nonnull;
import jakarta.annotation.Priority;
-import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Inject;
import java.util.Optional;
import org.apache.polaris.core.connection.AuthenticationType;
@@ -35,6 +35,7 @@ import
org.apache.polaris.core.identity.credential.AwsIamServiceIdentityCredenti
import org.apache.polaris.core.identity.credential.ServiceIdentityCredential;
import org.apache.polaris.core.identity.provider.ServiceIdentityProvider;
import org.apache.polaris.core.storage.aws.StsClientProvider;
+import org.apache.polaris.service.credentials.CredentialVendorPriorities;
import software.amazon.awssdk.services.sts.StsClient;
import software.amazon.awssdk.services.sts.model.AssumeRoleRequest;
import software.amazon.awssdk.services.sts.model.AssumeRoleResponse;
@@ -55,12 +56,12 @@ import
software.amazon.awssdk.services.sts.model.AssumeRoleResponse;
* <li>Returns temporary access key, secret key, and session token
* </ol>
*
- * <p>This is the default implementation with {@code @Priority(100)}. Custom
implementations can
- * override this by providing a higher priority value.
+ * <p>This is the default implementation with {@code
@Priority(CredentialVendorPriorities.DEFAULT)}.
+ * Custom implementations can override this by providing a higher priority
value.
*/
-@ApplicationScoped
+@RequestScoped
@AuthType(AuthenticationType.SIGV4)
-@Priority(100)
+@Priority(CredentialVendorPriorities.DEFAULT)
public class SigV4ConnectionCredentialVendor implements
ConnectionCredentialVendor {
private static final String DEFAULT_ROLE_SESSION_NAME = "polaris";
@@ -124,17 +125,16 @@ public class SigV4ConnectionCredentialVendor implements
ConnectionCredentialVend
// Build connection credentials from AWS temporary credentials
ConnectionCredentials.Builder builder = ConnectionCredentials.builder();
- builder.putCredential(
- CatalogAccessProperty.AWS_ACCESS_KEY_ID.getPropertyName(),
- response.credentials().accessKeyId());
- builder.putCredential(
- CatalogAccessProperty.AWS_SECRET_ACCESS_KEY.getPropertyName(),
- response.credentials().secretAccessKey());
- builder.putCredential(
- CatalogAccessProperty.AWS_SESSION_TOKEN.getPropertyName(),
- response.credentials().sessionToken());
+ builder.put(CatalogAccessProperty.AWS_ACCESS_KEY_ID,
response.credentials().accessKeyId());
+ builder.put(
+ CatalogAccessProperty.AWS_SECRET_ACCESS_KEY,
response.credentials().secretAccessKey());
+ builder.put(CatalogAccessProperty.AWS_SESSION_TOKEN,
response.credentials().sessionToken());
Optional.ofNullable(response.credentials().expiration())
- .ifPresent(expiration -> builder.expiresAt(expiration));
+ .ifPresent(
+ expiration ->
+ builder.put(
+ CatalogAccessProperty.AWS_SESSION_TOKEN_EXPIRES_AT_MS,
+ String.valueOf(expiration.toEpochMilli())));
return builder.build();
}
diff --git
a/runtime/service/src/test/java/org/apache/polaris/service/catalog/generic/PolarisGenericTableCatalogHandlerAuthzTest.java
b/runtime/service/src/test/java/org/apache/polaris/service/catalog/generic/PolarisGenericTableCatalogHandlerAuthzTest.java
index 00c87f899..b1296177b 100644
---
a/runtime/service/src/test/java/org/apache/polaris/service/catalog/generic/PolarisGenericTableCatalogHandlerAuthzTest.java
+++
b/runtime/service/src/test/java/org/apache/polaris/service/catalog/generic/PolarisGenericTableCatalogHandlerAuthzTest.java
@@ -54,7 +54,6 @@ public class PolarisGenericTableCatalogHandlerAuthzTest
extends PolarisAuthzTest
catalogName,
polarisAuthorizer,
null,
- null,
null);
}
diff --git
a/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogHandlerAuthzTest.java
b/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogHandlerAuthzTest.java
index 5148bf0ef..568c83c7d 100644
---
a/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogHandlerAuthzTest.java
+++
b/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogHandlerAuthzTest.java
@@ -126,7 +126,6 @@ public class IcebergCatalogHandlerAuthzTest extends
PolarisAuthzTestBase {
callContext,
resolutionManifestFactory,
metaStoreManager,
- userSecretsManager,
credentialManager,
securityContext(authenticatedPrincipal),
factory,
@@ -267,7 +266,6 @@ public class IcebergCatalogHandlerAuthzTest extends
PolarisAuthzTestBase {
callContext,
resolutionManifestFactory,
metaStoreManager,
- userSecretsManager,
credentialManager,
securityContext(authenticatedPrincipal),
callContextCatalogFactory,
@@ -306,7 +304,6 @@ public class IcebergCatalogHandlerAuthzTest extends
PolarisAuthzTestBase {
callContext,
resolutionManifestFactory,
metaStoreManager,
- userSecretsManager,
credentialManager,
securityContext(authenticatedPrincipal1),
callContextCatalogFactory,
@@ -1187,7 +1184,6 @@ public class IcebergCatalogHandlerAuthzTest extends
PolarisAuthzTestBase {
mockCallContext,
resolutionManifestFactory,
metaStoreManager,
- userSecretsManager,
credentialManager,
securityContext(authenticatedPrincipal),
factory,
diff --git
a/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogHandlerFineGrainedDisabledTest.java
b/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogHandlerFineGrainedDisabledTest.java
index 367f9e6d7..e6434020e 100644
---
a/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogHandlerFineGrainedDisabledTest.java
+++
b/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogHandlerFineGrainedDisabledTest.java
@@ -62,7 +62,6 @@ public class IcebergCatalogHandlerFineGrainedDisabledTest
extends PolarisAuthzTe
callContext,
resolutionManifestFactory,
metaStoreManager,
- userSecretsManager,
credentialManager,
securityContext(authenticatedPrincipal),
callContextCatalogFactory,
diff --git
a/runtime/service/src/test/java/org/apache/polaris/service/catalog/policy/PolicyCatalogHandlerAuthzTest.java
b/runtime/service/src/test/java/org/apache/polaris/service/catalog/policy/PolicyCatalogHandlerAuthzTest.java
index 9a85496db..990a9cff2 100644
---
a/runtime/service/src/test/java/org/apache/polaris/service/catalog/policy/PolicyCatalogHandlerAuthzTest.java
+++
b/runtime/service/src/test/java/org/apache/polaris/service/catalog/policy/PolicyCatalogHandlerAuthzTest.java
@@ -59,7 +59,6 @@ public class PolicyCatalogHandlerAuthzTest extends
PolarisAuthzTestBase {
catalogName,
polarisAuthorizer,
null,
- null,
null);
}
diff --git
a/runtime/service/src/test/java/org/apache/polaris/service/credentials/connection/BearerConnectionCredentialVendorTest.java
b/runtime/service/src/test/java/org/apache/polaris/service/credentials/connection/BearerConnectionCredentialVendorTest.java
new file mode 100644
index 000000000..d8c014142
--- /dev/null
+++
b/runtime/service/src/test/java/org/apache/polaris/service/credentials/connection/BearerConnectionCredentialVendorTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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.credentials.connection;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.time.Instant;
+import java.util.Map;
+import org.apache.polaris.core.connection.AuthenticationParametersDpo;
+import org.apache.polaris.core.connection.BearerAuthenticationParametersDpo;
+import org.apache.polaris.core.connection.ConnectionConfigInfoDpo;
+import
org.apache.polaris.core.connection.iceberg.IcebergRestConnectionConfigInfoDpo;
+import org.apache.polaris.core.credentials.connection.CatalogAccessProperty;
+import org.apache.polaris.core.credentials.connection.ConnectionCredentials;
+import org.apache.polaris.core.secrets.SecretReference;
+import org.apache.polaris.core.secrets.UserSecretsManager;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/** Tests for {@link BearerConnectionCredentialVendor}. */
+public class BearerConnectionCredentialVendorTest {
+
+ private BearerConnectionCredentialVendor bearerVendor;
+ private UserSecretsManager mockSecretsManager;
+
+ @BeforeEach
+ void setup() {
+ mockSecretsManager = mock(UserSecretsManager.class);
+ bearerVendor = new BearerConnectionCredentialVendor(mockSecretsManager);
+ }
+
+ @Test
+ public void testGetConnectionCredentials() {
+ // Setup
+ SecretReference bearerTokenRef =
+ new SecretReference("urn:polaris-secret:test:bearer-token", Map.of());
+
when(mockSecretsManager.readSecret(bearerTokenRef)).thenReturn("my-bearer-token-value");
+
+ BearerAuthenticationParametersDpo authParams =
+ new BearerAuthenticationParametersDpo(bearerTokenRef);
+
+ IcebergRestConnectionConfigInfoDpo connectionConfig =
+ new IcebergRestConnectionConfigInfoDpo(
+ "https://catalog.example.com", authParams, null, "test-catalog");
+
+ // Execute
+ ConnectionCredentials credentials =
bearerVendor.getConnectionCredentials(connectionConfig);
+
+ // Verify - only bearer token is provided
+ Assertions.assertThat(credentials.credentials())
+ .hasSize(1)
+ .containsEntry(
+ CatalogAccessProperty.BEARER_TOKEN.getPropertyName(),
"my-bearer-token-value");
+
Assertions.assertThat(credentials.expiresAt()).contains(Instant.ofEpochMilli(Long.MAX_VALUE));
+ }
+
+ @Test
+ public void testGetConnectionCredentialsWithWrongAuthType() {
+ // Setup - use a mock with wrong authentication type
+ ConnectionConfigInfoDpo mockConfig = mock(ConnectionConfigInfoDpo.class);
+ AuthenticationParametersDpo mockAuthParams =
mock(AuthenticationParametersDpo.class);
+
+ when(mockConfig.getAuthenticationParameters()).thenReturn(mockAuthParams);
+
+ // Execute & Verify - should fail precondition check
+ Assertions.assertThatThrownBy(() ->
bearerVendor.getConnectionCredentials(mockConfig))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("Expected BearerAuthenticationParametersDpo");
+ }
+
+ @Test
+ public void testGetConnectionCredentialsWithInvalidSecretReference() {
+ // Setup - secret reference that doesn't exist
+ SecretReference invalidSecretRef =
+ new SecretReference("urn:polaris-secret:test:non-existent", Map.of());
+ when(mockSecretsManager.readSecret(invalidSecretRef))
+ .thenThrow(new RuntimeException("Secret not found"));
+
+ BearerAuthenticationParametersDpo authParams =
+ new BearerAuthenticationParametersDpo(invalidSecretRef);
+
+ IcebergRestConnectionConfigInfoDpo connectionConfig =
+ new IcebergRestConnectionConfigInfoDpo(
+ "https://catalog.example.com", authParams, null, "test-catalog");
+
+ // Execute & Verify - should propagate the exception from secrets manager
+ Assertions.assertThatThrownBy(() ->
bearerVendor.getConnectionCredentials(connectionConfig))
+ .isInstanceOf(RuntimeException.class)
+ .hasMessageContaining("Secret not found");
+ }
+}
diff --git
a/runtime/service/src/test/java/org/apache/polaris/service/credentials/connection/ImplicitConnectionCredentialVendorTest.java
b/runtime/service/src/test/java/org/apache/polaris/service/credentials/connection/ImplicitConnectionCredentialVendorTest.java
new file mode 100644
index 000000000..a2c817671
--- /dev/null
+++
b/runtime/service/src/test/java/org/apache/polaris/service/credentials/connection/ImplicitConnectionCredentialVendorTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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.credentials.connection;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.time.Instant;
+import org.apache.polaris.core.connection.AuthenticationParametersDpo;
+import org.apache.polaris.core.connection.ConnectionConfigInfoDpo;
+import org.apache.polaris.core.connection.ImplicitAuthenticationParametersDpo;
+import
org.apache.polaris.core.connection.iceberg.IcebergRestConnectionConfigInfoDpo;
+import org.apache.polaris.core.credentials.connection.ConnectionCredentials;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/** Tests for {@link ImplicitConnectionCredentialVendor}. */
+public class ImplicitConnectionCredentialVendorTest {
+
+ private ImplicitConnectionCredentialVendor implicitVendor;
+
+ @BeforeEach
+ void setup() {
+ implicitVendor = new ImplicitConnectionCredentialVendor();
+ }
+
+ @Test
+ public void testGetConnectionCredentials() {
+ // Setup
+ ImplicitAuthenticationParametersDpo authParams = new
ImplicitAuthenticationParametersDpo();
+
+ IcebergRestConnectionConfigInfoDpo connectionConfig =
+ new IcebergRestConnectionConfigInfoDpo(
+ "https://catalog.example.com", authParams, null, "test-catalog");
+
+ // Execute
+ ConnectionCredentials credentials =
implicitVendor.getConnectionCredentials(connectionConfig);
+
+ // Verify - no credentials are provided, but expiration is set to infinite
+ Assertions.assertThat(credentials.credentials()).isEmpty();
+
Assertions.assertThat(credentials.expiresAt()).contains(Instant.ofEpochMilli(Long.MAX_VALUE));
+ }
+
+ @Test
+ public void testGetConnectionCredentialsWithWrongAuthType() {
+ // Setup - use a mock with wrong authentication type
+ ConnectionConfigInfoDpo mockConfig = mock(ConnectionConfigInfoDpo.class);
+ AuthenticationParametersDpo mockAuthParams =
mock(AuthenticationParametersDpo.class);
+
+ when(mockConfig.getAuthenticationParameters()).thenReturn(mockAuthParams);
+
+ // Execute & Verify - should fail precondition check
+ Assertions.assertThatThrownBy(() ->
implicitVendor.getConnectionCredentials(mockConfig))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("Expected ImplicitAuthenticationParametersDpo");
+ }
+}
diff --git
a/runtime/service/src/test/java/org/apache/polaris/service/credentials/connection/OAuthClientCredentialVendorTest.java
b/runtime/service/src/test/java/org/apache/polaris/service/credentials/connection/OAuthClientCredentialVendorTest.java
new file mode 100644
index 000000000..f60fd8151
--- /dev/null
+++
b/runtime/service/src/test/java/org/apache/polaris/service/credentials/connection/OAuthClientCredentialVendorTest.java
@@ -0,0 +1,116 @@
+/*
+ * 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.credentials.connection;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.time.Instant;
+import java.util.List;
+import java.util.Map;
+import org.apache.polaris.core.connection.AuthenticationParametersDpo;
+import org.apache.polaris.core.connection.ConnectionConfigInfoDpo;
+import org.apache.polaris.core.connection.OAuthClientCredentialsParametersDpo;
+import
org.apache.polaris.core.connection.iceberg.IcebergRestConnectionConfigInfoDpo;
+import org.apache.polaris.core.credentials.connection.CatalogAccessProperty;
+import org.apache.polaris.core.credentials.connection.ConnectionCredentials;
+import org.apache.polaris.core.secrets.SecretReference;
+import org.apache.polaris.core.secrets.UserSecretsManager;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/** Tests for {@link OAuthClientCredentialVendor}. */
+public class OAuthClientCredentialVendorTest {
+
+ private OAuthClientCredentialVendor oauthVendor;
+ private UserSecretsManager mockSecretsManager;
+
+ @BeforeEach
+ void setup() {
+ mockSecretsManager = mock(UserSecretsManager.class);
+ oauthVendor = new OAuthClientCredentialVendor(mockSecretsManager);
+ }
+
+ @Test
+ public void testGetConnectionCredentials() {
+ // Setup
+ SecretReference clientSecretRef =
+ new SecretReference("urn:polaris-secret:test:oauth-client-secret",
Map.of());
+
when(mockSecretsManager.readSecret(clientSecretRef)).thenReturn("my-client-secret");
+
+ OAuthClientCredentialsParametersDpo authParams =
+ new OAuthClientCredentialsParametersDpo(
+ "https://auth.example.com/token",
+ "my-client-id",
+ clientSecretRef,
+ List.of("catalog", "read:data"));
+
+ IcebergRestConnectionConfigInfoDpo connectionConfig =
+ new IcebergRestConnectionConfigInfoDpo(
+ "https://catalog.example.com", authParams, null, "test-catalog");
+
+ // Execute
+ ConnectionCredentials credentials =
oauthVendor.getConnectionCredentials(connectionConfig);
+
+ // Verify - only OAuth credential is provided
+ Assertions.assertThat(credentials.credentials())
+ .hasSize(1)
+ .containsEntry(
+ CatalogAccessProperty.OAUTH2_CREDENTIAL.getPropertyName(),
+ "my-client-id:my-client-secret");
+
Assertions.assertThat(credentials.expiresAt()).contains(Instant.ofEpochMilli(Long.MAX_VALUE));
+ }
+
+ @Test
+ public void testGetConnectionCredentialsWithWrongAuthType() {
+ // Setup - use a mock with wrong authentication type
+ ConnectionConfigInfoDpo mockConfig = mock(ConnectionConfigInfoDpo.class);
+ AuthenticationParametersDpo mockAuthParams =
mock(AuthenticationParametersDpo.class);
+
+ when(mockConfig.getAuthenticationParameters()).thenReturn(mockAuthParams);
+
+ // Execute & Verify - should fail precondition check
+ Assertions.assertThatThrownBy(() ->
oauthVendor.getConnectionCredentials(mockConfig))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("Expected OAuthClientCredentialsParametersDpo");
+ }
+
+ @Test
+ public void testGetConnectionCredentialsWithInvalidSecretReference() {
+ // Setup - secret reference that doesn't exist
+ SecretReference invalidSecretRef =
+ new SecretReference("urn:polaris-secret:test:non-existent", Map.of());
+ when(mockSecretsManager.readSecret(invalidSecretRef))
+ .thenThrow(new RuntimeException("Secret not found"));
+
+ OAuthClientCredentialsParametersDpo authParams =
+ new OAuthClientCredentialsParametersDpo(
+ "https://auth.example.com/token", "my-client-id",
invalidSecretRef, List.of("catalog"));
+
+ IcebergRestConnectionConfigInfoDpo connectionConfig =
+ new IcebergRestConnectionConfigInfoDpo(
+ "https://catalog.example.com", authParams, null, "test-catalog");
+
+ // Execute & Verify - should propagate the exception from secrets manager
+ Assertions.assertThatThrownBy(() ->
oauthVendor.getConnectionCredentials(connectionConfig))
+ .isInstanceOf(RuntimeException.class)
+ .hasMessageContaining("Secret not found");
+ }
+}
diff --git
a/runtime/service/src/testFixtures/java/org/apache/polaris/service/TestServices.java
b/runtime/service/src/testFixtures/java/org/apache/polaris/service/TestServices.java
index a77057274..b58cdd771 100644
---
a/runtime/service/src/testFixtures/java/org/apache/polaris/service/TestServices.java
+++
b/runtime/service/src/testFixtures/java/org/apache/polaris/service/TestServices.java
@@ -278,7 +278,6 @@ public record TestServices(
resolverFactory,
resolutionManifestFactory,
metaStoreManager,
- userSecretsManager,
credentialManager,
authorizer,
new DefaultCatalogPrefixParser(),