This is an automated email from the ASF dual-hosted git repository.

FANNG1 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 a1ba08b2e7 [#11900] feat(iceberg): Support HDFS Kerberos auth for IRC 
JDBC catalog (#11901)
a1ba08b2e7 is described below

commit a1ba08b2e7bd6896e4508607275be40729b7fda0
Author: MaSai <[email protected]>
AuthorDate: Thu Jul 9 08:54:14 2026 +0800

    [#11900] feat(iceberg): Support HDFS Kerberos auth for IRC JDBC catalog 
(#11901)
    
    ### What changes were proposed in this pull request?
    
    This PR adds Kerberos authentication support for the Iceberg REST
    Catalog (IRC) when using a **JDBC** catalog backend with an HDFS
    warehouse.
    
    - Add `ClosableJdbcCatalog` wrapping Iceberg `JdbcCatalog`, implementing
    `SupportsKerberos` and `Closeable` (aligned with `ClosableHiveCatalog`).
    - Update `IcebergCatalogUtil.loadJdbcCatalog` to use
    `ClosableJdbcCatalog`, configure HDFS security settings, and initialize
    Kerberos login.
    - Make `JdbcCatalogWithMetadataLocationSupport` extend
    `ClosableJdbcCatalog`.
    - Add unit tests (`TestClosableJdbcCatalog`) and integration test
    (`IcebergRestKerberosJdbcCatalogIT`).
    - Update IRC documentation and
    `gravitino-iceberg-rest-server.conf.template` with Kerberos
    configuration examples.
    
    Fix: #11900
    
    ### Why are the changes needed?
    
    The Hive catalog backend already supports HDFS Kerberos via
    `ClosableHiveCatalog`. JDBC-backed IRC deployments on Kerberized HDFS
    clusters currently lack equivalent support, causing warehouse access
    failures.
    
    ### Does this PR introduce _any_ user-facing change?
    
    Yes. IRC with JDBC backend now supports Kerberos authentication for HDFS
    warehouse access when configured:
    
    ```
    gravitino.iceberg-rest.authentication.type=kerberos
    gravitino.iceberg-rest.authentication.kerberos.principal=xxx@REALM
    
gravitino.iceberg-rest.authentication.kerberos.keytab-uri=file:///path/to/xxx.keytab
    gravitino.iceberg-rest.hadoop.security.authentication=kerberos
    ```
    
    JDBC metadata store authentication still uses `jdbc-user` and
    `jdbc-password`.
    
    ### How was this patch tested?
    
    - UT: `./gradlew :iceberg:iceberg-common:test --tests
    "org.apache.gravitino.iceberg.common.TestClosableJdbcCatalog" --tests
    "org.apache.gravitino.iceberg.common.utils.TestIcebergCatalogUtil"
    -PskipITs`
    - IT: `./gradlew :iceberg:iceberg-rest-server:test --tests
    
"org.apache.gravitino.iceberg.integration.test.IcebergRestKerberosJdbcCatalogIT"
    -PskipDockerTests=false`
    - `./gradlew :iceberg:iceberg-common:spotlessApply
    :iceberg:iceberg-rest-server:spotlessApply`
    
    ---------
    
    Co-authored-by: Cursor <[email protected]>
---
 conf/gravitino-iceberg-rest-server.conf.template   |  18 ++
 docs/iceberg-rest-service.md                       |  68 +++++-
 docs/lakehouse-iceberg-catalog.md                  |  26 +--
 .../iceberg/common/ClosableHiveCatalog.java        |  78 ++-----
 .../iceberg/common/ClosableJdbcCatalog.java        | 108 +++++++++
 .../iceberg/common/utils/IcebergCatalogUtil.java   |  18 +-
 .../iceberg/common/utils/KerberosCatalogUtils.java | 175 ++++++++++++++
 .../JdbcCatalogWithMetadataLocationSupport.java    |   3 +-
 .../iceberg/common/TestClosableJdbcCatalog.java    | 134 +++++++++++
 .../common/utils/TestIcebergCatalogUtil.java       |  26 ++-
 .../common/utils/TestKerberosCatalogUtils.java     |  64 ++++++
 .../integration/test/IcebergRESTServiceBaseIT.java |   9 +
 .../integration/test/IcebergRESTServiceIT.java     |   8 +-
 .../test/IcebergRestKerberosHiveCatalogIT.java     | 125 ++++------
 ...KerberosHiveWithUserImpersonationCatalogIT.java |   7 +-
 .../test/IcebergRestKerberosJdbcCatalogIT.java     | 133 +++++++++++
 ...erberosJdbcWithUserImpersonationCatalogIT.java} |  22 +-
 .../test/IcebergRestKerberosTestEnv.java           | 253 +++++++++++++++++++++
 .../util/IcebergRESTServerManagerForDeploy.java    |  93 +++++---
 19 files changed, 1144 insertions(+), 224 deletions(-)

diff --git a/conf/gravitino-iceberg-rest-server.conf.template 
b/conf/gravitino-iceberg-rest-server.conf.template
index 3aed55216a..62df320496 100644
--- a/conf/gravitino-iceberg-rest-server.conf.template
+++ b/conf/gravitino-iceberg-rest-server.conf.template
@@ -63,3 +63,21 @@ gravitino.iceberg-rest.warehouse = /tmp
 # gravitino.iceberg-rest.s3-secret-access-key = xxx
 # gravitino.iceberg-rest.s3-endpoint = http://192.168.215.4:9010
 # gravitino.iceberg-rest.s3-region = xxx
+
+# THE CONFIGURATION EXAMPLE FOR JDBC CATALOG BACKEND WITH KERBEROS-SECURED 
HDFS WAREHOUSE
+# JDBC username/password authenticates the metadata store; Kerberos 
authenticates HDFS access.
+
+# gravitino.iceberg-rest.catalog-backend = jdbc
+# gravitino.iceberg-rest.jdbc-driver = org.postgresql.Driver
+# gravitino.iceberg-rest.uri = jdbc:postgresql://127.0.0.1:5432/iceberg
+# gravitino.iceberg-rest.jdbc-user = iceberg
+# gravitino.iceberg-rest.jdbc-password = secret
+# gravitino.iceberg-rest.jdbc-initialize = true
+# gravitino.iceberg-rest.warehouse = 
hdfs://127.0.0.1:9000/user/hive/warehouse-jdbc
+# gravitino.iceberg-rest.authentication.type = kerberos
+# gravitino.iceberg-rest.authentication.kerberos.principal = 
[email protected]
+# gravitino.iceberg-rest.authentication.kerberos.keytab-uri = 
file:///etc/security/keytabs/iceberg.keytab
+# gravitino.iceberg-rest.hadoop.security.authentication = kerberos
+# gravitino.iceberg-rest.dfs.namenode.kerberos.principal = 
hdfs/[email protected]
+#
+# For Hive catalog Kerberos configuration, see docs/iceberg-rest-service.md 
(Backend Authentication).
diff --git a/docs/iceberg-rest-service.md b/docs/iceberg-rest-service.md
index 66900d8824..52e63bbf65 100644
--- a/docs/iceberg-rest-service.md
+++ b/docs/iceberg-rest-service.md
@@ -163,6 +163,8 @@ The Gravitino Iceberg REST catalog service uses the memory 
catalog backend by de
 | `gravitino.iceberg-rest.warehouse`                                        | 
The warehouse directory of the Hive catalog, such as 
`/user/hive/warehouse-hive/`.                                                   
        | (none)                                                                
         | Yes      | 0.2.0         |
 | `gravitino.iceberg-rest.catalog-backend-name`                             | 
The catalog backend name passed to underlying Iceberg catalog backend. Catalog 
name in JDBC backend is used to isolate namespace and tables. | `hive` for Hive 
backend, `jdbc` for JDBC backend, `memory` for memory backend  | No       | 
0.5.2         |
 
+For Kerberos-secured Hive Metastore and HDFS, see [Backend 
Authentication](#backend-authentication).
+
 #### JDBC Backend Configuration
 
 | Configuration item                            | Description                  
                                                                                
                                                                                
                                                          | Default value       
    | Required | Since Version |
@@ -180,6 +182,8 @@ The Gravitino Iceberg REST catalog service uses the memory 
catalog backend by de
 
 If you have a JDBC Iceberg catalog prior, you must set `catalog-backend-name` 
to keep consistent with your Jdbc Iceberg catalog name to operate the prior 
namespace and tables.
 
+Use `gravitino.iceberg-rest.jdbc-user` and 
`gravitino.iceberg-rest.jdbc-password` to authenticate the JDBC catalog 
metadata store. When the warehouse is on Kerberos-secured HDFS, set 
`gravitino.iceberg-rest.authentication.type` to `kerberos` and configure the 
Kerberos properties in [Backend Authentication](#backend-authentication) to 
access warehouse paths.
+
 :::caution
 Download the corresponding JDBC driver to the `iceberg-rest-server/libs` 
directory.
 If you are using multiple JDBC catalog backends, setting `jdbc-initialize` to 
true may not take effect for RDBMS like `Mysql`, you should create Iceberg meta 
tables explicitly.
@@ -428,18 +432,62 @@ Refer to [HTTPS 
Configuration](./security/how-to-use-https.md#apache-iceberg-res
 
 #### Backend Authentication
 
-For JDBC backend, you can use the `gravitino.iceberg-rest.jdbc-user` and 
`gravitino.iceberg-rest.jdbc-password` to authenticate the JDBC connection. For 
Hive backend, you can use the `gravitino.iceberg-rest.authentication.type` to 
specify the authentication type, and use the 
`gravitino.iceberg-rest.authentication.kerberos.principal` and 
`gravitino.iceberg-rest.authentication.kerberos.keytab-uri` to authenticate the 
Kerberos connection.
+For the JDBC catalog backend, use `gravitino.iceberg-rest.jdbc-user` and 
`gravitino.iceberg-rest.jdbc-password` to authenticate the JDBC metadata store 
connection. Use `gravitino.iceberg-rest.authentication.type` to specify how the 
catalog backend accesses the warehouse storage. When the warehouse is on HDFS, 
set it to `kerberos` or `simple`, and configure 
`gravitino.iceberg-rest.authentication.kerberos.principal` and 
`gravitino.iceberg-rest.authentication.kerberos.keytab-uri` for Kerber [...]
+
+For the Hive catalog backend, `gravitino.iceberg-rest.authentication.type` 
controls both Hive Metastore and HDFS access. When using Kerberos, also 
configure `gravitino.iceberg-rest.hive.metastore.sasl.enabled` and related Hive 
Metastore Kerberos properties.
+
 The detailed configuration items are as follows:
 
-| Configuration item                                                        | 
Description                                                                     
                                                                                
                                                                                
      | Default value | Required                                                
                                                                                
               [...]
-|---------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------|-------------------------------------------------------------------------------------------------------------------------------------------------------
 [...]
-| `gravitino.iceberg-rest.authentication.type`                              | 
The type of authentication for Iceberg rest catalog backend. This configuration 
only applicable for Hive backend, and only supports `Kerberos`, `simple` 
currently. As for JDBC backend, only username/password authentication was 
supported now. | `simple`      | No                                             
                                                                                
                            [...]
-| `gravitino.iceberg-rest.authentication.impersonation-enable`              | 
Whether to enable impersonation for the Iceberg catalog                         
                                                                                
                                                                                
      | `false`       | No                                                      
                                                                                
               [...]
-| `gravitino.iceberg-rest.hive.metastore.sasl.enabled`                      | 
Whether to enable SASL authentication protocol when connect to Kerberos Hive 
metastore.                                                                      
                                                                                
         | `false`       | No, This value should be true in most case(Some will 
use SSL protocol, but it rather rare) if the value of 
`gravitino.iceberg-rest.authentication.type [...]
-| `gravitino.iceberg-rest.authentication.kerberos.principal`                | 
The principal of the Kerberos authentication                                    
                                                                                
                                                                                
      | (none)        | required if the value of 
`gravitino.iceberg-rest.authentication.type` is Kerberos.                       
                                              [...]
-| `gravitino.iceberg-rest.authentication.kerberos.keytab-uri`               | 
The URI of The keytab for the Kerberos authentication.                          
                                                                                
                                                                                
      | (none)        | required if the value of 
`gravitino.iceberg-rest.authentication.type` is Kerberos.                       
                                              [...]
-| `gravitino.iceberg-rest.authentication.kerberos.check-interval-sec`       | 
The check interval of Kerberos credential for Iceberg catalog.                  
                                                                                
                                                                                
      | 60            | No                                                      
                                                                                
               [...]
-| `gravitino.iceberg-rest.authentication.kerberos.keytab-fetch-timeout-sec` | 
The fetch timeout of retrieving Kerberos keytab from 
`authentication.kerberos.keytab-uri`.                                           
                                                                                
                                 | 60            | No                           
                                                                                
                                          [...]
+| Configuration item                                                           
 | Description                                                                  
                                                                                
                                                                                
             | Default value     | Required                                     
                                                                                
              [...]
+| 
----------------------------------------------------------------------------- | 
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 | ----------------- | 
------------------------------------------------------------------------------------------------------------------------------------------
 [...]
+| `gravitino.iceberg-rest.authentication.type`                                 
 | The authentication type for HDFS warehouse access. Supports `kerberos` and 
`simple` for Hive and JDBC catalog backends.                                    
                                                                                
               | `simple`          | No                                         
                                                                                
                [...]
+| `gravitino.iceberg-rest.authentication.impersonation-enable`                 
 | Whether to enable impersonation for the Iceberg catalog.                     
                                                                                
                                                                                
             | `false`           | No                                           
                                                                                
              [...]
+| `gravitino.iceberg-rest.hive.metastore.sasl.enabled`                         
 | Whether to enable SASL when connecting to a Kerberos Hive Metastore.         
                                                                                
                                                                                
             | `false`           | No. Set to true in most cases when 
`gravitino.iceberg-rest.authentication.type` is `kerberos` (some deployments 
use SSL instead, but that  [...]
+| `gravitino.iceberg-rest.authentication.kerberos.principal`                   
 | The principal for Kerberos authentication.                                   
                                                                                
                                                                                
             | (none)            | Yes, if 
`gravitino.iceberg-rest.authentication.type` is `kerberos`.                     
                                                   [...]
+| `gravitino.iceberg-rest.authentication.kerberos.keytab-uri`                  
 | The URI of the keytab for Kerberos authentication.                           
                                                                                
                                                                                
             | (none)            | Yes, if 
`gravitino.iceberg-rest.authentication.type` is `kerberos`.                     
                                                   [...]
+| `gravitino.iceberg-rest.authentication.kerberos.check-interval-sec`          
 | The check interval of Kerberos credential for the Iceberg catalog.           
                                                                                
                                                                                
             | 60                | No                                           
                                                                                
              [...]
+| `gravitino.iceberg-rest.authentication.kerberos.keytab-fetch-timeout-sec`    
 | The fetch timeout for retrieving Kerberos keytab from 
`authentication.kerberos.keytab-uri`.                                           
                                                                                
                                    | 60                | No                    
                                                                                
                                     [...]
+
+Kerberos applies to **HDFS warehouse** access.
+
+**Hive backend with Kerberos** — authenticates to both Hive Metastore and HDFS:
+
+```text
+gravitino.iceberg-rest.catalog-backend = hive
+gravitino.iceberg-rest.uri = thrift://127.0.0.1:9083
+gravitino.iceberg-rest.warehouse = 
hdfs://127.0.0.1:9000/user/hive/warehouse-hive
+
+gravitino.iceberg-rest.authentication.type = kerberos
+gravitino.iceberg-rest.authentication.kerberos.principal = [email protected]
+gravitino.iceberg-rest.authentication.kerberos.keytab-uri = 
file:///etc/security/keytabs/iceberg.keytab
+
+gravitino.iceberg-rest.hive.metastore.sasl.enabled = true
+gravitino.iceberg-rest.hive.metastore.kerberos.principal = 
hive/[email protected]
+
+gravitino.iceberg-rest.hadoop.security.authentication = kerberos
+gravitino.iceberg-rest.dfs.namenode.kerberos.principal = 
hdfs/[email protected]
+```
+
+**JDBC backend with Kerberos-secured HDFS warehouse** — JDBC credentials 
authenticate the metadata store; Kerberos authenticates HDFS access only:
+
+```text
+gravitino.iceberg-rest.catalog-backend = jdbc
+gravitino.iceberg-rest.jdbc-driver = org.postgresql.Driver
+gravitino.iceberg-rest.uri = jdbc:postgresql://127.0.0.1:5432/iceberg
+gravitino.iceberg-rest.jdbc-user = iceberg
+gravitino.iceberg-rest.jdbc-password = secret
+gravitino.iceberg-rest.jdbc-initialize = true
+gravitino.iceberg-rest.warehouse = 
hdfs://127.0.0.1:9000/user/hive/warehouse-jdbc
+
+gravitino.iceberg-rest.authentication.type = kerberos
+gravitino.iceberg-rest.authentication.kerberos.principal = [email protected]
+gravitino.iceberg-rest.authentication.kerberos.keytab-uri = 
file:///etc/security/keytabs/iceberg.keytab
+
+gravitino.iceberg-rest.hadoop.security.authentication = kerberos
+gravitino.iceberg-rest.dfs.namenode.kerberos.principal = 
hdfs/[email protected]
+```
+
+Replace `host.example.com` and `EXAMPLE.COM` with your Hive/HDFS host name and 
Kerberos realm. Hive Metastore SASL properties are not required for the JDBC 
backend.
 
 #### Credential Vending
 
diff --git a/docs/lakehouse-iceberg-catalog.md 
b/docs/lakehouse-iceberg-catalog.md
index fdc3eca7f5..0badff0441 100644
--- a/docs/lakehouse-iceberg-catalog.md
+++ b/docs/lakehouse-iceberg-catalog.md
@@ -38,7 +38,7 @@ Mixing Iceberg JARs from different versions on the client 
classpath is not compa
 - Supports DDL operations for Iceberg schemas and tables.
 - Doesn't support snapshot or table management operations.
 - Supports multi storage, including S3, GCS, ADLS, OSS and HDFS.
-- Supports Kerberos or simple authentication for Iceberg catalog with Hive 
backend.
+- Supports Kerberos or simple authentication for warehouse storage access on 
HDFS for Hive and JDBC catalog backends.
 - Supports table metadata cache.
 
 ### Catalog Properties
@@ -60,7 +60,7 @@ If you are using the Gravitino with Spark, you can pass the 
Spark Iceberg connec
 
 #### JDBC Backend
 
-If you are using JDBC backend, you must provide properties like `jdbc-user`, 
`jdbc-password` and `jdbc-driver`.
+If you are using JDBC backend, you must provide properties like `jdbc-user`, 
`jdbc-password` and `jdbc-driver`. Use `jdbc-user` and `jdbc-password` for the 
JDBC metadata store connection. When the warehouse is on Kerberos-secured HDFS, 
set `authentication.type` to `kerberos` and configure the Kerberos properties 
in [Catalog Backend Security](#catalog-backend-security).
 
 | Property name     | Description                                              
                                               | Default value | Required | 
Since Version |
 
|-------------------|---------------------------------------------------------------------------------------------------------|---------------|----------|---------------|
@@ -228,17 +228,17 @@ Please set the `warehouse` parameter to 
`{storage_prefix}://{bucket_name}/${pref
 
 #### Catalog Backend Security
 
-Users can use the following properties to configure the security of the 
catalog backend if needed. For example, if you are using a Kerberos Hive 
catalog backend, you must set `authentication.type` to `Kerberos` and provide 
`authentication.kerberos.principal` and `authentication.kerberos.keytab-uri`.
-
-| Property name                                      | Description             
                                                                                
                                                                                
                                                         | Default value | 
Required                                                                        
                                                                                
     | Since Version    |
-|----------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------|
-| `authentication.type`                              | The type of 
authentication for Iceberg catalog backend. This configuration only applicable 
for for Hive backend, and only supports `Kerberos`, `simple` currently. As for 
JDBC backend, only username/password authentication was supported now. | 
`simple`      | No                                                              
                                                                                
                     | 0.6.0-incubating |
-| `authentication.impersonation-enable`              | Whether to enable 
impersonation for the Iceberg catalog                                           
                                                                                
                                                               | `false`       
| No                                                                            
                                                                                
       | 0.6.0-incubating |
-| `hive.metastore.sasl.enabled`                      | Whether to enable SASL 
authentication protocol when connect to Kerberos Hive metastore. This is a raw 
Hive configuration                                                              
                                                           | `false`       | 
No, This value should be true in most case(Some will use SSL protocol, but it 
rather rare) if the value of `gravitino.iceberg-rest.authentication.type` is 
Kerberos. | 0.6.0-incubating |
-| `authentication.kerberos.principal`                | The principal of the 
Kerberos authentication                                                         
                                                                                
                                                            | (none)        | 
required if the value of `authentication.type` is Kerberos.                     
                                                                                
     | 0.6.0-incubating |
-| `authentication.kerberos.keytab-uri`               | The URI of The keytab 
for the Kerberos authentication.                                                
                                                                                
                                                           | (none)        | 
required if the value of `authentication.type` is Kerberos.                     
                                                                                
     | 0.6.0-incubating |
-| `authentication.kerberos.check-interval-sec`       | The check interval of 
Kerberos credential for Iceberg catalog.                                        
                                                                                
                                                           | 60            | No 
                                                                                
                                                                                
  | 0.6.0-incubating |
-| `authentication.kerberos.keytab-fetch-timeout-sec` | The fetch timeout of 
retrieving Kerberos keytab from `authentication.kerberos.keytab-uri`.           
                                                                                
                                                            | 60            | 
No                                                                              
                                                                                
     | 0.6.0-incubating |
+Users can use the following properties to configure warehouse storage security 
when needed. For example, if you are using a Hive or JDBC catalog backend with 
a Kerberos-secured HDFS warehouse, set `authentication.type` to `kerberos` and 
provide `authentication.kerberos.principal` and 
`authentication.kerberos.keytab-uri`. For JDBC backend, JDBC username/password 
authentication for the metadata store is configured separately via `jdbc-user` 
and `jdbc-password`.
+
+| Property name                                          | Description         
                                                                                
                                                                                
                                                                     | Default 
value     | Required                                                            
                                                                                
               [...]
+| ------------------------------------------------------ | 
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 | ----------------- | 
------------------------------------------------------------------------------------------------------------------------------------------------------------------
 [...]
+| `authentication.type`                                  | The authentication 
type for HDFS warehouse access. Supports `kerberos` and `simple` for Hive and 
JDBC catalog backends.                                                          
                                                                        | 
`simple`          | No                                                          
                                                                                
                       [...]
+| `authentication.impersonation-enable`                  | Whether to enable 
impersonation for the Iceberg catalog.                                          
                                                                                
                                                                       | 
`false`           | No                                                          
                                                                                
                       [...]
+| `hive.metastore.sasl.enabled`                          | Whether to enable 
SASL when connecting to a Kerberos Hive Metastore. This is a raw Hive 
configuration.                                                                  
                                                                                
 | `false`           | No. Set to true in most cases when `authentication.type` 
is `kerberos` (some deployments use SSL instead, but that is rare).             
                          [...]
+| `authentication.kerberos.principal`                    | The principal for 
Kerberos authentication.                                                        
                                                                                
                                                                       | (none) 
           | Yes, if `authentication.type` is `kerberos`.                       
                                                                                
                [...]
+| `authentication.kerberos.keytab-uri`                   | The URI of the 
keytab for Kerberos authentication.                                             
                                                                                
                                                                          | 
(none)            | Yes, if `authentication.type` is `kerberos`.                
                                                                                
                       [...]
+| `authentication.kerberos.check-interval-sec`           | The check interval 
of Kerberos credential for the Iceberg catalog.                                 
                                                                                
                                                                      | 60      
          | No                                                                  
                                                                                
               [...]
+| `authentication.kerberos.keytab-fetch-timeout-sec`     | The fetch timeout 
for retrieving Kerberos keytab from `authentication.kerberos.keytab-uri`.       
                                                                                
                                                                       | 60     
           | No                                                                 
                                                                                
                [...]
 
 #### Table Metadata Cache
 
diff --git 
a/iceberg/iceberg-common/src/main/java/org/apache/gravitino/iceberg/common/ClosableHiveCatalog.java
 
b/iceberg/iceberg-common/src/main/java/org/apache/gravitino/iceberg/common/ClosableHiveCatalog.java
index 6fd8dcd7d3..b93eacd2ba 100644
--- 
a/iceberg/iceberg-common/src/main/java/org/apache/gravitino/iceberg/common/ClosableHiveCatalog.java
+++ 
b/iceberg/iceberg-common/src/main/java/org/apache/gravitino/iceberg/common/ClosableHiveCatalog.java
@@ -22,7 +22,6 @@ package org.apache.gravitino.iceberg.common;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.Lists;
 import java.io.Closeable;
-import java.io.File;
 import java.io.IOException;
 import java.lang.reflect.Field;
 import java.security.PrivilegedExceptionAction;
@@ -31,14 +30,12 @@ import java.util.Map;
 import java.util.concurrent.ScheduledExecutorService;
 import lombok.Getter;
 import org.apache.commons.lang3.reflect.FieldUtils;
-import org.apache.gravitino.catalog.hadoop.auth.KerberosAuthUtils;
 import org.apache.gravitino.catalog.hadoop.auth.KerberosClient;
 import org.apache.gravitino.iceberg.common.authentication.AuthenticationConfig;
 import org.apache.gravitino.iceberg.common.authentication.SupportsKerberos;
-import 
org.apache.gravitino.iceberg.common.authentication.kerberos.KerberosConfig;
 import 
org.apache.gravitino.iceberg.common.utils.CaffeineSchedulerExtractorUtils;
 import org.apache.gravitino.iceberg.common.utils.IcebergHiveCachedClientPool;
-import org.apache.gravitino.utils.PrincipalUtils;
+import org.apache.gravitino.iceberg.common.utils.KerberosCatalogUtils;
 import org.apache.hadoop.hive.metastore.IMetaStoreClient;
 import org.apache.hadoop.hive.thrift.DelegationTokenIdentifier;
 import org.apache.hadoop.security.UserGroupInformation;
@@ -83,7 +80,7 @@ public class ClosableHiveCatalog extends HiveCatalog 
implements Closeable, Suppo
 
     AuthenticationConfig authenticationConfig = new 
AuthenticationConfig(properties);
     if (authenticationConfig.isKerberosAuth()) {
-      this.kerberosClient = initKerberosClient();
+      this.kerberosClient = 
KerberosCatalogUtils.initKerberosClient(properties, getConf(), name());
     }
 
     try {
@@ -95,13 +92,7 @@ public class ClosableHiveCatalog extends HiveCatalog 
implements Closeable, Suppo
 
   @Override
   public void close() throws IOException {
-    if (kerberosClient != null) {
-      try {
-        kerberosClient.close();
-      } catch (Exception e) {
-        LOGGER.warn("Failed to close KerberosClient", e);
-      }
-    }
+    KerberosCatalogUtils.closeKerberosClient(kerberosClient, LOGGER);
 
     // Do clean up work here. We need a mechanism to close the HiveCatalog; 
however, HiveCatalog
     // doesn't implement the Closeable interface.
@@ -124,24 +115,19 @@ public class ClosableHiveCatalog extends HiveCatalog 
implements Closeable, Suppo
 
   @Override
   public <R> R doKerberosOperations(Executable<R> executable) throws Throwable 
{
-    Map<String, String> properties = this.properties();
-    AuthenticationConfig authenticationConfig = new 
AuthenticationConfig(properties);
-
-    final String finalPrincipalName;
-    String proxyKerberosPrincipalName = 
PrincipalUtils.getCurrentPrincipal().getName();
-
-    if (!proxyKerberosPrincipalName.contains("@")) {
-      finalPrincipalName =
-          String.format("%s@%s", proxyKerberosPrincipalName, 
kerberosClient.getRealm());
-    } else {
-      finalPrincipalName = proxyKerberosPrincipalName;
+    AuthenticationConfig authenticationConfig = new 
AuthenticationConfig(this.properties());
+    if (!authenticationConfig.isKerberosAuth()) {
+      return executable.execute();
+    }
+    if (kerberosClient == null) {
+      throw new IllegalStateException(
+          "Kerberos is configured but KerberosClient is not initialized");
     }
 
+    String finalPrincipalName = 
KerberosCatalogUtils.resolveProxyPrincipal(kerberosClient);
     UserGroupInformation realUser =
-        authenticationConfig.isImpersonationEnabled()
-            ? UserGroupInformation.createProxyUser(
-                finalPrincipalName, kerberosClient.getLoginUser())
-            : kerberosClient.getLoginUser();
+        KerberosCatalogUtils.createRealUser(
+            authenticationConfig, kerberosClient, finalPrincipalName);
     try {
       ClientPool<IMetaStoreClient, TException> newClientPool =
           (ClientPool<IMetaStoreClient, TException>) 
FieldUtils.readField(this, "clients", true);
@@ -166,18 +152,7 @@ public class ClosableHiveCatalog extends HiveCatalog 
implements Closeable, Suppo
       throw new RuntimeException(
           "Failed to get delegation token for principal: " + 
finalPrincipalName, e);
     }
-    return realUser.doAs(
-        (PrivilegedExceptionAction<R>)
-            () -> {
-              try {
-                return executable.execute();
-              } catch (Throwable e) {
-                if (RuntimeException.class.isAssignableFrom(e.getClass())) {
-                  throw (RuntimeException) e;
-                }
-                throw new RuntimeException("Failed to invoke method", e);
-              }
-            });
+    return KerberosCatalogUtils.executeAs(realUser, executable);
   }
 
   private ClientPool<IMetaStoreClient, TException> resetIcebergHiveClientPool()
@@ -272,29 +247,4 @@ public class ClosableHiveCatalog extends HiveCatalog 
implements Closeable, Suppo
       LOGGER.warn("Failed to close HiveCatalog internal client pool", e);
     }
   }
-
-  private KerberosClient initKerberosClient() {
-    try {
-      KerberosConfig kerberosConfig = new KerberosConfig(this.properties());
-      KerberosClient kerberosClient =
-          KerberosClient.builder(kerberosConfig.getPrincipalName(), 
this.getConf())
-              .loginMode(KerberosAuthUtils.LoginMode.CURRENT_USER)
-              .checkIntervalSec(kerberosConfig.getCheckIntervalSec())
-              .build();
-      // catalog_uuid always exists for Gravitino managed catalogs, `0` is 
just a fallback value.
-      String catalogUUID = properties().getOrDefault("catalog_uuid", "0");
-      File keytabFile =
-          new File(String.format(KerberosConfig.GRAVITINO_KEYTAB_FORMAT, 
catalogUUID));
-      KerberosAuthUtils.saveKeytabFromUri(
-          kerberosConfig.getKeytab(),
-          keytabFile,
-          kerberosConfig.getFetchTimeoutSec(),
-          false,
-          this.getConf());
-      kerberosClient.login(keytabFile.getAbsolutePath());
-      return kerberosClient;
-    } catch (IOException e) {
-      throw new RuntimeException("Failed to login with kerberos", e);
-    }
-  }
 }
diff --git 
a/iceberg/iceberg-common/src/main/java/org/apache/gravitino/iceberg/common/ClosableJdbcCatalog.java
 
b/iceberg/iceberg-common/src/main/java/org/apache/gravitino/iceberg/common/ClosableJdbcCatalog.java
new file mode 100644
index 0000000000..6e2dd5c07e
--- /dev/null
+++ 
b/iceberg/iceberg-common/src/main/java/org/apache/gravitino/iceberg/common/ClosableJdbcCatalog.java
@@ -0,0 +1,108 @@
+/*
+ * 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;
+
+import java.io.Closeable;
+import java.util.Map;
+import java.util.function.Function;
+import javax.annotation.Nullable;
+import org.apache.gravitino.catalog.hadoop.auth.KerberosClient;
+import org.apache.gravitino.iceberg.common.authentication.AuthenticationConfig;
+import org.apache.gravitino.iceberg.common.authentication.SupportsKerberos;
+import org.apache.gravitino.iceberg.common.utils.KerberosCatalogUtils;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.iceberg.io.FileIO;
+import org.apache.iceberg.jdbc.JdbcCatalog;
+import org.apache.iceberg.jdbc.JdbcClientPool;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * ClosableJdbcCatalog is a wrapper class to wrap Iceberg JdbcCatalog to do 
some clean-up work like
+ * closing resources and supporting Kerberos authentication for HDFS access.
+ */
+public class ClosableJdbcCatalog extends JdbcCatalog implements Closeable, 
SupportsKerberos {
+
+  private static final Logger LOGGER = 
LoggerFactory.getLogger(ClosableJdbcCatalog.class);
+
+  @Nullable private Configuration hadoopConf;
+
+  private KerberosClient kerberosClient;
+
+  public ClosableJdbcCatalog() {
+    super();
+  }
+
+  public ClosableJdbcCatalog(
+      Function<Map<String, String>, FileIO> ioBuilder,
+      Function<Map<String, String>, JdbcClientPool> clientPoolBuilder,
+      boolean initializeCatalogTables) {
+    super(ioBuilder, clientPoolBuilder, initializeCatalogTables);
+  }
+
+  @Override
+  public void setConf(Object conf) {
+    super.setConf(conf);
+    this.hadoopConf = (Configuration) conf;
+  }
+
+  /**
+   * Initialize the ClosableJdbcCatalog with the given input name and 
properties.
+   *
+   * <p>Note: This method can only be called once as it will create new client 
pools.
+   *
+   * @param inputName name of the catalog
+   * @param properties properties for the catalog
+   */
+  @Override
+  public void initialize(String inputName, Map<String, String> properties) {
+    super.initialize(inputName, properties);
+
+    AuthenticationConfig authenticationConfig = new 
AuthenticationConfig(properties);
+    if (authenticationConfig.isKerberosAuth()) {
+      try {
+        this.kerberosClient =
+            KerberosCatalogUtils.initKerberosClient(properties, hadoopConf, 
name());
+      } catch (RuntimeException e) {
+        try {
+          close();
+        } catch (Exception closeException) {
+          e.addSuppressed(closeException);
+        }
+        throw e;
+      }
+    }
+  }
+
+  @Override
+  public void close() {
+    KerberosCatalogUtils.closeKerberosClient(kerberosClient, LOGGER);
+    try {
+      super.close();
+    } catch (Exception e) {
+      LOGGER.warn("Failed to close JdbcCatalog", e);
+    }
+  }
+
+  @Override
+  public <R> R doKerberosOperations(Executable<R> executable) throws Throwable 
{
+    return KerberosCatalogUtils.doKerberosOperations(this.properties(), 
kerberosClient, executable);
+  }
+}
diff --git 
a/iceberg/iceberg-common/src/main/java/org/apache/gravitino/iceberg/common/utils/IcebergCatalogUtil.java
 
b/iceberg/iceberg-common/src/main/java/org/apache/gravitino/iceberg/common/utils/IcebergCatalogUtil.java
index 0d2d5462d0..a5587964e5 100644
--- 
a/iceberg/iceberg-common/src/main/java/org/apache/gravitino/iceberg/common/utils/IcebergCatalogUtil.java
+++ 
b/iceberg/iceberg-common/src/main/java/org/apache/gravitino/iceberg/common/utils/IcebergCatalogUtil.java
@@ -32,6 +32,7 @@ import 
org.apache.gravitino.catalog.lakehouse.iceberg.IcebergCatalogBackend;
 import org.apache.gravitino.catalog.lakehouse.iceberg.IcebergConstants;
 import org.apache.gravitino.exceptions.ConnectionFailedException;
 import org.apache.gravitino.iceberg.common.ClosableHiveCatalog;
+import org.apache.gravitino.iceberg.common.ClosableJdbcCatalog;
 import org.apache.gravitino.iceberg.common.IcebergConfig;
 import org.apache.gravitino.iceberg.common.authentication.AuthenticationConfig;
 import 
org.apache.gravitino.iceberg.common.rest.auth.UserPrincipalForwardingAuthManager;
@@ -108,7 +109,7 @@ public class IcebergCatalogUtil {
     } catch (ClassNotFoundException e) {
       throw new IllegalArgumentException("Couldn't load jdbc driver " + 
driverClassName);
     }
-    JdbcCatalog jdbcCatalog =
+    ClosableJdbcCatalog jdbcCatalog =
         new JdbcCatalogWithMetadataLocationSupport(
             icebergConfig.get(IcebergConfig.JDBC_INIT_TABLES));
 
@@ -123,9 +124,20 @@ public class IcebergCatalogUtil {
 
     HdfsConfiguration hdfsConfiguration = new HdfsConfiguration();
     properties.forEach(hdfsConfiguration::set);
-    jdbcCatalog.setConf(hdfsConfiguration);
+    AuthenticationConfig authenticationConfig = new 
AuthenticationConfig(properties);
     try {
-      jdbcCatalog.initialize(icebergCatalogName, properties);
+      if (authenticationConfig.isSimpleAuth()) {
+        jdbcCatalog.setConf(hdfsConfiguration);
+        jdbcCatalog.initialize(icebergCatalogName, properties);
+      } else if (authenticationConfig.isKerberosAuth()) {
+        hdfsConfiguration.set(HADOOP_SECURITY_AUTHORIZATION, "true");
+        hdfsConfiguration.set(HADOOP_SECURITY_AUTHENTICATION, "kerberos");
+        jdbcCatalog.setConf(hdfsConfiguration);
+        jdbcCatalog.initialize(icebergCatalogName, properties);
+      } else {
+        throw new UnsupportedOperationException(
+            "Unsupported authentication method: " + 
authenticationConfig.getAuthType());
+      }
     } catch (UncheckedSQLException e) {
       if (e.getCause() instanceof SQLException
           && e.getCause().getMessage().contains("Access denied")) {
diff --git 
a/iceberg/iceberg-common/src/main/java/org/apache/gravitino/iceberg/common/utils/KerberosCatalogUtils.java
 
b/iceberg/iceberg-common/src/main/java/org/apache/gravitino/iceberg/common/utils/KerberosCatalogUtils.java
new file mode 100644
index 0000000000..d7749914e3
--- /dev/null
+++ 
b/iceberg/iceberg-common/src/main/java/org/apache/gravitino/iceberg/common/utils/KerberosCatalogUtils.java
@@ -0,0 +1,175 @@
+/*
+ * 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.utils;
+
+import java.io.File;
+import java.io.IOException;
+import java.security.PrivilegedExceptionAction;
+import java.util.Map;
+import javax.annotation.Nullable;
+import org.apache.gravitino.catalog.hadoop.auth.KerberosAuthUtils;
+import org.apache.gravitino.catalog.hadoop.auth.KerberosClient;
+import org.apache.gravitino.iceberg.common.authentication.AuthenticationConfig;
+import org.apache.gravitino.iceberg.common.authentication.SupportsKerberos;
+import 
org.apache.gravitino.iceberg.common.authentication.kerberos.KerberosConfig;
+import org.apache.gravitino.utils.PrincipalUtils;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.security.UserGroupInformation;
+import org.slf4j.Logger;
+
+/** Shared Kerberos initialization and execution helpers for Iceberg closable 
catalogs. */
+public final class KerberosCatalogUtils {
+
+  private KerberosCatalogUtils() {}
+
+  /**
+   * Initializes and logs in a {@link KerberosClient} for the given catalog.
+   *
+   * @param properties Catalog properties containing Kerberos configuration.
+   * @param hadoopConf Hadoop configuration used for keytab download and login.
+   * @param catalogName Catalog name used to derive the local keytab path.
+   * @return The initialized and logged-in Kerberos client.
+   */
+  public static KerberosClient initKerberosClient(
+      Map<String, String> properties, Configuration hadoopConf, String 
catalogName) {
+    try {
+      KerberosConfig kerberosConfig = new KerberosConfig(properties);
+      KerberosClient kerberosClient =
+          KerberosClient.builder(kerberosConfig.getPrincipalName(), hadoopConf)
+              .loginMode(KerberosAuthUtils.LoginMode.CURRENT_USER)
+              .checkIntervalSec(kerberosConfig.getCheckIntervalSec())
+              .build();
+      File keytabFile =
+          new File(String.format(KerberosConfig.GRAVITINO_KEYTAB_FORMAT, 
catalogName));
+      KerberosAuthUtils.saveKeytabFromUri(
+          kerberosConfig.getKeytab(),
+          keytabFile,
+          kerberosConfig.getFetchTimeoutSec(),
+          false,
+          hadoopConf);
+      kerberosClient.login(keytabFile.getAbsolutePath());
+      return kerberosClient;
+    } catch (IOException e) {
+      throw new RuntimeException("Failed to login with kerberos", e);
+    }
+  }
+
+  /**
+   * Closes a Kerberos client and logs failures without rethrowing.
+   *
+   * @param kerberosClient The Kerberos client to close, or null.
+   * @param logger Logger used to record close failures.
+   */
+  public static void closeKerberosClient(@Nullable KerberosClient 
kerberosClient, Logger logger) {
+    if (kerberosClient != null) {
+      try {
+        kerberosClient.close();
+      } catch (Exception e) {
+        logger.warn("Failed to close KerberosClient", e);
+      }
+    }
+  }
+
+  /**
+   * Executes catalog operations under Kerberos when configured, otherwise 
runs directly.
+   *
+   * @param properties Catalog properties.
+   * @param kerberosClient Initialized Kerberos client, or null when Kerberos 
is not configured.
+   * @param executable Operation to execute.
+   * @param <R> Result type.
+   * @return Operation result.
+   * @throws Throwable If the operation fails.
+   */
+  public static <R> R doKerberosOperations(
+      Map<String, String> properties,
+      @Nullable KerberosClient kerberosClient,
+      SupportsKerberos.Executable<R> executable)
+      throws Throwable {
+    AuthenticationConfig authenticationConfig = new 
AuthenticationConfig(properties);
+    if (!authenticationConfig.isKerberosAuth()) {
+      return executable.execute();
+    }
+    if (kerberosClient == null) {
+      throw new IllegalStateException(
+          "Kerberos is configured but KerberosClient is not initialized");
+    }
+
+    String finalPrincipalName = resolveProxyPrincipal(kerberosClient);
+    UserGroupInformation realUser =
+        createRealUser(authenticationConfig, kerberosClient, 
finalPrincipalName);
+    return executeAs(realUser, executable);
+  }
+
+  /**
+   * Resolves the proxy Kerberos principal for the current subject.
+   *
+   * @param kerberosClient Kerberos client used to obtain the realm when 
needed.
+   * @return Fully qualified Kerberos principal name.
+   */
+  public static String resolveProxyPrincipal(KerberosClient kerberosClient) {
+    String proxyKerberosPrincipalName = 
PrincipalUtils.getCurrentPrincipal().getName();
+    if (!proxyKerberosPrincipalName.contains("@")) {
+      return String.format("%s@%s", proxyKerberosPrincipalName, 
kerberosClient.getRealm());
+    }
+    return proxyKerberosPrincipalName;
+  }
+
+  /**
+   * Creates the effective UGI for catalog operations, optionally as a proxy 
user.
+   *
+   * @param authenticationConfig Authentication configuration.
+   * @param kerberosClient Kerberos client providing the login user.
+   * @param finalPrincipalName Proxy principal name when impersonation is 
enabled.
+   * @return UGI used to execute catalog operations.
+   */
+  public static UserGroupInformation createRealUser(
+      AuthenticationConfig authenticationConfig,
+      KerberosClient kerberosClient,
+      String finalPrincipalName) {
+    return authenticationConfig.isImpersonationEnabled()
+        ? UserGroupInformation.createProxyUser(finalPrincipalName, 
kerberosClient.getLoginUser())
+        : kerberosClient.getLoginUser();
+  }
+
+  /**
+   * Executes an operation as the given UGI.
+   *
+   * @param userGroupInformation UGI to run as.
+   * @param executable Operation to execute.
+   * @param <R> Result type.
+   * @return Operation result.
+   * @throws Throwable If the operation fails.
+   */
+  public static <R> R executeAs(
+      UserGroupInformation userGroupInformation, 
SupportsKerberos.Executable<R> executable)
+      throws Throwable {
+    return userGroupInformation.doAs(
+        (PrivilegedExceptionAction<R>)
+            () -> {
+              try {
+                return executable.execute();
+              } catch (Throwable e) {
+                if (RuntimeException.class.isAssignableFrom(e.getClass())) {
+                  throw (RuntimeException) e;
+                }
+                throw new RuntimeException("Failed to invoke method", e);
+              }
+            });
+  }
+}
diff --git 
a/iceberg/iceberg-common/src/main/java/org/apache/iceberg/jdbc/JdbcCatalogWithMetadataLocationSupport.java
 
b/iceberg/iceberg-common/src/main/java/org/apache/iceberg/jdbc/JdbcCatalogWithMetadataLocationSupport.java
index e3f51ef5c1..d1b17c2ced 100644
--- 
a/iceberg/iceberg-common/src/main/java/org/apache/iceberg/jdbc/JdbcCatalogWithMetadataLocationSupport.java
+++ 
b/iceberg/iceberg-common/src/main/java/org/apache/iceberg/jdbc/JdbcCatalogWithMetadataLocationSupport.java
@@ -23,6 +23,7 @@ import com.google.common.base.Preconditions;
 import java.sql.SQLException;
 import java.util.Map;
 import org.apache.commons.lang3.reflect.FieldUtils;
+import org.apache.gravitino.iceberg.common.ClosableJdbcCatalog;
 import org.apache.gravitino.iceberg.common.cache.SupportsMetadataLocation;
 import org.apache.iceberg.MetastoreRegisterTableUtils;
 import org.apache.iceberg.Table;
@@ -33,7 +34,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 // Use Iceberg package to reuse JdbcUtil related classes.
-public class JdbcCatalogWithMetadataLocationSupport extends JdbcCatalog
+public class JdbcCatalogWithMetadataLocationSupport extends ClosableJdbcCatalog
     implements SupportsMetadataLocation {
   private static final Logger LOG =
       LoggerFactory.getLogger(JdbcCatalogWithMetadataLocationSupport.class);
diff --git 
a/iceberg/iceberg-common/src/test/java/org/apache/gravitino/iceberg/common/TestClosableJdbcCatalog.java
 
b/iceberg/iceberg-common/src/test/java/org/apache/gravitino/iceberg/common/TestClosableJdbcCatalog.java
new file mode 100644
index 0000000000..4e386fa5fa
--- /dev/null
+++ 
b/iceberg/iceberg-common/src/test/java/org/apache/gravitino/iceberg/common/TestClosableJdbcCatalog.java
@@ -0,0 +1,134 @@
+/*
+ * 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;
+
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.gravitino.catalog.lakehouse.iceberg.IcebergConstants;
+import org.apache.gravitino.iceberg.common.authentication.AuthenticationConfig;
+import 
org.apache.gravitino.iceberg.common.authentication.kerberos.KerberosConfig;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hdfs.HdfsConfiguration;
+import org.apache.iceberg.CatalogProperties;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+public class TestClosableJdbcCatalog {
+
+  @TempDir private Path warehouse;
+
+  @Test
+  void testSimpleInit() {
+    ClosableJdbcCatalog catalog = new ClosableJdbcCatalog();
+    Configuration conf = new HdfsConfiguration();
+    catalog.setConf(conf);
+    catalog.initialize("test", newJdbcCatalogProperties());
+
+    Assertions.assertDoesNotThrow(catalog::close);
+  }
+
+  @Test
+  void testKerberosInitNoConf() {
+    ClosableJdbcCatalog catalog = new ClosableJdbcCatalog();
+    Map<String, String> properties = newJdbcCatalogProperties();
+    properties.put(AuthenticationConfig.AUTH_TYPE_KEY, "kerberos");
+    properties.put(KerberosConfig.PRINCIPAL_KEY, "cli@HADOOPKRB");
+    properties.put(KerberosConfig.KET_TAB_URI_KEY, "/tmp/missing.keytab");
+
+    Assertions.assertThrows(RuntimeException.class, () -> 
catalog.initialize("test", properties));
+  }
+
+  @Test
+  void testKerberosInitBadKeytab() {
+    ClosableJdbcCatalog catalog = new ClosableJdbcCatalog();
+    Configuration conf = new HdfsConfiguration();
+    catalog.setConf(conf);
+
+    Map<String, String> properties = newJdbcCatalogProperties();
+    properties.put(AuthenticationConfig.AUTH_TYPE_KEY, "kerberos");
+    properties.put(KerberosConfig.PRINCIPAL_KEY, "cli@HADOOPKRB");
+    properties.put(KerberosConfig.KET_TAB_URI_KEY, "/tmp/missing.keytab");
+
+    RuntimeException exception =
+        Assertions.assertThrows(
+            RuntimeException.class, () -> catalog.initialize("test", 
properties));
+    Assertions.assertTrue(exception.getMessage().contains("Failed to login 
with kerberos"));
+  }
+
+  @Test
+  void testSimpleOps() throws Throwable {
+    ClosableJdbcCatalog catalog = new ClosableJdbcCatalog();
+    catalog.setConf(new HdfsConfiguration());
+    catalog.initialize("test", newJdbcCatalogProperties());
+
+    Assertions.assertEquals("ok", catalog.doKerberosOperations(() -> "ok"));
+  }
+
+  @Test
+  void testImpersonationConfig() {
+    Map<String, String> properties = newJdbcCatalogProperties();
+    properties.put(AuthenticationConfig.IMPERSONATION_ENABLE_KEY, "true");
+    Assertions.assertTrue(new 
AuthenticationConfig(properties).isImpersonationEnabled());
+
+    properties.put(AuthenticationConfig.IMPERSONATION_ENABLE_KEY, "false");
+    Assertions.assertFalse(new 
AuthenticationConfig(properties).isImpersonationEnabled());
+  }
+
+  @Test
+  void testOpsNoImpersonation() throws Throwable {
+    ClosableJdbcCatalog catalog = new ClosableJdbcCatalog();
+    catalog.setConf(new HdfsConfiguration());
+
+    Map<String, String> properties = newJdbcCatalogProperties();
+    properties.put(AuthenticationConfig.IMPERSONATION_ENABLE_KEY, "false");
+    catalog.initialize("test", properties);
+
+    Assertions.assertEquals("ok", catalog.doKerberosOperations(() -> "ok"));
+  }
+
+  @Test
+  void testOpsNoClient() {
+    ClosableJdbcCatalog catalog = new ClosableJdbcCatalog();
+    Configuration conf = new HdfsConfiguration();
+    catalog.setConf(conf);
+
+    Map<String, String> properties = newJdbcCatalogProperties();
+    properties.put(AuthenticationConfig.AUTH_TYPE_KEY, "kerberos");
+    properties.put(KerberosConfig.PRINCIPAL_KEY, "cli@HADOOPKRB");
+    properties.put(KerberosConfig.KET_TAB_URI_KEY, "/tmp/missing.keytab");
+
+    Assertions.assertThrows(RuntimeException.class, () -> 
catalog.initialize("test", properties));
+    Assertions.assertThrows(
+        IllegalStateException.class, () -> catalog.doKerberosOperations(() -> 
"ok"));
+  }
+
+  private Map<String, String> newJdbcCatalogProperties() {
+    Map<String, String> properties = new HashMap<>();
+    properties.put(CatalogProperties.URI, "jdbc:sqlite::memory:");
+    properties.put(CatalogProperties.WAREHOUSE_LOCATION, warehouse.toString());
+    properties.put(IcebergConstants.GRAVITINO_JDBC_DRIVER, "org.sqlite.JDBC");
+    properties.put(IcebergConstants.ICEBERG_JDBC_USER, "test");
+    properties.put(IcebergConstants.ICEBERG_JDBC_PASSWORD, "test");
+    properties.put(IcebergConstants.ICEBERG_JDBC_INITIALIZE, "true");
+    return properties;
+  }
+}
diff --git 
a/iceberg/iceberg-common/src/test/java/org/apache/gravitino/iceberg/common/utils/TestIcebergCatalogUtil.java
 
b/iceberg/iceberg-common/src/test/java/org/apache/gravitino/iceberg/common/utils/TestIcebergCatalogUtil.java
index 0bf3745538..1b302f4437 100644
--- 
a/iceberg/iceberg-common/src/test/java/org/apache/gravitino/iceberg/common/utils/TestIcebergCatalogUtil.java
+++ 
b/iceberg/iceberg-common/src/test/java/org/apache/gravitino/iceberg/common/utils/TestIcebergCatalogUtil.java
@@ -24,7 +24,9 @@ import java.util.HashMap;
 import java.util.Map;
 import org.apache.gravitino.catalog.lakehouse.iceberg.IcebergCatalogBackend;
 import org.apache.gravitino.catalog.lakehouse.iceberg.IcebergConstants;
+import org.apache.gravitino.iceberg.common.ClosableJdbcCatalog;
 import org.apache.gravitino.iceberg.common.IcebergConfig;
+import org.apache.gravitino.iceberg.common.authentication.AuthenticationConfig;
 import org.apache.iceberg.CatalogProperties;
 import org.apache.iceberg.Schema;
 import org.apache.iceberg.catalog.Catalog;
@@ -33,7 +35,6 @@ import org.apache.iceberg.catalog.TableIdentifier;
 import org.apache.iceberg.exceptions.NoSuchNamespaceException;
 import org.apache.iceberg.hive.HiveCatalog;
 import org.apache.iceberg.inmemory.InMemoryCatalog;
-import org.apache.iceberg.jdbc.JdbcCatalog;
 import org.apache.iceberg.jdbc.JdbcCatalogWithMetadataLocationSupport;
 import org.apache.iceberg.types.Types;
 import org.junit.jupiter.api.Assertions;
@@ -80,7 +81,7 @@ public class TestIcebergCatalogUtil {
     catalog =
         IcebergCatalogUtil.loadCatalogBackend(
             IcebergCatalogBackend.JDBC, new IcebergConfig(properties));
-    Assertions.assertTrue(catalog instanceof JdbcCatalog);
+    Assertions.assertInstanceOf(ClosableJdbcCatalog.class, catalog);
 
     Assertions.assertThrowsExactly(
         IllegalArgumentException.class,
@@ -89,6 +90,27 @@ public class TestIcebergCatalogUtil {
         });
   }
 
+  @Test
+  void testJdbcBadAuth() {
+    Map<String, String> properties = new HashMap<>();
+    properties.put(CatalogProperties.URI, "jdbc:sqlite::memory:");
+    properties.put(CatalogProperties.WAREHOUSE_LOCATION, warehouse.toString());
+    properties.put(IcebergConstants.GRAVITINO_JDBC_DRIVER, "org.sqlite.JDBC");
+    properties.put(IcebergConstants.ICEBERG_JDBC_USER, "test");
+    properties.put(IcebergConstants.ICEBERG_JDBC_PASSWORD, "test");
+    properties.put(IcebergConstants.ICEBERG_JDBC_INITIALIZE, "true");
+    properties.put(AuthenticationConfig.AUTH_TYPE_KEY, "oauth");
+
+    UnsupportedOperationException exception =
+        Assertions.assertThrows(
+            UnsupportedOperationException.class,
+            () ->
+                IcebergCatalogUtil.loadCatalogBackend(
+                    IcebergCatalogBackend.JDBC, new 
IcebergConfig(properties)));
+    Assertions.assertTrue(
+        exception.getMessage().contains("Unsupported authentication method: 
oauth"));
+  }
+
   @Test
   void testJdbcCatalogDefaultSchemaVersionIsV1() {
     Map<String, String> properties = new HashMap<>();
diff --git 
a/iceberg/iceberg-common/src/test/java/org/apache/gravitino/iceberg/common/utils/TestKerberosCatalogUtils.java
 
b/iceberg/iceberg-common/src/test/java/org/apache/gravitino/iceberg/common/utils/TestKerberosCatalogUtils.java
new file mode 100644
index 0000000000..7798cf490f
--- /dev/null
+++ 
b/iceberg/iceberg-common/src/test/java/org/apache/gravitino/iceberg/common/utils/TestKerberosCatalogUtils.java
@@ -0,0 +1,64 @@
+/*
+ * 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.utils;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.gravitino.catalog.hadoop.auth.KerberosClient;
+import org.apache.gravitino.iceberg.common.authentication.AuthenticationConfig;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+public class TestKerberosCatalogUtils {
+
+  @Test
+  void testOpsSimpleAuth() throws Throwable {
+    Map<String, String> properties = new HashMap<>();
+    properties.put(AuthenticationConfig.AUTH_TYPE_KEY, "simple");
+
+    String result = KerberosCatalogUtils.doKerberosOperations(properties, 
null, () -> "ok");
+
+    Assertions.assertEquals("ok", result);
+  }
+
+  @Test
+  void testOpsNoClient() {
+    Map<String, String> properties = new HashMap<>();
+    properties.put(AuthenticationConfig.AUTH_TYPE_KEY, "kerberos");
+
+    IllegalStateException exception =
+        Assertions.assertThrows(
+            IllegalStateException.class,
+            () -> KerberosCatalogUtils.doKerberosOperations(properties, null, 
() -> "ok"));
+
+    Assertions.assertTrue(
+        exception.getMessage().contains("Kerberos is configured but 
KerberosClient is not"));
+  }
+
+  @Test
+  void testProxyPrincipalRealm() {
+    KerberosClient kerberosClient = Mockito.mock(KerberosClient.class);
+    Mockito.when(kerberosClient.getRealm()).thenReturn("EXAMPLE.COM");
+
+    String principal = 
KerberosCatalogUtils.resolveProxyPrincipal(kerberosClient);
+
+    Assertions.assertTrue(principal.endsWith("@EXAMPLE.COM"));
+  }
+}
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..b48db1c6e4 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
@@ -76,9 +76,13 @@ public abstract class IcebergRESTServiceBaseIT {
   void stopIcebergTestEnv() throws Exception {
     stopSparkEnv();
     icebergRESTServerManager.stopIcebergRESTServer();
+    afterIcebergRESTServerStopped();
     LOG.info("Gravitino and Spark env stopped,{}", catalogType);
   }
 
+  /** Hook invoked after the Iceberg REST server is stopped during test 
teardown. */
+  protected void afterIcebergRESTServerStopped() {}
+
   boolean catalogTypeNotMemory() {
     return !catalogType.equals(IcebergCatalogBackend.MEMORY);
   }
@@ -152,9 +156,14 @@ public abstract class IcebergRESTServiceBaseIT {
           "spark.sql.catalog.rest.header.X-Iceberg-Access-Delegation", 
"vended-credentials");
     }
 
+    customizeSparkConf(sparkConf);
+
     sparkSession = 
SparkSession.builder().master("local[1]").config(sparkConf).getOrCreate();
   }
 
+  /** Hook to customize Spark configuration before the test Spark session is 
created. */
+  protected void customizeSparkConf(SparkConf sparkConf) {}
+
   private void stopSparkEnv() {
     if (sparkSession != null) {
       sparkSession.close();
diff --git 
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRESTServiceIT.java
 
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRESTServiceIT.java
index 9109f9734a..4f6e4c6590 100644
--- 
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRESTServiceIT.java
+++ 
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRESTServiceIT.java
@@ -93,7 +93,13 @@ public abstract class IcebergRESTServiceIT extends 
IcebergRESTServiceBaseIT {
   }
 
   @AfterAll
-  void cleanup() {
+  @Override
+  void stopIcebergTestEnv() throws Exception {
+    cleanup();
+    super.stopIcebergTestEnv();
+  }
+
+  private void cleanup() {
     purgeAllIcebergTestNamespaces();
   }
 
diff --git 
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRestKerberosHiveCatalogIT.java
 
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRestKerberosHiveCatalogIT.java
index 36969f0e31..0b494d0861 100644
--- 
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRestKerberosHiveCatalogIT.java
+++ 
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRestKerberosHiveCatalogIT.java
@@ -18,31 +18,24 @@
  */
 package org.apache.gravitino.iceberg.integration.test;
 
-import java.io.File;
-import java.lang.reflect.Method;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
 import java.util.HashMap;
 import java.util.Map;
-import java.util.Objects;
-import org.apache.commons.io.FileUtils;
 import org.apache.gravitino.catalog.lakehouse.iceberg.IcebergCatalogBackend;
 import org.apache.gravitino.iceberg.common.IcebergConfig;
 import org.apache.gravitino.integration.test.container.HiveContainer;
 import org.apache.gravitino.integration.test.util.GravitinoITUtils;
-import org.apache.gravitino.integration.test.util.ITUtils;
+import org.apache.spark.SparkConf;
 import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.TestInstance;
 import org.junit.jupiter.api.TestInstance.Lifecycle;
-import org.junit.jupiter.api.condition.EnabledIf;
+import org.junit.jupiter.api.condition.DisabledIf;
 
 @Tag("gravitino-docker-test")
 @TestInstance(Lifecycle.PER_CLASS)
-@EnabledIf("isEmbedded")
 public class IcebergRestKerberosHiveCatalogIT extends IcebergRESTHiveCatalogIT 
{
 
   protected static final String HIVE_METASTORE_CLIENT_PRINCIPAL = 
"cli@HADOOPKRB";
-  protected static final String HIVE_METASTORE_CLIENT_KEYTAB = 
"/client.keytab";
 
   protected static String tempDir;
 
@@ -51,58 +44,12 @@ public class IcebergRestKerberosHiveCatalogIT extends 
IcebergRESTHiveCatalogIT {
   }
 
   void initEnv() {
-    containerSuite.startKerberosHiveContainer();
-    try {
-
-      // Init kerberos configurations;
-      File baseDir = new File(System.getProperty("java.io.tmpdir"));
-      File file = Files.createTempDirectory(baseDir.toPath(), "test").toFile();
-      file.deleteOnExit();
-      tempDir = file.getAbsolutePath();
-
-      HiveContainer kerberosHiveContainer = 
containerSuite.getKerberosHiveContainer();
-      kerberosHiveContainer
-          .getContainer()
-          .copyFileFromContainer("/etc/admin.keytab", tempDir + 
HIVE_METASTORE_CLIENT_KEYTAB);
-
-      String tmpKrb5Path = tempDir + "/krb5.conf_tmp";
-      String krb5Path = tempDir + "/krb5.conf";
-      
kerberosHiveContainer.getContainer().copyFileFromContainer("/etc/krb5.conf", 
tmpKrb5Path);
-
-      // Modify the krb5.conf and change the kdc and admin_server to the 
container IP
-      String ip = 
containerSuite.getKerberosHiveContainer().getContainerIpAddress();
-      String content = FileUtils.readFileToString(new File(tmpKrb5Path), 
StandardCharsets.UTF_8);
-      content = content.replace("kdc = localhost:88", "kdc = " + ip + ":88");
-      content = content.replace("admin_server = localhost", "admin_server = " 
+ ip + ":749");
-      FileUtils.write(new File(krb5Path), content, StandardCharsets.UTF_8);
-
-      LOG.info("Kerberos kdc config:\n{}, path: {}", content, krb5Path);
-      System.setProperty("java.security.krb5.conf", krb5Path);
-      System.setProperty("sun.security.krb5.debug", "true");
-      System.setProperty("java.security.krb5.realm", "HADOOPKRB");
-      System.setProperty("java.security.krb5.kdc", ip);
-
-      refreshKerberosConfig();
-      resetDefaultRealm();
-
-      // Give cli@HADOOPKRB permission to access the hdfs
-      containerSuite
-          .getKerberosHiveContainer()
-          .executeInContainer("hadoop", "fs", "-chown", "-R", "cli", 
"/user/hive/");
-
-    } catch (Exception e) {
-      throw new RuntimeException(e);
-    }
+    tempDir = IcebergRestKerberosTestEnv.init(containerSuite);
   }
 
-  void resetDefaultRealm() {
-    try {
-      String kerberosNameClass = 
"org.apache.hadoop.security.authentication.util.KerberosName";
-      Class<?> cl = Class.forName(kerberosNameClass);
-      cl.getMethod("resetDefaultRealm").invoke(null);
-    } catch (Exception e) {
-      throw new RuntimeException(e);
-    }
+  @Override
+  protected void afterIcebergRESTServerStopped() {
+    IcebergRestKerberosTestEnv.reset();
   }
 
   @Override
@@ -115,7 +62,7 @@ public class IcebergRestKerberosHiveCatalogIT extends 
IcebergRESTHiveCatalogIT {
         HIVE_METASTORE_CLIENT_PRINCIPAL);
     configMap.put(
         "gravitino.iceberg-rest.authentication.kerberos.keytab-uri",
-        tempDir + HIVE_METASTORE_CLIENT_KEYTAB);
+        tempDir + IcebergRestKerberosTestEnv.CLIENT_KEYTAB);
     configMap.put("gravitino.iceberg-rest.hive.metastore.sasl.enabled", 
"true");
     configMap.put(
         "gravitino.iceberg-rest.hive.metastore.kerberos.principal",
@@ -150,28 +97,44 @@ public class IcebergRestKerberosHiveCatalogIT extends 
IcebergRESTHiveCatalogIT {
     return configMap;
   }
 
-  protected static boolean isEmbedded() {
-    String mode =
-        System.getProperty(ITUtils.TEST_MODE) == null
-            ? ITUtils.EMBEDDED_TEST_MODE
-            : System.getProperty(ITUtils.TEST_MODE);
+  @Override
+  protected void customizeSparkConf(SparkConf sparkConf) {
+    IcebergRestKerberosTestEnv.configureSparkKerberos(
+        sparkConf,
+        HIVE_METASTORE_CLIENT_PRINCIPAL,
+        containerSuite.getKerberosHiveContainer().getHostName());
+  }
 
-    return Objects.equals(mode, ITUtils.EMBEDDED_TEST_MODE);
+  // Spark writes data files to HDFS directly in these tests; keep them in 
embedded mode only.
+  @Test
+  @DisabledIf(
+      
"org.apache.gravitino.iceberg.integration.test.IcebergRestKerberosTestEnv#isDeployMode")
+  @Override
+  void testDML() {
+    super.testDML();
   }
 
-  protected static void refreshKerberosConfig() {
-    Class<?> classRef;
-    try {
-      if (System.getProperty("java.vendor").contains("IBM")) {
-        classRef = Class.forName("com.ibm.security.krb5.internal.Config");
-      } else {
-        classRef = Class.forName("sun.security.krb5.Config");
-      }
-
-      Method refreshMethod = classRef.getMethod("refresh");
-      refreshMethod.invoke(null);
-    } catch (Exception e) {
-      throw new RuntimeException(e);
-    }
+  @Test
+  @DisabledIf(
+      
"org.apache.gravitino.iceberg.integration.test.IcebergRestKerberosTestEnv#isDeployMode")
+  @Override
+  void testRegisterTable() {
+    super.testRegisterTable();
+  }
+
+  @Test
+  @DisabledIf(
+      
"org.apache.gravitino.iceberg.integration.test.IcebergRestKerberosTestEnv#isDeployMode")
+  @Override
+  void testSnapshot() {
+    super.testSnapshot();
+  }
+
+  @Test
+  @DisabledIf(
+      
"org.apache.gravitino.iceberg.integration.test.IcebergRestKerberosTestEnv#isDeployMode")
+  @Override
+  void testRegisterTableOverwrite() throws Exception {
+    super.testRegisterTableOverwrite();
   }
 }
diff --git 
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRestKerberosHiveWithUserImpersonationCatalogIT.java
 
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRestKerberosHiveWithUserImpersonationCatalogIT.java
index 28a7ee4dcc..f4209305e2 100644
--- 
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRestKerberosHiveWithUserImpersonationCatalogIT.java
+++ 
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRestKerberosHiveWithUserImpersonationCatalogIT.java
@@ -29,11 +29,9 @@ import org.junit.jupiter.api.Tag;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.TestInstance;
 import org.junit.jupiter.api.TestInstance.Lifecycle;
-import org.junit.jupiter.api.condition.EnabledIf;
 
 @Tag("gravitino-docker-test")
 @TestInstance(Lifecycle.PER_CLASS)
-@EnabledIf("isEmbedded")
 public class IcebergRestKerberosHiveWithUserImpersonationCatalogIT
     extends IcebergRestKerberosHiveCatalogIT {
 
@@ -95,6 +93,11 @@ public class 
IcebergRestKerberosHiveWithUserImpersonationCatalogIT
           "spark.sql.catalog.rest.header.X-Iceberg-Access-Delegation", 
"vended-credentials");
     }
 
+    IcebergRestKerberosTestEnv.configureSparkKerberos(
+        sparkConf,
+        HIVE_METASTORE_CLIENT_PRINCIPAL,
+        containerSuite.getKerberosHiveContainer().getHostName());
+
     sparkSession = 
SparkSession.builder().master("local[1]").config(sparkConf).getOrCreate();
   }
 
diff --git 
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRestKerberosJdbcCatalogIT.java
 
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRestKerberosJdbcCatalogIT.java
new file mode 100644
index 0000000000..a97f7f59c9
--- /dev/null
+++ 
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRestKerberosJdbcCatalogIT.java
@@ -0,0 +1,133 @@
+/*
+ * 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.integration.test;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.gravitino.catalog.lakehouse.iceberg.IcebergCatalogBackend;
+import org.apache.gravitino.catalog.lakehouse.iceberg.IcebergConstants;
+import org.apache.gravitino.iceberg.common.IcebergConfig;
+import org.apache.gravitino.integration.test.container.ContainerSuite;
+import org.apache.gravitino.integration.test.container.HiveContainer;
+import org.apache.gravitino.integration.test.util.GravitinoITUtils;
+import org.apache.spark.SparkConf;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.api.TestInstance.Lifecycle;
+import org.junit.jupiter.api.condition.DisabledIf;
+
+@Tag("gravitino-docker-test")
+@TestInstance(Lifecycle.PER_CLASS)
+public class IcebergRestKerberosJdbcCatalogIT extends IcebergRESTServiceIT {
+
+  private static final ContainerSuite containerSuite = 
ContainerSuite.getInstance();
+
+  protected static final String HDFS_CLIENT_PRINCIPAL = "cli@HADOOPKRB";
+
+  private static String tempDir;
+
+  public IcebergRestKerberosJdbcCatalogIT() {
+    catalogType = IcebergCatalogBackend.JDBC;
+  }
+
+  @Override
+  void initEnv() {
+    tempDir = IcebergRestKerberosTestEnv.init(containerSuite);
+  }
+
+  @Override
+  protected void afterIcebergRESTServerStopped() {
+    IcebergRestKerberosTestEnv.reset();
+  }
+
+  @Override
+  Map<String, String> getCatalogConfig() {
+    Map<String, String> configMap = new HashMap<>();
+
+    configMap.put("gravitino.iceberg-rest.authentication.type", "kerberos");
+    configMap.put(
+        "gravitino.iceberg-rest.authentication.kerberos.principal", 
HDFS_CLIENT_PRINCIPAL);
+    configMap.put(
+        "gravitino.iceberg-rest.authentication.kerberos.keytab-uri",
+        tempDir + IcebergRestKerberosTestEnv.CLIENT_KEYTAB);
+    configMap.put("gravitino.iceberg-rest.hadoop.security.authentication", 
"kerberos");
+    
configMap.put("gravitino.iceberg-rest.authentication.impersonation-enable", 
"false");
+    configMap.put(
+        "gravitino.iceberg-rest.dfs.namenode.kerberos.principal",
+        "hdfs/_HOST@HADOOPKRB"
+            .replace("_HOST", 
containerSuite.getKerberosHiveContainer().getHostName()));
+
+    configMap.put(
+        IcebergConfig.ICEBERG_CONFIG_PREFIX + 
IcebergConfig.CATALOG_BACKEND.getKey(),
+        IcebergCatalogBackend.JDBC.toString().toLowerCase());
+    configMap.put(
+        IcebergConfig.ICEBERG_CONFIG_PREFIX + 
IcebergConfig.JDBC_DRIVER.getKey(),
+        "org.sqlite.JDBC");
+    configMap.put(
+        IcebergConfig.ICEBERG_CONFIG_PREFIX + 
IcebergConfig.CATALOG_URI.getKey(),
+        "jdbc:sqlite::memory:");
+    configMap.put(
+        IcebergConfig.ICEBERG_CONFIG_PREFIX + 
IcebergConstants.ICEBERG_JDBC_USER, "iceberg");
+    configMap.put(
+        IcebergConfig.ICEBERG_CONFIG_PREFIX + 
IcebergConstants.ICEBERG_JDBC_PASSWORD, "iceberg");
+    configMap.put(
+        IcebergConfig.ICEBERG_CONFIG_PREFIX + 
IcebergConfig.JDBC_INIT_TABLES.getKey(), "true");
+    configMap.put(IcebergConfig.ICEBERG_CONFIG_PREFIX + "jdbc.schema-version", 
"V1");
+    configMap.put(
+        IcebergConfig.ICEBERG_CONFIG_PREFIX + 
IcebergConfig.CATALOG_WAREHOUSE.getKey(),
+        GravitinoITUtils.genRandomName(
+            String.format(
+                "hdfs://%s:%d/user/hive/warehouse-jdbc-kerberos",
+                
containerSuite.getKerberosHiveContainer().getContainerIpAddress(),
+                HiveContainer.HDFS_DEFAULTFS_PORT)));
+    return configMap;
+  }
+
+  @Override
+  protected void customizeSparkConf(SparkConf sparkConf) {
+    IcebergRestKerberosTestEnv.configureSparkKerberos(
+        sparkConf, HDFS_CLIENT_PRINCIPAL, 
containerSuite.getKerberosHiveContainer().getHostName());
+  }
+
+  // Spark writes data files to HDFS directly in these tests; keep them in 
embedded mode only.
+  @Test
+  @DisabledIf(
+      
"org.apache.gravitino.iceberg.integration.test.IcebergRestKerberosTestEnv#isDeployMode")
+  @Override
+  void testDML() {
+    super.testDML();
+  }
+
+  @Test
+  @DisabledIf(
+      
"org.apache.gravitino.iceberg.integration.test.IcebergRestKerberosTestEnv#isDeployMode")
+  @Override
+  void testRegisterTable() {
+    super.testRegisterTable();
+  }
+
+  @Test
+  @DisabledIf(
+      
"org.apache.gravitino.iceberg.integration.test.IcebergRestKerberosTestEnv#isDeployMode")
+  @Override
+  void testSnapshot() {
+    super.testSnapshot();
+  }
+}
diff --git 
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRestKerberosHiveWithUserImpersonationCatalogIT.java
 
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRestKerberosJdbcWithUserImpersonationCatalogIT.java
similarity index 87%
copy from 
iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRestKerberosHiveWithUserImpersonationCatalogIT.java
copy to 
iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRestKerberosJdbcWithUserImpersonationCatalogIT.java
index 28a7ee4dcc..72d2b0b038 100644
--- 
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRestKerberosHiveWithUserImpersonationCatalogIT.java
+++ 
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRestKerberosJdbcWithUserImpersonationCatalogIT.java
@@ -21,6 +21,7 @@ package org.apache.gravitino.iceberg.integration.test;
 import java.util.HashMap;
 import java.util.Map;
 import javax.annotation.Nullable;
+import org.apache.gravitino.integration.test.container.ContainerSuite;
 import org.apache.spark.SparkConf;
 import org.apache.spark.sql.SparkSession;
 import org.junit.jupiter.api.BeforeAll;
@@ -29,27 +30,21 @@ import org.junit.jupiter.api.Tag;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.TestInstance;
 import org.junit.jupiter.api.TestInstance.Lifecycle;
-import org.junit.jupiter.api.condition.EnabledIf;
 
 @Tag("gravitino-docker-test")
 @TestInstance(Lifecycle.PER_CLASS)
-@EnabledIf("isEmbedded")
-public class IcebergRestKerberosHiveWithUserImpersonationCatalogIT
-    extends IcebergRestKerberosHiveCatalogIT {
+public class IcebergRestKerberosJdbcWithUserImpersonationCatalogIT
+    extends IcebergRestKerberosJdbcCatalogIT {
 
   private static final String NORMAL_USER = "normal";
 
-  public IcebergRestKerberosHiveWithUserImpersonationCatalogIT() {
-    super();
-  }
+  private static final ContainerSuite containerSuite = 
ContainerSuite.getInstance();
 
   @BeforeAll
   void prepareSQLContext() {
-
     // Change the ownership of /user/hive to normal user for user 
impersonation test. If we do not
-    // change the ownership, the normal user will not have the permission to 
create table in Hive
-    // as the /user/hive is owned by user `cli`, please see what's done in 
`initEnv` method in
-    // superclass.
+    // change the ownership, the normal user will not have the permission to 
create table on HDFS
+    // warehouse as the /user/hive is owned by user `cli`.
     containerSuite
         .getKerberosHiveContainer()
         .executeInContainer("hadoop", "fs", "-chown", "-R", NORMAL_USER, 
"/user/hive/");
@@ -95,6 +90,9 @@ public class 
IcebergRestKerberosHiveWithUserImpersonationCatalogIT
           "spark.sql.catalog.rest.header.X-Iceberg-Access-Delegation", 
"vended-credentials");
     }
 
+    IcebergRestKerberosTestEnv.configureSparkKerberos(
+        sparkConf, HDFS_CLIENT_PRINCIPAL, 
containerSuite.getKerberosHiveContainer().getHostName());
+
     sparkSession = 
SparkSession.builder().master("local[1]").config(sparkConf).getOrCreate();
   }
 
@@ -106,7 +104,7 @@ public class 
IcebergRestKerberosHiveWithUserImpersonationCatalogIT
       parentNamespace = "iceberg_rest.nested.table_test";
       separator = ".";
     } else {
-      parentNamespace = "iceberg_rest_with_kerberos_impersonation_table_test";
+      parentNamespace = 
"iceberg_rest_with_kerberos_jdbc_impersonation_table_test";
       separator = "_";
     }
 
diff --git 
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRestKerberosTestEnv.java
 
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRestKerberosTestEnv.java
new file mode 100644
index 0000000000..abf24cd04b
--- /dev/null
+++ 
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRestKerberosTestEnv.java
@@ -0,0 +1,253 @@
+/*
+ * 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.integration.test;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.commons.io.FileUtils;
+import org.apache.gravitino.integration.test.container.ContainerSuite;
+import org.apache.gravitino.integration.test.container.HiveContainer;
+import org.apache.gravitino.integration.test.util.ITUtils;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.spark.SparkConf;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** Shared Kerberos test environment setup for Iceberg REST integration tests. 
*/
+public final class IcebergRestKerberosTestEnv {
+
+  private static final Logger LOG = 
LoggerFactory.getLogger(IcebergRestKerberosTestEnv.class);
+
+  private static final String KRB5_CONF_PROPERTY = "java.security.krb5.conf";
+  private static final String KRB5_DEBUG_PROPERTY = "sun.security.krb5.debug";
+  private static final String KRB5_REALM_PROPERTY = "java.security.krb5.realm";
+  private static final String KRB5_KDC_PROPERTY = "java.security.krb5.kdc";
+
+  private static final String[] MANAGED_SYSTEM_PROPERTIES = {
+    KRB5_CONF_PROPERTY, KRB5_DEBUG_PROPERTY, KRB5_REALM_PROPERTY, 
KRB5_KDC_PROPERTY
+  };
+
+  /** Local path suffix for the copied client keytab under the temp directory. 
*/
+  public static final String CLIENT_KEYTAB = "/client.keytab";
+
+  private static String tempDir;
+
+  /**
+   * Configures Spark to authenticate to Kerberos-protected HDFS.
+   *
+   * <p>In deploy mode the Iceberg REST server runs in a separate JVM, so 
Spark must perform its own
+   * Kerberos login to write data files to HDFS during DML tests.
+   *
+   * @param sparkConf Spark configuration to update
+   * @param clientPrincipal Kerberos principal for the Spark client
+   * @param hdfsHostName HDFS namenode host name used to build the namenode 
Kerberos principal
+   */
+  public static void configureSparkKerberos(
+      SparkConf sparkConf, String clientPrincipal, String hdfsHostName) {
+    if (tempDir == null) {
+      throw new IllegalStateException(
+          "Kerberos test environment is not initialized, call init() first");
+    }
+    String keytabPath = tempDir + CLIENT_KEYTAB;
+    String hdfsPrincipal = "hdfs/_HOST@HADOOPKRB".replace("_HOST", 
hdfsHostName);
+    try {
+      Configuration configuration = new Configuration();
+      configuration.set("hadoop.security.authentication", "kerberos");
+      configuration.set("dfs.namenode.kerberos.principal", hdfsPrincipal);
+      UserGroupInformation.setConfiguration(configuration);
+      if (!UserGroupInformation.isLoginKeytabBased()) {
+        UserGroupInformation.loginUserFromKeytab(clientPrincipal, keytabPath);
+      }
+    } catch (IOException e) {
+      throw new RuntimeException("Failed to login Spark client with Kerberos", 
e);
+    }
+
+    sparkConf
+        .set("spark.hadoop.security.authentication", "kerberos")
+        .set("spark.hadoop.dfs.namenode.kerberos.principal", hdfsPrincipal)
+        .set("spark.kerberos.keytab", keytabPath)
+        .set("spark.kerberos.principal", clientPrincipal);
+
+    String krb5Path = System.getProperty(KRB5_CONF_PROPERTY);
+    if (krb5Path != null) {
+      StringBuilder opts = new StringBuilder();
+      opts.append(String.format("-Djava.security.krb5.conf=%s", krb5Path));
+      String realm = System.getProperty(KRB5_REALM_PROPERTY);
+      if (realm != null) {
+        opts.append(String.format(" -Djava.security.krb5.realm=%s", realm));
+      }
+      String kdc = System.getProperty(KRB5_KDC_PROPERTY);
+      if (kdc != null) {
+        opts.append(String.format(" -Djava.security.krb5.kdc=%s", kdc));
+      }
+      sparkConf.set("spark.driver.extraJavaOptions", opts.toString());
+      sparkConf.set("spark.executor.extraJavaOptions", opts.toString());
+    }
+  }
+
+  private static final String KEYTAB_CONTAINER_PATH = "/etc/admin.keytab";
+
+  private static Map<String, String> savedSystemProperties;
+  private static int initRefCount;
+
+  private IcebergRestKerberosTestEnv() {}
+
+  /** Returns whether integration tests are running in deploy mode. */
+  public static boolean isDeployMode() {
+    String mode =
+        System.getProperty(ITUtils.TEST_MODE) == null
+            ? ITUtils.EMBEDDED_TEST_MODE
+            : System.getProperty(ITUtils.TEST_MODE);
+    return ITUtils.DEPLOY_TEST_MODE.equals(mode);
+  }
+
+  /**
+   * Starts the Kerberos Hive container and configures the JVM krb5 settings 
for tests.
+   *
+   * @param containerSuite shared docker container suite
+   * @return temp directory containing krb5.conf and client keytab
+   */
+  public static String init(ContainerSuite containerSuite) {
+    if (initRefCount == 0) {
+      savedSystemProperties = saveSystemProperties(MANAGED_SYSTEM_PROPERTIES);
+    }
+    initRefCount++;
+
+    containerSuite.startKerberosHiveContainer();
+    try {
+      File baseDir = new File(System.getProperty("java.io.tmpdir"));
+      File file = Files.createTempDirectory(baseDir.toPath(), "test").toFile();
+      file.deleteOnExit();
+      String tempDir = file.getAbsolutePath();
+
+      HiveContainer kerberosHiveContainer = 
containerSuite.getKerberosHiveContainer();
+      kerberosHiveContainer
+          .getContainer()
+          .copyFileFromContainer(KEYTAB_CONTAINER_PATH, tempDir + 
CLIENT_KEYTAB);
+
+      String tmpKrb5Path = tempDir + "/krb5.conf_tmp";
+      String krb5Path = tempDir + "/krb5.conf";
+      
kerberosHiveContainer.getContainer().copyFileFromContainer("/etc/krb5.conf", 
tmpKrb5Path);
+
+      String ip = kerberosHiveContainer.getContainerIpAddress();
+      String content = FileUtils.readFileToString(new File(tmpKrb5Path), 
StandardCharsets.UTF_8);
+      content = content.replace("kdc = localhost:88", "kdc = " + ip + ":88");
+      content = content.replace("admin_server = localhost", "admin_server = " 
+ ip + ":749");
+      FileUtils.write(new File(krb5Path), content, StandardCharsets.UTF_8);
+
+      LOG.info("Kerberos kdc config:\n{}, path: {}", content, krb5Path);
+      System.setProperty(KRB5_CONF_PROPERTY, krb5Path);
+      if (Boolean.getBoolean(KRB5_DEBUG_PROPERTY)) {
+        System.setProperty(KRB5_DEBUG_PROPERTY, "true");
+      } else {
+        System.clearProperty(KRB5_DEBUG_PROPERTY);
+      }
+      System.setProperty(KRB5_REALM_PROPERTY, "HADOOPKRB");
+      System.setProperty(KRB5_KDC_PROPERTY, ip);
+
+      refreshKerberosConfig();
+      resetDefaultRealm();
+
+      kerberosHiveContainer.executeInContainer(
+          "hadoop", "fs", "-chown", "-R", "cli", "/user/hive/");
+
+      IcebergRestKerberosTestEnv.tempDir = tempDir;
+      return tempDir;
+    } catch (Exception e) {
+      reset();
+      throw new RuntimeException(e);
+    }
+  }
+
+  /** Restores JVM Kerberos system properties changed by {@link 
#init(ContainerSuite)}. */
+  public static void reset() {
+    if (initRefCount == 0) {
+      return;
+    }
+    initRefCount--;
+    if (initRefCount > 0) {
+      return;
+    }
+
+    if (savedSystemProperties != null) {
+      restoreSystemProperties(savedSystemProperties);
+      savedSystemProperties = null;
+      tempDir = null;
+      try {
+        refreshKerberosConfig();
+        resetDefaultRealm();
+      } catch (RuntimeException e) {
+        LOG.warn("Failed to refresh Kerberos configuration after test 
cleanup", e);
+      }
+    }
+  }
+
+  /** Refreshes the JVM Kerberos configuration after updating krb5.conf. */
+  public static void refreshKerberosConfig() {
+    Class<?> classRef;
+    try {
+      if (System.getProperty("java.vendor").contains("IBM")) {
+        classRef = Class.forName("com.ibm.security.krb5.internal.Config");
+      } else {
+        classRef = Class.forName("sun.security.krb5.Config");
+      }
+
+      Method refreshMethod = classRef.getMethod("refresh");
+      refreshMethod.invoke(null);
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  /** Resets Hadoop KerberosName default realm after JVM krb5 properties 
change. */
+  public static void resetDefaultRealm() {
+    try {
+      String kerberosNameClass = 
"org.apache.hadoop.security.authentication.util.KerberosName";
+      Class<?> cl = Class.forName(kerberosNameClass);
+      cl.getMethod("resetDefaultRealm").invoke(null);
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  private static Map<String, String> saveSystemProperties(String... keys) {
+    Map<String, String> saved = new HashMap<>();
+    for (String key : keys) {
+      saved.put(key, System.getProperty(key));
+    }
+    return saved;
+  }
+
+  private static void restoreSystemProperties(Map<String, String> saved) {
+    saved.forEach(
+        (key, value) -> {
+          if (value == null) {
+            System.clearProperty(key);
+          } else {
+            System.setProperty(key, value);
+          }
+        });
+  }
+}
diff --git 
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/util/IcebergRESTServerManagerForDeploy.java
 
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/util/IcebergRESTServerManagerForDeploy.java
index c283a19d2a..57d4d7ecd2 100644
--- 
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/util/IcebergRESTServerManagerForDeploy.java
+++ 
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/util/IcebergRESTServerManagerForDeploy.java
@@ -23,6 +23,7 @@ import java.io.File;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.Map;
 import java.util.Optional;
 import java.util.UUID;
 import java.util.concurrent.Future;
@@ -35,10 +36,16 @@ import 
org.apache.gravitino.integration.test.util.ProcessData.TypesOfData;
 public class IcebergRESTServerManagerForDeploy extends 
IcebergRESTServerManager {
 
   private static final String SCRIPT_NAME = "gravitino-iceberg-rest-server.sh";
-  private Path icebergRESTServerHome;
+  private static final String KRB5_CONF_PLACEHOLDER =
+      "#JAVA_OPTS+=\" -Djava.security.krb5.conf=/etc/krb5.conf\"";
+  private static final String KRB5_CONF_PROPERTY = "java.security.krb5.conf";
+  private static final String KRB5_REALM_PROPERTY = "java.security.krb5.realm";
+  private static final String KRB5_KDC_PROPERTY = "java.security.krb5.kdc";
   private static final String SQLITE_DRIVER_DOWNLOAD_URL =
       
"https://repo1.maven.org/maven2/org/xerial/sqlite-jdbc/3.42.0.0/sqlite-jdbc-3.42.0.0.jar";;
 
+  private final Path icebergRESTServerHome;
+
   public IcebergRESTServerManagerForDeploy() {
     String gravitinoRootDir = System.getenv("GRAVITINO_ROOT_DIR");
     this.icebergRESTServerHome = Paths.get(gravitinoRootDir, "distribution", 
"package");
@@ -56,48 +63,64 @@ public class IcebergRESTServerManagerForDeploy extends 
IcebergRESTServerManager
         Paths.get(icebergRESTServerHome.toString(), "iceberg-rest-server", 
"libs").toString());
 
     String gravitinoRestStartShell = icebergRESTServerHome.toString() + 
"/bin/" + SCRIPT_NAME;
-    String krb5Path = System.getProperty("java.security.krb5.conf");
+    String startShell = gravitinoRestStartShell;
+    String krb5Path = System.getProperty(KRB5_CONF_PROPERTY);
     if (krb5Path != null) {
       LOG.info("java.security.krb5.conf: {}", krb5Path);
-      String modifiedGravitinoStartShell =
-          String.format(
-              "%s/bin/gravitino-iceberg-rest-server_%s.sh",
-              icebergRESTServerHome.toString(), UUID.randomUUID());
-      // Replace '/etc/krb5.conf' with the one in the test
-      try {
-        String content =
-            FileUtils.readFileToString(new File(gravitinoRestStartShell), 
StandardCharsets.UTF_8);
-        content =
-            content.replace(
-                "#JAVA_OPTS+=\" -Djava.security.krb5.conf=/etc/krb5.conf\"",
-                String.format("JAVA_OPTS+=\" -Djava.security.krb5.conf=%s\"", 
krb5Path));
-        File tmp = new File(modifiedGravitinoStartShell);
-        FileUtils.write(tmp, content, StandardCharsets.UTF_8);
-        tmp.setExecutable(true);
-        LOG.info("modifiedGravitinoStartShell content: \n{}", content);
-        CommandExecutor.executeCommandLocalHost(
-            modifiedGravitinoStartShell + " start", false, 
ProcessData.TypesOfData.OUTPUT);
-      } catch (Exception e) {
-        LOG.error("Can replace /etc/krb5.conf with real kerberos 
configuration", e);
-      }
-    } else {
-      String cmd = String.format("%s/bin/%s start", 
icebergRESTServerHome.toString(), SCRIPT_NAME);
-      CommandExecutor.executeCommandLocalHost(
-          cmd,
-          false,
-          ProcessData.TypesOfData.OUTPUT,
-          ImmutableMap.of("GRAVITINO_HOME", icebergRESTServerHome.toString()));
+      startShell = prepareKerberosStartShell(gravitinoRestStartShell, 
krb5Path);
     }
+
+    CommandExecutor.executeCommandLocalHost(
+        startShell + " start", false, ProcessData.TypesOfData.OUTPUT, 
deployEnvironment());
     return Optional.empty();
   }
 
   @Override
   public void doStopIcebergRESTServer() {
     String cmd = String.format("%s/bin/%s stop", 
icebergRESTServerHome.toString(), SCRIPT_NAME);
-    CommandExecutor.executeCommandLocalHost(
-        cmd,
-        false,
-        TypesOfData.ERROR,
-        ImmutableMap.of("GRAVITINO_HOME", icebergRESTServerHome.toString()));
+    CommandExecutor.executeCommandLocalHost(cmd, false, TypesOfData.ERROR, 
deployEnvironment());
+  }
+
+  private Map<String, String> deployEnvironment() {
+    return ImmutableMap.of("GRAVITINO_HOME", icebergRESTServerHome.toString());
+  }
+
+  private String prepareKerberosStartShell(String gravitinoRestStartShell, 
String krb5Path)
+      throws Exception {
+    String modifiedGravitinoStartShell =
+        String.format(
+            "%s/bin/gravitino-iceberg-rest-server_%s.sh",
+            icebergRESTServerHome.toString(), UUID.randomUUID());
+    String content =
+        FileUtils.readFileToString(new File(gravitinoRestStartShell), 
StandardCharsets.UTF_8);
+    if (!content.contains(KRB5_CONF_PLACEHOLDER)) {
+      throw new IllegalStateException(
+          String.format(
+              "Failed to patch Kerberos options in %s: missing placeholder %s",
+              gravitinoRestStartShell, KRB5_CONF_PLACEHOLDER));
+    }
+    content =
+        content.replace(
+            KRB5_CONF_PLACEHOLDER,
+            String.format("JAVA_OPTS+=\"%s\"", 
buildKerberosJavaOpts(krb5Path)));
+    File tmp = new File(modifiedGravitinoStartShell);
+    FileUtils.write(tmp, content, StandardCharsets.UTF_8);
+    tmp.setExecutable(true);
+    LOG.info("modifiedGravitinoStartShell content: \n{}", content);
+    return modifiedGravitinoStartShell;
+  }
+
+  private String buildKerberosJavaOpts(String krb5Path) {
+    StringBuilder opts = new StringBuilder();
+    opts.append(String.format(" -Djava.security.krb5.conf=%s", krb5Path));
+    String realm = System.getProperty(KRB5_REALM_PROPERTY);
+    if (realm != null) {
+      opts.append(String.format(" -Djava.security.krb5.realm=%s", realm));
+    }
+    String kdc = System.getProperty(KRB5_KDC_PROPERTY);
+    if (kdc != null) {
+      opts.append(String.format(" -Djava.security.krb5.kdc=%s", kdc));
+    }
+    return opts.toString();
   }
 }

Reply via email to