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 7cad7718d0 [#14029] fix(core): Expose inferred credential-providers in
catalog properties (#14030)
7cad7718d0 is described below
commit 7cad7718d0239bd05cf022b7052fb87937e792da
Author: Yuhui <[email protected]>
AuthorDate: Wed Oct 21 14:34:22 2026 +0800
[#14029] fix(core): Expose inferred credential-providers in catalog
properties (#14030)
### What changes were proposed in this pull request?
When a catalog doesn't set `credential-providers`,
`BaseCatalog.properties()` now includes the inferred provider names.
Only the names are exposed, never the credential values. An explicitly
configured list is returned unchanged.
### Why are the changes needed?
The Spark connector decides whether to route an Iceberg catalog through
the Iceberg REST server based on `credential-providers` in the catalog
properties. The server infers this value when it is omitted, but clients
couldn't see the inferred value, so Spark rejected catalogs that the
Iceberg REST server serves.
Fix: #14029
### Does this PR introduce _any_ user-facing change?
Yes. Catalog properties returned by the server now include
`credential-providers` when the providers are inferred.
### How was this patch tested?
Added `TestBaseCatalogCredentialProviders` and extended
`TestJdbcCatalogCredential`.
---
.../catalog/jdbc/TestJdbcCatalogCredential.java | 4 +
.../apache/gravitino/connector/BaseCatalog.java | 10 ++
.../TestBaseCatalogCredentialProviders.java | 111 +++++++++++++++++++++
docs/security/credential-vending.md | 3 +
.../testsets/jdbc-mysql/00008_alter_catalog.txt | 8 +-
5 files changed, 132 insertions(+), 4 deletions(-)
diff --git
a/catalogs/catalog-jdbc-common/src/test/java/org/apache/gravitino/catalog/jdbc/TestJdbcCatalogCredential.java
b/catalogs/catalog-jdbc-common/src/test/java/org/apache/gravitino/catalog/jdbc/TestJdbcCatalogCredential.java
index 9577be3e16..a2feb54b02 100644
---
a/catalogs/catalog-jdbc-common/src/test/java/org/apache/gravitino/catalog/jdbc/TestJdbcCatalogCredential.java
+++
b/catalogs/catalog-jdbc-common/src/test/java/org/apache/gravitino/catalog/jdbc/TestJdbcCatalogCredential.java
@@ -233,6 +233,10 @@ public class TestJdbcCatalogCredential {
Assertions.assertEquals("test-user",
publicProps.get(JdbcConfig.USERNAME.getKey()));
Assertions.assertEquals(
HiddenPropertyMaskUtils.MASKED_VALUE,
publicProps.get(JdbcConfig.PASSWORD.getKey()));
+ // The inferred provider name is exposed without backfill
+ Assertions.assertEquals(
+ JdbcCredential.JDBC_CREDENTIAL_TYPE,
+ publicProps.get(CredentialConstants.CREDENTIAL_PROVIDERS));
// propertiesWithCredentialProviders must still see the raw credentials
Map<String, String> credProps =
jdbcCatalog.propertiesWithCredentialProviders();
diff --git a/core/src/main/java/org/apache/gravitino/connector/BaseCatalog.java
b/core/src/main/java/org/apache/gravitino/connector/BaseCatalog.java
index 4b36da86e1..89d89fd2b2 100644
--- a/core/src/main/java/org/apache/gravitino/connector/BaseCatalog.java
+++ b/core/src/main/java/org/apache/gravitino/connector/BaseCatalog.java
@@ -518,6 +518,16 @@ public abstract class BaseCatalog<T extends BaseCatalog>
tempProperties.putIfAbsent(
PROPERTY_IN_USE,
catalogPropertiesMetadata().getDefaultValue(PROPERTY_IN_USE).toString());
+ // Expose the inferred provider names (never the credential values)
so that clients
+ // make routing decisions from the same provider list the server
vends credentials
+ // with. An explicitly configured list is returned as-is.
+ if
(StringUtils.isBlank(tempProperties.get(CredentialConstants.CREDENTIAL_PROVIDERS)))
{
+ String inferredProviders =
+
propertiesWithCredentialProviders().get(CredentialConstants.CREDENTIAL_PROVIDERS);
+ if (StringUtils.isNotBlank(inferredProviders)) {
+ tempProperties.put(CredentialConstants.CREDENTIAL_PROVIDERS,
inferredProviders);
+ }
+ }
properties = tempProperties;
}
diff --git
a/core/src/test/java/org/apache/gravitino/connector/TestBaseCatalogCredentialProviders.java
b/core/src/test/java/org/apache/gravitino/connector/TestBaseCatalogCredentialProviders.java
new file mode 100644
index 0000000000..8b9ff46acd
--- /dev/null
+++
b/core/src/test/java/org/apache/gravitino/connector/TestBaseCatalogCredentialProviders.java
@@ -0,0 +1,111 @@
+/*
+ * 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.connector;
+
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.commons.lang3.reflect.FieldUtils;
+import org.apache.gravitino.Catalog;
+import org.apache.gravitino.Namespace;
+import org.apache.gravitino.connector.capability.Capability;
+import org.apache.gravitino.credential.CredentialConstants;
+import org.apache.gravitino.credential.S3SecretKeyCredential;
+import org.apache.gravitino.meta.AuditInfo;
+import org.apache.gravitino.meta.CatalogEntity;
+import org.apache.gravitino.storage.S3Properties;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+public class TestBaseCatalogCredentialProviders {
+
+ @Test
+ void testInferredCredentialProvidersExposed() throws IllegalAccessException {
+ Map<String, String> props = new HashMap<>();
+ props.put(S3Properties.GRAVITINO_S3_ACCESS_KEY_ID, "access-key");
+ props.put(S3Properties.GRAVITINO_S3_SECRET_ACCESS_KEY, "secret-key");
+
+ Map<String, String> result = newCatalog(props).properties();
+ Assertions.assertEquals(
+ S3SecretKeyCredential.S3_SECRET_KEY_CREDENTIAL_TYPE,
+ result.get(CredentialConstants.CREDENTIAL_PROVIDERS));
+ }
+
+ @Test
+ void testExplicitCredentialProvidersKept() throws IllegalAccessException {
+ Map<String, String> props = new HashMap<>();
+ props.put(S3Properties.GRAVITINO_S3_ACCESS_KEY_ID, "access-key");
+ props.put(S3Properties.GRAVITINO_S3_SECRET_ACCESS_KEY, "secret-key");
+ props.put(CredentialConstants.CREDENTIAL_PROVIDERS, "s3-token");
+
+ Map<String, String> result = newCatalog(props).properties();
+ Assertions.assertEquals("s3-token",
result.get(CredentialConstants.CREDENTIAL_PROVIDERS));
+ }
+
+ @Test
+ void testNoCredentialProvidersWithoutCredentials() throws
IllegalAccessException {
+ Map<String, String> props = new HashMap<>();
+ props.put(S3Properties.GRAVITINO_S3_ACCESS_KEY_ID, "access-key");
+
+ Map<String, String> result = newCatalog(props).properties();
+
Assertions.assertFalse(result.containsKey(CredentialConstants.CREDENTIAL_PROVIDERS));
+ }
+
+ private TestCatalogImpl newCatalog(Map<String, String> props) throws
IllegalAccessException {
+ CatalogEntity entity =
+ CatalogEntity.builder()
+ .withId(1L)
+ .withName("test")
+ .withNamespace(Namespace.of("metalake"))
+ .withType(Catalog.Type.RELATIONAL)
+ .withProvider("test")
+ .withComment("comment")
+ .withProperties(props)
+ .withAuditInfo(
+
AuditInfo.builder().withCreator("tester").withCreateTime(Instant.now()).build())
+ .build();
+ TestCatalogImpl catalog = new TestCatalogImpl();
+ FieldUtils.writeField(catalog, "entity", entity, true);
+ return catalog;
+ }
+
+ private static class TestCatalogImpl extends BaseCatalog<TestCatalogImpl> {
+
+ @Override
+ public String shortName() {
+ return "test";
+ }
+
+ @Override
+ protected CatalogOperations newOps(Map<String, String> config) {
+ return Mockito.mock(CatalogOperations.class);
+ }
+
+ @Override
+ protected Capability newCapability() {
+ return Capability.DEFAULT;
+ }
+
+ @Override
+ public PropertiesMetadata catalogPropertiesMetadata() throws
UnsupportedOperationException {
+ return BaseCatalogPropertiesMetadata.BASIC_CATALOG_PROPERTIES_METADATA;
+ }
+ }
+}
diff --git a/docs/security/credential-vending.md
b/docs/security/credential-vending.md
index b323ab3587..c30cf9012e 100755
--- a/docs/security/credential-vending.md
+++ b/docs/security/credential-vending.md
@@ -130,12 +130,15 @@ If a catalog does not set `credential-providers`,
Gravitino infers providers fro
| `s3-access-key-id` and `s3-secret-access-key` |
`s3-secret-key` |
| `oss-access-key-id` and `oss-secret-access-key` |
`oss-secret-key` |
| `azure-storage-account-name` and `azure-storage-account-key` |
`azure-account-key` |
+| `cos-access-key-id` and `cos-secret-access-key` |
`cos-secret-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`.
+The inferred provider list is returned as the catalog's `credential-providers`
property when the catalog is loaded, so clients such as the Spark connector see
the same list the server vends credentials with. Only the provider names are
returned, never the credential values.
+
Four providers have no inference rule and must always be set explicitly:
`s3-token`, `oss-token`, `adls-token`, and `aws-irsa`. In particular, setting
`s3-role-arn` without `credential-providers` does not enable `s3-token`. The
catalog falls back to `s3-secret-key` and vends the static access key instead,
which is long-lived and not scoped to the table path. Set
`credential-providers` explicitly whenever you want token-based vending.
### Static providers and `getCredentials` privilege risk
diff --git
a/trino-connector/integration-test/src/test/resources/trino-ci-testset/testsets/jdbc-mysql/00008_alter_catalog.txt
b/trino-connector/integration-test/src/test/resources/trino-ci-testset/testsets/jdbc-mysql/00008_alter_catalog.txt
index 2c9ffc204f..ff68ff67fd 100644
---
a/trino-connector/integration-test/src/test/resources/trino-ci-testset/testsets/jdbc-mysql/00008_alter_catalog.txt
+++
b/trino-connector/integration-test/src/test/resources/trino-ci-testset/testsets/jdbc-mysql/00008_alter_catalog.txt
@@ -1,17 +1,17 @@
CALL
-"gt_mysql_xxx1","jdbc-mysql","{""in-use"":""true"",""jdbc-driver"":""com.mysql.cj.jdbc.Driver"",""jdbc-password"":""******"",""jdbc-url"":""jdbc:mysql://%/?useSSL=false"",""jdbc-user"":""trino""}","test"
+"gt_mysql_xxx1","jdbc-mysql","{""credential-providers"":""jdbc-user-password"",""in-use"":""true"",""jdbc-driver"":""com.mysql.cj.jdbc.Driver"",""jdbc-password"":""******"",""jdbc-url"":""jdbc:mysql://%/?useSSL=false"",""jdbc-user"":""trino""}","test"
CALL
-"gt_mysql_xxx1","jdbc-mysql","{""in-use"":""true"",""jdbc-driver"":""com.mysql.cj.jdbc.Driver"",""jdbc-password"":""******"",""jdbc-url"":""jdbc:mysql://%/?useSSL=false"",""jdbc-user"":""trino"",""test_key"":""test_value"",""trino.bypass.join-pushdown.strategy"":""EAGER""}","test"
+"gt_mysql_xxx1","jdbc-mysql","{""credential-providers"":""jdbc-user-password"",""in-use"":""true"",""jdbc-driver"":""com.mysql.cj.jdbc.Driver"",""jdbc-password"":""******"",""jdbc-url"":""jdbc:mysql://%/?useSSL=false"",""jdbc-user"":""trino"",""test_key"":""test_value"",""trino.bypass.join-pushdown.strategy"":""EAGER""}","test"
CALL
-"gt_mysql_xxx1","jdbc-mysql","{""in-use"":""true"",""jdbc-driver"":""com.mysql.cj.jdbc.Driver"",""jdbc-password"":""******"",""jdbc-url"":""jdbc:mysql://%/?useSSL=false"",""jdbc-user"":""trino"",""test_key"":""test_value""}","test"
+"gt_mysql_xxx1","jdbc-mysql","{""credential-providers"":""jdbc-user-password"",""in-use"":""true"",""jdbc-driver"":""com.mysql.cj.jdbc.Driver"",""jdbc-password"":""******"",""jdbc-url"":""jdbc:mysql://%/?useSSL=false"",""jdbc-user"":""trino"",""test_key"":""test_value""}","test"
CALL
-"gt_mysql_xxx1","jdbc-mysql","{""in-use"":""true"",""jdbc-driver"":""com.mysql.cj.jdbc.Driver"",""jdbc-password"":""******"",""jdbc-url"":""jdbc:mysql://%/?useSSL=false"",""jdbc-user"":""trino"",""trino.bypass.join-pushdown.strategy"":""EAGER""}","test"
+"gt_mysql_xxx1","jdbc-mysql","{""credential-providers"":""jdbc-user-password"",""in-use"":""true"",""jdbc-driver"":""com.mysql.cj.jdbc.Driver"",""jdbc-password"":""******"",""jdbc-url"":""jdbc:mysql://%/?useSSL=false"",""jdbc-user"":""trino"",""trino.bypass.join-pushdown.strategy"":""EAGER""}","test"
CALL