diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ad9d759b1..0ef1645c5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -83,6 +83,7 @@ request adding CHANGELOG notes for breaking (!) changes and possibly other secti ### New Features +- Added KMS properties (optional) to catalog storage config to enable S3 data encryption. - Added a finer grained authorization model for UpdateTable requests. Existing privileges continue to work for granting UpdateTable, such as `TABLE_WRITE_PROPERTIES`. However, you can now instead grant privileges just for specific operations, such as `TABLE_ADD_SNAPSHOT` - Added a Management API endpoint to reset principal credentials, controlled by the `ENABLE_CREDENTIAL_RESET` (default: true) feature flag. diff --git a/api/management-model/src/test/java/org/apache/polaris/core/admin/model/CatalogSerializationTest.java b/api/management-model/src/test/java/org/apache/polaris/core/admin/model/CatalogSerializationTest.java index c4210486ba..3244f14732 100644 --- a/api/management-model/src/test/java/org/apache/polaris/core/admin/model/CatalogSerializationTest.java +++ b/api/management-model/src/test/java/org/apache/polaris/core/admin/model/CatalogSerializationTest.java @@ -35,6 +35,7 @@ public class CatalogSerializationTest { private static final String TEST_LOCATION = "s3://test/"; private static final String TEST_CATALOG_NAME = "test-catalog"; private static final String TEST_ROLE_ARN = "arn:aws:iam::123456789012:role/test-role"; + private static final String KMS_KEY = "arn:aws:kms:us-east-1:012345678901:key/allowed-key-1"; @BeforeEach public void setUp() { @@ -70,6 +71,36 @@ public void testJsonFormat() throws JsonProcessingException { + "\"properties\":{\"default-base-location\":\"s3://test/\"}," + "\"storageConfigInfo\":{" + "\"roleArn\":\"arn:aws:iam::123456789012:role/test-role\"," + + "\"allowedKmsKeys\":[]," + + "\"pathStyleAccess\":false," + + "\"storageType\":\"S3\"," + + "\"allowedLocations\":[]" + + "}}"); + } + + @Test + public void testJsonFormatWithKmsProperties() throws JsonProcessingException { + Catalog catalog = + new Catalog( + Catalog.TypeEnum.INTERNAL, + TEST_CATALOG_NAME, + new CatalogProperties(TEST_LOCATION), + AwsStorageConfigInfo.builder(StorageConfigInfo.StorageTypeEnum.S3) + .setRoleArn(TEST_ROLE_ARN) + .setCurrentKmsKey(KMS_KEY) + .build()); + + String json = mapper.writeValueAsString(catalog); + + assertThat(json) + .isEqualTo( + "{\"type\":\"INTERNAL\"," + + "\"name\":\"test-catalog\"," + + "\"properties\":{\"default-base-location\":\"s3://test/\"}," + + "\"storageConfigInfo\":{" + + "\"roleArn\":\"arn:aws:iam::123456789012:role/test-role\"," + + "\"currentKmsKey\":\"arn:aws:kms:us-east-1:012345678901:key/allowed-key-1\"," + + "\"allowedKmsKeys\":[]," + "\"pathStyleAccess\":false," + "\"storageType\":\"S3\"," + "\"allowedLocations\":[]" diff --git a/polaris-core/src/main/java/org/apache/polaris/core/entity/CatalogEntity.java b/polaris-core/src/main/java/org/apache/polaris/core/entity/CatalogEntity.java index 40cf1969d6..4607728b35 100644 --- a/polaris-core/src/main/java/org/apache/polaris/core/entity/CatalogEntity.java +++ b/polaris-core/src/main/java/org/apache/polaris/core/entity/CatalogEntity.java @@ -161,6 +161,8 @@ private StorageConfigInfo getStorageInfo(Map internalProperties) .setRoleArn(awsConfig.getRoleARN()) .setExternalId(awsConfig.getExternalId()) .setUserArn(awsConfig.getUserARN()) + .setCurrentKmsKey(awsConfig.getCurrentKmsKey()) + .setAllowedKmsKeys(awsConfig.getAllowedKmsKeys()) .setStorageType(StorageConfigInfo.StorageTypeEnum.S3) .setAllowedLocations(awsConfig.getAllowedLocations()) .setRegion(awsConfig.getRegion()) @@ -308,6 +310,8 @@ public Builder setStorageConfigurationInfo( AwsStorageConfigurationInfo.builder() .allowedLocations(allowedLocations) .roleARN(awsConfigModel.getRoleArn()) + .currentKmsKey(awsConfigModel.getCurrentKmsKey()) + .allowedKmsKeys(awsConfigModel.getAllowedKmsKeys()) .externalId(awsConfigModel.getExternalId()) .region(awsConfigModel.getRegion()) .endpoint(awsConfigModel.getEndpoint()) diff --git a/polaris-core/src/main/java/org/apache/polaris/core/storage/aws/AwsCredentialsStorageIntegration.java b/polaris-core/src/main/java/org/apache/polaris/core/storage/aws/AwsCredentialsStorageIntegration.java index e393911f71..2996006954 100644 --- a/polaris-core/src/main/java/org/apache/polaris/core/storage/aws/AwsCredentialsStorageIntegration.java +++ b/polaris-core/src/main/java/org/apache/polaris/core/storage/aws/AwsCredentialsStorageIntegration.java @@ -23,6 +23,7 @@ import jakarta.annotation.Nonnull; import java.net.URI; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -33,6 +34,8 @@ import org.apache.polaris.core.storage.StorageAccessProperty; import org.apache.polaris.core.storage.StorageUtil; import org.apache.polaris.core.storage.aws.StsClientProvider.StsDestination; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; import software.amazon.awssdk.policybuilder.iam.IamConditionOperator; import software.amazon.awssdk.policybuilder.iam.IamEffect; @@ -49,6 +52,9 @@ public class AwsCredentialsStorageIntegration private final StsClientProvider stsClientProvider; private final Optional credentialsProvider; + private static final Logger LOGGER = + LoggerFactory.getLogger(AwsCredentialsStorageIntegration.class); + public AwsCredentialsStorageIntegration( AwsStorageConfigurationInfo config, StsClient fixedClient) { this(config, (destination) -> fixedClient); @@ -80,6 +86,7 @@ public StorageAccessConfig getSubscopedCreds( realmConfig.getConfig(STORAGE_CREDENTIAL_DURATION_SECONDS); AwsStorageConfigurationInfo storageConfig = config(); String region = storageConfig.getRegion(); + String accountId = storageConfig.getAwsAccountId(); StorageAccessConfig.Builder accessConfig = StorageAccessConfig.builder(); if (shouldUseSts(storageConfig)) { @@ -90,10 +97,12 @@ public StorageAccessConfig getSubscopedCreds( .roleSessionName("PolarisAwsCredentialsStorageIntegration") .policy( policyString( - storageConfig.getAwsPartition(), + storageConfig, allowListOperation, allowedReadLocations, - allowedWriteLocations) + allowedWriteLocations, + region, + accountId) .toJson()) .durationSeconds(storageCredentialDurationSeconds); credentialsProvider.ifPresent( @@ -163,12 +172,13 @@ private boolean shouldUseSts(AwsStorageConfigurationInfo storageConfig) { * ListBucket privileges with no resources. This prevents us from sending an empty policy to AWS * and just assuming the role with full privileges. */ - // TODO - add KMS key access private IamPolicy policyString( - String awsPartition, + AwsStorageConfigurationInfo storageConfigurationInfo, boolean allowList, Set readLocations, - Set writeLocations) { + Set writeLocations, + String region, + String accountId) { IamPolicy.Builder policyBuilder = IamPolicy.builder(); IamStatement.Builder allowGetObjectStatementBuilder = IamStatement.builder() @@ -178,7 +188,9 @@ private IamPolicy policyString( Map bucketListStatementBuilder = new HashMap<>(); Map bucketGetLocationStatementBuilder = new HashMap<>(); - String arnPrefix = arnPrefixForPartition(awsPartition); + String arnPrefix = arnPrefixForPartition(storageConfigurationInfo.getAwsPartition()); + String currentKmsKey = storageConfigurationInfo.getCurrentKmsKey(); + List allowedKmsKeys = storageConfigurationInfo.getAllowedKmsKeys(); Stream.concat(readLocations.stream(), writeLocations.stream()) .distinct() .forEach( @@ -225,6 +237,9 @@ private IamPolicy policyString( arnPrefix + StorageUtil.concatFilePrefixes(parseS3Path(uri), "*", "/"))); }); policyBuilder.addStatement(allowPutObjectStatementBuilder.build()); + addKmsKeyPolicy(currentKmsKey, allowedKmsKeys, policyBuilder, true, region, accountId); + } else { + addKmsKeyPolicy(currentKmsKey, allowedKmsKeys, policyBuilder, false, region, accountId); } if (!bucketListStatementBuilder.isEmpty()) { bucketListStatementBuilder @@ -242,6 +257,86 @@ private IamPolicy policyString( return policyBuilder.addStatement(allowGetObjectStatementBuilder.build()).build(); } + private static void addKmsKeyPolicy( + String kmsKeyArn, + List allowedKmsKeys, + IamPolicy.Builder policyBuilder, + boolean canWrite, + String region, + String accountId) { + + IamStatement.Builder allowKms = buildBaseKmsStatement(canWrite); + boolean hasCurrentKey = kmsKeyArn != null; + boolean hasAllowedKeys = hasAllowedKmsKeys(allowedKmsKeys); + + if (hasCurrentKey) { + addKmsKeyResource(kmsKeyArn, allowKms); + } + + if (hasAllowedKeys) { + addAllowedKmsKeyResources(allowedKmsKeys, allowKms); + } + + // Add KMS statement if we have any KMS key configuration + if (hasCurrentKey || hasAllowedKeys) { + policyBuilder.addStatement(allowKms.build()); + } else if (!canWrite) { + // Only add wildcard KMS access for read-only operations when no specific keys are configured + // this check is for minio because it doesn't have region or account id + if (region != null && accountId != null) { + addAllKeysResource(region, accountId, allowKms); + policyBuilder.addStatement(allowKms.build()); + } + } + } + + private static IamStatement.Builder buildBaseKmsStatement(boolean canEncrypt) { + IamStatement.Builder allowKms = + IamStatement.builder() + .effect(IamEffect.ALLOW) + .addAction("kms:GenerateDataKeyWithoutPlaintext") + .addAction("kms:DescribeKey") + .addAction("kms:Decrypt") + .addAction("kms:GenerateDataKey"); + + if (canEncrypt) { + allowKms.addAction("kms:Encrypt"); + } + + return allowKms; + } + + private static void addKmsKeyResource(String kmsKeyArn, IamStatement.Builder allowKms) { + if (kmsKeyArn != null) { + LOGGER.debug("Adding KMS key policy for key {}", kmsKeyArn); + allowKms.addResource(IamResource.create(kmsKeyArn)); + } + } + + private static boolean hasAllowedKmsKeys(List allowedKmsKeys) { + return allowedKmsKeys != null && !allowedKmsKeys.isEmpty(); + } + + private static void addAllowedKmsKeyResources( + List allowedKmsKeys, IamStatement.Builder allowKms) { + allowedKmsKeys.forEach( + keyArn -> { + LOGGER.debug("Adding allowed KMS key policy for key {}", keyArn); + allowKms.addResource(IamResource.create(keyArn)); + }); + } + + private static void addAllKeysResource( + String region, String accountId, IamStatement.Builder allowKms) { + String allKeysArn = arnKeyAll(region, accountId); + allowKms.addResource(IamResource.create(allKeysArn)); + LOGGER.debug("Adding KMS key policy for all keys in account {}", accountId); + } + + private static String arnKeyAll(String region, String accountId) { + return String.format("arn:aws:kms:%s:%s:key/*", region, accountId); + } + private static String arnPrefixForPartition(String awsPartition) { return String.format("arn:%s:s3:::", awsPartition != null ? awsPartition : "aws"); } diff --git a/polaris-core/src/main/java/org/apache/polaris/core/storage/aws/AwsStorageConfigurationInfo.java b/polaris-core/src/main/java/org/apache/polaris/core/storage/aws/AwsStorageConfigurationInfo.java index 69c669222d..b62265f929 100644 --- a/polaris-core/src/main/java/org/apache/polaris/core/storage/aws/AwsStorageConfigurationInfo.java +++ b/polaris-core/src/main/java/org/apache/polaris/core/storage/aws/AwsStorageConfigurationInfo.java @@ -27,6 +27,7 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize; import jakarta.annotation.Nullable; import java.net.URI; +import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.polaris.core.storage.PolarisStorageConfigurationInfo; @@ -63,6 +64,14 @@ public String getFileIoImplClassName() { @Nullable public abstract String getRoleARN(); + /** KMS Key ARN for server-side encryption,used for writes, optional */ + @Nullable + public abstract String getCurrentKmsKey(); + + /** Comma-separated list of allowed KMS Key ARNs, optional */ + @Nullable + public abstract List getAllowedKmsKeys(); + /** AWS external ID, optional */ @Nullable public abstract String getExternalId(); diff --git a/polaris-core/src/test/java/org/apache/polaris/service/storage/aws/AwsCredentialsStorageIntegrationTest.java b/polaris-core/src/test/java/org/apache/polaris/service/storage/aws/AwsCredentialsStorageIntegrationTest.java index fb0c63c403..2732577489 100644 --- a/polaris-core/src/test/java/org/apache/polaris/service/storage/aws/AwsCredentialsStorageIntegrationTest.java +++ b/polaris-core/src/test/java/org/apache/polaris/service/storage/aws/AwsCredentialsStorageIntegrationTest.java @@ -389,6 +389,8 @@ public void testGetSubscopedCredsInlinePolicyWithoutWrites() { String warehouseKeyPrefix = "path/to/warehouse"; String firstPath = warehouseKeyPrefix + "/namespace/table"; String secondPath = warehouseKeyPrefix + "/oldnamespace/table"; + String region = "us-east-2"; + String accountId = "012345678901"; Mockito.when(stsClient.assumeRole(Mockito.isA(AssumeRoleRequest.class))) .thenAnswer( invocation -> { @@ -402,8 +404,26 @@ public void testGetSubscopedCredsInlinePolicyWithoutWrites() { assertThat(policy) .extracting(IamPolicy::statements) .asInstanceOf(InstanceOfAssertFactories.list(IamStatement.class)) - .hasSize(3) + .hasSize(4) .satisfiesExactly( + statement -> + assertThat(statement) + .returns(IamEffect.ALLOW, IamStatement::effect) + .returns( + List.of( + IamAction.create( + "kms:GenerateDataKeyWithoutPlaintext"), + IamAction.create("kms:DescribeKey"), + IamAction.create("kms:Decrypt"), + IamAction.create("kms:GenerateDataKey")), + IamStatement::actions) + .returns( + List.of( + IamResource.create( + String.format( + "arn:aws:kms:%s:%s:key/*", + region, accountId))), + IamStatement::resources), statement -> assertThat(statement) .returns(IamEffect.ALLOW, IamStatement::effect) @@ -456,7 +476,7 @@ public void testGetSubscopedCredsInlinePolicyWithoutWrites() { .addAllowedLocation(s3Path(bucket, warehouseKeyPrefix)) .roleARN(roleARN) .externalId(externalId) - .region("us-east-2") + .region(region) .build(), stsClient) .getSubscopedCreds( @@ -482,6 +502,8 @@ public void testGetSubscopedCredsInlinePolicyWithEmptyReadAndWrite() { String externalId = "externalId"; String bucket = "bucket"; String warehouseKeyPrefix = "path/to/warehouse"; + String region = "us-east-2"; + String accountId = "012345678901"; Mockito.when(stsClient.assumeRole(Mockito.isA(AssumeRoleRequest.class))) .thenAnswer( invocation -> { @@ -495,8 +517,26 @@ public void testGetSubscopedCredsInlinePolicyWithEmptyReadAndWrite() { assertThat(policy) .extracting(IamPolicy::statements) .asInstanceOf(InstanceOfAssertFactories.list(IamStatement.class)) - .hasSize(2) - .satisfiesExactly( + .hasSize(3) + .satisfiesExactlyInAnyOrder( + statement -> + assertThat(statement) + .returns(IamEffect.ALLOW, IamStatement::effect) + .returns( + List.of( + IamAction.create( + "kms:GenerateDataKeyWithoutPlaintext"), + IamAction.create("kms:DescribeKey"), + IamAction.create("kms:Decrypt"), + IamAction.create("kms:GenerateDataKey")), + IamStatement::actions) + .returns( + List.of( + IamResource.create( + String.format( + "arn:aws:kms:%s:%s:key/*", + region, accountId))), + IamStatement::resources), statement -> assertThat(statement) .returns(IamEffect.ALLOW, IamStatement::effect) @@ -523,7 +563,7 @@ public void testGetSubscopedCredsInlinePolicyWithEmptyReadAndWrite() { .addAllowedLocation(s3Path(bucket, warehouseKeyPrefix)) .roleARN(roleARN) .externalId(externalId) - .region("us-east-2") + .region(region) .build(), stsClient) .getSubscopedCreds( @@ -662,6 +702,177 @@ public void testNoClientRegion(String awsPartition) { ; } + @Test + public void testKmsKeyPolicyLogic() { + StsClient stsClient = Mockito.mock(StsClient.class); + String roleARN = "arn:aws:iam::012345678901:role/jdoe"; + String externalId = "externalId"; + String bucket = "bucket"; + String warehouseKeyPrefix = "path/to/warehouse"; + String region = "us-east-1"; + String accountId = "012345678901"; + String currentKmsKey = "arn:aws:kms:us-east-1:012345678901:key/current-key"; + List allowedKmsKeys = + List.of( + "arn:aws:kms:us-east-1:012345678901:key/allowed-key-1", + "arn:aws:kms:us-east-1:012345678901:key/allowed-key-2"); + + // Test with current KMS key and write permissions + Mockito.when(stsClient.assumeRole(Mockito.isA(AssumeRoleRequest.class))) + .thenAnswer( + invocation -> { + AssumeRoleRequest request = invocation.getArgument(0); + IamPolicy policy = IamPolicy.fromJson(request.policy()); + + // Verify KMS statement exists with write permissions + assertThat(policy.statements()) + .anySatisfy( + stmt -> { + assertThat(stmt.actions()) + .containsAll( + List.of( + IamAction.create("kms:GenerateDataKeyWithoutPlaintext"), + IamAction.create("kms:DescribeKey"), + IamAction.create("kms:Decrypt"), + IamAction.create("kms:GenerateDataKey"), + IamAction.create("kms:Encrypt"))); + assertThat(stmt.resources()).contains(IamResource.create(currentKmsKey)); + }); + + return ASSUME_ROLE_RESPONSE; + }); + + new AwsCredentialsStorageIntegration( + AwsStorageConfigurationInfo.builder() + .addAllowedLocation(s3Path(bucket, warehouseKeyPrefix)) + .roleARN(roleARN) + .externalId(externalId) + .region(region) + .currentKmsKey(currentKmsKey) + .build(), + stsClient) + .getSubscopedCreds( + EMPTY_REALM_CONFIG, + true, + Set.of(s3Path(bucket, warehouseKeyPrefix + "/table")), + Set.of(s3Path(bucket, warehouseKeyPrefix + "/table")), + Optional.empty()); + + // Test with allowed KMS keys and read-only permissions + Mockito.reset(stsClient); + Mockito.when(stsClient.assumeRole(Mockito.isA(AssumeRoleRequest.class))) + .thenAnswer( + invocation -> { + AssumeRoleRequest request = invocation.getArgument(0); + IamPolicy policy = IamPolicy.fromJson(request.policy()); + + // Verify KMS statement exists with read-only permissions + assertThat(policy.statements()) + .anySatisfy( + stmt -> { + assertThat(stmt.actions()) + .containsAll( + List.of( + IamAction.create("kms:GenerateDataKeyWithoutPlaintext"), + IamAction.create("kms:DescribeKey"), + IamAction.create("kms:Decrypt"), + IamAction.create("kms:GenerateDataKey"))); + assertThat(stmt.actions()).doesNotContain(IamAction.create("kms:Encrypt")); + assertThat(stmt.resources()) + .containsExactlyInAnyOrder( + IamResource.create(allowedKmsKeys.get(0)), + IamResource.create(allowedKmsKeys.get(1))); + }); + + return ASSUME_ROLE_RESPONSE; + }); + + new AwsCredentialsStorageIntegration( + AwsStorageConfigurationInfo.builder() + .addAllowedLocation(s3Path(bucket, warehouseKeyPrefix)) + .roleARN(roleARN) + .externalId(externalId) + .region(region) + .allowedKmsKeys(allowedKmsKeys) + .build(), + stsClient) + .getSubscopedCreds( + EMPTY_REALM_CONFIG, + true, + Set.of(s3Path(bucket, warehouseKeyPrefix + "/table")), + Set.of(), + Optional.empty()); + + // Test with no KMS keys and read-only (should add wildcard KMS access) + Mockito.reset(stsClient); + Mockito.when(stsClient.assumeRole(Mockito.isA(AssumeRoleRequest.class))) + .thenAnswer( + invocation -> { + AssumeRoleRequest request = invocation.getArgument(0); + IamPolicy policy = IamPolicy.fromJson(request.policy()); + + // Verify wildcard KMS statement exists + assertThat(policy.statements()) + .anySatisfy( + stmt -> { + assertThat(stmt.resources()) + .contains( + IamResource.create( + String.format("arn:aws:kms:%s:%s:key/*", region, accountId))); + }); + + return ASSUME_ROLE_RESPONSE; + }); + + new AwsCredentialsStorageIntegration( + AwsStorageConfigurationInfo.builder() + .addAllowedLocation(s3Path(bucket, warehouseKeyPrefix)) + .roleARN(roleARN) + .externalId(externalId) + .region(region) + .build(), + stsClient) + .getSubscopedCreds( + EMPTY_REALM_CONFIG, + true, + Set.of(s3Path(bucket, warehouseKeyPrefix + "/table")), + Set.of(), + Optional.empty()); + + // Test with no KMS keys and write permissions (should not add KMS statement) + Mockito.reset(stsClient); + Mockito.when(stsClient.assumeRole(Mockito.isA(AssumeRoleRequest.class))) + .thenAnswer( + invocation -> { + AssumeRoleRequest request = invocation.getArgument(0); + IamPolicy policy = IamPolicy.fromJson(request.policy()); + + // Verify no KMS statement exists + assertThat(policy.statements()) + .noneMatch( + stmt -> + stmt.actions().stream() + .anyMatch(action -> action.value().startsWith("kms:"))); + + return ASSUME_ROLE_RESPONSE; + }); + + new AwsCredentialsStorageIntegration( + AwsStorageConfigurationInfo.builder() + .addAllowedLocation(s3Path(bucket, warehouseKeyPrefix)) + .roleARN(roleARN) + .externalId(externalId) + .region(region) + .build(), + stsClient) + .getSubscopedCreds( + EMPTY_REALM_CONFIG, + true, + Set.of(s3Path(bucket, warehouseKeyPrefix + "/table")), + Set.of(s3Path(bucket, warehouseKeyPrefix + "/table")), + Optional.empty()); + } + private static @Nonnull String s3Arn(String partition, String bucket, String keyPrefix) { String bucketArn = "arn:" + partition + ":s3:::" + bucket; if (keyPrefix == null) { diff --git a/runtime/service/src/test/java/org/apache/polaris/service/entity/CatalogEntityTest.java b/runtime/service/src/test/java/org/apache/polaris/service/entity/CatalogEntityTest.java index 5c3c220c82..24c7814ce8 100644 --- a/runtime/service/src/test/java/org/apache/polaris/service/entity/CatalogEntityTest.java +++ b/runtime/service/src/test/java/org/apache/polaris/service/entity/CatalogEntityTest.java @@ -321,6 +321,7 @@ public void testCatalogTypeDefaultsToInternal() { AwsStorageConfigInfo.builder() .setRoleArn("arn:aws:iam::012345678901:role/test-role") .setExternalId("externalId") + .setCurrentKmsKey("arn:aws:kms:us-east-1:012345678901:key/444343245") .setUserArn("aws::a:user:arn") .setStorageType(StorageConfigInfo.StorageTypeEnum.S3) .setAllowedLocations(List.of(baseLocation)) @@ -334,6 +335,8 @@ public void testCatalogTypeDefaultsToInternal() { Catalog catalog = catalogEntity.asCatalog(serviceIdentityProvider); assertThat(catalog.getType()).isEqualTo(Catalog.TypeEnum.INTERNAL); + assertThat(((AwsStorageConfigInfo) catalog.getStorageConfigInfo()).getCurrentKmsKey()) + .isEqualTo("arn:aws:kms:us-east-1:012345678901:key/444343245"); } @Test @@ -342,6 +345,7 @@ public void testCatalogTypeExternalPreserved() { AwsStorageConfigInfo storageConfigModel = AwsStorageConfigInfo.builder() .setRoleArn("arn:aws:iam::012345678901:role/test-role") + .setCurrentKmsKey("arn:aws:kms:us-east-1:012345678901:key/444343245") .setExternalId("externalId") .setUserArn("aws::a:user:arn") .setStorageType(StorageConfigInfo.StorageTypeEnum.S3) @@ -357,6 +361,8 @@ public void testCatalogTypeExternalPreserved() { Catalog catalog = catalogEntity.asCatalog(serviceIdentityProvider); assertThat(catalog.getType()).isEqualTo(Catalog.TypeEnum.EXTERNAL); + assertThat(((AwsStorageConfigInfo) catalog.getStorageConfigInfo()).getCurrentKmsKey()) + .isEqualTo("arn:aws:kms:us-east-1:012345678901:key/444343245"); } @Test diff --git a/spec/polaris-management-service.yml b/spec/polaris-management-service.yml index d1775b7598..03c894b6e4 100644 --- a/spec/polaris-management-service.yml +++ b/spec/polaris-management-service.yml @@ -1103,6 +1103,16 @@ components: type: string description: the aws user arn used to assume the aws role example: "arn:aws:iam::123456789001:user/abc1-b-self1234" + currentKmsKey: + type: string + description: the aws kms key arn used to encrypt s3 data + example: "arn:aws:kms::123456789001:key/01234578" + allowedKmsKeys: + type: array + description: The list of kms keys that this catalog and its clients are allow to use for reading s3 data + items: + type: string + example: ["arn:aws:kms::123456789001:key/01234578"] region: type: string description: the aws region where data is stored