This is an automated email from the ASF dual-hosted git repository.
roryqi pushed a commit to branch branch-1.3
in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/branch-1.3 by this push:
new 4eb2b57c05 [Cherry-pick to branch-1.3] [#12958] fix(iceberg): Support
Azure service principal authentication for ADLS FileIO (#13009)
4eb2b57c05 is described below
commit 4eb2b57c05797ac4ce94a0da6f7202e2f504b7b1
Author: roryqi <[email protected]>
AuthorDate: Wed Sep 9 09:40:43 2026 +0800
[Cherry-pick to branch-1.3] [#12958] fix(iceberg): Support Azure service
principal authentication for ADLS FileIO (#13009)
### What changes were proposed in this pull request?
Cherry-pick #12959 to branch-1.3. Adapt the client-secret visibility
test to this branch's hidden-property filtering behavior.
### Why are the changes needed?
Support Azure service principal authentication for ADLS FileIO.
Related to #12958.
### Does this PR introduce _any_ user-facing change?
Enables Azure service principal authentication and hides the Azure
client secret from catalog properties.
### How was this patch tested?
Property conversion and Azure credential provider unit tests passed.
Catalog tests encountered a branch compatibility issue; the test was
adapted but has not been rerun.
---
.../lakehouse/iceberg/IcebergConstants.java | 12 +++
.../lakehouse/iceberg/IcebergPropertiesUtils.java | 79 ++++++++++++++++--
.../iceberg/TestIcebergPropertiesUtils.java | 93 ++++++++++++++++++++++
.../catalog/hive/TestHiveCatalogOperations.java | 5 +-
.../lakehouse/iceberg/TestIcebergCatalog.java | 23 ++++++
.../cloud/storage/AzurePropertiesMetadata.java | 8 ++
iceberg/iceberg-common/build.gradle.kts | 2 +
.../AzureClientSecretTokenCredentialProvider.java | 54 +++++++++++++
...stAzureClientSecretTokenCredentialProvider.java | 67 ++++++++++++++++
.../integration/test/IcebergRESTADLSTokenIT.java | 7 --
.../integration/test/IcebergRESTServiceBaseIT.java | 3 +-
.../test/util/IcebergRESTServerManager.java | 3 +-
12 files changed, 340 insertions(+), 16 deletions(-)
diff --git
a/catalogs/catalog-common/src/main/java/org/apache/gravitino/catalog/lakehouse/iceberg/IcebergConstants.java
b/catalogs/catalog-common/src/main/java/org/apache/gravitino/catalog/lakehouse/iceberg/IcebergConstants.java
index 2d29e9e754..e7f97b6bdf 100644
---
a/catalogs/catalog-common/src/main/java/org/apache/gravitino/catalog/lakehouse/iceberg/IcebergConstants.java
+++
b/catalogs/catalog-common/src/main/java/org/apache/gravitino/catalog/lakehouse/iceberg/IcebergConstants.java
@@ -65,6 +65,18 @@ public class IcebergConstants {
"adls.auth.shared-key.account.name";
public static final String ICEBERG_ADLS_STORAGE_ACCOUNT_KEY =
"adls.auth.shared-key.account.key";
+ /** Iceberg property that specifies the ADLS token credential provider
implementation. */
+ public static final String ICEBERG_ADLS_TOKEN_CREDENTIAL_PROVIDER =
+ "adls.token-credential-provider";
+
+ /** Prefix for properties passed to the Iceberg ADLS token credential
provider. */
+ public static final String ICEBERG_ADLS_TOKEN_CREDENTIAL_PROVIDER_PREFIX =
+ ICEBERG_ADLS_TOKEN_CREDENTIAL_PROVIDER + ".";
+
+ /** Gravitino's Azure client-secret token credential provider
implementation. */
+ public static final String AZURE_CLIENT_SECRET_TOKEN_CREDENTIAL_PROVIDER =
+
"org.apache.gravitino.iceberg.common.credential.AzureClientSecretTokenCredentialProvider";
+
// Iceberg Table properties constants
public static final String COMMENT = "comment";
diff --git
a/catalogs/catalog-common/src/main/java/org/apache/gravitino/catalog/lakehouse/iceberg/IcebergPropertiesUtils.java
b/catalogs/catalog-common/src/main/java/org/apache/gravitino/catalog/lakehouse/iceberg/IcebergPropertiesUtils.java
index c9b96ee5f7..4f7ae7bd7e 100644
---
a/catalogs/catalog-common/src/main/java/org/apache/gravitino/catalog/lakehouse/iceberg/IcebergPropertiesUtils.java
+++
b/catalogs/catalog-common/src/main/java/org/apache/gravitino/catalog/lakehouse/iceberg/IcebergPropertiesUtils.java
@@ -18,17 +18,40 @@
*/
package org.apache.gravitino.catalog.lakehouse.iceberg;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
+import org.apache.commons.lang3.StringUtils;
import org.apache.gravitino.storage.AzureProperties;
import org.apache.gravitino.storage.OSSProperties;
import org.apache.gravitino.storage.S3Properties;
public class IcebergPropertiesUtils {
+ private static final String ICEBERG_AZURE_TENANT_ID =
+ IcebergConstants.ICEBERG_ADLS_TOKEN_CREDENTIAL_PROVIDER_PREFIX
+ + AzureProperties.GRAVITINO_AZURE_TENANT_ID;
+ private static final String ICEBERG_AZURE_CLIENT_ID =
+ IcebergConstants.ICEBERG_ADLS_TOKEN_CREDENTIAL_PROVIDER_PREFIX
+ + AzureProperties.GRAVITINO_AZURE_CLIENT_ID;
+ private static final String ICEBERG_AZURE_CLIENT_SECRET =
+ IcebergConstants.ICEBERG_ADLS_TOKEN_CREDENTIAL_PROVIDER_PREFIX
+ + AzureProperties.GRAVITINO_AZURE_CLIENT_SECRET;
+ private static final List<String> ICEBERG_AZURE_SHARED_KEY_PROPERTIES =
+ Arrays.asList(
+ IcebergConstants.ICEBERG_ADLS_STORAGE_ACCOUNT_NAME,
+ IcebergConstants.ICEBERG_ADLS_STORAGE_ACCOUNT_KEY);
+ private static final List<String> ICEBERG_AZURE_SERVICE_PRINCIPAL_PROPERTIES
=
+ Arrays.asList(ICEBERG_AZURE_TENANT_ID, ICEBERG_AZURE_CLIENT_ID,
ICEBERG_AZURE_CLIENT_SECRET);
+ private static final Map<String, String>
ICEBERG_AZURE_SERVICE_PRINCIPAL_DEFAULTS =
+ Collections.singletonMap(
+ IcebergConstants.ICEBERG_ADLS_TOKEN_CREDENTIAL_PROVIDER,
+ IcebergConstants.AZURE_CLIENT_SECRET_TOKEN_CREDENTIAL_PROVIDER);
+
// Map that maintains the mapping of keys in Gravitino to that in Iceberg,
for example, users
// will only need to set the configuration 'catalog-backend' in Gravitino
and Gravitino will
// change it to `catalogType` automatically and pass it to Iceberg.
@@ -70,6 +93,9 @@ public class IcebergPropertiesUtils {
map.put(
AzureProperties.GRAVITINO_AZURE_STORAGE_ACCOUNT_KEY,
IcebergConstants.ICEBERG_ADLS_STORAGE_ACCOUNT_KEY);
+ map.put(AzureProperties.GRAVITINO_AZURE_TENANT_ID,
ICEBERG_AZURE_TENANT_ID);
+ map.put(AzureProperties.GRAVITINO_AZURE_CLIENT_ID,
ICEBERG_AZURE_CLIENT_ID);
+ map.put(AzureProperties.GRAVITINO_AZURE_CLIENT_SECRET,
ICEBERG_AZURE_CLIENT_SECRET);
// Table metadata cache
map.put(IcebergConstants.TABLE_METADATA_CACHE_IMPL,
IcebergConstants.TABLE_METADATA_CACHE_IMPL);
map.put(
@@ -107,12 +133,12 @@ public class IcebergPropertiesUtils {
public static Map<String, String> toIcebergCatalogProperties(
Map<String, String> gravitinoProperties) {
Map<String, String> icebergProperties = new HashMap<>();
- gravitinoProperties.forEach(
- (key, value) -> {
- if (GRAVITINO_CONFIG_TO_ICEBERG.containsKey(key)) {
- icebergProperties.put(GRAVITINO_CONFIG_TO_ICEBERG.get(key), value);
- }
- });
+ convertProperties(GRAVITINO_CONFIG_TO_ICEBERG, gravitinoProperties,
icebergProperties);
+ applyExclusivePropertyMapping(
+ icebergProperties,
+ ICEBERG_AZURE_SHARED_KEY_PROPERTIES,
+ ICEBERG_AZURE_SERVICE_PRINCIPAL_PROPERTIES,
+ ICEBERG_AZURE_SERVICE_PRINCIPAL_DEFAULTS);
return icebergProperties;
}
@@ -133,4 +159,45 @@ public class IcebergPropertiesUtils {
.map(s -> s.toLowerCase(Locale.ROOT))
.orElse("memory");
}
+
+ private static void convertProperties(
+ Map<String, String> propertyMapping,
+ Map<String, String> gravitinoProperties,
+ Map<String, String> icebergProperties) {
+ propertyMapping.forEach(
+ (gravitinoKey, icebergKey) -> {
+ if (gravitinoProperties.containsKey(gravitinoKey)) {
+ icebergProperties.put(icebergKey,
gravitinoProperties.get(gravitinoKey));
+ }
+ });
+ }
+
+ /**
+ * Keeps the preferred property set when it is complete. Otherwise, it
selects the alternative
+ * when complete and adds the properties required by that alternative. If
neither set is complete,
+ * the preferred properties are retained so downstream validation remains
unchanged.
+ */
+ private static void applyExclusivePropertyMapping(
+ Map<String, String> properties,
+ List<String> preferredProperties,
+ List<String> alternativeProperties,
+ Map<String, String> alternativeAdditionalProperties) {
+ boolean useAlternative =
+ !containsAllProperties(properties, preferredProperties)
+ && containsAllProperties(properties, alternativeProperties);
+
+ if (useAlternative) {
+ preferredProperties.forEach(properties::remove);
+ properties.putAll(alternativeAdditionalProperties);
+ } else {
+ // Do not pass credentials for an unselected or incomplete alternative.
+ alternativeProperties.forEach(properties::remove);
+ }
+ }
+
+ private static boolean containsAllProperties(
+ Map<String, String> properties, List<String> requiredProperties) {
+ return requiredProperties.stream()
+ .allMatch(property ->
StringUtils.isNotBlank(properties.get(property)));
+ }
}
diff --git
a/catalogs/catalog-common/src/test/java/org/apache/gravitino/lakehouse/iceberg/TestIcebergPropertiesUtils.java
b/catalogs/catalog-common/src/test/java/org/apache/gravitino/lakehouse/iceberg/TestIcebergPropertiesUtils.java
index 8a60f2de24..25c9ed3055 100644
---
a/catalogs/catalog-common/src/test/java/org/apache/gravitino/lakehouse/iceberg/TestIcebergPropertiesUtils.java
+++
b/catalogs/catalog-common/src/test/java/org/apache/gravitino/lakehouse/iceberg/TestIcebergPropertiesUtils.java
@@ -23,6 +23,7 @@ import com.google.common.collect.ImmutableMap;
import java.util.Map;
import org.apache.gravitino.catalog.lakehouse.iceberg.IcebergConstants;
import org.apache.gravitino.catalog.lakehouse.iceberg.IcebergPropertiesUtils;
+import org.apache.gravitino.storage.AzureProperties;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
@@ -57,6 +58,98 @@ public class TestIcebergPropertiesUtils {
"2000",
icebergProps.get(IcebergConstants.ICEBERG_REST_CLIENT_SOCKET_TIMEOUT_MS));
}
+ @Test
+ void testAzureServicePrincipalPropertiesAreMapped() {
+ Map<String, String> gravitinoProps =
+ ImmutableMap.of(
+ AzureProperties.GRAVITINO_AZURE_STORAGE_ACCOUNT_NAME,
+ "account",
+ AzureProperties.GRAVITINO_AZURE_TENANT_ID,
+ "tenant",
+ AzureProperties.GRAVITINO_AZURE_CLIENT_ID,
+ "client",
+ AzureProperties.GRAVITINO_AZURE_CLIENT_SECRET,
+ "secret");
+
+ Map<String, String> icebergProps =
+ IcebergPropertiesUtils.toIcebergCatalogProperties(gravitinoProps);
+
+ Assertions.assertFalse(
+
icebergProps.containsKey(IcebergConstants.ICEBERG_ADLS_STORAGE_ACCOUNT_NAME));
+ Assertions.assertFalse(
+
icebergProps.containsKey(IcebergConstants.ICEBERG_ADLS_STORAGE_ACCOUNT_KEY));
+ Assertions.assertEquals(
+ IcebergConstants.AZURE_CLIENT_SECRET_TOKEN_CREDENTIAL_PROVIDER,
+
icebergProps.get(IcebergConstants.ICEBERG_ADLS_TOKEN_CREDENTIAL_PROVIDER));
+ Assertions.assertEquals(
+ "tenant",
+ icebergProps.get(
+ IcebergConstants.ICEBERG_ADLS_TOKEN_CREDENTIAL_PROVIDER_PREFIX
+ + AzureProperties.GRAVITINO_AZURE_TENANT_ID));
+ Assertions.assertEquals(
+ "client",
+ icebergProps.get(
+ IcebergConstants.ICEBERG_ADLS_TOKEN_CREDENTIAL_PROVIDER_PREFIX
+ + AzureProperties.GRAVITINO_AZURE_CLIENT_ID));
+ Assertions.assertEquals(
+ "secret",
+ icebergProps.get(
+ IcebergConstants.ICEBERG_ADLS_TOKEN_CREDENTIAL_PROVIDER_PREFIX
+ + AzureProperties.GRAVITINO_AZURE_CLIENT_SECRET));
+ }
+
+ @Test
+ void testAzureSharedKeyTakesPrecedenceOverServicePrincipal() {
+ Map<String, String> gravitinoProps =
+ ImmutableMap.of(
+ AzureProperties.GRAVITINO_AZURE_STORAGE_ACCOUNT_NAME,
+ "account",
+ AzureProperties.GRAVITINO_AZURE_STORAGE_ACCOUNT_KEY,
+ "account-key",
+ AzureProperties.GRAVITINO_AZURE_TENANT_ID,
+ "tenant",
+ AzureProperties.GRAVITINO_AZURE_CLIENT_ID,
+ "client",
+ AzureProperties.GRAVITINO_AZURE_CLIENT_SECRET,
+ "secret");
+
+ Map<String, String> icebergProps =
+ IcebergPropertiesUtils.toIcebergCatalogProperties(gravitinoProps);
+
+ Assertions.assertEquals(
+ "account",
icebergProps.get(IcebergConstants.ICEBERG_ADLS_STORAGE_ACCOUNT_NAME));
+ Assertions.assertEquals(
+ "account-key",
icebergProps.get(IcebergConstants.ICEBERG_ADLS_STORAGE_ACCOUNT_KEY));
+ Assertions.assertFalse(
+
icebergProps.containsKey(IcebergConstants.ICEBERG_ADLS_TOKEN_CREDENTIAL_PROVIDER));
+ Assertions.assertFalse(
+ icebergProps.containsKey(
+ IcebergConstants.ICEBERG_ADLS_TOKEN_CREDENTIAL_PROVIDER_PREFIX
+ + AzureProperties.GRAVITINO_AZURE_CLIENT_SECRET));
+ }
+
+ @Test
+ void testIncompleteAzureServicePrincipalPreservesSharedKeyValidation() {
+ Map<String, String> gravitinoProps =
+ ImmutableMap.of(
+ AzureProperties.GRAVITINO_AZURE_STORAGE_ACCOUNT_NAME,
+ "account",
+ AzureProperties.GRAVITINO_AZURE_TENANT_ID,
+ "tenant");
+
+ Map<String, String> icebergProps =
+ IcebergPropertiesUtils.toIcebergCatalogProperties(gravitinoProps);
+
+ Assertions.assertEquals(
+ "account",
icebergProps.get(IcebergConstants.ICEBERG_ADLS_STORAGE_ACCOUNT_NAME));
+ Assertions.assertFalse(
+
icebergProps.containsKey(IcebergConstants.ICEBERG_ADLS_TOKEN_CREDENTIAL_PROVIDER));
+ Assertions.assertFalse(
+ icebergProps.containsKey(
+ IcebergConstants.ICEBERG_ADLS_TOKEN_CREDENTIAL_PROVIDER_PREFIX
+ + AzureProperties.GRAVITINO_AZURE_TENANT_ID));
+ }
+
@Test
void testGetCatalogBackendName() {
Map<String, String> catalogProperties =
diff --git
a/catalogs/catalog-hive/src/test/java/org/apache/gravitino/catalog/hive/TestHiveCatalogOperations.java
b/catalogs/catalog-hive/src/test/java/org/apache/gravitino/catalog/hive/TestHiveCatalogOperations.java
index e0b600fb69..832ec7fc67 100644
---
a/catalogs/catalog-hive/src/test/java/org/apache/gravitino/catalog/hive/TestHiveCatalogOperations.java
+++
b/catalogs/catalog-hive/src/test/java/org/apache/gravitino/catalog/hive/TestHiveCatalogOperations.java
@@ -75,6 +75,7 @@ import org.apache.gravitino.rel.expressions.sorts.SortOrder;
import org.apache.gravitino.rel.expressions.transforms.Transform;
import org.apache.gravitino.rel.indexes.Index;
import org.apache.gravitino.rel.types.Types;
+import org.apache.gravitino.storage.AzureProperties;
import org.apache.gravitino.utils.ClientPool;
import org.apache.hadoop.hive.conf.HiveConf.ConfVars;
import org.apache.thrift.TException;
@@ -88,7 +89,7 @@ class TestHiveCatalogOperations {
Map<String, PropertyEntry<?>> propertyEntryMap =
HIVE_PROPERTIES_METADATA.catalogPropertiesMetadata().propertyEntries();
- Assertions.assertEquals(25, propertyEntryMap.size());
+ Assertions.assertEquals(26, propertyEntryMap.size());
Assertions.assertTrue(propertyEntryMap.containsKey(METASTORE_URIS));
Assertions.assertTrue(propertyEntryMap.containsKey(Catalog.PROPERTY_PACKAGE));
Assertions.assertTrue(propertyEntryMap.containsKey(BaseCatalog.CATALOG_OPERATION_IMPL));
@@ -98,6 +99,8 @@ class TestHiveCatalogOperations {
Assertions.assertTrue(propertyEntryMap.containsKey(IMPERSONATION_ENABLE));
Assertions.assertTrue(propertyEntryMap.containsKey(LIST_ALL_TABLES));
Assertions.assertTrue(propertyEntryMap.containsKey(DEFAULT_CATALOG));
+ Assertions.assertTrue(
+
propertyEntryMap.get(AzureProperties.GRAVITINO_AZURE_CLIENT_SECRET).isHidden());
Assertions.assertTrue(propertyEntryMap.get(METASTORE_URIS).isRequired());
Assertions.assertFalse(propertyEntryMap.get(Catalog.PROPERTY_PACKAGE).isRequired());
Assertions.assertFalse(propertyEntryMap.get(CLIENT_POOL_SIZE).isRequired());
diff --git
a/catalogs/catalog-lakehouse-iceberg/src/test/java/org/apache/gravitino/catalog/lakehouse/iceberg/TestIcebergCatalog.java
b/catalogs/catalog-lakehouse-iceberg/src/test/java/org/apache/gravitino/catalog/lakehouse/iceberg/TestIcebergCatalog.java
index 9249088593..51e184526f 100644
---
a/catalogs/catalog-lakehouse-iceberg/src/test/java/org/apache/gravitino/catalog/lakehouse/iceberg/TestIcebergCatalog.java
+++
b/catalogs/catalog-lakehouse-iceberg/src/test/java/org/apache/gravitino/catalog/lakehouse/iceberg/TestIcebergCatalog.java
@@ -86,6 +86,29 @@ public class TestIcebergCatalog {
}
};
+ @Test
+ void testCatalogPropertiesHideAzureClientSecret() {
+ AuditInfo auditInfo =
+
AuditInfo.builder().withCreator("creator").withCreateTime(Instant.now()).build();
+ Map<String, String> properties =
+ Map.of(AzureProperties.GRAVITINO_AZURE_CLIENT_SECRET,
"azure-client-secret");
+ CatalogEntity entity =
+ CatalogEntity.builder()
+ .withId(1L)
+ .withName("azure-catalog")
+ .withNamespace(Namespace.of("metalake"))
+ .withType(IcebergCatalog.Type.RELATIONAL)
+ .withProvider("iceberg")
+ .withAuditInfo(auditInfo)
+ .withProperties(properties)
+ .build();
+ IcebergCatalog catalog =
+ new
IcebergCatalog().withCatalogConf(properties).withCatalogEntity(entity);
+
+ Assertions.assertFalse(
+
catalog.properties().containsKey(AzureProperties.GRAVITINO_AZURE_CLIENT_SECRET));
+ }
+
@Test
public void testListDatabases() {
AuditInfo auditInfo =
diff --git
a/core/src/main/java/org/apache/gravitino/cloud/storage/AzurePropertiesMetadata.java
b/core/src/main/java/org/apache/gravitino/cloud/storage/AzurePropertiesMetadata.java
index c6adb5a2d3..15d7cf5e08 100644
---
a/core/src/main/java/org/apache/gravitino/cloud/storage/AzurePropertiesMetadata.java
+++
b/core/src/main/java/org/apache/gravitino/cloud/storage/AzurePropertiesMetadata.java
@@ -46,6 +46,14 @@ public class AzurePropertiesMetadata {
false /* immutable */,
null /* defaultValue */,
true /* hidden */))
+ .put(
+ AzureProperties.GRAVITINO_AZURE_CLIENT_SECRET,
+ stringOptionalPropertyEntry(
+ AzureProperties.GRAVITINO_AZURE_CLIENT_SECRET,
+ "Azure Active Directory client secret",
+ false /* immutable */,
+ null /* defaultValue */,
+ true /* hidden */))
.build();
private AzurePropertiesMetadata() {}
diff --git a/iceberg/iceberg-common/build.gradle.kts
b/iceberg/iceberg-common/build.gradle.kts
index bd0bd45f92..c6275006d4 100644
--- a/iceberg/iceberg-common/build.gradle.kts
+++ b/iceberg/iceberg-common/build.gradle.kts
@@ -105,8 +105,10 @@ dependencies {
annotationProcessor(libs.lombok)
compileOnly(libs.lombok)
+ compileOnly(libs.azure.identity)
testImplementation(project(":server-common"))
+ testImplementation(libs.azure.identity)
testImplementation(libs.junit.jupiter.api)
testImplementation(libs.junit.jupiter.params)
testImplementation(libs.mockito.core)
diff --git
a/iceberg/iceberg-common/src/main/java/org/apache/gravitino/iceberg/common/credential/AzureClientSecretTokenCredentialProvider.java
b/iceberg/iceberg-common/src/main/java/org/apache/gravitino/iceberg/common/credential/AzureClientSecretTokenCredentialProvider.java
new file mode 100644
index 0000000000..9d1771177e
--- /dev/null
+++
b/iceberg/iceberg-common/src/main/java/org/apache/gravitino/iceberg/common/credential/AzureClientSecretTokenCredentialProvider.java
@@ -0,0 +1,54 @@
+/*
+ * 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.iceberg.common.credential;
+
+import com.azure.core.credential.TokenCredential;
+import com.azure.identity.ClientSecretCredentialBuilder;
+import java.util.Map;
+import javax.annotation.Nullable;
+import org.apache.gravitino.credential.config.AzureCredentialConfig;
+import org.apache.iceberg.azure.AdlsTokenCredentialProvider;
+
+/** Supplies an Azure client-secret credential to Iceberg's ADLS FileIO. */
+public class AzureClientSecretTokenCredentialProvider implements
AdlsTokenCredentialProvider {
+
+ @Nullable private TokenCredential credential;
+
+ /** {@inheritDoc} */
+ @Override
+ public TokenCredential credential() {
+ if (credential == null) {
+ throw new IllegalStateException(
+ "The Azure credential provider has not been initialized. Call
initialize(properties) first.");
+ }
+ return credential;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void initialize(Map<String, String> properties) {
+ AzureCredentialConfig config = new AzureCredentialConfig(properties);
+ credential =
+ new ClientSecretCredentialBuilder()
+ .tenantId(config.tenantId())
+ .clientId(config.clientId())
+ .clientSecret(config.clientSecret())
+ .build();
+ }
+}
diff --git
a/iceberg/iceberg-common/src/test/java/org/apache/gravitino/iceberg/common/credential/TestAzureClientSecretTokenCredentialProvider.java
b/iceberg/iceberg-common/src/test/java/org/apache/gravitino/iceberg/common/credential/TestAzureClientSecretTokenCredentialProvider.java
new file mode 100644
index 0000000000..b1caca526e
--- /dev/null
+++
b/iceberg/iceberg-common/src/test/java/org/apache/gravitino/iceberg/common/credential/TestAzureClientSecretTokenCredentialProvider.java
@@ -0,0 +1,67 @@
+/*
+ * 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.iceberg.common.credential;
+
+import com.azure.identity.ClientSecretCredential;
+import com.google.common.collect.ImmutableMap;
+import java.util.Map;
+import org.apache.gravitino.catalog.lakehouse.iceberg.IcebergPropertiesUtils;
+import org.apache.gravitino.storage.AzureProperties;
+import org.apache.iceberg.azure.AdlsTokenCredentialProvider;
+import org.apache.iceberg.azure.AdlsTokenCredentialProviders;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TestAzureClientSecretTokenCredentialProvider {
+
+ @Test
+ void testCredentialBeforeInitialize() {
+ AzureClientSecretTokenCredentialProvider provider =
+ new AzureClientSecretTokenCredentialProvider();
+
+ IllegalStateException exception =
+ Assertions.assertThrows(IllegalStateException.class,
provider::credential);
+ Assertions.assertEquals(
+ "The Azure credential provider has not been initialized. Call
initialize(properties) first.",
+ exception.getMessage());
+ }
+
+ @Test
+ void testLoadAndInitializeProviderThroughIceberg() {
+ Map<String, String> gravitinoProperties =
+ ImmutableMap.of(
+ AzureProperties.GRAVITINO_AZURE_STORAGE_ACCOUNT_NAME,
+ "account",
+ AzureProperties.GRAVITINO_AZURE_TENANT_ID,
+ "tenant",
+ AzureProperties.GRAVITINO_AZURE_CLIENT_ID,
+ "client",
+ AzureProperties.GRAVITINO_AZURE_CLIENT_SECRET,
+ "secret");
+ Map<String, String> icebergProperties =
+ IcebergPropertiesUtils.toIcebergCatalogProperties(gravitinoProperties);
+
+ AdlsTokenCredentialProvider provider =
AdlsTokenCredentialProviders.from(icebergProperties);
+
+
Assertions.assertInstanceOf(AzureClientSecretTokenCredentialProvider.class,
provider);
+ Assertions.assertInstanceOf(ClientSecretCredential.class,
provider.credential());
+ Assertions.assertSame(provider.credential(), provider.credential());
+ }
+}
diff --git
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRESTADLSTokenIT.java
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRESTADLSTokenIT.java
index 1305c5af69..edfeca2fd8 100644
---
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRESTADLSTokenIT.java
+++
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRESTADLSTokenIT.java
@@ -38,7 +38,6 @@ import
org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
public class IcebergRESTADLSTokenIT extends IcebergRESTJdbcCatalogIT {
private String storageAccountName;
- private String storageAccountKey;
private String tenantId;
private String clientId;
private String clientSecret;
@@ -49,9 +48,6 @@ public class IcebergRESTADLSTokenIT extends
IcebergRESTJdbcCatalogIT {
this.storageAccountName =
System.getenv()
.getOrDefault("GRAVITINO_AZURE_STORAGE_ACCOUNT_NAME",
"{STORAGE_ACCOUNT_NAME}");
- this.storageAccountKey =
- System.getenv()
- .getOrDefault("GRAVITINO_AZURE_STORAGE_ACCOUNT_KEY",
"{STORAGE_ACCOUNT_KEY}");
this.tenantId = System.getenv().getOrDefault("GRAVITINO_AZURE_TENANT_ID",
"{TENANT_ID}");
this.clientId = System.getenv().getOrDefault("GRAVITINO_AZURE_CLIENT_ID",
"{CLIENT_ID}");
this.clientSecret =
@@ -89,9 +85,6 @@ public class IcebergRESTADLSTokenIT extends
IcebergRESTJdbcCatalogIT {
configMap.put(
IcebergConfig.ICEBERG_CONFIG_PREFIX +
AzureProperties.GRAVITINO_AZURE_STORAGE_ACCOUNT_NAME,
storageAccountName);
- configMap.put(
- IcebergConfig.ICEBERG_CONFIG_PREFIX +
AzureProperties.GRAVITINO_AZURE_STORAGE_ACCOUNT_KEY,
- storageAccountKey);
configMap.put(
IcebergConfig.ICEBERG_CONFIG_PREFIX +
AzureProperties.GRAVITINO_AZURE_TENANT_ID, tenantId);
configMap.put(
diff --git
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRESTServiceBaseIT.java
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRESTServiceBaseIT.java
index ab32e165d6..acfef5d7d9 100644
---
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRESTServiceBaseIT.java
+++
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRESTServiceBaseIT.java
@@ -122,7 +122,8 @@ public abstract class IcebergRESTServiceBaseIT {
IcebergConfig.ICEBERG_CONFIG_PREFIX +
IcebergConfig.TABLE_METADATA_CACHE_IMPL.getKey(),
LocalTableMetadataCache.class.getName());
icebergRESTServerManager.registerCustomConfigs(icebergConfigs);
- LOG.info("Iceberg REST service config registered, {}",
StringUtils.join(icebergConfigs));
+ // Configuration values may contain credentials. Log only keys for test
diagnostics.
+ LOG.info("Iceberg REST service config keys registered: {}",
icebergConfigs.keySet());
}
protected int getServerPort() {
diff --git
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/util/IcebergRESTServerManager.java
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/util/IcebergRESTServerManager.java
index c70888d7bb..f3c0ad5218 100644
---
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/util/IcebergRESTServerManager.java
+++
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/util/IcebergRESTServerManager.java
@@ -139,7 +139,8 @@ public abstract class IcebergRESTServerManager {
Properties properties =
serverConfig.loadPropertiesFromFile(configFile.toFile());
serverConfig.loadFromProperties(properties);
- LOG.info("Server config:{}.", serverConfig.getAllConfig());
+ // Configuration values may contain credentials. Log only keys for test
diagnostics.
+ LOG.info("Server config keys: {}.", serverConfig.getAllConfig().keySet());
JettyServerConfig jettyServerConfig =
JettyServerConfig.fromConfig(serverConfig,
IcebergConfig.ICEBERG_CONFIG_PREFIX);