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);

Reply via email to