Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
8940129
Added interface for reporting metrics
cccs-cat001 Oct 24, 2025
0acbb64
Added metrics reporting config to application.properties
cccs-cat001 Oct 24, 2025
95e0566
PR Fixes
cccs-cat001 Oct 27, 2025
2a78172
fixes for the PR fixes
cccs-cat001 Oct 27, 2025
c9097f7
reworked iceberg metrics logging to be one class with configurable lo…
cccs-cat001 Oct 27, 2025
ef22353
simplified report logging
cccs-cat001 Oct 27, 2025
5d0b0d5
run precommit
cccs-cat001 Oct 27, 2025
a5e9d37
fix tests for metrics addition
cccs-cat001 Oct 27, 2025
d39abc4
Update runtime/defaults/src/main/resources/application.properties
cccs-cat001 Oct 28, 2025
0215cac
Update runtime/service/src/main/java/org/apache/polaris/service/repor…
cccs-cat001 Oct 28, 2025
44b039b
Update runtime/service/src/main/java/org/apache/polaris/service/repor…
cccs-cat001 Oct 28, 2025
1849ab0
Update integration-tests/src/main/java/org/apache/polaris/service/it/…
cccs-cat001 Oct 28, 2025
e6677d3
pre-commit + testing changes
cccs-cat001 Oct 28, 2025
236f65b
Merge branch 'apache:main' into main
cccs-cat001 Oct 28, 2025
ff0c342
added license to new files
cccs-cat001 Oct 29, 2025
c41a23e
Merge branch 'apache:main' into main
cccs-cat001 Oct 29, 2025
b5362fb
Added info to changelog
cccs-cat001 Oct 30, 2025
8498ffb
reworded properties
cccs-cat001 Oct 30, 2025
cfe0851
removed reportMetrics from icebergCatalogHandler
cccs-cat001 Oct 30, 2025
9b9552e
cleanup tests
cccs-cat001 Oct 30, 2025
380de92
changed warehouse to catalogName
cccs-cat001 Oct 30, 2025
f3eaf23
removed scope
cccs-cat001 Nov 4, 2025
dcacc5c
added back scope
cccs-cat001 Nov 4, 2025
ebee75c
Merge branch 'apache:main' into main
cccs-cat001 Nov 5, 2025
e4618ca
fixed issue where value cannot be compared with nil
cccs-cat001 Nov 5, 2025
0260b44
customized build scripts
cccs-cat001 Nov 7, 2025
fa7b282
made the regex more generic
cccs-cat001 Nov 7, 2025
c340ab3
cleanup aisle 2
cccs-cat001 Nov 7, 2025
bd2c3f8
updated ROLE_ARN_PATTERN comment
cccs-cat001 Nov 7, 2025
79fd424
removed test-case that would not be considered valid
cccs-cat001 Nov 7, 2025
ec4eb9a
Added this change to the changelog
cccs-cat001 Nov 7, 2025
9dc3aee
added a test case
cccs-cat001 Nov 7, 2025
101fcf3
added more tests for s3 ARN
cccs-cat001 Nov 7, 2025
2e0a1b9
tests work properly
cccs-cat001 Nov 7, 2025
022f4da
Merge branch 'apache:main' into main
cccs-cat001 Nov 10, 2025
5a93a5f
Merge branch 'apache:main' into main
cccs-cat001 Nov 10, 2025
98d008e
Merge branch 'main' into cccs-main
cccs-cat001 Nov 10, 2025
1d2727c
Changed metrics reporter to use already existing json library
cccs-cat001 Nov 12, 2025
7d21203
first pass as webidentitytoken thing
cccs-cat001 Nov 21, 2025
e549edd
Added user token to STS
cccs-cat001 Nov 25, 2025
f67f03d
Cleaned up tests
cccs-cat001 Nov 26, 2025
9f7ccde
added user_token_sts for cli
cccs-cat001 Nov 26, 2025
6100407
removed custom things 🙃
cccs-cat001 Nov 26, 2025
81df525
code cleanup
cccs-cat001 Nov 26, 2025
ddae40e
Merge branch 'main' of github.com:cccs-cat001/polaris
cccs-cat001 Nov 26, 2025
09f71fc
Merge branch 'main' into s3
cccs-cat001 Nov 26, 2025
0776e59
merge fixes
cccs-cat001 Nov 26, 2025
3c1b8b5
fixing tests (hopefully)
cccs-cat001 Nov 26, 2025
82bc103
the crux of the problem (hopefully)
cccs-cat001 Nov 26, 2025
a053b23
Set default for new property
cccs-cat001 Nov 27, 2025
0cb4607
Added token to polarisPrincipal
cccs-cat001 Nov 27, 2025
6271bc9
cleanup
cccs-cat001 Nov 27, 2025
165c461
Moved polarisCredential back to where it came from
cccs-cat001 Nov 27, 2025
465c4b5
removed quarkus from core
cccs-cat001 Nov 27, 2025
9fc4d76
added token to authentication
cccs-cat001 Nov 27, 2025
4c6b96f
removed unused library
cccs-cat001 Nov 27, 2025
d42eaf1
removed CLI changes (will open a new PR for that)
cccs-cat001 Nov 27, 2025
230ca71
cleaned up polarisCredential of functions
cccs-cat001 Nov 27, 2025
44848f8
cleanup
cccs-cat001 Nov 27, 2025
907234a
fixing some tests hopefully.
cccs-cat001 Nov 28, 2025
13f935a
removed build.gradle changes (again)
cccs-cat001 Dec 2, 2025
f3e1e85
removed cli change
cccs-cat001 Dec 2, 2025
a68225c
set config same as stsUnavailable
cccs-cat001 Dec 2, 2025
ea6afe5
Merge branch 'main' into s3
cccs-cat001 Dec 3, 2025
f3763e6
Passing PolarisPrincipal's token down the rabbit hole
cccs-cat001 Dec 3, 2025
7ef2de0
Merge branch 's3' of github.com:cccs-cat001/polaris into s3
cccs-cat001 Dec 3, 2025
d1fdb38
Fixing tests
cccs-cat001 Dec 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,11 @@ hs_err_pid*

# Python Virtual Env
.venv
venv

# Development Experience

# You can create runtime/defaults/src/main/resources/application-local.properties file
# to override default properties for local development.
# And then use `./gradlew run -Dquarkus.profile=local` to run Polaris with dev profile.
application-local.properties
application-local.properties
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/
package org.apache.polaris.core.auth;

import jakarta.annotation.Nullable;
import java.security.Principal;
import java.util.Map;
import java.util.Set;
Expand All @@ -39,7 +40,23 @@ public interface PolarisPrincipal extends Principal {
* @param roles the set of roles associated with the principal
*/
static PolarisPrincipal of(PrincipalEntity principalEntity, Set<String> roles) {
return of(principalEntity.getName(), principalEntity.getInternalPropertiesAsMap(), roles);
return of(principalEntity.getName(), principalEntity.getInternalPropertiesAsMap(), roles, null);
}

/**
* Creates a new instance of {@link PolarisPrincipal} from the given {@link PrincipalEntity} and
* roles.
*
* <p>The created principal will have the same ID and name as the {@link PrincipalEntity}, and its
* properties will be derived from the internal properties of the entity.
*
* @param principalEntity the principal entity representing the user or service
* @param roles the set of roles associated with the principal
* @param token the access token of the current user
*/
static PolarisPrincipal of(PrincipalEntity principalEntity, Set<String> roles, String token) {
return of(
principalEntity.getName(), principalEntity.getInternalPropertiesAsMap(), roles, token);
}

/**
Expand All @@ -51,9 +68,24 @@ static PolarisPrincipal of(PrincipalEntity principalEntity, Set<String> roles) {
* @param roles the set of roles associated with the principal
*/
static PolarisPrincipal of(String name, Map<String, String> properties, Set<String> roles) {
return of(name, properties, roles, null);
}

/**
* Creates a new instance of {@link PolarisPrincipal} with the specified ID, name, roles, and
* properties.
*
* @param name the name of the principal
* @param properties additional properties associated with the principal
* @param roles the set of roles associated with the principal
* @param token the access token of the current user
*/
static PolarisPrincipal of(
String name, Map<String, String> properties, Set<String> roles, @Nullable String token) {
return ImmutablePolarisPrincipal.builder()
.name(name)
.properties(properties)
.token(token)
.roles(roles)
.build();
}
Expand All @@ -74,4 +106,8 @@ static PolarisPrincipal of(String name, Map<String, String> properties, Set<Stri
* as permissions, preferences, or other metadata.
*/
Map<String, String> getProperties();

/** Returns the access token of the current user. */
@Nullable
String getToken();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: WDYT about Optional<String> here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems the same to me, what's bad about using @Nullable?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a "nit" comment :) Optional is slightly preferable IMHO because it's an essential piece of information for making the StorageAccessConfig, but the content of the token is optional... really fine point :) I'm ok either way.

Copy link
Contributor

@dimas-b dimas-b Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may want to hold this until you deal with StorageCredentialCacheKey, then use whatever is more convenient on the cache side.

}
Original file line number Diff line number Diff line change
Expand Up @@ -1600,7 +1600,8 @@ public void deletePrincipalSecrets(
boolean allowListOperation,
@Nonnull Set<String> allowedReadLocations,
@Nonnull Set<String> allowedWriteLocations,
Optional<String> refreshCredentialsEndpoint) {
Optional<String> refreshCredentialsEndpoint,
Optional<String> token) {

// get meta store session we should be using
BasePersistence ms = callCtx.getMetaStore();
Expand Down Expand Up @@ -1641,7 +1642,8 @@ public void deletePrincipalSecrets(
allowListOperation,
allowedReadLocations,
allowedWriteLocations,
refreshCredentialsEndpoint);
refreshCredentialsEndpoint,
token);
return new ScopedCredentialsResult(storageAccessConfig);
} catch (Exception ex) {
return new ScopedCredentialsResult(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,8 @@ public void deletePrincipalSecrets(
boolean allowListOperation,
@Nonnull Set<String> allowedReadLocations,
@Nonnull Set<String> allowedWriteLocations,
Optional<String> refreshCredentialsEndpoint) {
Optional<String> refreshCredentialsEndpoint,
Optional<String> token) {
return delegate.getSubscopedCredsForEntity(
callCtx,
catalogId,
Expand All @@ -333,7 +334,8 @@ public void deletePrincipalSecrets(
allowListOperation,
allowedReadLocations,
allowedWriteLocations,
refreshCredentialsEndpoint);
refreshCredentialsEndpoint,
token);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2098,7 +2098,8 @@ private PolarisEntityResolver resolveSecurableToRoleGrant(
boolean allowListOperation,
@Nonnull Set<String> allowedReadLocations,
@Nonnull Set<String> allowedWriteLocations,
Optional<String> refreshCredentialsEndpoint) {
Optional<String> refreshCredentialsEndpoint,
Optional<String> token) {

// get meta store session we should be using
TransactionalPersistence ms = ((TransactionalPersistence) callCtx.getMetaStore());
Expand Down Expand Up @@ -2134,7 +2135,8 @@ private PolarisEntityResolver resolveSecurableToRoleGrant(
allowListOperation,
allowedReadLocations,
allowedWriteLocations,
refreshCredentialsEndpoint);
refreshCredentialsEndpoint,
token);
return new ScopedCredentialsResult(storageAccessConfig);
} catch (Exception ex) {
return new ScopedCredentialsResult(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,6 @@ ScopedCredentialsResult getSubscopedCredsForEntity(
boolean allowListOperation,
@Nonnull Set<String> allowedReadLocations,
@Nonnull Set<String> allowedWriteLocations,
Optional<String> refreshCredentialsEndpoint);
Optional<String> refreshCredentialsEndpoint,
Optional<String> token);
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ public abstract StorageAccessConfig getSubscopedCreds(
boolean allowListOperation,
@Nonnull Set<String> allowedReadLocations,
@Nonnull Set<String> allowedWriteLocations,
Optional<String> refreshCredentialsEndpoint);
Optional<String> refreshCredentialsEndpoint,
Optional<String> token);

/**
* Validate access for the provided operation actions and locations.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ public ScopedCredentialsResult getSubscopedCredsForEntity(
boolean allowListOperation,
@Nonnull Set<String> allowedReadLocations,
@Nonnull Set<String> allowedWriteLocations,
Optional<String> refreshCredentialsEndpoint) {
Optional<String> refreshCredentialsEndpoint,
Optional<String> token) {
return polarisCredentialVendor.getSubscopedCredsForEntity(
callContext.getPolarisCallContext(),
entity.getCatalogId(),
Expand All @@ -76,6 +77,7 @@ public ScopedCredentialsResult getSubscopedCredsForEntity(
allowListOperation,
allowedReadLocations,
allowedWriteLocations,
refreshCredentialsEndpoint);
refreshCredentialsEndpoint,
token);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@
import software.amazon.awssdk.policybuilder.iam.IamStatement;
import software.amazon.awssdk.services.sts.StsClient;
import software.amazon.awssdk.services.sts.model.AssumeRoleRequest;
import software.amazon.awssdk.services.sts.model.AssumeRoleResponse;
import software.amazon.awssdk.services.sts.model.AssumeRoleWithWebIdentityRequest;
import software.amazon.awssdk.services.sts.model.Credentials;

/** Credential vendor that supports generating */
public class AwsCredentialsStorageIntegration
Expand Down Expand Up @@ -81,7 +82,8 @@ public StorageAccessConfig getSubscopedCreds(
boolean allowListOperation,
@Nonnull Set<String> allowedReadLocations,
@Nonnull Set<String> allowedWriteLocations,
Optional<String> refreshCredentialsEndpoint) {
Optional<String> refreshCredentialsEndpoint,
Optional<String> token) {
int storageCredentialDurationSeconds =
realmConfig.getConfig(STORAGE_CREDENTIAL_DURATION_SECONDS);
AwsStorageConfigurationInfo storageConfig = config();
Expand All @@ -90,35 +92,58 @@ public StorageAccessConfig getSubscopedCreds(
StorageAccessConfig.Builder accessConfig = StorageAccessConfig.builder();

if (shouldUseSts(storageConfig)) {
AssumeRoleRequest.Builder request =
AssumeRoleRequest.builder()
.externalId(storageConfig.getExternalId())
.roleArn(storageConfig.getRoleARN())
.roleSessionName("PolarisAwsCredentialsStorageIntegration")
.policy(
policyString(
storageConfig,
allowListOperation,
allowedReadLocations,
allowedWriteLocations,
region,
accountId)
.toJson())
.durationSeconds(storageCredentialDurationSeconds);
credentialsProvider.ifPresent(
cp -> request.overrideConfiguration(b -> b.credentialsProvider(cp)));

@SuppressWarnings("resource")
// Note: stsClientProvider returns "thin" clients that do not need closing
StsClient stsClient =
stsClientProvider.stsClient(StsDestination.of(storageConfig.getStsEndpointUri(), region));

AssumeRoleResponse response = stsClient.assumeRole(request.build());
accessConfig.put(StorageAccessProperty.AWS_KEY_ID, response.credentials().accessKeyId());
accessConfig.put(
StorageAccessProperty.AWS_SECRET_KEY, response.credentials().secretAccessKey());
accessConfig.put(StorageAccessProperty.AWS_TOKEN, response.credentials().sessionToken());
Optional.ofNullable(response.credentials().expiration())
Credentials credentials;
if (Boolean.TRUE.equals(storageConfig.getPropagateApiUserIdentity())) {
AssumeRoleWithWebIdentityRequest.Builder request =
AssumeRoleWithWebIdentityRequest.builder()
.webIdentityToken(
token.orElseThrow(
() ->
new IllegalArgumentException(
"Token must be provided when PROPAGATE_API_USER_IDENTITY is true")))
.roleArn(storageConfig.getRoleARN())
.roleSessionName("PolarisAwsCredentialsStorageIntegration")
.policy(
policyString(
storageConfig,
allowListOperation,
allowedReadLocations,
allowedWriteLocations,
region,
accountId)
.toJson())
.durationSeconds(storageCredentialDurationSeconds);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't see a policy block added here (like it is in the non-token code path). If this is intentional, please explain why the scoping-down of credentials isn't applicable here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

from what I gather with this flow, we don't need to provide a policy string. Seems that the STS reads the users token and provides you with all the credentials you have permission for.

I'm not an expert in S3 or STS so I could be way off and feel free to correct me if I am, but from testing with our appliance this is the way it seems to be.

Copy link
Contributor

@dimas-b dimas-b Nov 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Access down-scoping (policy-based restrictions) makes more sense in the case when Polaris has its own "service" credential because we do not want to delegate more rights to the user than what is necessary for the API request.

In this case the STS session is based in the unmodified API user identity. The user can gain the same access by talking to STS directly.

The only reason for restricting access might be to enforce catalog-level location settings. However, that would be mostly about preventing accidental mistakes on the client side rather than a true security control.

That said, for the sake of narrowing Polaris behaviour variations it might still be worth applying the same policy as in the "service" account case... just for ease of reasoning about how the system works.

Taking this one step further, it might be worth controlling the policy application with yet another config property, if there are use cases when the extra policy is not desirable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay, I'll test out the policy string on our storage before committing it

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

from my POV, it would be ok to do the policy change in a follow-up PR. Current change is already under a dedicated config, so existing clients and servers will not be not affected even if we merge "as is".


credentials = stsClient.assumeRoleWithWebIdentity(request.build()).credentials();
} else {
AssumeRoleRequest.Builder request =
AssumeRoleRequest.builder()
.externalId(storageConfig.getExternalId())
.roleArn(storageConfig.getRoleARN())
.roleSessionName("PolarisAwsCredentialsStorageIntegration")
.policy(
policyString(
storageConfig,
allowListOperation,
allowedReadLocations,
allowedWriteLocations,
region,
accountId)
.toJson())
.durationSeconds(storageCredentialDurationSeconds);
credentialsProvider.ifPresent(
cp -> request.overrideConfiguration(b -> b.credentialsProvider(cp)));
credentials = stsClient.assumeRole(request.build()).credentials();
}
accessConfig.put(StorageAccessProperty.AWS_KEY_ID, credentials.accessKeyId());
accessConfig.put(StorageAccessProperty.AWS_SECRET_KEY, credentials.secretAccessKey());
accessConfig.put(StorageAccessProperty.AWS_TOKEN, credentials.sessionToken());
Optional.ofNullable(credentials.expiration())
.ifPresent(
i -> {
accessConfig.put(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ public String getFileIoImplClassName() {
@Nullable
public abstract String getRoleARN();

public abstract @Nullable Boolean getPropagateApiUserIdentity();

/** KMS Key ARN for server-side encryption,used for writes, optional */
@Nullable
public abstract String getCurrentKmsKey();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ public StorageAccessConfig getSubscopedCreds(
boolean allowListOperation,
@Nonnull Set<String> allowedReadLocations,
@Nonnull Set<String> allowedWriteLocations,
Optional<String> refreshCredentialsEndpoint) {
Optional<String> refreshCredentialsEndpoint,
Optional<String> token) {
String loc =
!allowedWriteLocations.isEmpty()
? allowedWriteLocations.stream().findAny().orElse(null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@ public StorageAccessConfig getOrGenerateSubScopeCreds(
boolean allowListOperation,
@Nonnull Set<String> allowedReadLocations,
@Nonnull Set<String> allowedWriteLocations,
Optional<String> refreshCredentialsEndpoint) {
Optional<String> refreshCredentialsEndpoint,
Optional<String> token) {
RealmContext realmContext = storageCredentialsVendor.getRealmContext();
RealmConfig realmConfig = storageCredentialsVendor.getRealmConfig();
if (!isTypeSupported(polarisEntity.getType())) {
Expand All @@ -134,7 +135,8 @@ public StorageAccessConfig getOrGenerateSubScopeCreds(
allowListOperation,
allowedReadLocations,
allowedWriteLocations,
refreshCredentialsEndpoint);
refreshCredentialsEndpoint,
token);
if (scopedCredentialsResult.isSuccess()) {
long maxCacheDurationMs = maxCacheDurationMs(realmConfig);
return new StorageCredentialCacheEntry(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ public StorageAccessConfig getSubscopedCreds(
boolean allowListOperation,
@Nonnull Set<String> allowedReadLocations,
@Nonnull Set<String> allowedWriteLocations,
Optional<String> refreshCredentialsEndpoint) {
Optional<String> refreshCredentialsEndpoint,
Optional<String> token) {
try {
sourceCredentials.refresh();
} catch (IOException e) {
Expand All @@ -93,9 +94,9 @@ public StorageAccessConfig getSubscopedCreds(
.setSourceCredential(sourceCredentials)
.setCredentialAccessBoundary(accessBoundary)
.build();
AccessToken token;
AccessToken accessToken;
try {
token = credentials.refreshAccessToken();
accessToken = credentials.refreshAccessToken();
} catch (IOException e) {
LOGGER
.atError()
Expand All @@ -110,10 +111,10 @@ public StorageAccessConfig getSubscopedCreds(
// If expires_in missing, use source credential's expire time, which require another api call to
// get.
StorageAccessConfig.Builder accessConfig = StorageAccessConfig.builder();
accessConfig.put(StorageAccessProperty.GCS_ACCESS_TOKEN, token.getTokenValue());
accessConfig.put(StorageAccessProperty.GCS_ACCESS_TOKEN, accessToken.getTokenValue());
accessConfig.put(
StorageAccessProperty.GCS_ACCESS_TOKEN_EXPIRES_AT,
String.valueOf(token.getExpirationTime().getTime()));
String.valueOf(accessToken.getExpirationTime().getTime()));

refreshCredentialsEndpoint.ifPresent(
endpoint -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,8 @@ public StorageAccessConfig getSubscopedCreds(
boolean allowListOperation,
@Nonnull Set<String> allowedReadLocations,
@Nonnull Set<String> allowedWriteLocations,
Optional<String> refreshCredentialsEndpoint) {
Optional<String> refreshCredentialsEndpoint,
Optional<String> token) {
return null;
}
}
Expand Down
Loading
Loading