This is an automated email from the ASF dual-hosted git repository.
diqiu50 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/main by this push:
new 2bbb9f2a90 [#13598] feat(credential): Add Aws/Dlf secret-key
credentials (#13599)
2bbb9f2a90 is described below
commit 2bbb9f2a90935ab7fa73883d28289120f389df1b
Author: MaSai <[email protected]>
AuthorDate: Wed Sep 30 14:57:23 2026 +0800
[#13598] feat(credential): Add Aws/Dlf secret-key credentials (#13599)
### What changes were proposed in this pull request?
- Add `AwsSecretKeyCredential` + provider; wire `GlueCatalog`
- Add `DlfSecretKeyCredential` + provider; wire `PaimonCatalog` and
Spark/Flink Paimon injection
- Spark/Trino Glue connectors consume `AwsSecretKeyCredential` (Glue
API) and `S3SecretKeyCredential` (storage)
- Python client factory supports `aws-secret-key` / `dlf-secret-key`
- Hide `dlf-access-key-id`; omit `dlf-security-token` from `getSecrets`
without `INCLUDE_CREDENTIAL_SECRETS`
- When `credential-providers` is set explicitly, Glue still ensures
`aws-secret-key` but does **not** force-append `s3-secret-key`
(preserves `s3-token`)
- Explicit Paimon `credential-providers` still appends JDBC/DLF when
those keys are present (aligned with Jdbc/Iceberg catalogs)
### Why are the changes needed?
Completes Glue AWS and Paimon DLF coverage under credential vending
after #13539.
Fix: #13598
### Does this PR introduce _any_ user-facing change?
Yes:
- New credential types `aws-secret-key` and `dlf-secret-key` via
`getCredentials`
- `dlf-access-key-id` masked on catalog load
- `dlf-security-token` gated like other cloud credential secrets on
`getSecrets`
- Explicit Glue `credential-providers` no longer auto-appends
`s3-secret-key`
- Explicit Paimon `credential-providers` now also gets JDBC/DLF when
configured
### How was this patch tested?
Unit tests for providers, Glue/Paimon wiring, Spark/Trino Glue
credential injection, Python CredentialFactory, Paimon connector
property mapping, and cloud-key filtering.
---------
Co-authored-by: Cursor <[email protected]>
---
.../credential/AwsSecretKeyCredential.java | 112 +++++++++++++++
.../credential/DlfSecretKeyCredential.java | 150 ++++++++++++++++++++
.../org.apache.gravitino.credential.Credential | 2 +
.../lakehouse/paimon/PaimonPropertiesUtils.java | 12 ++
.../CloudStorageCredentialPropertyKeys.java | 3 +-
.../TestCloudStorageCredentialPropertyKeys.java | 25 ++++
.../apache/gravitino/catalog/glue/GlueCatalog.java | 12 +-
.../catalog/glue/TestGlueCatalogCredentials.java | 152 +++++++++++++++++++++
.../catalog/lakehouse/paimon/PaimonCatalog.java | 36 ++++-
.../paimon/PaimonCatalogPropertiesMetadata.java | 2 +-
.../lakehouse/paimon/TestPaimonCatalog.java | 145 +++++++++++++++++++-
.../TestPaimonCatalogPropertiesMetadata.java | 5 +-
.../api/credential/aws_secret_key_credential.py | 62 +++++++++
.../api/credential/dlf_secret_key_credential.py | 70 ++++++++++
.../gravitino/utils/credential_factory.py | 6 +
.../tests/unittests/test_credential_factory.py | 44 ++++++
.../credential/CredentialPropertyUtils.java | 13 +-
.../credential/TestCredentialFactory.java | 67 +++++++++
.../credential/TestCredentialPropertiesUtils.java | 39 ++++++
.../credential/AwsSecretKeyCredentialProvider.java | 70 ++++++++++
.../credential/DlfSecretKeyCredentialProvider.java | 72 ++++++++++
....apache.gravitino.credential.CredentialProvider | 2 +
.../TestAwsSecretKeyCredentialProvider.java | 72 ++++++++++
.../TestDlfSecretKeyCredentialProvider.java | 83 +++++++++++
....apache.gravitino.credential.CredentialProvider | 2 +
design-docs/gravitino-glue-catalog.md | 4 +-
docs/aws-glue-catalog.md | 7 +-
docs/lakehouse-paimon-catalog.md | 8 +-
docs/security/credential-vending.md | 15 +-
.../paimon/TestGravitinoPaimonCatalog.java | 20 +++
.../paimon/TestPaimonPropertiesConverter.java | 27 ++++
.../spark/connector/glue/GravitinoGlueCatalog.java | 29 ++--
.../connector/glue/TestGravitinoGlueCatalog.java | 19 +++
.../catalog/glue/GlueConnectorAdapter.java | 26 +++-
.../catalog/glue/TestGlueConnectorAdapter.java | 15 ++
35 files changed, 1382 insertions(+), 46 deletions(-)
diff --git
a/api/src/main/java/org/apache/gravitino/credential/AwsSecretKeyCredential.java
b/api/src/main/java/org/apache/gravitino/credential/AwsSecretKeyCredential.java
new file mode 100644
index 0000000000..25c7a53bef
--- /dev/null
+++
b/api/src/main/java/org/apache/gravitino/credential/AwsSecretKeyCredential.java
@@ -0,0 +1,112 @@
+/*
+ * 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.gravitino.credential;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+import java.util.Map;
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * Static AWS access-key credential for Glue (and similar) API authentication.
+ *
+ * <p>Distinct from {@link S3SecretKeyCredential}: credential-info keys are
{@code
+ * aws-access-key-id} / {@code aws-secret-access-key}, matching Glue catalog
properties.
+ */
+public class AwsSecretKeyCredential implements Credential {
+
+ /** AWS secret-key credential type. */
+ public static final String AWS_SECRET_KEY_CREDENTIAL_TYPE = "aws-secret-key";
+ /** AWS access key ID. */
+ public static final String GRAVITINO_AWS_ACCESS_KEY_ID = "aws-access-key-id";
+ /** AWS secret access key. */
+ public static final String GRAVITINO_AWS_SECRET_ACCESS_KEY =
"aws-secret-access-key";
+
+ private String accessKeyId;
+ private String secretAccessKey;
+
+ /**
+ * Constructs an {@link AwsSecretKeyCredential}.
+ *
+ * @param accessKeyId the AWS access key ID
+ * @param secretAccessKey the AWS secret access key
+ */
+ public AwsSecretKeyCredential(String accessKeyId, String secretAccessKey) {
+ validate(accessKeyId, secretAccessKey, 0);
+ this.accessKeyId = accessKeyId;
+ this.secretAccessKey = secretAccessKey;
+ }
+
+ /** Used by the credential factory. */
+ public AwsSecretKeyCredential() {}
+
+ @Override
+ public String credentialType() {
+ return AWS_SECRET_KEY_CREDENTIAL_TYPE;
+ }
+
+ @Override
+ public long expireTimeInMs() {
+ return 0;
+ }
+
+ @Override
+ public Map<String, String> credentialInfo() {
+ return new ImmutableMap.Builder<String, String>()
+ .put(GRAVITINO_AWS_ACCESS_KEY_ID, accessKeyId)
+ .put(GRAVITINO_AWS_SECRET_ACCESS_KEY, secretAccessKey)
+ .build();
+ }
+
+ @Override
+ public void initialize(Map<String, String> credentialInfo, long
expireTimeInMs) {
+ String accessKeyId = credentialInfo.get(GRAVITINO_AWS_ACCESS_KEY_ID);
+ String secretAccessKey =
credentialInfo.get(GRAVITINO_AWS_SECRET_ACCESS_KEY);
+ validate(accessKeyId, secretAccessKey, expireTimeInMs);
+ this.accessKeyId = accessKeyId;
+ this.secretAccessKey = secretAccessKey;
+ }
+
+ /**
+ * Returns the AWS access key ID.
+ *
+ * @return access key ID
+ */
+ public String accessKeyId() {
+ return accessKeyId;
+ }
+
+ /**
+ * Returns the AWS secret access key.
+ *
+ * @return secret access key
+ */
+ public String secretAccessKey() {
+ return secretAccessKey;
+ }
+
+ private void validate(String accessKeyId, String secretAccessKey, long
expireTimeInMs) {
+ Preconditions.checkArgument(
+ StringUtils.isNotBlank(accessKeyId), "AWS access key ID should not be
empty");
+ Preconditions.checkArgument(
+ StringUtils.isNotBlank(secretAccessKey), "AWS secret access key should
not be empty");
+ Preconditions.checkArgument(
+ expireTimeInMs == 0, "The expiration time of AwsSecretKeyCredential
should be 0");
+ }
+}
diff --git
a/api/src/main/java/org/apache/gravitino/credential/DlfSecretKeyCredential.java
b/api/src/main/java/org/apache/gravitino/credential/DlfSecretKeyCredential.java
new file mode 100644
index 0000000000..a10921512a
--- /dev/null
+++
b/api/src/main/java/org/apache/gravitino/credential/DlfSecretKeyCredential.java
@@ -0,0 +1,150 @@
+/*
+ * 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.gravitino.credential;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+import java.util.Map;
+import javax.annotation.Nullable;
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * Static Alibaba Cloud DLF (Data Lake Formation) access-key credential for
Paimon DLF catalogs.
+ *
+ * <p>Credential-info keys match Gravitino Paimon catalog properties: {@code
dlf-access-key-id},
+ * {@code dlf-access-key-secret}, and optionally {@code dlf-security-token}.
Connectors map these to
+ * Paimon REST keys {@code dlf.access-key-id}, {@code dlf.access-key-secret},
and {@code
+ * dlf.security-token}.
+ *
+ * <p>{@code dlf-security-token} is passed through as-is. {@link
#expireTimeInMs()} is always {@code
+ * 0}; Gravitino does not refresh this STS token. For rotating DLF tokens,
configure {@code
+ * dlf-token-loader} / {@code dlf-token-path} on the Paimon catalog instead.
+ */
+public class DlfSecretKeyCredential implements Credential {
+
+ /** DLF secret-key credential type. */
+ public static final String DLF_SECRET_KEY_CREDENTIAL_TYPE = "dlf-secret-key";
+ /** DLF access key ID. */
+ public static final String GRAVITINO_DLF_ACCESS_KEY_ID = "dlf-access-key-id";
+ /** DLF access key secret. */
+ public static final String GRAVITINO_DLF_ACCESS_KEY_SECRET =
"dlf-access-key-secret";
+ /** Optional DLF security token. */
+ public static final String GRAVITINO_DLF_SECURITY_TOKEN =
"dlf-security-token";
+
+ private String accessKeyId;
+ private String accessKeySecret;
+ @Nullable private String securityToken;
+
+ /**
+ * Constructs a {@link DlfSecretKeyCredential} without a security token.
+ *
+ * @param accessKeyId the DLF access key ID
+ * @param accessKeySecret the DLF access key secret
+ */
+ public DlfSecretKeyCredential(String accessKeyId, String accessKeySecret) {
+ this(accessKeyId, accessKeySecret, null);
+ }
+
+ /**
+ * Constructs a {@link DlfSecretKeyCredential}.
+ *
+ * @param accessKeyId the DLF access key ID
+ * @param accessKeySecret the DLF access key secret
+ * @param securityToken optional security token
+ */
+ public DlfSecretKeyCredential(
+ String accessKeyId, String accessKeySecret, @Nullable String
securityToken) {
+ validate(accessKeyId, accessKeySecret, 0);
+ this.accessKeyId = accessKeyId;
+ this.accessKeySecret = accessKeySecret;
+ this.securityToken = securityToken;
+ }
+
+ /** Used by the credential factory. */
+ public DlfSecretKeyCredential() {}
+
+ @Override
+ public String credentialType() {
+ return DLF_SECRET_KEY_CREDENTIAL_TYPE;
+ }
+
+ @Override
+ public long expireTimeInMs() {
+ return 0;
+ }
+
+ @Override
+ public Map<String, String> credentialInfo() {
+ ImmutableMap.Builder<String, String> builder =
+ new ImmutableMap.Builder<String, String>()
+ .put(GRAVITINO_DLF_ACCESS_KEY_ID, accessKeyId)
+ .put(GRAVITINO_DLF_ACCESS_KEY_SECRET, accessKeySecret);
+ if (StringUtils.isNotBlank(securityToken)) {
+ builder.put(GRAVITINO_DLF_SECURITY_TOKEN, securityToken);
+ }
+ return builder.build();
+ }
+
+ @Override
+ public void initialize(Map<String, String> credentialInfo, long
expireTimeInMs) {
+ String accessKeyId = credentialInfo.get(GRAVITINO_DLF_ACCESS_KEY_ID);
+ String accessKeySecret =
credentialInfo.get(GRAVITINO_DLF_ACCESS_KEY_SECRET);
+ validate(accessKeyId, accessKeySecret, expireTimeInMs);
+ this.accessKeyId = accessKeyId;
+ this.accessKeySecret = accessKeySecret;
+ this.securityToken = credentialInfo.get(GRAVITINO_DLF_SECURITY_TOKEN);
+ }
+
+ /**
+ * Returns the DLF access key ID.
+ *
+ * @return access key ID
+ */
+ public String accessKeyId() {
+ return accessKeyId;
+ }
+
+ /**
+ * Returns the DLF access key secret.
+ *
+ * @return access key secret
+ */
+ public String accessKeySecret() {
+ return accessKeySecret;
+ }
+
+ /**
+ * Returns the optional DLF security token.
+ *
+ * @return security token, or null
+ */
+ @Nullable
+ public String securityToken() {
+ return securityToken;
+ }
+
+ private void validate(String accessKeyId, String accessKeySecret, long
expireTimeInMs) {
+ Preconditions.checkArgument(
+ StringUtils.isNotBlank(accessKeyId), "DLF access key ID should not be
empty");
+ Preconditions.checkArgument(
+ StringUtils.isNotBlank(accessKeySecret), "DLF access key secret should
not be empty");
+ Preconditions.checkArgument(
+ expireTimeInMs == 0, "The expiration time of DlfSecretKeyCredential
should be 0");
+ }
+}
diff --git
a/api/src/main/resources/META-INF/services/org.apache.gravitino.credential.Credential
b/api/src/main/resources/META-INF/services/org.apache.gravitino.credential.Credential
index 5e4f705c5b..bfed13388b 100644
---
a/api/src/main/resources/META-INF/services/org.apache.gravitino.credential.Credential
+++
b/api/src/main/resources/META-INF/services/org.apache.gravitino.credential.Credential
@@ -28,3 +28,5 @@ org.apache.gravitino.credential.ADLSTokenCredential
org.apache.gravitino.credential.AzureAccountKeyCredential
org.apache.gravitino.credential.AwsIrsaCredential
org.apache.gravitino.credential.JdbcCredential
+org.apache.gravitino.credential.AwsSecretKeyCredential
+org.apache.gravitino.credential.DlfSecretKeyCredential
diff --git
a/catalogs/catalog-common/src/main/java/org/apache/gravitino/catalog/lakehouse/paimon/PaimonPropertiesUtils.java
b/catalogs/catalog-common/src/main/java/org/apache/gravitino/catalog/lakehouse/paimon/PaimonPropertiesUtils.java
index 7b1832fe56..415e47db0b 100644
---
a/catalogs/catalog-common/src/main/java/org/apache/gravitino/catalog/lakehouse/paimon/PaimonPropertiesUtils.java
+++
b/catalogs/catalog-common/src/main/java/org/apache/gravitino/catalog/lakehouse/paimon/PaimonPropertiesUtils.java
@@ -60,6 +60,18 @@ public class PaimonPropertiesUtils {
OSSProperties.GRAVITINO_OSS_ACCESS_KEY_ID,
PaimonConstants.OSS_ACCESS_KEY);
gravitinoConfigToPaimon.put(
OSSProperties.GRAVITINO_OSS_ACCESS_KEY_SECRET,
PaimonConstants.OSS_SECRET_KEY);
+ // DLF (Data Lake Formation)
+ gravitinoConfigToPaimon.put(
+ PaimonConstants.GRAVITINO_DLF_ACCESS_KEY_ID,
PaimonConstants.PAIMON_DLF_ACCESS_KEY_ID);
+ gravitinoConfigToPaimon.put(
+ PaimonConstants.GRAVITINO_DLF_ACCESS_KEY_SECRET,
+ PaimonConstants.PAIMON_DLF_ACCESS_KEY_SECRET);
+ gravitinoConfigToPaimon.put(
+ PaimonConstants.GRAVITINO_DLF_SECURITY_TOKEN,
PaimonConstants.PAIMON_DLF_SECURITY_TOKEN);
+ gravitinoConfigToPaimon.put(
+ PaimonConstants.GRAVITINO_DLF_TOKEN_PATH,
PaimonConstants.PAIMON_DLF_TOKEN_PATH);
+ gravitinoConfigToPaimon.put(
+ PaimonConstants.GRAVITINO_DLF_TOKEN_LOADER,
PaimonConstants.PAIMON_DLF_TOKEN_LOADER);
GRAVITINO_CONFIG_TO_PAIMON =
Collections.unmodifiableMap(gravitinoConfigToPaimon);
gravitinoConfigToPaimon.forEach(
(key, value) -> {
diff --git
a/catalogs/catalog-common/src/main/java/org/apache/gravitino/storage/CloudStorageCredentialPropertyKeys.java
b/catalogs/catalog-common/src/main/java/org/apache/gravitino/storage/CloudStorageCredentialPropertyKeys.java
index 400c46fb14..fd97207ed8 100644
---
a/catalogs/catalog-common/src/main/java/org/apache/gravitino/storage/CloudStorageCredentialPropertyKeys.java
+++
b/catalogs/catalog-common/src/main/java/org/apache/gravitino/storage/CloudStorageCredentialPropertyKeys.java
@@ -80,7 +80,8 @@ public final class CloudStorageCredentialPropertyKeys {
AzureProperties.GRAVITINO_AZURE_STORAGE_ACCOUNT_KEY,
AzureProperties.GRAVITINO_AZURE_CLIENT_SECRET,
PaimonConstants.GRAVITINO_DLF_ACCESS_KEY_ID,
- PaimonConstants.GRAVITINO_DLF_ACCESS_KEY_SECRET);
+ PaimonConstants.GRAVITINO_DLF_ACCESS_KEY_SECRET,
+ PaimonConstants.GRAVITINO_DLF_SECURITY_TOKEN);
private CloudStorageCredentialPropertyKeys() {}
diff --git
a/catalogs/catalog-common/src/test/java/org/apache/gravitino/storage/TestCloudStorageCredentialPropertyKeys.java
b/catalogs/catalog-common/src/test/java/org/apache/gravitino/storage/TestCloudStorageCredentialPropertyKeys.java
index 7319467126..09e25f1b64 100644
---
a/catalogs/catalog-common/src/test/java/org/apache/gravitino/storage/TestCloudStorageCredentialPropertyKeys.java
+++
b/catalogs/catalog-common/src/test/java/org/apache/gravitino/storage/TestCloudStorageCredentialPropertyKeys.java
@@ -156,6 +156,31 @@ public class TestCloudStorageCredentialPropertyKeys {
assertTrue(
CloudStorageCredentialPropertyKeys.isCloudAccessKeyPairKey(
PaimonConstants.GRAVITINO_DLF_ACCESS_KEY_SECRET));
+ assertTrue(
+ CloudStorageCredentialPropertyKeys.isCloudAccessKeyPairKey(
+ PaimonConstants.GRAVITINO_DLF_SECURITY_TOKEN));
assertFalse(CloudStorageCredentialPropertyKeys.isCloudAccessKeyPairKey("jdbc-password"));
}
+
+ @Test
+ void testOmitCloudAccessKeyPairOmitsDlfSecurityToken() {
+ Map<String, String> secrets =
+ Map.of(
+ PaimonConstants.GRAVITINO_DLF_ACCESS_KEY_ID,
+ "dlf-ak",
+ PaimonConstants.GRAVITINO_DLF_ACCESS_KEY_SECRET,
+ "dlf-sk",
+ PaimonConstants.GRAVITINO_DLF_SECURITY_TOKEN,
+ "dlf-token",
+ "jdbc-password",
+ "db-pass");
+
+ Map<String, String> filtered =
+
CloudStorageCredentialPropertyKeys.omitCloudAccessKeyPairProperties(secrets);
+
+ assertEquals("db-pass", filtered.get("jdbc-password"));
+
assertFalse(filtered.containsKey(PaimonConstants.GRAVITINO_DLF_ACCESS_KEY_ID));
+
assertFalse(filtered.containsKey(PaimonConstants.GRAVITINO_DLF_ACCESS_KEY_SECRET));
+
assertFalse(filtered.containsKey(PaimonConstants.GRAVITINO_DLF_SECURITY_TOKEN));
+ }
}
diff --git
a/catalogs/catalog-glue/src/main/java/org/apache/gravitino/catalog/glue/GlueCatalog.java
b/catalogs/catalog-glue/src/main/java/org/apache/gravitino/catalog/glue/GlueCatalog.java
index 8222c0d71b..ae460201e9 100644
---
a/catalogs/catalog-glue/src/main/java/org/apache/gravitino/catalog/glue/GlueCatalog.java
+++
b/catalogs/catalog-glue/src/main/java/org/apache/gravitino/catalog/glue/GlueCatalog.java
@@ -25,6 +25,7 @@ import org.apache.gravitino.connector.BaseCatalog;
import org.apache.gravitino.connector.CatalogOperations;
import org.apache.gravitino.connector.PropertiesMetadata;
import org.apache.gravitino.connector.capability.Capability;
+import org.apache.gravitino.credential.AwsSecretKeyCredential;
import org.apache.gravitino.storage.S3Properties;
/**
@@ -89,14 +90,17 @@ public class GlueCatalog extends BaseCatalog<GlueCatalog> {
public Map<String, String> propertiesWithCredentialProviders() {
Map<String, String> props = super.propertiesWithCredentialProviders();
// super() skips addCatalogSpecificCredentialProviders() when
credential-providers is already
- // set, so the aws-* → s3-* key mapping never runs. Apply it
unconditionally here so that
- // S3SecretKeyProvider.initialize() can read s3-access-key-id regardless
of how the catalog
- // was configured.
+ // set, so the aws-* → s3-* key mapping never runs. Remap keys and ensure
aws-secret-key so Glue
+ // API credentials stay available via getCredentials. Do not force-append
s3-secret-key when the
+ // user set credential-providers explicitly (e.g. s3-token); that would
inject static S3 keys
+ // beside STS and break path-based credential selection. Auto-detect still
registers
+ // s3-secret-key via addStorageCredentialProviders when providers are
omitted.
String accessKeyId = props.get(GlueConstants.AWS_ACCESS_KEY_ID);
String secretAccessKey = props.get(GlueConstants.AWS_SECRET_ACCESS_KEY);
if (StringUtils.isNotBlank(accessKeyId) &&
StringUtils.isNotBlank(secretAccessKey)) {
props.putIfAbsent(S3Properties.GRAVITINO_S3_ACCESS_KEY_ID, accessKeyId);
props.putIfAbsent(S3Properties.GRAVITINO_S3_SECRET_ACCESS_KEY,
secretAccessKey);
+ ensureCredentialProviderListed(props,
AwsSecretKeyCredential.AWS_SECRET_KEY_CREDENTIAL_TYPE);
}
return props;
}
@@ -107,6 +111,8 @@ public class GlueCatalog extends BaseCatalog<GlueCatalog> {
String accessKeyId = properties.get(GlueConstants.AWS_ACCESS_KEY_ID);
String secretAccessKey =
properties.get(GlueConstants.AWS_SECRET_ACCESS_KEY);
if (StringUtils.isNotBlank(accessKeyId) &&
StringUtils.isNotBlank(secretAccessKey)) {
+ // Glue API auth via aws-* keys; also remap to s3-* for storage
credential vending.
+
credentialProviders.add(AwsSecretKeyCredential.AWS_SECRET_KEY_CREDENTIAL_TYPE);
properties.putIfAbsent(S3Properties.GRAVITINO_S3_ACCESS_KEY_ID,
accessKeyId);
properties.putIfAbsent(S3Properties.GRAVITINO_S3_SECRET_ACCESS_KEY,
secretAccessKey);
}
diff --git
a/catalogs/catalog-glue/src/test/java/org/apache/gravitino/catalog/glue/TestGlueCatalogCredentials.java
b/catalogs/catalog-glue/src/test/java/org/apache/gravitino/catalog/glue/TestGlueCatalogCredentials.java
new file mode 100644
index 0000000000..90ee4804c5
--- /dev/null
+++
b/catalogs/catalog-glue/src/test/java/org/apache/gravitino/catalog/glue/TestGlueCatalogCredentials.java
@@ -0,0 +1,152 @@
+/*
+ * 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.gravitino.catalog.glue;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import org.apache.gravitino.Catalog;
+import org.apache.gravitino.Namespace;
+import org.apache.gravitino.credential.AwsSecretKeyCredential;
+import org.apache.gravitino.credential.CredentialConstants;
+import org.apache.gravitino.credential.S3SecretKeyCredential;
+import org.apache.gravitino.credential.S3TokenCredential;
+import org.apache.gravitino.meta.AuditInfo;
+import org.apache.gravitino.meta.CatalogEntity;
+import org.apache.gravitino.storage.S3Properties;
+import org.junit.jupiter.api.Test;
+
+public class TestGlueCatalogCredentials {
+
+ @Test
+ void testAwsKeysAutoDetectAwsAndS3Providers() {
+ Map<String, String> props =
+ Map.of(
+ GlueConstants.AWS_ACCESS_KEY_ID,
+ "AKIA",
+ GlueConstants.AWS_SECRET_ACCESS_KEY,
+ "secret",
+ GlueConstants.AWS_REGION,
+ "us-east-1");
+
+ CatalogEntity entity =
+ CatalogEntity.builder()
+ .withId(1L)
+ .withName("glue")
+ .withNamespace(Namespace.of("metalake"))
+ .withType(Catalog.Type.RELATIONAL)
+ .withProvider("glue")
+ .withProperties(props)
+ .withAuditInfo(
+
AuditInfo.builder().withCreator("test").withCreateTime(Instant.now()).build())
+ .build();
+
+ GlueCatalog catalog = new
GlueCatalog().withCatalogEntity(entity).withCatalogConf(props);
+ Map<String, String> withProviders =
catalog.propertiesWithCredentialProviders();
+
+ List<String> providers = splitProviders(withProviders);
+
assertTrue(providers.contains(AwsSecretKeyCredential.AWS_SECRET_KEY_CREDENTIAL_TYPE));
+
assertTrue(providers.contains(S3SecretKeyCredential.S3_SECRET_KEY_CREDENTIAL_TYPE));
+ assertEquals("AKIA",
withProviders.get(S3Properties.GRAVITINO_S3_ACCESS_KEY_ID));
+ assertEquals("secret",
withProviders.get(S3Properties.GRAVITINO_S3_SECRET_ACCESS_KEY));
+ assertEquals("AKIA", withProviders.get(GlueConstants.AWS_ACCESS_KEY_ID));
+ }
+
+ @Test
+ void testExplicitCredentialProvidersStillGetsAwsButNotS3SecretKey() {
+ Map<String, String> props =
+ Map.of(
+ GlueConstants.AWS_ACCESS_KEY_ID,
+ "AKIA",
+ GlueConstants.AWS_SECRET_ACCESS_KEY,
+ "secret",
+ GlueConstants.AWS_REGION,
+ "us-east-1",
+ CredentialConstants.CREDENTIAL_PROVIDERS,
+ "custom-provider");
+
+ CatalogEntity entity =
+ CatalogEntity.builder()
+ .withId(2L)
+ .withName("glue-explicit")
+ .withNamespace(Namespace.of("metalake"))
+ .withType(Catalog.Type.RELATIONAL)
+ .withProvider("glue")
+ .withProperties(props)
+ .withAuditInfo(
+
AuditInfo.builder().withCreator("test").withCreateTime(Instant.now()).build())
+ .build();
+
+ GlueCatalog catalog = new
GlueCatalog().withCatalogEntity(entity).withCatalogConf(props);
+ Map<String, String> withProviders =
catalog.propertiesWithCredentialProviders();
+
+ List<String> providers = splitProviders(withProviders);
+ assertEquals(
+ List.of("custom-provider",
AwsSecretKeyCredential.AWS_SECRET_KEY_CREDENTIAL_TYPE),
+ providers);
+
assertFalse(providers.contains(S3SecretKeyCredential.S3_SECRET_KEY_CREDENTIAL_TYPE));
+ assertEquals("AKIA",
withProviders.get(S3Properties.GRAVITINO_S3_ACCESS_KEY_ID));
+ }
+
+ @Test
+ void testExplicitS3TokenDoesNotAppendS3SecretKey() {
+ Map<String, String> props =
+ Map.of(
+ GlueConstants.AWS_ACCESS_KEY_ID,
+ "AKIA",
+ GlueConstants.AWS_SECRET_ACCESS_KEY,
+ "secret",
+ GlueConstants.AWS_REGION,
+ "us-east-1",
+ CredentialConstants.CREDENTIAL_PROVIDERS,
+ S3TokenCredential.S3_TOKEN_CREDENTIAL_TYPE);
+
+ CatalogEntity entity =
+ CatalogEntity.builder()
+ .withId(3L)
+ .withName("glue-s3-token")
+ .withNamespace(Namespace.of("metalake"))
+ .withType(Catalog.Type.RELATIONAL)
+ .withProvider("glue")
+ .withProperties(props)
+ .withAuditInfo(
+
AuditInfo.builder().withCreator("test").withCreateTime(Instant.now()).build())
+ .build();
+
+ GlueCatalog catalog = new
GlueCatalog().withCatalogEntity(entity).withCatalogConf(props);
+ Map<String, String> withProviders =
catalog.propertiesWithCredentialProviders();
+
+ assertEquals(
+ List.of(
+ S3TokenCredential.S3_TOKEN_CREDENTIAL_TYPE,
+ AwsSecretKeyCredential.AWS_SECRET_KEY_CREDENTIAL_TYPE),
+ splitProviders(withProviders));
+ }
+
+ private static List<String> splitProviders(Map<String, String>
withProviders) {
+ String providers =
withProviders.get(CredentialConstants.CREDENTIAL_PROVIDERS);
+ return
Arrays.stream(providers.split(",")).map(String::trim).collect(Collectors.toList());
+ }
+}
diff --git
a/catalogs/catalog-lakehouse-paimon/src/main/java/org/apache/gravitino/catalog/lakehouse/paimon/PaimonCatalog.java
b/catalogs/catalog-lakehouse-paimon/src/main/java/org/apache/gravitino/catalog/lakehouse/paimon/PaimonCatalog.java
index 5c7afa7a63..1898072b67 100644
---
a/catalogs/catalog-lakehouse-paimon/src/main/java/org/apache/gravitino/catalog/lakehouse/paimon/PaimonCatalog.java
+++
b/catalogs/catalog-lakehouse-paimon/src/main/java/org/apache/gravitino/catalog/lakehouse/paimon/PaimonCatalog.java
@@ -20,12 +20,14 @@ package org.apache.gravitino.catalog.lakehouse.paimon;
import java.util.List;
import java.util.Map;
+import java.util.function.Consumer;
import org.apache.commons.lang3.StringUtils;
import org.apache.gravitino.Catalog;
import org.apache.gravitino.connector.BaseCatalog;
import org.apache.gravitino.connector.CatalogOperations;
import org.apache.gravitino.connector.PropertiesMetadata;
import org.apache.gravitino.connector.capability.Capability;
+import org.apache.gravitino.credential.DlfSecretKeyCredential;
import org.apache.gravitino.credential.JdbcCredential;
import org.apache.gravitino.rel.ViewCatalog;
@@ -88,8 +90,24 @@ public class PaimonCatalog extends
BaseCatalog<PaimonCatalog> {
}
/**
- * Adds a JDBC credential provider when the backend is JDBC and credentials
are configured, then
- * delegates to the parent for storage (S3/OSS/Azure/GCS) credential
provider detection.
+ * Ensures JDBC / DLF providers stay listed even when {@code
credential-providers} was set
+ * explicitly. {@code super} skips {@link
#addCatalogSpecificCredentialProviders} in that case, so
+ * without this override {@code getCredentials} would not vend JDBC/DLF
(callers with {@code
+ * USE_SECRETS} can still recover those keys via {@code getSecrets}; DLF
cloud pairs also need
+ * {@code INCLUDE_CREDENTIAL_SECRETS}).
+ *
+ * @return catalog properties with credential providers
+ */
+ @Override
+ public Map<String, String> propertiesWithCredentialProviders() {
+ Map<String, String> props = super.propertiesWithCredentialProviders();
+ detectJdbcAndDlfProviders(props, type ->
ensureCredentialProviderListed(props, type));
+ return props;
+ }
+
+ /**
+ * Adds JDBC and/or DLF credential providers when those credentials are
configured, then delegates
+ * to the parent for storage (S3/OSS/Azure/GCS) credential provider
detection.
*
* @param properties the raw catalog properties
* @param credentialProviders the list to append detected provider names to
@@ -97,15 +115,25 @@ public class PaimonCatalog extends
BaseCatalog<PaimonCatalog> {
@Override
protected void addCatalogSpecificCredentialProviders(
Map<String, String> properties, List<String> credentialProviders) {
+ detectJdbcAndDlfProviders(properties, credentialProviders::add);
+ addStorageCredentialProviders(properties, credentialProviders);
+ }
+
+ private static void detectJdbcAndDlfProviders(
+ Map<String, String> properties, Consumer<String> onProvider) {
String catalogBackend = properties.get(PaimonConstants.CATALOG_BACKEND);
if (catalogBackend != null
&& PaimonCatalogBackend.JDBC.name().equalsIgnoreCase(catalogBackend)) {
String jdbcUser = properties.get(PaimonConstants.GRAVITINO_JDBC_USER);
String jdbcPassword =
properties.get(PaimonConstants.GRAVITINO_JDBC_PASSWORD);
if (StringUtils.isNotBlank(jdbcUser) && jdbcPassword != null) {
- credentialProviders.add(JdbcCredential.JDBC_CREDENTIAL_TYPE);
+ onProvider.accept(JdbcCredential.JDBC_CREDENTIAL_TYPE);
}
}
- addStorageCredentialProviders(properties, credentialProviders);
+ String dlfAccessKeyId =
properties.get(PaimonConstants.GRAVITINO_DLF_ACCESS_KEY_ID);
+ String dlfAccessKeySecret =
properties.get(PaimonConstants.GRAVITINO_DLF_ACCESS_KEY_SECRET);
+ if (StringUtils.isNotBlank(dlfAccessKeyId) &&
StringUtils.isNotBlank(dlfAccessKeySecret)) {
+ onProvider.accept(DlfSecretKeyCredential.DLF_SECRET_KEY_CREDENTIAL_TYPE);
+ }
}
}
diff --git
a/catalogs/catalog-lakehouse-paimon/src/main/java/org/apache/gravitino/catalog/lakehouse/paimon/PaimonCatalogPropertiesMetadata.java
b/catalogs/catalog-lakehouse-paimon/src/main/java/org/apache/gravitino/catalog/lakehouse/paimon/PaimonCatalogPropertiesMetadata.java
index 2092d53ced..270a1602bf 100644
---
a/catalogs/catalog-lakehouse-paimon/src/main/java/org/apache/gravitino/catalog/lakehouse/paimon/PaimonCatalogPropertiesMetadata.java
+++
b/catalogs/catalog-lakehouse-paimon/src/main/java/org/apache/gravitino/catalog/lakehouse/paimon/PaimonCatalogPropertiesMetadata.java
@@ -132,7 +132,7 @@ public class PaimonCatalogPropertiesMetadata extends
BaseCatalogPropertiesMetada
"The access key ID for Aliyun DLF",
false /* immutable */,
null /* defaultValue */,
- false /* hidden */))
+ true /* hidden */))
.put(
PaimonConstants.GRAVITINO_DLF_ACCESS_KEY_SECRET,
stringOptionalPropertyEntry(
diff --git
a/catalogs/catalog-lakehouse-paimon/src/test/java/org/apache/gravitino/catalog/lakehouse/paimon/TestPaimonCatalog.java
b/catalogs/catalog-lakehouse-paimon/src/test/java/org/apache/gravitino/catalog/lakehouse/paimon/TestPaimonCatalog.java
index 998d1d9386..a6c267de84 100644
---
a/catalogs/catalog-lakehouse-paimon/src/test/java/org/apache/gravitino/catalog/lakehouse/paimon/TestPaimonCatalog.java
+++
b/catalogs/catalog-lakehouse-paimon/src/test/java/org/apache/gravitino/catalog/lakehouse/paimon/TestPaimonCatalog.java
@@ -39,6 +39,7 @@ import org.apache.gravitino.connector.HasPropertyMetadata;
import org.apache.gravitino.connector.PropertiesMetadata;
import org.apache.gravitino.credential.AzureAccountKeyCredential;
import org.apache.gravitino.credential.CredentialConstants;
+import org.apache.gravitino.credential.DlfSecretKeyCredential;
import org.apache.gravitino.credential.JdbcCredential;
import org.apache.gravitino.credential.OSSSecretKeyCredential;
import org.apache.gravitino.credential.S3SecretKeyCredential;
@@ -313,6 +314,38 @@ public class TestPaimonCatalog {
Assertions.assertNull(credentialProviders);
}
+ @Test
+ void testDlfCredentialProviderAutoDetected() {
+ AuditInfo auditInfo =
+
AuditInfo.builder().withCreator("creator").withCreateTime(Instant.now()).build();
+
+ Map<String, String> dlfProps = Maps.newHashMap();
+ dlfProps.put(PaimonConstants.CATALOG_BACKEND, "rest");
+ dlfProps.put(PaimonConstants.WAREHOUSE, tempDir);
+ dlfProps.put(PaimonConstants.GRAVITINO_DLF_ACCESS_KEY_ID, "dlf-ak");
+ dlfProps.put(PaimonConstants.GRAVITINO_DLF_ACCESS_KEY_SECRET, "dlf-sk");
+
+ CatalogEntity dlfEntity =
+ CatalogEntity.builder()
+ .withId(4L)
+ .withName("dlf-catalog")
+ .withNamespace(Namespace.of("metalake"))
+ .withType(PaimonCatalog.Type.RELATIONAL)
+ .withProvider("lakehouse-paimon")
+ .withAuditInfo(auditInfo)
+ .withProperties(dlfProps)
+ .build();
+
+ PaimonCatalog dlfCatalog =
+ new
PaimonCatalog().withCatalogConf(dlfProps).withCatalogEntity(dlfEntity);
+ Map<String, String> properties =
dlfCatalog.propertiesWithCredentialProviders();
+
+ String credentialProviders =
properties.get(CredentialConstants.CREDENTIAL_PROVIDERS);
+ Assertions.assertNotNull(credentialProviders);
+ Assertions.assertTrue(
+
credentialProviders.contains(DlfSecretKeyCredential.DLF_SECRET_KEY_CREDENTIAL_TYPE));
+ }
+
@Test
void testJdbcBackendWithOSSCredentialProviders() {
AuditInfo auditInfo =
@@ -390,11 +423,40 @@ public class TestPaimonCatalog {
}
@Test
- void testExplicitCredentialProvidersNotOverridden() {
+ void testExplicitCredentialProvidersWithoutJdbcOrDlfUnchanged() {
AuditInfo auditInfo =
AuditInfo.builder().withCreator("creator").withCreateTime(Instant.now()).build();
- // Test that explicit credential-providers setting is not overridden
+ Map<String, String> explicitProps = Maps.newHashMap();
+ explicitProps.put(PaimonConstants.CATALOG_BACKEND, "filesystem");
+ explicitProps.put(PaimonConstants.WAREHOUSE, tempDir);
+ explicitProps.put(CredentialConstants.CREDENTIAL_PROVIDERS,
"custom-provider");
+
+ CatalogEntity explicitEntity =
+ CatalogEntity.builder()
+ .withId(4L)
+ .withName("explicit-catalog")
+ .withNamespace(Namespace.of("metalake"))
+ .withType(PaimonCatalog.Type.RELATIONAL)
+ .withProvider("lakehouse-paimon")
+ .withAuditInfo(auditInfo)
+ .withProperties(explicitProps)
+ .build();
+
+ PaimonCatalog explicitCatalog =
+ new
PaimonCatalog().withCatalogConf(explicitProps).withCatalogEntity(explicitEntity);
+ Map<String, String> properties =
explicitCatalog.propertiesWithCredentialProviders();
+
+ Assertions.assertEquals(
+ "custom-provider",
properties.get(CredentialConstants.CREDENTIAL_PROVIDERS));
+ }
+
+ @Test
+ void testExplicitCredentialProvidersStillGetsJdbc() {
+ AuditInfo auditInfo =
+
AuditInfo.builder().withCreator("creator").withCreateTime(Instant.now()).build();
+
+ // Explicit credential-providers is kept, and JDBC is still appended when
JDBC keys exist.
Map<String, String> explicitProps = Maps.newHashMap();
explicitProps.put(PaimonConstants.CATALOG_BACKEND, "jdbc");
explicitProps.put(PaimonConstants.URI, "jdbc:sqlite::memory:");
@@ -405,8 +467,8 @@ public class TestPaimonCatalog {
CatalogEntity explicitEntity =
CatalogEntity.builder()
- .withId(4L)
- .withName("explicit-catalog")
+ .withId(5L)
+ .withName("explicit-jdbc-catalog")
.withNamespace(Namespace.of("metalake"))
.withType(PaimonCatalog.Type.RELATIONAL)
.withProvider("lakehouse-paimon")
@@ -418,9 +480,78 @@ public class TestPaimonCatalog {
new
PaimonCatalog().withCatalogConf(explicitProps).withCatalogEntity(explicitEntity);
Map<String, String> properties =
explicitCatalog.propertiesWithCredentialProviders();
- // Should keep explicit credential providers, not override
- String credentialProviders =
properties.get(CredentialConstants.CREDENTIAL_PROVIDERS);
- Assertions.assertEquals("custom-provider", credentialProviders);
+ Assertions.assertEquals(
+ "custom-provider," + JdbcCredential.JDBC_CREDENTIAL_TYPE,
+ properties.get(CredentialConstants.CREDENTIAL_PROVIDERS));
+ }
+
+ @Test
+ void testExplicitCredentialProvidersStillGetsDlf() {
+ AuditInfo auditInfo =
+
AuditInfo.builder().withCreator("creator").withCreateTime(Instant.now()).build();
+
+ Map<String, String> dlfProps = Maps.newHashMap();
+ dlfProps.put(PaimonConstants.CATALOG_BACKEND, "rest");
+ dlfProps.put(PaimonConstants.WAREHOUSE, tempDir);
+ dlfProps.put(PaimonConstants.GRAVITINO_DLF_ACCESS_KEY_ID, "dlf-ak");
+ dlfProps.put(PaimonConstants.GRAVITINO_DLF_ACCESS_KEY_SECRET, "dlf-sk");
+ dlfProps.put(CredentialConstants.CREDENTIAL_PROVIDERS, "custom-provider");
+
+ CatalogEntity dlfEntity =
+ CatalogEntity.builder()
+ .withId(8L)
+ .withName("dlf-explicit-catalog")
+ .withNamespace(Namespace.of("metalake"))
+ .withType(PaimonCatalog.Type.RELATIONAL)
+ .withProvider("lakehouse-paimon")
+ .withAuditInfo(auditInfo)
+ .withProperties(dlfProps)
+ .build();
+
+ PaimonCatalog dlfCatalog =
+ new
PaimonCatalog().withCatalogConf(dlfProps).withCatalogEntity(dlfEntity);
+ Map<String, String> properties =
dlfCatalog.propertiesWithCredentialProviders();
+
+ Assertions.assertEquals(
+ "custom-provider," +
DlfSecretKeyCredential.DLF_SECRET_KEY_CREDENTIAL_TYPE,
+ properties.get(CredentialConstants.CREDENTIAL_PROVIDERS));
+ }
+
+ @Test
+ void testExplicitCredentialProvidersStillGetsJdbcAndDlf() {
+ AuditInfo auditInfo =
+
AuditInfo.builder().withCreator("creator").withCreateTime(Instant.now()).build();
+
+ Map<String, String> props = Maps.newHashMap();
+ props.put(PaimonConstants.CATALOG_BACKEND, "jdbc");
+ props.put(PaimonConstants.URI, "jdbc:sqlite::memory:");
+ props.put(PaimonConstants.WAREHOUSE, tempDir);
+ props.put(PaimonConstants.GRAVITINO_JDBC_USER, "test-user");
+ props.put(PaimonConstants.GRAVITINO_JDBC_PASSWORD, "test-password");
+ props.put(PaimonConstants.GRAVITINO_DLF_ACCESS_KEY_ID, "dlf-ak");
+ props.put(PaimonConstants.GRAVITINO_DLF_ACCESS_KEY_SECRET, "dlf-sk");
+ props.put(CredentialConstants.CREDENTIAL_PROVIDERS, "custom-provider");
+
+ CatalogEntity entity =
+ CatalogEntity.builder()
+ .withId(9L)
+ .withName("jdbc-dlf-explicit-catalog")
+ .withNamespace(Namespace.of("metalake"))
+ .withType(PaimonCatalog.Type.RELATIONAL)
+ .withProvider("lakehouse-paimon")
+ .withAuditInfo(auditInfo)
+ .withProperties(props)
+ .build();
+
+ PaimonCatalog catalog = new
PaimonCatalog().withCatalogConf(props).withCatalogEntity(entity);
+ Map<String, String> properties =
catalog.propertiesWithCredentialProviders();
+
+ Assertions.assertEquals(
+ "custom-provider,"
+ + JdbcCredential.JDBC_CREDENTIAL_TYPE
+ + ","
+ + DlfSecretKeyCredential.DLF_SECRET_KEY_CREDENTIAL_TYPE,
+ properties.get(CredentialConstants.CREDENTIAL_PROVIDERS));
}
private PaimonCatalog newPaimonCatalog(String catalogName) {
diff --git
a/catalogs/catalog-lakehouse-paimon/src/test/java/org/apache/gravitino/catalog/lakehouse/paimon/TestPaimonCatalogPropertiesMetadata.java
b/catalogs/catalog-lakehouse-paimon/src/test/java/org/apache/gravitino/catalog/lakehouse/paimon/TestPaimonCatalogPropertiesMetadata.java
index bdfe09903a..d19b24061d 100644
---
a/catalogs/catalog-lakehouse-paimon/src/test/java/org/apache/gravitino/catalog/lakehouse/paimon/TestPaimonCatalogPropertiesMetadata.java
+++
b/catalogs/catalog-lakehouse-paimon/src/test/java/org/apache/gravitino/catalog/lakehouse/paimon/TestPaimonCatalogPropertiesMetadata.java
@@ -42,10 +42,9 @@ public class TestPaimonCatalogPropertiesMetadata {
assertTrue(metadata.isHiddenProperty(AzureProperties.GRAVITINO_AZURE_STORAGE_ACCOUNT_KEY));
assertTrue(metadata.isHiddenProperty(COSProperties.GRAVITINO_COS_ACCESS_KEY_ID));
assertTrue(metadata.isHiddenProperty(COSProperties.GRAVITINO_COS_ACCESS_KEY_SECRET));
- // REST bearer token and DLF secret halves are hidden; DLF access-key-id
stays visible until
- // a dedicated credential type lands.
+ // REST bearer token and DLF credential keys (including access-key-id) are
hidden.
assertTrue(metadata.isHiddenProperty(PaimonConstants.TOKEN));
-
assertFalse(metadata.isHiddenProperty(PaimonConstants.GRAVITINO_DLF_ACCESS_KEY_ID));
+
assertTrue(metadata.isHiddenProperty(PaimonConstants.GRAVITINO_DLF_ACCESS_KEY_ID));
assertTrue(metadata.isHiddenProperty(PaimonConstants.GRAVITINO_DLF_ACCESS_KEY_SECRET));
assertTrue(metadata.isHiddenProperty(PaimonConstants.GRAVITINO_DLF_SECURITY_TOKEN));
}
diff --git
a/clients/client-python/gravitino/api/credential/aws_secret_key_credential.py
b/clients/client-python/gravitino/api/credential/aws_secret_key_credential.py
new file mode 100644
index 0000000000..d0f4cb7ef1
--- /dev/null
+++
b/clients/client-python/gravitino/api/credential/aws_secret_key_credential.py
@@ -0,0 +1,62 @@
+# 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.
+
+from abc import ABC
+from typing import Dict
+
+from gravitino.api.credential.credential import Credential
+from gravitino.utils.precondition import Precondition
+
+
+class AwsSecretKeyCredential(Credential, ABC):
+ """Represents AWS secret key credential for Glue API authentication."""
+
+ AWS_SECRET_KEY_CREDENTIAL_TYPE: str = "aws-secret-key"
+ _ACCESS_KEY_ID: str = "aws-access-key-id"
+ _SECRET_ACCESS_KEY: str = "aws-secret-access-key"
+
+ def __init__(self, credential_info: Dict[str, str], expire_time: int):
+ self._access_key_id = credential_info.get(self._ACCESS_KEY_ID, None)
+ self._secret_access_key = credential_info.get(self._SECRET_ACCESS_KEY,
None)
+ Precondition.check_string_not_empty(
+ self._access_key_id, "AWS access key id should not be empty"
+ )
+ Precondition.check_string_not_empty(
+ self._secret_access_key, "AWS secret access key should not be
empty"
+ )
+ Precondition.check_argument(
+ expire_time == 0,
+ "The expiration time of AWS secret key credential should be 0",
+ )
+
+ def credential_type(self) -> str:
+ return self.AWS_SECRET_KEY_CREDENTIAL_TYPE
+
+ def expire_time_in_ms(self) -> int:
+ return 0
+
+ def credential_info(self) -> Dict[str, str]:
+ return {
+ self._ACCESS_KEY_ID: self._access_key_id,
+ self._SECRET_ACCESS_KEY: self._secret_access_key,
+ }
+
+ def access_key_id(self) -> str:
+ return self._access_key_id
+
+ def secret_access_key(self) -> str:
+ return self._secret_access_key
diff --git
a/clients/client-python/gravitino/api/credential/dlf_secret_key_credential.py
b/clients/client-python/gravitino/api/credential/dlf_secret_key_credential.py
new file mode 100644
index 0000000000..7b3d048e4d
--- /dev/null
+++
b/clients/client-python/gravitino/api/credential/dlf_secret_key_credential.py
@@ -0,0 +1,70 @@
+# 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.
+
+from abc import ABC
+from typing import Dict, Optional
+
+from gravitino.api.credential.credential import Credential
+from gravitino.utils.precondition import Precondition
+
+
+class DlfSecretKeyCredential(Credential, ABC):
+ """Represents DLF secret key credential for Paimon DLF catalogs."""
+
+ DLF_SECRET_KEY_CREDENTIAL_TYPE: str = "dlf-secret-key"
+ _ACCESS_KEY_ID: str = "dlf-access-key-id"
+ _ACCESS_KEY_SECRET: str = "dlf-access-key-secret"
+ _SECURITY_TOKEN: str = "dlf-security-token"
+
+ def __init__(self, credential_info: Dict[str, str], expire_time: int):
+ self._access_key_id = credential_info.get(self._ACCESS_KEY_ID, None)
+ self._access_key_secret = credential_info.get(self._ACCESS_KEY_SECRET,
None)
+ self._security_token = credential_info.get(self._SECURITY_TOKEN, None)
+ Precondition.check_string_not_empty(
+ self._access_key_id, "DLF access key id should not be empty"
+ )
+ Precondition.check_string_not_empty(
+ self._access_key_secret, "DLF access key secret should not be
empty"
+ )
+ Precondition.check_argument(
+ expire_time == 0,
+ "The expiration time of DLF secret key credential should be 0",
+ )
+
+ def credential_type(self) -> str:
+ return self.DLF_SECRET_KEY_CREDENTIAL_TYPE
+
+ def expire_time_in_ms(self) -> int:
+ return 0
+
+ def credential_info(self) -> Dict[str, str]:
+ info = {
+ self._ACCESS_KEY_ID: self._access_key_id,
+ self._ACCESS_KEY_SECRET: self._access_key_secret,
+ }
+ if self._security_token:
+ info[self._SECURITY_TOKEN] = self._security_token
+ return info
+
+ def access_key_id(self) -> str:
+ return self._access_key_id
+
+ def access_key_secret(self) -> str:
+ return self._access_key_secret
+
+ def security_token(self) -> Optional[str]:
+ return self._security_token
diff --git a/clients/client-python/gravitino/utils/credential_factory.py
b/clients/client-python/gravitino/utils/credential_factory.py
index 5d566e509b..1ad32887f1 100644
--- a/clients/client-python/gravitino/utils/credential_factory.py
+++ b/clients/client-python/gravitino/utils/credential_factory.py
@@ -17,7 +17,9 @@
from typing import Dict
+from gravitino.api.credential.aws_secret_key_credential import
AwsSecretKeyCredential
from gravitino.api.credential.credential import Credential
+from gravitino.api.credential.dlf_secret_key_credential import
DlfSecretKeyCredential
from gravitino.api.credential.gcs_token_credential import GCSTokenCredential
from gravitino.api.credential.oss_token_credential import OSSTokenCredential
from gravitino.api.credential.s3_secret_key_credential import
S3SecretKeyCredential
@@ -53,6 +55,10 @@ class CredentialFactory:
== AzureAccountKeyCredential.AZURE_ACCOUNT_KEY_CREDENTIAL_TYPE
):
credential = AzureAccountKeyCredential(credential_info,
expire_time_in_ms)
+ elif credential_type ==
AwsSecretKeyCredential.AWS_SECRET_KEY_CREDENTIAL_TYPE:
+ credential = AwsSecretKeyCredential(credential_info,
expire_time_in_ms)
+ elif credential_type ==
DlfSecretKeyCredential.DLF_SECRET_KEY_CREDENTIAL_TYPE:
+ credential = DlfSecretKeyCredential(credential_info,
expire_time_in_ms)
else:
raise NotImplementedError(
f"Credential type {credential_type} is not supported"
diff --git a/clients/client-python/tests/unittests/test_credential_factory.py
b/clients/client-python/tests/unittests/test_credential_factory.py
index 0e67e81fea..a2385b72de 100644
--- a/clients/client-python/tests/unittests/test_credential_factory.py
+++ b/clients/client-python/tests/unittests/test_credential_factory.py
@@ -19,6 +19,8 @@
import unittest
+from gravitino.api.credential.aws_secret_key_credential import
AwsSecretKeyCredential
+from gravitino.api.credential.dlf_secret_key_credential import
DlfSecretKeyCredential
from gravitino.api.credential.gcs_token_credential import GCSTokenCredential
from gravitino.api.credential.oss_token_credential import OSSTokenCredential
from gravitino.api.credential.s3_secret_key_credential import
S3SecretKeyCredential
@@ -185,3 +187,45 @@ class TestCredentialFactory(unittest.TestCase):
self.assertEqual("account_name", check_credential.account_name())
self.assertEqual("account_key", check_credential.account_key())
self.assertEqual(0, check_credential.expire_time_in_ms())
+
+ def test_aws_secret_key_credential(self):
+ aws_credential_info = {
+ AwsSecretKeyCredential._ACCESS_KEY_ID: "access_key",
+ AwsSecretKeyCredential._SECRET_ACCESS_KEY: "secret_key",
+ }
+ aws_credential = AwsSecretKeyCredential(aws_credential_info, 0)
+ check_credential = CredentialFactory.create(
+ aws_credential.credential_type(),
+ aws_credential.credential_info(),
+ aws_credential.expire_time_in_ms(),
+ )
+ self.assertEqual(
+ AwsSecretKeyCredential.AWS_SECRET_KEY_CREDENTIAL_TYPE,
+ check_credential.credential_type(),
+ )
+ self.assertIsInstance(check_credential, AwsSecretKeyCredential)
+ self.assertEqual("access_key", check_credential.access_key_id())
+ self.assertEqual("secret_key", check_credential.secret_access_key())
+ self.assertEqual(0, check_credential.expire_time_in_ms())
+
+ def test_dlf_secret_key_credential(self):
+ dlf_credential_info = {
+ DlfSecretKeyCredential._ACCESS_KEY_ID: "dlf_ak",
+ DlfSecretKeyCredential._ACCESS_KEY_SECRET: "dlf_sk",
+ DlfSecretKeyCredential._SECURITY_TOKEN: "dlf_token",
+ }
+ dlf_credential = DlfSecretKeyCredential(dlf_credential_info, 0)
+ check_credential = CredentialFactory.create(
+ dlf_credential.credential_type(),
+ dlf_credential.credential_info(),
+ dlf_credential.expire_time_in_ms(),
+ )
+ self.assertEqual(
+ DlfSecretKeyCredential.DLF_SECRET_KEY_CREDENTIAL_TYPE,
+ check_credential.credential_type(),
+ )
+ self.assertIsInstance(check_credential, DlfSecretKeyCredential)
+ self.assertEqual("dlf_ak", check_credential.access_key_id())
+ self.assertEqual("dlf_sk", check_credential.access_key_secret())
+ self.assertEqual("dlf_token", check_credential.security_token())
+ self.assertEqual(0, check_credential.expire_time_in_ms())
diff --git
a/common/src/main/java/org/apache/gravitino/credential/CredentialPropertyUtils.java
b/common/src/main/java/org/apache/gravitino/credential/CredentialPropertyUtils.java
index 734c991d28..12e17abb78 100644
---
a/common/src/main/java/org/apache/gravitino/credential/CredentialPropertyUtils.java
+++
b/common/src/main/java/org/apache/gravitino/credential/CredentialPropertyUtils.java
@@ -27,6 +27,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
+import org.apache.commons.lang3.StringUtils;
import org.apache.gravitino.Catalog;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -88,6 +89,9 @@ public class CredentialPropertyUtils {
private static final String PAIMON_S3_SECRET_KEY = "s3.secret-key";
private static final String PAIMON_OSS_ACCESS_KEY_ID = "fs.oss.accessKeyId";
private static final String PAIMON_OSS_ACCESS_KEY_SECRET =
"fs.oss.accessKeySecret";
+ @VisibleForTesting static final String PAIMON_DLF_ACCESS_KEY_ID =
"dlf.access-key-id";
+ @VisibleForTesting static final String PAIMON_DLF_ACCESS_KEY_SECRET =
"dlf.access-key-secret";
+ @VisibleForTesting static final String PAIMON_DLF_SECURITY_TOKEN =
"dlf.security-token";
private static Map<String, String> icebergCredentialPropertyMap =
ImmutableMap.<String, String>builder()
@@ -157,7 +161,7 @@ public class CredentialPropertyUtils {
}
/**
- * Injects vended credentials into Paimon catalog properties. Supports JDBC,
S3, and OSS
+ * Injects vended credentials into Paimon catalog properties. Supports JDBC,
S3, OSS, and DLF
* credential types.
*
* @param credentials the credentials to apply
@@ -177,6 +181,13 @@ public class CredentialPropertyUtils {
OSSSecretKeyCredential oss = (OSSSecretKeyCredential) credential;
props.put(PAIMON_OSS_ACCESS_KEY_ID, oss.accessKeyId());
props.put(PAIMON_OSS_ACCESS_KEY_SECRET, oss.secretAccessKey());
+ } else if (credential instanceof DlfSecretKeyCredential) {
+ DlfSecretKeyCredential dlf = (DlfSecretKeyCredential) credential;
+ props.put(PAIMON_DLF_ACCESS_KEY_ID, dlf.accessKeyId());
+ props.put(PAIMON_DLF_ACCESS_KEY_SECRET, dlf.accessKeySecret());
+ if (StringUtils.isNotBlank(dlf.securityToken())) {
+ props.put(PAIMON_DLF_SECURITY_TOKEN, dlf.securityToken());
+ }
} else {
LOG.warn(
"Received unrecognized credential type '{}' for Paimon catalog,
skipping",
diff --git
a/common/src/test/java/org/apache/gravitino/credential/TestCredentialFactory.java
b/common/src/test/java/org/apache/gravitino/credential/TestCredentialFactory.java
index 4feb4b4e49..c0f58a225b 100644
---
a/common/src/test/java/org/apache/gravitino/credential/TestCredentialFactory.java
+++
b/common/src/test/java/org/apache/gravitino/credential/TestCredentialFactory.java
@@ -285,6 +285,73 @@ public class TestCredentialFactory {
Assertions.assertEquals(expireTime, jdbcCredential.expireTimeInMs());
}
+ @Test
+ void testAwsSecretKeyCredential() {
+ Map<String, String> awsSecretKeyCredentialInfo =
+ ImmutableMap.of(
+ AwsSecretKeyCredential.GRAVITINO_AWS_ACCESS_KEY_ID,
+ "accessKeyId",
+ AwsSecretKeyCredential.GRAVITINO_AWS_SECRET_ACCESS_KEY,
+ "secretAccessKey");
+ long expireTime = 0;
+ Credential credential =
+ CredentialFactory.create(
+ AwsSecretKeyCredential.AWS_SECRET_KEY_CREDENTIAL_TYPE,
+ awsSecretKeyCredentialInfo,
+ expireTime);
+ Assertions.assertEquals(
+ AwsSecretKeyCredential.AWS_SECRET_KEY_CREDENTIAL_TYPE,
credential.credentialType());
+ Assertions.assertInstanceOf(AwsSecretKeyCredential.class, credential);
+ AwsSecretKeyCredential aws = (AwsSecretKeyCredential) credential;
+ Assertions.assertEquals("accessKeyId", aws.accessKeyId());
+ Assertions.assertEquals("secretAccessKey", aws.secretAccessKey());
+ Assertions.assertEquals(expireTime, aws.expireTimeInMs());
+ }
+
+ @Test
+ void testDlfSecretKeyCredential() {
+ Map<String, String> dlfSecretKeyCredentialInfo =
+ ImmutableMap.of(
+ DlfSecretKeyCredential.GRAVITINO_DLF_ACCESS_KEY_ID,
+ "dlf-ak",
+ DlfSecretKeyCredential.GRAVITINO_DLF_ACCESS_KEY_SECRET,
+ "dlf-sk",
+ DlfSecretKeyCredential.GRAVITINO_DLF_SECURITY_TOKEN,
+ "dlf-token");
+ long expireTime = 0;
+ Credential credential =
+ CredentialFactory.create(
+ DlfSecretKeyCredential.DLF_SECRET_KEY_CREDENTIAL_TYPE,
+ dlfSecretKeyCredentialInfo,
+ expireTime);
+ Assertions.assertEquals(
+ DlfSecretKeyCredential.DLF_SECRET_KEY_CREDENTIAL_TYPE,
credential.credentialType());
+ Assertions.assertInstanceOf(DlfSecretKeyCredential.class, credential);
+ DlfSecretKeyCredential dlf = (DlfSecretKeyCredential) credential;
+ Assertions.assertEquals("dlf-ak", dlf.accessKeyId());
+ Assertions.assertEquals("dlf-sk", dlf.accessKeySecret());
+ Assertions.assertEquals("dlf-token", dlf.securityToken());
+ Assertions.assertEquals(expireTime, dlf.expireTimeInMs());
+ }
+
+ @Test
+ void testDlfSecretKeyCredentialWithoutToken() {
+ Map<String, String> dlfSecretKeyCredentialInfo =
+ ImmutableMap.of(
+ DlfSecretKeyCredential.GRAVITINO_DLF_ACCESS_KEY_ID,
+ "dlf-ak",
+ DlfSecretKeyCredential.GRAVITINO_DLF_ACCESS_KEY_SECRET,
+ "dlf-sk");
+ Credential credential =
+ CredentialFactory.create(
+ DlfSecretKeyCredential.DLF_SECRET_KEY_CREDENTIAL_TYPE,
dlfSecretKeyCredentialInfo, 0);
+ Assertions.assertInstanceOf(DlfSecretKeyCredential.class, credential);
+ DlfSecretKeyCredential dlf = (DlfSecretKeyCredential) credential;
+ Assertions.assertEquals("dlf-ak", dlf.accessKeyId());
+ Assertions.assertEquals("dlf-sk", dlf.accessKeySecret());
+ Assertions.assertNull(dlf.securityToken());
+ }
+
@Test
void testUnknownCredentialType() {
RuntimeException e =
diff --git
a/common/src/test/java/org/apache/gravitino/credential/TestCredentialPropertiesUtils.java
b/common/src/test/java/org/apache/gravitino/credential/TestCredentialPropertiesUtils.java
index 0ee13adac5..cf53c36b39 100644
---
a/common/src/test/java/org/apache/gravitino/credential/TestCredentialPropertiesUtils.java
+++
b/common/src/test/java/org/apache/gravitino/credential/TestCredentialPropertiesUtils.java
@@ -20,6 +20,7 @@
package org.apache.gravitino.credential;
import com.google.common.collect.ImmutableMap;
+import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
@@ -147,4 +148,42 @@ public class TestCredentialPropertiesUtils {
refreshEndpointProperties.get(
CredentialPropertyUtils.ICEBERG_ADLS_REFRESH_CREDENTIALS_ENDPOINT));
}
+
+ @Test
+ void testApplyPaimonCredentials() {
+ Map<String, String> props = new HashMap<>();
+ CredentialPropertyUtils.applyPaimonCredentials(
+ new Credential[] {
+ new JdbcCredential("jdbc-user", "jdbc-password"),
+ new S3SecretKeyCredential("s3-key", "s3-secret"),
+ new OSSSecretKeyCredential("oss-key", "oss-secret"),
+ new DlfSecretKeyCredential("dlf-ak", "dlf-sk", "dlf-token")
+ },
+ props);
+
+ Assertions.assertEquals("jdbc-user",
props.get(CredentialPropertyUtils.ICEBERG_JDBC_USER));
+ Assertions.assertEquals(
+ "jdbc-password",
props.get(CredentialPropertyUtils.ICEBERG_JDBC_PASSWORD));
+ Assertions.assertEquals("s3-key", props.get("s3.access-key"));
+ Assertions.assertEquals("s3-secret", props.get("s3.secret-key"));
+ Assertions.assertEquals("oss-key", props.get("fs.oss.accessKeyId"));
+ Assertions.assertEquals("oss-secret", props.get("fs.oss.accessKeySecret"));
+ Assertions.assertEquals("dlf-ak",
props.get(CredentialPropertyUtils.PAIMON_DLF_ACCESS_KEY_ID));
+ Assertions.assertEquals(
+ "dlf-sk",
props.get(CredentialPropertyUtils.PAIMON_DLF_ACCESS_KEY_SECRET));
+ Assertions.assertEquals(
+ "dlf-token",
props.get(CredentialPropertyUtils.PAIMON_DLF_SECURITY_TOKEN));
+ }
+
+ @Test
+ void testApplyPaimonDlfCredentialsWithoutToken() {
+ Map<String, String> props = new HashMap<>();
+ CredentialPropertyUtils.applyPaimonCredentials(
+ new Credential[] {new DlfSecretKeyCredential("dlf-ak", "dlf-sk")},
props);
+
+ Assertions.assertEquals("dlf-ak",
props.get(CredentialPropertyUtils.PAIMON_DLF_ACCESS_KEY_ID));
+ Assertions.assertEquals(
+ "dlf-sk",
props.get(CredentialPropertyUtils.PAIMON_DLF_ACCESS_KEY_SECRET));
+
Assertions.assertFalse(props.containsKey(CredentialPropertyUtils.PAIMON_DLF_SECURITY_TOKEN));
+ }
}
diff --git
a/core/src/main/java/org/apache/gravitino/credential/AwsSecretKeyCredentialProvider.java
b/core/src/main/java/org/apache/gravitino/credential/AwsSecretKeyCredentialProvider.java
new file mode 100644
index 0000000000..f6a3ecd9f9
--- /dev/null
+++
b/core/src/main/java/org/apache/gravitino/credential/AwsSecretKeyCredentialProvider.java
@@ -0,0 +1,70 @@
+/*
+ * 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.gravitino.credential;
+
+import com.google.common.base.Preconditions;
+import java.util.Map;
+import javax.annotation.Nullable;
+import org.apache.commons.lang3.StringUtils;
+
+/** Generates static AWS access-key credentials for Glue API authentication. */
+public class AwsSecretKeyCredentialProvider implements CredentialProvider {
+
+ private String accessKeyId;
+ private String secretAccessKey;
+
+ @Override
+ public void initialize(Map<String, String> properties) {
+ if (properties == null) {
+ return;
+ }
+ this.accessKeyId =
properties.get(AwsSecretKeyCredential.GRAVITINO_AWS_ACCESS_KEY_ID);
+ this.secretAccessKey =
properties.get(AwsSecretKeyCredential.GRAVITINO_AWS_SECRET_ACCESS_KEY);
+ boolean hasAccessKeyId = StringUtils.isNotBlank(accessKeyId);
+ boolean hasSecretAccessKey = StringUtils.isNotBlank(secretAccessKey);
+ Preconditions.checkArgument(
+ hasAccessKeyId == hasSecretAccessKey,
+ "Incomplete AWS static credential pair for %s: both %s and %s are
required",
+ AwsSecretKeyCredential.AWS_SECRET_KEY_CREDENTIAL_TYPE,
+ AwsSecretKeyCredential.GRAVITINO_AWS_ACCESS_KEY_ID,
+ AwsSecretKeyCredential.GRAVITINO_AWS_SECRET_ACCESS_KEY);
+ }
+
+ @Override
+ public void close() {}
+
+ @Override
+ public String credentialType() {
+ return AwsSecretKeyCredential.AWS_SECRET_KEY_CREDENTIAL_TYPE;
+ }
+
+ @Override
+ public boolean supportsScheme(String scheme) {
+ return false;
+ }
+
+ @Nullable
+ @Override
+ public Credential getCredential(CredentialContext context) {
+ if (StringUtils.isBlank(accessKeyId) ||
StringUtils.isBlank(secretAccessKey)) {
+ return null;
+ }
+ return new AwsSecretKeyCredential(accessKeyId, secretAccessKey);
+ }
+}
diff --git
a/core/src/main/java/org/apache/gravitino/credential/DlfSecretKeyCredentialProvider.java
b/core/src/main/java/org/apache/gravitino/credential/DlfSecretKeyCredentialProvider.java
new file mode 100644
index 0000000000..ce695c4f0f
--- /dev/null
+++
b/core/src/main/java/org/apache/gravitino/credential/DlfSecretKeyCredentialProvider.java
@@ -0,0 +1,72 @@
+/*
+ * 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.gravitino.credential;
+
+import com.google.common.base.Preconditions;
+import java.util.Map;
+import javax.annotation.Nullable;
+import org.apache.commons.lang3.StringUtils;
+
+/** Generates static DLF access-key credentials for Paimon DLF catalogs. */
+public class DlfSecretKeyCredentialProvider implements CredentialProvider {
+
+ private String accessKeyId;
+ private String accessKeySecret;
+ @Nullable private String securityToken;
+
+ @Override
+ public void initialize(Map<String, String> properties) {
+ if (properties == null) {
+ return;
+ }
+ this.accessKeyId =
properties.get(DlfSecretKeyCredential.GRAVITINO_DLF_ACCESS_KEY_ID);
+ this.accessKeySecret =
properties.get(DlfSecretKeyCredential.GRAVITINO_DLF_ACCESS_KEY_SECRET);
+ this.securityToken =
properties.get(DlfSecretKeyCredential.GRAVITINO_DLF_SECURITY_TOKEN);
+ boolean hasAccessKeyId = StringUtils.isNotBlank(accessKeyId);
+ boolean hasAccessKeySecret = StringUtils.isNotBlank(accessKeySecret);
+ Preconditions.checkArgument(
+ hasAccessKeyId == hasAccessKeySecret,
+ "Incomplete DLF static credential pair for %s: both %s and %s are
required",
+ DlfSecretKeyCredential.DLF_SECRET_KEY_CREDENTIAL_TYPE,
+ DlfSecretKeyCredential.GRAVITINO_DLF_ACCESS_KEY_ID,
+ DlfSecretKeyCredential.GRAVITINO_DLF_ACCESS_KEY_SECRET);
+ }
+
+ @Override
+ public void close() {}
+
+ @Override
+ public String credentialType() {
+ return DlfSecretKeyCredential.DLF_SECRET_KEY_CREDENTIAL_TYPE;
+ }
+
+ @Override
+ public boolean supportsScheme(String scheme) {
+ return false;
+ }
+
+ @Nullable
+ @Override
+ public Credential getCredential(CredentialContext context) {
+ if (StringUtils.isBlank(accessKeyId) ||
StringUtils.isBlank(accessKeySecret)) {
+ return null;
+ }
+ return new DlfSecretKeyCredential(accessKeyId, accessKeySecret,
securityToken);
+ }
+}
diff --git
a/core/src/main/resources/META-INF/services/org.apache.gravitino.credential.CredentialProvider
b/core/src/main/resources/META-INF/services/org.apache.gravitino.credential.CredentialProvider
index 6b992cebfb..1fab871e48 100644
---
a/core/src/main/resources/META-INF/services/org.apache.gravitino.credential.CredentialProvider
+++
b/core/src/main/resources/META-INF/services/org.apache.gravitino.credential.CredentialProvider
@@ -17,3 +17,5 @@
# under the License.
#
org.apache.gravitino.credential.JdbcCredentialProvider
+org.apache.gravitino.credential.AwsSecretKeyCredentialProvider
+org.apache.gravitino.credential.DlfSecretKeyCredentialProvider
diff --git
a/core/src/test/java/org/apache/gravitino/credential/TestAwsSecretKeyCredentialProvider.java
b/core/src/test/java/org/apache/gravitino/credential/TestAwsSecretKeyCredentialProvider.java
new file mode 100644
index 0000000000..ae5736f6e1
--- /dev/null
+++
b/core/src/test/java/org/apache/gravitino/credential/TestAwsSecretKeyCredentialProvider.java
@@ -0,0 +1,72 @@
+/*
+ * 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.gravitino.credential;
+
+import com.google.common.collect.ImmutableMap;
+import java.util.Map;
+import java.util.Optional;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TestAwsSecretKeyCredentialProvider {
+
+ @Test
+ void testAwsSecretKeyCredentialProvider() {
+ Map<String, String> catalogProperties =
+ ImmutableMap.of(
+ AwsSecretKeyCredential.GRAVITINO_AWS_ACCESS_KEY_ID,
+ "AKIA",
+ AwsSecretKeyCredential.GRAVITINO_AWS_SECRET_ACCESS_KEY,
+ "secret");
+ CredentialProvider provider =
+ CredentialProviderFactory.create(
+ AwsSecretKeyCredential.AWS_SECRET_KEY_CREDENTIAL_TYPE,
catalogProperties);
+ Assertions.assertEquals(
+ AwsSecretKeyCredential.AWS_SECRET_KEY_CREDENTIAL_TYPE,
provider.credentialType());
+ Optional<Credential> credential =
+ provider.getCredentialOptional(new CatalogCredentialContext("u"));
+ Assertions.assertTrue(credential.isPresent());
+ AwsSecretKeyCredential aws = (AwsSecretKeyCredential) credential.get();
+ Assertions.assertEquals("AKIA", aws.accessKeyId());
+ Assertions.assertEquals("secret", aws.secretAccessKey());
+ Assertions.assertEquals("AKIA",
aws.credentialInfo().get("aws-access-key-id"));
+ Assertions.assertEquals("secret",
aws.credentialInfo().get("aws-secret-access-key"));
+ }
+
+ @Test
+ void testMissingPropertiesReturnsNull() {
+ CredentialProvider provider =
+ CredentialProviderFactory.create(
+ AwsSecretKeyCredential.AWS_SECRET_KEY_CREDENTIAL_TYPE,
ImmutableMap.of());
+ Assertions.assertTrue(
+ provider.getCredentialOptional(new
CatalogCredentialContext("u")).isEmpty());
+ }
+
+ @Test
+ void testIncompletePairThrows() {
+ RuntimeException e =
+ Assertions.assertThrows(
+ RuntimeException.class,
+ () ->
+ CredentialProviderFactory.create(
+ AwsSecretKeyCredential.AWS_SECRET_KEY_CREDENTIAL_TYPE,
+
ImmutableMap.of(AwsSecretKeyCredential.GRAVITINO_AWS_ACCESS_KEY_ID, "AKIA")));
+ Assertions.assertInstanceOf(IllegalArgumentException.class, e.getCause());
+ }
+}
diff --git
a/core/src/test/java/org/apache/gravitino/credential/TestDlfSecretKeyCredentialProvider.java
b/core/src/test/java/org/apache/gravitino/credential/TestDlfSecretKeyCredentialProvider.java
new file mode 100644
index 0000000000..2508670ef5
--- /dev/null
+++
b/core/src/test/java/org/apache/gravitino/credential/TestDlfSecretKeyCredentialProvider.java
@@ -0,0 +1,83 @@
+/*
+ * 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.gravitino.credential;
+
+import com.google.common.collect.ImmutableMap;
+import java.util.Map;
+import java.util.Optional;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TestDlfSecretKeyCredentialProvider {
+
+ @Test
+ void testDlfSecretKeyCredentialProvider() {
+ Map<String, String> catalogProperties =
+ ImmutableMap.of(
+ DlfSecretKeyCredential.GRAVITINO_DLF_ACCESS_KEY_ID,
+ "dlf-ak",
+ DlfSecretKeyCredential.GRAVITINO_DLF_ACCESS_KEY_SECRET,
+ "dlf-sk",
+ DlfSecretKeyCredential.GRAVITINO_DLF_SECURITY_TOKEN,
+ "tok");
+ CredentialProvider provider =
+ CredentialProviderFactory.create(
+ DlfSecretKeyCredential.DLF_SECRET_KEY_CREDENTIAL_TYPE,
catalogProperties);
+ Optional<Credential> credential =
+ provider.getCredentialOptional(new CatalogCredentialContext("u"));
+ Assertions.assertTrue(credential.isPresent());
+ DlfSecretKeyCredential dlf = (DlfSecretKeyCredential) credential.get();
+ Assertions.assertEquals("dlf-ak", dlf.accessKeyId());
+ Assertions.assertEquals("dlf-sk", dlf.accessKeySecret());
+ Assertions.assertEquals("tok", dlf.securityToken());
+ Assertions.assertEquals("dlf-ak",
dlf.credentialInfo().get("dlf-access-key-id"));
+ Assertions.assertEquals("dlf-sk",
dlf.credentialInfo().get("dlf-access-key-secret"));
+ Assertions.assertEquals("tok",
dlf.credentialInfo().get("dlf-security-token"));
+ }
+
+ @Test
+ void testWithoutSecurityToken() {
+ Map<String, String> catalogProperties =
+ ImmutableMap.of(
+ DlfSecretKeyCredential.GRAVITINO_DLF_ACCESS_KEY_ID,
+ "dlf-ak",
+ DlfSecretKeyCredential.GRAVITINO_DLF_ACCESS_KEY_SECRET,
+ "dlf-sk");
+ CredentialProvider provider =
+ CredentialProviderFactory.create(
+ DlfSecretKeyCredential.DLF_SECRET_KEY_CREDENTIAL_TYPE,
catalogProperties);
+ DlfSecretKeyCredential dlf =
+ (DlfSecretKeyCredential)
+ provider.getCredentialOptional(new
CatalogCredentialContext("u")).get();
+ Assertions.assertNull(dlf.securityToken());
+
Assertions.assertFalse(dlf.credentialInfo().containsKey("dlf-security-token"));
+ }
+
+ @Test
+ void testIncompletePairThrows() {
+ RuntimeException e =
+ Assertions.assertThrows(
+ RuntimeException.class,
+ () ->
+ CredentialProviderFactory.create(
+ DlfSecretKeyCredential.DLF_SECRET_KEY_CREDENTIAL_TYPE,
+
ImmutableMap.of(DlfSecretKeyCredential.GRAVITINO_DLF_ACCESS_KEY_ID, "dlf-ak")));
+ Assertions.assertInstanceOf(IllegalArgumentException.class, e.getCause());
+ }
+}
diff --git
a/core/src/test/resources/META-INF/services/org.apache.gravitino.credential.CredentialProvider
b/core/src/test/resources/META-INF/services/org.apache.gravitino.credential.CredentialProvider
index e672bd50eb..d2afa97d36 100644
---
a/core/src/test/resources/META-INF/services/org.apache.gravitino.credential.CredentialProvider
+++
b/core/src/test/resources/META-INF/services/org.apache.gravitino.credential.CredentialProvider
@@ -19,3 +19,5 @@
org.apache.gravitino.credential.DummyCredentialProvider
org.apache.gravitino.credential.Dummy2CredentialProvider
org.apache.gravitino.credential.JdbcCredentialProvider
+org.apache.gravitino.credential.AwsSecretKeyCredentialProvider
+org.apache.gravitino.credential.DlfSecretKeyCredentialProvider
diff --git a/design-docs/gravitino-glue-catalog.md
b/design-docs/gravitino-glue-catalog.md
index c64f449325..cca5624a96 100644
--- a/design-docs/gravitino-glue-catalog.md
+++ b/design-docs/gravitino-glue-catalog.md
@@ -127,8 +127,8 @@ Glue is a separate AWS service from S3. The Glue region and
credentials may diff
| Property | Required | Default |
Description
|
| ----------------------- | -------- | ----------------------------- |
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
| `aws-region` | Yes | — | AWS
region for the Glue Data Catalog
|
-| `aws-access-key-id` | No | Default credential chain | AWS
access key for Glue API authentication. Hidden from catalog readers; returned
as `******`. Plaintext via `getSecrets` with `USE_SECRETS` and
`INCLUDE_CREDENTIAL_SECRETS`. Also remapped to `s3-*` for storage credential
vending.
|
-| `aws-secret-access-key` | No | Default credential chain | AWS
secret key for Glue API authentication. Hidden from catalog readers; returned
as `******`. Plaintext via `getSecrets` with `USE_SECRETS` and
`INCLUDE_CREDENTIAL_SECRETS`.
|
+| `aws-access-key-id` | No | Default credential chain | AWS
access key for Glue API authentication. Hidden from catalog readers; returned
as `******`. Plaintext via `getCredentials` (`AwsSecretKeyCredential`), or via
`getSecrets` with `USE_SECRETS` and `INCLUDE_CREDENTIAL_SECRETS`. Also remapped
to `s3-*` for storage credential vending.
[...]
+| `aws-secret-access-key` | No | Default credential chain | AWS
secret key for Glue API authentication. Hidden from catalog readers; returned
as `******`. Plaintext via `getCredentials` (`AwsSecretKeyCredential`), or via
`getSecrets` with `USE_SECRETS` and `INCLUDE_CREDENTIAL_SECRETS`.
|
| `aws-glue-catalog-id` | Yes | — | Glue
catalog ID. Required because an AWS account can have multiple Glue catalogs
(e.g., default catalog and federated S3 Tables catalog).
|
| `aws-glue-endpoint` | No | AWS default regional endpoint | Custom
Glue endpoint URL (for VPC endpoints or LocalStack testing).
|
| `default-table-format` | No | `iceberg` | Default
format for tables created via Gravitino's `createTable()` API. Accepted values:
`iceberg`, `hive`.
|
diff --git a/docs/aws-glue-catalog.md b/docs/aws-glue-catalog.md
index 9ee2cdcf12..a0ed1ff042 100644
--- a/docs/aws-glue-catalog.md
+++ b/docs/aws-glue-catalog.md
@@ -55,7 +55,12 @@ Besides the [common catalog
properties](./gravitino-server-config.md#catalog-pro
Refer to [Manage Catalogs and
Schemas](./manage-catalogs-and-schemas.md#catalog-operations) for more details.
:::note
-Sensitive catalog properties such as credential-vending keys are hidden from
the default load catalog response. Retrieve secret-manager-backed properties
(including keys that overlap with credential vending) via `getSecrets` / `GET
.../objects/{type}/{fullName}/secrets`. The [credential vending
API](security/credential-vending.md) remains available for typed credential
delivery.
+Sensitive catalog properties such as `aws-access-key-id` /
`aws-secret-access-key` are hidden from the default load catalog response.
Recover them via:
+
+- `getCredentials` / `AwsSecretKeyCredential` (and remapped
`S3SecretKeyCredential` when auto-detected) — no dedicated privilege beyond
loading the catalog
+- `getSecrets` / `GET .../objects/{type}/{fullName}/secrets` with
`USE_SECRETS` and `INCLUDE_CREDENTIAL_SECRETS`
+
+See [credential vending](security/credential-vending.md) for details.
:::
## Schema
diff --git a/docs/lakehouse-paimon-catalog.md b/docs/lakehouse-paimon-catalog.md
index 2ebd10fe3e..2cc78c4f07 100644
--- a/docs/lakehouse-paimon-catalog.md
+++ b/docs/lakehouse-paimon-catalog.md
@@ -136,7 +136,13 @@ Download the corresponding JDBC driver and place it to the
`catalogs/lakehouse-p
Refer to [Manage Catalogs and
Schemas](./manage-catalogs-and-schemas.md#catalog-operations) for more details.
:::note
-Sensitive catalog properties such as `jdbc-password` are hidden from the
default load catalog response (`jdbc-user` is returned in plaintext). Retrieve
secret-manager-backed properties (including `jdbc-password` when stored as a
secret URN) via `getSecrets` / `GET .../objects/{type}/{fullName}/secrets`. The
[credential vending API](security/credential-vending.md) (`getCredentials` /
`JdbcCredential`) remains available for typed credential delivery.
+Sensitive catalog properties such as `jdbc-password` and DLF credential keys
are hidden from the default load catalog response (`jdbc-user` is returned in
plaintext). Recover JDBC / DLF credential fields via either path:
+
+- `getCredentials` / `JdbcCredential` / `DlfSecretKeyCredential` (no dedicated
privilege beyond loading the catalog)
+- `getSecrets` / `GET .../objects/{type}/{fullName}/secrets` with
`USE_SECRETS` (DLF cloud access-key pairs also need
`INCLUDE_CREDENTIAL_SECRETS`)
+
+Other non-credential secrets (secret-manager URNs, declared `hidden`
properties, undeclared sensitive-named keys) use `getSecrets` only.
+
:::
## Schema
diff --git a/docs/security/credential-vending.md
b/docs/security/credential-vending.md
index 839f8ab953..0db656e06d 100755
--- a/docs/security/credential-vending.md
+++ b/docs/security/credential-vending.md
@@ -116,6 +116,8 @@ Catalogs defined in `gravitino.conf` are not registered in
a metalake, so Gravit
| `cos-token` | COS | A temporary STS token
|
| `cos-secret-key` | COS | The configured static access key and secret
|
| `jdbc-user-password` | JDBC | The configured JDBC username and password
|
+| `aws-secret-key` | Glue | The configured AWS access key and secret
for Glue API auth |
+| `dlf-secret-key` | Paimon | The configured DLF access key and secret
(optional token) |
Each value has its own properties, listed in the sections below. To vend for
more than one storage type on a catalog, separate values with a comma. Custom
providers can be added by implementing `CredentialProvider`, described under
[Custom Credentials](#custom-credentials).
@@ -129,6 +131,8 @@ If a catalog does not set `credential-providers`, Gravitino
infers providers fro
| `oss-access-key-id` and `oss-secret-access-key` |
`oss-secret-key` |
| `azure-storage-account-name` and `azure-storage-account-key` |
`azure-account-key` |
| `gcs-service-account-file` | `gcs-token`
|
+| `aws-access-key-id` and `aws-secret-access-key` |
`aws-secret-key` |
+| `dlf-access-key-id` and `dlf-access-key-secret` |
`dlf-secret-key` |
JDBC catalogs additionally infer `jdbc-user-password` from `jdbc-user` and
`jdbc-password`.
@@ -137,10 +141,10 @@ Four providers have no inference rule and must always be
set explicitly: `s3-tok
### Static providers and `getCredentials` privilege risk
Static providers such as `s3-secret-key`, `oss-secret-key`, `cos-secret-key`,
`azure-account-key`,
-and `jdbc-user-password` return the configured long-lived plaintext keys from
-`getCredentials` / `GET .../credentials`. That endpoint does **not** require a
dedicated privilege
-beyond being able to load the metadata object (unlike `getSecrets`, which
requires `USE_SECRETS`,
-with cloud access-key pairs gated by `INCLUDE_CREDENTIAL_SECRETS`).
+`jdbc-user-password`, `aws-secret-key`, and `dlf-secret-key` return the
configured long-lived
+plaintext keys from `getCredentials` / `GET .../credentials`. That endpoint
does **not** require a
+dedicated privilege beyond being able to load the metadata object (unlike
`getSecrets`, which
+requires `USE_SECRETS`, with cloud access-key pairs gated by
`INCLUDE_CREDENTIAL_SECRETS`).
**Risk:** any principal that can load a catalog (or fileset) configured with
these static providers
can retrieve the same static AK/SK or JDBC password that Gravitino uses
server-side.
@@ -505,7 +509,8 @@ Bundle jars on Maven Central:
## Upgrading From a Release Earlier Than 1.3.0
-Sensitive catalog properties such as `s3-access-key-id`,
`s3-secret-access-key`, `jdbc-password`, `aws-access-key-id` /
`aws-secret-access-key` (Glue), and `dlf-access-key-id` /
`dlf-access-key-secret` (Paimon DLF) are masked or excluded from the default
`GET /api/metalakes/{metalake}/catalogs/{catalog}` response (`jdbc-user` and
`azure-storage-account-name` are returned in plaintext when not hidden).
Plaintext secrets are available via `getSecrets` when the caller holds
`USE_SECRETS` (o [...]
+Sensitive catalog properties such as `s3-access-key-id`,
`s3-secret-access-key`, `jdbc-password`, `aws-access-key-id` /
`aws-secret-access-key` (Glue), and `dlf-access-key-id` /
`dlf-access-key-secret` / `dlf-security-token` (Paimon DLF) are masked or
excluded from the default `GET /api/metalakes/{metalake}/catalogs/{catalog}`
response (`jdbc-user` and `azure-storage-account-name` are returned in
plaintext when not hidden). Plaintext secrets are available via `getSecrets`
when the caller [...]
+
For a zero-downtime migration, set the following in `gravitino.conf`:
diff --git
a/flink-connector/flink-common/src/test/java/org/apache/gravitino/flink/connector/paimon/TestGravitinoPaimonCatalog.java
b/flink-connector/flink-common/src/test/java/org/apache/gravitino/flink/connector/paimon/TestGravitinoPaimonCatalog.java
index f2586ec68f..519b2c33a0 100644
---
a/flink-connector/flink-common/src/test/java/org/apache/gravitino/flink/connector/paimon/TestGravitinoPaimonCatalog.java
+++
b/flink-connector/flink-common/src/test/java/org/apache/gravitino/flink/connector/paimon/TestGravitinoPaimonCatalog.java
@@ -40,6 +40,7 @@ import org.apache.flink.table.factories.CatalogFactory;
import org.apache.gravitino.Catalog;
import org.apache.gravitino.catalog.lakehouse.paimon.PaimonConstants;
import org.apache.gravitino.credential.Credential;
+import org.apache.gravitino.credential.DlfSecretKeyCredential;
import org.apache.gravitino.credential.JdbcCredential;
import org.apache.gravitino.credential.OSSSecretKeyCredential;
import org.apache.gravitino.credential.S3SecretKeyCredential;
@@ -628,6 +629,25 @@ public abstract class TestGravitinoPaimonCatalog {
"jdbc-password",
catalog.capturedOptions.get(PaimonConstants.PAIMON_JDBC_PASSWORD));
}
+ /** Verifies that DLF backend credentials remain Paimon catalog options. */
+ @Test
+ public void testOpenKeepsDlfCredentialsInPaimonOptions() {
+ Catalog mockCatalog =
+ catalogWithCredentials(new DlfSecretKeyCredential("dlf-ak", "dlf-sk",
"dlf-token"));
+ Map<String, String> options = new HashMap<>();
+ options.put("warehouse", "file:/tmp/test-paimon-warehouse");
+
+ CapturingPaimonCatalog catalog = new CapturingPaimonCatalog(options,
mockCatalog);
+ catalog.open();
+
+ Assertions.assertEquals(
+ "dlf-ak",
catalog.capturedOptions.get(PaimonConstants.PAIMON_DLF_ACCESS_KEY_ID));
+ Assertions.assertEquals(
+ "dlf-sk",
catalog.capturedOptions.get(PaimonConstants.PAIMON_DLF_ACCESS_KEY_SECRET));
+ Assertions.assertEquals(
+ "dlf-token",
catalog.capturedOptions.get(PaimonConstants.PAIMON_DLF_SECURITY_TOKEN));
+ }
+
private static Catalog catalogWithCredentials(Credential... credentials) {
Catalog catalog = mock(Catalog.class);
SupportsCredentials supportsCredentials = mock(SupportsCredentials.class);
diff --git
a/flink-connector/flink-common/src/test/java/org/apache/gravitino/flink/connector/paimon/TestPaimonPropertiesConverter.java
b/flink-connector/flink-common/src/test/java/org/apache/gravitino/flink/connector/paimon/TestPaimonPropertiesConverter.java
index c59135b64b..6dd4b3085d 100644
---
a/flink-connector/flink-common/src/test/java/org/apache/gravitino/flink/connector/paimon/TestPaimonPropertiesConverter.java
+++
b/flink-connector/flink-common/src/test/java/org/apache/gravitino/flink/connector/paimon/TestPaimonPropertiesConverter.java
@@ -77,6 +77,33 @@ public class TestPaimonPropertiesConverter {
Assertions.assertEquals(testUri,
flinkCatalogProperties.get(PaimonConstants.URI));
}
+ @Test
+ public void testToPaimonDlfCatalog() {
+ Map<String, String> catalogProperties =
+ ImmutableMap.<String, String>builder()
+ .put(PaimonConstants.WAREHOUSE, localWarehouse)
+ .put(PaimonConstants.CATALOG_BACKEND, "rest")
+ .put(PaimonConstants.URI, "https://dlf.aliyuncs.com")
+ .put(PaimonConstants.GRAVITINO_DLF_ACCESS_KEY_ID, "dlf-ak")
+ .put(PaimonConstants.GRAVITINO_DLF_ACCESS_KEY_SECRET, "dlf-sk")
+ .put(PaimonConstants.GRAVITINO_DLF_SECURITY_TOKEN, "dlf-token")
+ .build();
+ Map<String, String> flinkCatalogProperties =
+ CONVERTER.toFlinkCatalogProperties(catalogProperties);
+ Assertions.assertEquals(
+ GravitinoPaimonCatalogFactoryOptions.IDENTIFIER,
flinkCatalogProperties.get("type"));
+ Assertions.assertEquals(localWarehouse,
flinkCatalogProperties.get(PaimonConstants.WAREHOUSE));
+ Assertions.assertEquals("rest",
flinkCatalogProperties.get(PaimonConstants.METASTORE));
+ Assertions.assertEquals(
+ "https://dlf.aliyuncs.com",
flinkCatalogProperties.get(PaimonConstants.URI));
+ Assertions.assertEquals(
+ "dlf-ak",
flinkCatalogProperties.get(PaimonConstants.PAIMON_DLF_ACCESS_KEY_ID));
+ Assertions.assertEquals(
+ "dlf-sk",
flinkCatalogProperties.get(PaimonConstants.PAIMON_DLF_ACCESS_KEY_SECRET));
+ Assertions.assertEquals(
+ "dlf-token",
flinkCatalogProperties.get(PaimonConstants.PAIMON_DLF_SECURITY_TOKEN));
+ }
+
@Test
public void testToGravitinoCatalogProperties() {
String testUser = "testUser";
diff --git
a/spark-connector/spark-common/src/main/java/org/apache/gravitino/spark/connector/glue/GravitinoGlueCatalog.java
b/spark-connector/spark-common/src/main/java/org/apache/gravitino/spark/connector/glue/GravitinoGlueCatalog.java
index 6ac600140a..99c692dc9c 100644
---
a/spark-connector/spark-common/src/main/java/org/apache/gravitino/spark/connector/glue/GravitinoGlueCatalog.java
+++
b/spark-connector/spark-common/src/main/java/org/apache/gravitino/spark/connector/glue/GravitinoGlueCatalog.java
@@ -23,6 +23,7 @@ import com.google.common.base.Preconditions;
import java.util.HashMap;
import java.util.Map;
import org.apache.gravitino.catalog.glue.GlueConstants;
+import org.apache.gravitino.credential.AwsSecretKeyCredential;
import org.apache.gravitino.credential.Credential;
import org.apache.gravitino.credential.CredentialPropertyUtils;
import org.apache.gravitino.credential.S3SecretKeyCredential;
@@ -112,10 +113,10 @@ public class GravitinoGlueCatalog extends BaseCatalog {
}
/**
- * Obtains S3 credentials via Gravitino credential vending and injects them
into {@code props} as
- * {@code hadoop.fs.s3a.*} for the non-Iceberg (Hive) path's S3 data access.
Returns the vended
- * credentials keyed by Gravitino catalog property names for reuse in the
Iceberg path. Returns an
- * empty map if credential vending is unavailable.
+ * Obtains credentials via Gravitino credential vending. {@link
AwsSecretKeyCredential} supplies
+ * Glue API keys (returned for the Iceberg path). {@link
S3SecretKeyCredential} supplies {@code
+ * hadoop.fs.s3a.*} for Hive-path S3 data access. When only {@link
S3SecretKeyCredential} is
+ * present (legacy remap), it also fills Glue API keys for backward
compatibility.
*
* @return map of vended AWS credentials (Gravitino key names), or empty map
if none vended
*/
@@ -130,19 +131,27 @@ public class GravitinoGlueCatalog extends BaseCatalog {
"Failed to obtain credentials from Glue catalog, S3 credential
injection skipped", e);
return vended;
}
+ S3SecretKeyCredential s3SecretKey = null;
for (Credential credential : credentials) {
- if (credential instanceof S3SecretKeyCredential) {
- S3SecretKeyCredential s3 = (S3SecretKeyCredential) credential;
- props.put("hadoop.fs.s3a.access.key", s3.accessKeyId());
- props.put("hadoop.fs.s3a.secret.key", s3.secretAccessKey());
- vended.put(GluePropertiesConverter.AWS_ACCESS_KEY_ID,
s3.accessKeyId());
- vended.put(GluePropertiesConverter.AWS_SECRET_ACCESS_KEY,
s3.secretAccessKey());
+ if (credential instanceof AwsSecretKeyCredential) {
+ AwsSecretKeyCredential aws = (AwsSecretKeyCredential) credential;
+ vended.put(GluePropertiesConverter.AWS_ACCESS_KEY_ID,
aws.accessKeyId());
+ vended.put(GluePropertiesConverter.AWS_SECRET_ACCESS_KEY,
aws.secretAccessKey());
+ } else if (credential instanceof S3SecretKeyCredential) {
+ s3SecretKey = (S3SecretKeyCredential) credential;
+ props.put("hadoop.fs.s3a.access.key", s3SecretKey.accessKeyId());
+ props.put("hadoop.fs.s3a.secret.key", s3SecretKey.secretAccessKey());
} else {
LOG.warn(
"Received unrecognized credential type '{}' for Glue catalog,
skipping",
credential.getClass().getName());
}
}
+ // Legacy catalogs that only vend remapped S3SecretKeyCredential still
need Glue API keys.
+ if (vended.isEmpty() && s3SecretKey != null) {
+ vended.put(GluePropertiesConverter.AWS_ACCESS_KEY_ID,
s3SecretKey.accessKeyId());
+ vended.put(GluePropertiesConverter.AWS_SECRET_ACCESS_KEY,
s3SecretKey.secretAccessKey());
+ }
return vended;
}
diff --git
a/spark-connector/spark-common/src/test/java/org/apache/gravitino/spark/connector/glue/TestGravitinoGlueCatalog.java
b/spark-connector/spark-common/src/test/java/org/apache/gravitino/spark/connector/glue/TestGravitinoGlueCatalog.java
index 364dcc74e2..ab2ed9a193 100644
---
a/spark-connector/spark-common/src/test/java/org/apache/gravitino/spark/connector/glue/TestGravitinoGlueCatalog.java
+++
b/spark-connector/spark-common/src/test/java/org/apache/gravitino/spark/connector/glue/TestGravitinoGlueCatalog.java
@@ -32,6 +32,7 @@ import java.util.Map;
import org.apache.gravitino.Catalog;
import org.apache.gravitino.catalog.glue.GlueConstants;
import org.apache.gravitino.client.GravitinoClient;
+import org.apache.gravitino.credential.AwsSecretKeyCredential;
import org.apache.gravitino.credential.Credential;
import org.apache.gravitino.credential.S3SecretKeyCredential;
import org.apache.gravitino.credential.SupportsCredentials;
@@ -212,6 +213,24 @@ public class TestGravitinoGlueCatalog {
"test-secret-key",
vended.get(GluePropertiesConverter.AWS_SECRET_ACCESS_KEY));
}
+ @Test
+ void testApplyS3CredentialUsesAwsForGlueAndS3ForStorage() {
+ Catalog mockCatalog = mock(Catalog.class);
+ AwsSecretKeyCredential awsCred = new AwsSecretKeyCredential("glue-ak",
"glue-sk");
+ S3SecretKeyCredential s3Cred = new S3SecretKeyCredential("s3-ak", "s3-sk");
+ SupportsCredentials supportsCredentials = mock(SupportsCredentials.class);
+ when(mockCatalog.supportsCredentials()).thenReturn(supportsCredentials);
+ when(supportsCredentials.getCredentials()).thenReturn(new Credential[]
{awsCred, s3Cred});
+
+ Map<String, String> props = new HashMap<>();
+ Map<String, String> vended =
GravitinoGlueCatalog.applyS3Credential(mockCatalog, props);
+
+ Assertions.assertEquals("s3-ak", props.get("hadoop.fs.s3a.access.key"));
+ Assertions.assertEquals("s3-sk", props.get("hadoop.fs.s3a.secret.key"));
+ Assertions.assertEquals("glue-ak",
vended.get(GluePropertiesConverter.AWS_ACCESS_KEY_ID));
+ Assertions.assertEquals("glue-sk",
vended.get(GluePropertiesConverter.AWS_SECRET_ACCESS_KEY));
+ }
+
@Test
void testApplyS3CredentialReturnsEmptyWhenNoCredentials() {
Catalog mockCatalog = mock(Catalog.class);
diff --git
a/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/catalog/glue/GlueConnectorAdapter.java
b/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/catalog/glue/GlueConnectorAdapter.java
index ac6a56ad01..78b55fb77d 100644
---
a/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/catalog/glue/GlueConnectorAdapter.java
+++
b/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/catalog/glue/GlueConnectorAdapter.java
@@ -24,6 +24,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.credential.AwsSecretKeyCredential;
import org.apache.gravitino.credential.Credential;
import org.apache.gravitino.credential.S3SecretKeyCredential;
import org.apache.gravitino.trino.connector.catalog.CatalogConnectorAdapter;
@@ -91,17 +92,28 @@ public class GlueConnectorAdapter implements
CatalogConnectorAdapter {
return config;
}
+ /**
+ * Applies Glue API keys from {@link AwsSecretKeyCredential} and S3
data-access keys from {@link
+ * S3SecretKeyCredential}. When only {@link S3SecretKeyCredential} is
present (legacy remap), it
+ * fills both Glue and S3 keys for backward compatibility.
+ */
static void applyS3Credential(Credential[] credentials, Map<String, String>
config) {
+ S3SecretKeyCredential s3SecretKey = null;
for (Credential credential : credentials) {
- if (credential instanceof S3SecretKeyCredential) {
- S3SecretKeyCredential s3 = (S3SecretKeyCredential) credential;
- config.put(HIVE_METASTORE_GLUE_ACCESS_KEY, s3.accessKeyId());
- config.put(HIVE_METASTORE_GLUE_SECRET_KEY, s3.secretAccessKey());
- config.put(HIVE_S3_ACCESS_KEY, s3.accessKeyId());
- config.put(HIVE_S3_SECRET_KEY, s3.secretAccessKey());
- return;
+ if (credential instanceof AwsSecretKeyCredential) {
+ AwsSecretKeyCredential aws = (AwsSecretKeyCredential) credential;
+ config.put(HIVE_METASTORE_GLUE_ACCESS_KEY, aws.accessKeyId());
+ config.put(HIVE_METASTORE_GLUE_SECRET_KEY, aws.secretAccessKey());
+ } else if (credential instanceof S3SecretKeyCredential) {
+ s3SecretKey = (S3SecretKeyCredential) credential;
+ config.put(HIVE_S3_ACCESS_KEY, s3SecretKey.accessKeyId());
+ config.put(HIVE_S3_SECRET_KEY, s3SecretKey.secretAccessKey());
}
}
+ if (!config.containsKey(HIVE_METASTORE_GLUE_ACCESS_KEY) && s3SecretKey !=
null) {
+ config.put(HIVE_METASTORE_GLUE_ACCESS_KEY, s3SecretKey.accessKeyId());
+ config.put(HIVE_METASTORE_GLUE_SECRET_KEY,
s3SecretKey.secretAccessKey());
+ }
}
@Override
diff --git
a/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/catalog/glue/TestGlueConnectorAdapter.java
b/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/catalog/glue/TestGlueConnectorAdapter.java
index 81313f305d..58200a0e19 100644
---
a/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/catalog/glue/TestGlueConnectorAdapter.java
+++
b/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/catalog/glue/TestGlueConnectorAdapter.java
@@ -19,8 +19,10 @@
package org.apache.gravitino.trino.connector.catalog.glue;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
import java.util.Map;
import org.apache.gravitino.Catalog;
+import org.apache.gravitino.credential.AwsSecretKeyCredential;
import org.apache.gravitino.credential.Credential;
import org.apache.gravitino.credential.S3SecretKeyCredential;
import org.apache.gravitino.trino.connector.metadata.GravitinoCatalog;
@@ -76,4 +78,17 @@ class TestGlueConnectorAdapter {
Assertions.assertEquals(
"https://glue.custom.endpoint",
config.get("hive.metastore.glue.endpoint-url"));
}
+
+ @Test
+ void testApplyS3CredentialUsesAwsForGlueAndS3ForStorage() {
+ Map<String, String> config = Maps.newHashMap();
+ Credential[] credentials = {
+ new AwsSecretKeyCredential("glue-ak", "glue-sk"), new
S3SecretKeyCredential("s3-ak", "s3-sk")
+ };
+ GlueConnectorAdapter.applyS3Credential(credentials, config);
+ Assertions.assertEquals("glue-ak",
config.get("hive.metastore.glue.aws-access-key"));
+ Assertions.assertEquals("glue-sk",
config.get("hive.metastore.glue.aws-secret-key"));
+ Assertions.assertEquals("s3-ak", config.get("hive.s3.aws-access-key"));
+ Assertions.assertEquals("s3-sk", config.get("hive.s3.aws-secret-key"));
+ }
}