Skip to content

Commit 9e6d929

Browse files
authored
add refresh credentials property to loadTableResult (#2341)
* add refresh credentials property to loadTableResult * IcebergCatalogAdapterTest: Added test to ensure refresh credentials endpoint is included * delegate refresh credential endpoint configuration to storage integration * GCP: Add refresh credential properties
1 parent 95ebdd3 commit 9e6d929

29 files changed

+341
-93
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@ at locations that better optimize for object storage.
8383

8484
- Introduced bootstrap command options to specify custom schema files for database initialization.
8585

86+
- Added refresh credentials endpoint configuration to LoadTableResponse for AWS, Azure, and GCP. Enabling
87+
automatic storage credential refresh per table on the client side. Java client version >= 1.8.0 is required.
88+
The endpoint path is always returned when using vended credentials, but clients must enable the
89+
refresh-credentials flag for the desired storage provider.
90+
8691
### Changes
8792

8893
- Polaris Management API clients must be prepared to deal with new attributes in `AwsStorageConfigInfo` objects.

polaris-core/src/main/java/org/apache/polaris/core/persistence/AtomicOperationMetaStoreManager.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1582,7 +1582,8 @@ private void revokeGrantRecord(
15821582
PolarisEntityType entityType,
15831583
boolean allowListOperation,
15841584
@Nonnull Set<String> allowedReadLocations,
1585-
@Nonnull Set<String> allowedWriteLocations) {
1585+
@Nonnull Set<String> allowedWriteLocations,
1586+
Optional<String> refreshCredentialsEndpoint) {
15861587

15871588
// get meta store session we should be using
15881589
BasePersistence ms = callCtx.getMetaStore();
@@ -1622,7 +1623,8 @@ private void revokeGrantRecord(
16221623
callCtx.getRealmConfig(),
16231624
allowListOperation,
16241625
allowedReadLocations,
1625-
allowedWriteLocations);
1626+
allowedWriteLocations,
1627+
refreshCredentialsEndpoint);
16261628
return new ScopedCredentialsResult(accessConfig);
16271629
} catch (Exception ex) {
16281630
return new ScopedCredentialsResult(

polaris-core/src/main/java/org/apache/polaris/core/persistence/TransactionWorkspaceMetaStoreManager.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -327,15 +327,17 @@ public ScopedCredentialsResult getSubscopedCredsForEntity(
327327
PolarisEntityType entityType,
328328
boolean allowListOperation,
329329
@Nonnull Set<String> allowedReadLocations,
330-
@Nonnull Set<String> allowedWriteLocations) {
330+
@Nonnull Set<String> allowedWriteLocations,
331+
Optional<String> refreshCredentialsEndpoint) {
331332
return delegate.getSubscopedCredsForEntity(
332333
callCtx,
333334
catalogId,
334335
entityId,
335336
entityType,
336337
allowListOperation,
337338
allowedReadLocations,
338-
allowedWriteLocations);
339+
allowedWriteLocations,
340+
refreshCredentialsEndpoint);
339341
}
340342

341343
@Override

polaris-core/src/main/java/org/apache/polaris/core/persistence/transactional/TransactionalMetaStoreManagerImpl.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2040,7 +2040,8 @@ private PolarisEntityResolver resolveSecurableToRoleGrant(
20402040
PolarisEntityType entityType,
20412041
boolean allowListOperation,
20422042
@Nonnull Set<String> allowedReadLocations,
2043-
@Nonnull Set<String> allowedWriteLocations) {
2043+
@Nonnull Set<String> allowedWriteLocations,
2044+
Optional<String> refreshCredentialsEndpoint) {
20442045

20452046
// get meta store session we should be using
20462047
TransactionalPersistence ms = ((TransactionalPersistence) callCtx.getMetaStore());
@@ -2075,7 +2076,8 @@ private PolarisEntityResolver resolveSecurableToRoleGrant(
20752076
callCtx.getRealmConfig(),
20762077
allowListOperation,
20772078
allowedReadLocations,
2078-
allowedWriteLocations);
2079+
allowedWriteLocations,
2080+
refreshCredentialsEndpoint);
20792081
return new ScopedCredentialsResult(accessConfig);
20802082
} catch (Exception ex) {
20812083
return new ScopedCredentialsResult(

polaris-core/src/main/java/org/apache/polaris/core/rest/PolarisResourcePaths.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,17 @@ public String genericTables(Namespace ns) {
5757
"polaris", "v1", prefix, "namespaces", RESTUtil.encodeNamespace(ns), "generic-tables");
5858
}
5959

60+
public String credentialsPath(TableIdentifier ident) {
61+
return SLASH.join(
62+
"v1",
63+
prefix,
64+
"namespaces",
65+
RESTUtil.encodeNamespace(ident.namespace()),
66+
"tables",
67+
RESTUtil.encodeString(ident.name()),
68+
"credentials");
69+
}
70+
6071
public String genericTable(TableIdentifier ident) {
6172
return SLASH.join(
6273
"polaris",

polaris-core/src/main/java/org/apache/polaris/core/storage/PolarisCredentialVendor.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
package org.apache.polaris.core.storage;
2020

2121
import jakarta.annotation.Nonnull;
22+
import java.util.Optional;
2223
import java.util.Set;
2324
import org.apache.polaris.core.PolarisCallContext;
2425
import org.apache.polaris.core.entity.PolarisEntityType;
@@ -37,6 +38,10 @@ public interface PolarisCredentialVendor {
3738
* allowedWriteLocations
3839
* @param allowedReadLocations a set of allowed to read locations
3940
* @param allowedWriteLocations a set of allowed to write locations
41+
* @param refreshCredentialsEndpoint an optional endpoint to use for refreshing credentials. If
42+
* supported by the storage type it will be returned to the client in the appropriate
43+
* properties. The endpoint may be relative to the base URI and the client is responsible for
44+
* handling the relative path
4045
* @return an enum map containing the scoped credentials
4146
*/
4247
@Nonnull
@@ -47,5 +52,6 @@ ScopedCredentialsResult getSubscopedCredsForEntity(
4752
PolarisEntityType entityType,
4853
boolean allowListOperation,
4954
@Nonnull Set<String> allowedReadLocations,
50-
@Nonnull Set<String> allowedWriteLocations);
55+
@Nonnull Set<String> allowedWriteLocations,
56+
Optional<String> refreshCredentialsEndpoint);
5157
}

polaris-core/src/main/java/org/apache/polaris/core/storage/PolarisStorageIntegration.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import jakarta.annotation.Nonnull;
2222
import java.util.Map;
23+
import java.util.Optional;
2324
import java.util.Set;
2425
import org.apache.polaris.core.config.RealmConfig;
2526

@@ -55,13 +56,18 @@ public String getStorageIdentifierOrId() {
5556
* locations
5657
* @param allowedReadLocations a set of allowed to read locations
5758
* @param allowedWriteLocations a set of allowed to write locations
59+
* @param refreshCredentialsEndpoint an optional endpoint to use for refreshing credentials. If
60+
* supported by the storage type it will be returned to the client in the appropriate
61+
* properties. The endpoint may be relative to the base URI and the client is responsible for
62+
* handling the relative path
5863
* @return An enum map including the scoped credentials
5964
*/
6065
public abstract AccessConfig getSubscopedCreds(
6166
@Nonnull RealmConfig realmConfig,
6267
boolean allowListOperation,
6368
@Nonnull Set<String> allowedReadLocations,
64-
@Nonnull Set<String> allowedWriteLocations);
69+
@Nonnull Set<String> allowedWriteLocations,
70+
Optional<String> refreshCredentialsEndpoint);
6571

6672
/**
6773
* Validate access for the provided operation actions and locations.

polaris-core/src/main/java/org/apache/polaris/core/storage/StorageAccessProperty.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
*/
1919
package org.apache.polaris.core.storage;
2020

21+
import org.apache.iceberg.aws.AwsClientProperties;
22+
import org.apache.iceberg.gcp.GCPProperties;
23+
2124
/**
2225
* A subset of Iceberg catalog properties recognized by Polaris.
2326
*
@@ -39,6 +42,12 @@ public enum StorageAccessProperty {
3942
Boolean.class, "s3.path-style-access", "whether to use S3 path style access", false),
4043
CLIENT_REGION(
4144
String.class, "client.region", "region to configure client for making requests to AWS"),
45+
AWS_REFRESH_CREDENTIALS_ENDPOINT(
46+
String.class,
47+
AwsClientProperties.REFRESH_CREDENTIALS_ENDPOINT,
48+
"the endpoint to load vended credentials for a table from the catalog",
49+
false,
50+
false),
4251

4352
GCS_ACCESS_TOKEN(String.class, "gcs.oauth2.token", "the gcs scoped access token"),
4453
GCS_ACCESS_TOKEN_EXPIRES_AT(
@@ -47,11 +56,23 @@ public enum StorageAccessProperty {
4756
"the time the gcs access token expires, in milliseconds",
4857
true,
4958
true),
59+
GCS_REFRESH_CREDENTIALS_ENDPOINT(
60+
String.class,
61+
GCPProperties.GCS_OAUTH2_REFRESH_CREDENTIALS_ENDPOINT,
62+
"the endpoint to load vended credentials for a table from the catalog",
63+
false,
64+
false),
5065

5166
// Currently not using ACCESS TOKEN as the ResolvingFileIO is using ADLSFileIO for azure case and
5267
// it expects for SAS
5368
AZURE_ACCESS_TOKEN(String.class, "", "the azure scoped access token"),
5469
AZURE_SAS_TOKEN(String.class, "adls.sas-token.", "an azure shared access signature token"),
70+
AZURE_REFRESH_CREDENTIALS_ENDPOINT(
71+
String.class,
72+
"adls.refresh-credentials-endpoint",
73+
"the endpoint to load vended credentials for a table from the catalog",
74+
false,
75+
false),
5576
EXPIRATION_TIME(
5677
Long.class,
5778
"expiration-time",

polaris-core/src/main/java/org/apache/polaris/core/storage/aws/AwsCredentialsStorageIntegration.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@ public AccessConfig getSubscopedCreds(
7474
@Nonnull RealmConfig realmConfig,
7575
boolean allowListOperation,
7676
@Nonnull Set<String> allowedReadLocations,
77-
@Nonnull Set<String> allowedWriteLocations) {
77+
@Nonnull Set<String> allowedWriteLocations,
78+
Optional<String> refreshCredentialsEndpoint) {
7879
int storageCredentialDurationSeconds =
7980
realmConfig.getConfig(STORAGE_CREDENTIAL_DURATION_SECONDS);
8081
AwsStorageConfigurationInfo storageConfig = config();
@@ -120,6 +121,11 @@ public AccessConfig getSubscopedCreds(
120121
accessConfig.put(StorageAccessProperty.CLIENT_REGION, region);
121122
}
122123

124+
refreshCredentialsEndpoint.ifPresent(
125+
endpoint -> {
126+
accessConfig.put(StorageAccessProperty.AWS_REFRESH_CREDENTIALS_ENDPOINT, endpoint);
127+
});
128+
123129
URI endpointUri = storageConfig.getEndpointUri();
124130
if (endpointUri != null) {
125131
accessConfig.put(StorageAccessProperty.AWS_ENDPOINT, endpointUri.toString());

polaris-core/src/main/java/org/apache/polaris/core/storage/azure/AzureCredentialsStorageIntegration.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import java.time.temporal.ChronoUnit;
4747
import java.util.HashSet;
4848
import java.util.Objects;
49+
import java.util.Optional;
4950
import java.util.Set;
5051
import org.apache.polaris.core.config.RealmConfig;
5152
import org.apache.polaris.core.storage.AccessConfig;
@@ -76,7 +77,8 @@ public AccessConfig getSubscopedCreds(
7677
@Nonnull RealmConfig realmConfig,
7778
boolean allowListOperation,
7879
@Nonnull Set<String> allowedReadLocations,
79-
@Nonnull Set<String> allowedWriteLocations) {
80+
@Nonnull Set<String> allowedWriteLocations,
81+
Optional<String> refreshCredentialsEndpoint) {
8082
String loc =
8183
!allowedWriteLocations.isEmpty()
8284
? allowedWriteLocations.stream().findAny().orElse(null)
@@ -169,15 +171,24 @@ public AccessConfig getSubscopedCreds(
169171
String.format("Endpoint %s not supported", location.getEndpoint()));
170172
}
171173

172-
return toAccessConfig(sasToken, storageDnsName, sanitizedEndTime.toInstant());
174+
return toAccessConfig(
175+
sasToken, storageDnsName, sanitizedEndTime.toInstant(), refreshCredentialsEndpoint);
173176
}
174177

175178
@VisibleForTesting
176-
static AccessConfig toAccessConfig(String sasToken, String storageDnsName, Instant expiresAt) {
179+
static AccessConfig toAccessConfig(
180+
String sasToken,
181+
String storageDnsName,
182+
Instant expiresAt,
183+
Optional<String> refreshCredentialsEndpoint) {
177184
AccessConfig.Builder accessConfig = AccessConfig.builder();
178185
handleAzureCredential(accessConfig, sasToken, storageDnsName);
179186
accessConfig.put(
180187
StorageAccessProperty.EXPIRATION_TIME, String.valueOf(expiresAt.toEpochMilli()));
188+
refreshCredentialsEndpoint.ifPresent(
189+
endpoint -> {
190+
accessConfig.put(StorageAccessProperty.AZURE_REFRESH_CREDENTIALS_ENDPOINT, endpoint);
191+
});
181192
return accessConfig.build();
182193
}
183194

0 commit comments

Comments
 (0)