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 b8fa0f6352 [#13933] fix(spark-connector): Reuse Basic credentials for
routed Iceberg REST catalogs (#13935)
b8fa0f6352 is described below
commit b8fa0f63526cac278b09af9c49eb9ab61f009170
Author: Yuhui <[email protected]>
AuthorDate: Mon Oct 19 17:12:51 2026 +0800
[#13933] fix(spark-connector): Reuse Basic credentials for routed Iceberg
REST catalogs (#13935)
### What changes were proposed in this pull request?
When `spark.sql.gravitino.authType=basic`, pass the Gravitino user name
and password to the auto-routed Iceberg REST client as
`rest.auth.type=basic` and `rest.auth.basic.*`. Any explicit
`rest.auth.*` or `token` property keeps priority and disables the reuse.
### Why are the changes needed?
A routed Iceberg catalog connects to the Iceberg REST service without
credentials under Basic auth, so users must repeat the user name and
password in `spark.sql.gravitino.iceberg.rest.rest.auth.*`.
Fix: #13933
### Does this PR introduce _any_ user-facing change?
Yes. Basic credentials are now reused for routed Iceberg REST catalogs.
Docs are updated. No new property keys.
### How was this patch tested?
Added unit tests in `TestIcebergRestOAuthConfig`.
---
docs/spark-connector/spark-catalog-iceberg.md | 2 +
.../connector/iceberg/IcebergRestOAuthConfig.java | 28 +++++++++++
.../iceberg/TestIcebergRestOAuthConfig.java | 54 ++++++++++++++++++++++
3 files changed, 84 insertions(+)
diff --git a/docs/spark-connector/spark-catalog-iceberg.md
b/docs/spark-connector/spark-catalog-iceberg.md
index 15ecec1b75..22769b20dc 100644
--- a/docs/spark-connector/spark-catalog-iceberg.md
+++ b/docs/spark-connector/spark-catalog-iceberg.md
@@ -197,6 +197,8 @@ spark.sql.gravitino.iceberg.rest.rest.auth.basic.password
<password>
See [Connect Spark to Iceberg REST](../iceberg-rest-engine/spark.md) for the
full set of supported
`rest.auth.*` properties and how to configure them when connecting directly to
the IRC endpoint.
+When the Gravitino client uses Basic authentication
(`spark.sql.gravitino.authType=basic`), the connector reuses its user name and
password for IRC. Any `rest.auth.*` or `token` property set through
`spark.sql.gravitino.iceberg.rest.*` disables the reuse.
+
When the Gravitino client uses OAuth2, the connector reuses its OAuth2 client
configuration for IRC by
default. This avoids duplicating configuration when both endpoints accept the
same client identity. The
equivalent explicit setting is:
diff --git
a/spark-connector/spark-common/src/main/java/org/apache/gravitino/spark/connector/iceberg/IcebergRestOAuthConfig.java
b/spark-connector/spark-common/src/main/java/org/apache/gravitino/spark/connector/iceberg/IcebergRestOAuthConfig.java
index 6d419c3c4b..1a65415aef 100644
---
a/spark-connector/spark-common/src/main/java/org/apache/gravitino/spark/connector/iceberg/IcebergRestOAuthConfig.java
+++
b/spark-connector/spark-common/src/main/java/org/apache/gravitino/spark/connector/iceberg/IcebergRestOAuthConfig.java
@@ -19,6 +19,7 @@
package org.apache.gravitino.spark.connector.iceberg;
+import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import java.util.ArrayList;
import java.util.HashMap;
@@ -45,6 +46,9 @@ class IcebergRestOAuthConfig {
static final String CREDENTIAL = "credential";
static final String OAUTH2_SERVER_URI = "oauth2-server-uri";
static final String SCOPE = "scope";
+ static final String AUTH_TYPE_BASIC = "basic";
+ static final String BASIC_USERNAME = "rest.auth.basic.username";
+ static final String BASIC_PASSWORD = "rest.auth.basic.password";
// Properties whose presence alone implies the user is explicitly
configuring legacy OAuth2
// (triggering validation), as opposed to optional extras that ride along
with it. TOKEN is not
@@ -76,6 +80,10 @@ class IcebergRestOAuthConfig {
String authType =
sparkConf.get(GravitinoSparkConfig.GRAVITINO_AUTH_TYPE,
AuthProperties.SIMPLE_AUTH_TYPE);
+ if (AuthProperties.isBasic(authType)) {
+ return applyBasicCredentials(sparkConf, result);
+ }
+
boolean reuseOAuth2 =
sparkConf.getBoolean(GravitinoSparkConfig.GRAVITINO_ICEBERG_REUSE_OAUTH2, true)
&& AuthProperties.isOAuth2(authType);
@@ -107,6 +115,26 @@ class IcebergRestOAuthConfig {
return result;
}
+ // Reuses Gravitino's Basic credentials unless the caller already chose an
auth type.
+ private static Map<String, String> applyBasicCredentials(
+ SparkConf sparkConf, Map<String, String> config) {
+ if (StringUtils.isNotBlank(config.get(AUTH_TYPE))) {
+ return config;
+ }
+
+ String username =
sparkConf.get(GravitinoSparkConfig.GRAVITINO_BASIC_USERNAME, null);
+ String password =
sparkConf.get(GravitinoSparkConfig.GRAVITINO_BASIC_PASSWORD, null);
+ Preconditions.checkArgument(
+ StringUtils.isNotBlank(username) && StringUtils.isNotBlank(password),
+ "Incomplete Iceberg REST Basic configuration; %s and %s are required",
+ GravitinoSparkConfig.GRAVITINO_BASIC_USERNAME,
+ GravitinoSparkConfig.GRAVITINO_BASIC_PASSWORD);
+ config.put(AUTH_TYPE, AUTH_TYPE_BASIC);
+ config.put(BASIC_USERNAME, username);
+ config.put(BASIC_PASSWORD, password);
+ return config;
+ }
+
private static void putIfConfigured(Map<String, String> config, String key,
String value) {
if (!config.containsKey(key) && StringUtils.isNotBlank(value)) {
config.put(key, value);
diff --git
a/spark-connector/spark-common/src/test/java/org/apache/gravitino/spark/connector/iceberg/TestIcebergRestOAuthConfig.java
b/spark-connector/spark-common/src/test/java/org/apache/gravitino/spark/connector/iceberg/TestIcebergRestOAuthConfig.java
index e4fae252e3..4abcd2fed7 100644
---
a/spark-connector/spark-common/src/test/java/org/apache/gravitino/spark/connector/iceberg/TestIcebergRestOAuthConfig.java
+++
b/spark-connector/spark-common/src/test/java/org/apache/gravitino/spark/connector/iceberg/TestIcebergRestOAuthConfig.java
@@ -193,6 +193,60 @@ public class TestIcebergRestOAuthConfig {
Assertions.assertTrue(exception.getMessage().contains("oauth2-server-uri"));
}
+ @Test
+ void testDerivesIcebergBasicConfig() {
+ SparkConf sparkConf = basicSparkConf("alice", "secret");
+
+ Map<String, String> result = IcebergRestOAuthConfig.resolve(sparkConf,
Collections.emptyMap());
+
+ Assertions.assertEquals("basic",
result.get(IcebergRestOAuthConfig.AUTH_TYPE));
+ Assertions.assertEquals("alice",
result.get(IcebergRestOAuthConfig.BASIC_USERNAME));
+ Assertions.assertEquals("secret",
result.get(IcebergRestOAuthConfig.BASIC_PASSWORD));
+ }
+
+ @Test
+ void testExplicitConfigTakesPrecedenceOverReusedBasic() {
+ SparkConf sparkConf = basicSparkConf("alice", "secret");
+ Map<String, String> explicit =
+ ImmutableMap.of("rest.auth.basic.username", "admin",
"rest.auth.basic.password", "pwd");
+
+ Map<String, String> result = IcebergRestOAuthConfig.resolve(sparkConf,
explicit);
+
+ Assertions.assertEquals(explicit, result);
+ }
+
+ @Test
+ void testExplicitTokenSkipsBasicReuse() {
+ SparkConf sparkConf = basicSparkConf("alice", "secret");
+ Map<String, String> explicit = ImmutableMap.of("token", "bearer-token");
+
+ Map<String, String> result = IcebergRestOAuthConfig.resolve(sparkConf,
explicit);
+
+ Assertions.assertEquals(explicit, result);
+ }
+
+ @Test
+ void testBasicReuseFailsWhenCredentialsAreIncomplete() {
+ SparkConf sparkConf = new SparkConf(false);
+ sparkConf.set(GravitinoSparkConfig.GRAVITINO_AUTH_TYPE, "basic");
+ sparkConf.set(GravitinoSparkConfig.GRAVITINO_BASIC_USERNAME, "alice");
+
+ IllegalArgumentException exception =
+ Assertions.assertThrows(
+ IllegalArgumentException.class,
+ () -> IcebergRestOAuthConfig.resolve(sparkConf,
Collections.emptyMap()));
+
+ Assertions.assertTrue(exception.getMessage().contains("basic.password"));
+ }
+
+ private SparkConf basicSparkConf(String username, String password) {
+ SparkConf sparkConf = new SparkConf(false);
+ sparkConf.set(GravitinoSparkConfig.GRAVITINO_AUTH_TYPE, "basic");
+ sparkConf.set(GravitinoSparkConfig.GRAVITINO_BASIC_USERNAME, username);
+ sparkConf.set(GravitinoSparkConfig.GRAVITINO_BASIC_PASSWORD, password);
+ return sparkConf;
+ }
+
private SparkConf oauthSparkConf(String serverUri, String tokenPath) {
SparkConf sparkConf = new SparkConf(false);
sparkConf.set(GravitinoSparkConfig.GRAVITINO_AUTH_TYPE, "oauth2");