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

roryqi 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 a2daf9506f [#8674] fix(authz): Isolate Ranger catalog owner roles by 
catalog ID (#12517)
a2daf9506f is described below

commit a2daf9506f14d5807e40941c60b9a69d8de2cee0
Author: roryqi <[email protected]>
AuthorDate: Fri Aug 21 12:02:37 2026 +0800

    [#8674] fix(authz): Isolate Ranger catalog owner roles by catalog ID 
(#12517)
    
    ### What changes were proposed in this pull request?
    
    Pass the stable catalog entity ID to authorization plugins and use it to
    create catalog-specific Ranger owner roles.
    
    Catalog owner roles now follow this format:
    
    `GRAVITINO_CATALOG_OWNER_ROLE_<catalog_id>`
    
    The chained authorization plugin also propagates the catalog ID to its
    child plugins.
    
    ### Why are the changes needed?
    
    Ranger previously used one shared `GRAVITINO_CATALOG_OWNER_ROLE` for
    every catalog. As a result, the owner of one catalog could receive owner
    permissions on other catalogs using the same Ranger service.
    
    Using the stable catalog ID isolates role membership without depending
    on the mutable catalog name.
    
    Fix: #8674
    
    ### Does this PR introduce _any_ user-facing change?
    
    Yes. Ranger creates a separate catalog owner role for each catalog using
    the catalog ID as the role-name suffix.
    
    No user-configurable property is added.
    
    ### How was this patch tested?
    
    - Added unit tests for passing catalog IDs to authorization plugins.
    - Added unit tests for generating and creating catalog-specific Ranger
    owner roles.
    - Updated the Ranger integration test expectation.
    - Ran Spotless successfully.
---
 .../chain/ChainedAuthorizationPlugin.java          |   4 +
 .../ranger/RangerAuthorizationPlugin.java          |  50 ++++---
 .../authorization/ranger/RangerHelper.java         |  58 +++++++--
 .../authorization/ranger/TestRangerHelper.java     | 116 +++++++++++++++++
 .../ranger/integration/test/RangerHiveIT.java      | 143 ++++++++++++++++++++-
 .../ranger/integration/test/RangerITEnv.java       |  15 ++-
 .../apache/gravitino/catalog/CatalogManager.java   |  15 ++-
 .../apache/gravitino/connector/BaseCatalog.java    |  14 +-
 .../connector/authorization/BaseAuthorization.java |   6 +
 .../connector/authorization/TestAuthorization.java |  10 +-
 .../ranger/TestRangerAuthorization.java            |  15 +++
 docs/security/authorization-pushdown.md            |  47 ++++++-
 12 files changed, 457 insertions(+), 36 deletions(-)

diff --git 
a/authorizations/authorization-chain/src/main/java/org/apache/gravitino/authorization/chain/ChainedAuthorizationPlugin.java
 
b/authorizations/authorization-chain/src/main/java/org/apache/gravitino/authorization/chain/ChainedAuthorizationPlugin.java
index 1ff842616e..a9a5e0724f 100644
--- 
a/authorizations/authorization-chain/src/main/java/org/apache/gravitino/authorization/chain/ChainedAuthorizationPlugin.java
+++ 
b/authorizations/authorization-chain/src/main/java/org/apache/gravitino/authorization/chain/ChainedAuthorizationPlugin.java
@@ -62,6 +62,10 @@ public class ChainedAuthorizationPlugin implements 
AuthorizationPlugin {
               String authzProvider = 
chainedAuthzProperties.getPluginProvider(pluginName);
               Map<String, String> pluginConfig =
                   chainedAuthzProperties.fetchAuthPluginProperties(pluginName);
+              pluginConfig.put(
+                  BaseAuthorization.METALAKE_ID, 
properties.get(BaseAuthorization.METALAKE_ID));
+              pluginConfig.put(
+                  BaseAuthorization.CATALOG_ID, 
properties.get(BaseAuthorization.CATALOG_ID));
 
               ArrayList<String> libAndResourcesPaths = Lists.newArrayList();
               BaseAuthorization.buildAuthorizationPkgPath(
diff --git 
a/authorizations/authorization-ranger/src/main/java/org/apache/gravitino/authorization/ranger/RangerAuthorizationPlugin.java
 
b/authorizations/authorization-ranger/src/main/java/org/apache/gravitino/authorization/ranger/RangerAuthorizationPlugin.java
index 37ac3487f3..bdb63ba7de 100644
--- 
a/authorizations/authorization-ranger/src/main/java/org/apache/gravitino/authorization/ranger/RangerAuthorizationPlugin.java
+++ 
b/authorizations/authorization-ranger/src/main/java/org/apache/gravitino/authorization/ranger/RangerAuthorizationPlugin.java
@@ -53,6 +53,7 @@ import 
org.apache.gravitino.authorization.ranger.reference.VXGroupList;
 import org.apache.gravitino.authorization.ranger.reference.VXUser;
 import org.apache.gravitino.authorization.ranger.reference.VXUserList;
 import org.apache.gravitino.connector.authorization.AuthorizationPlugin;
+import org.apache.gravitino.connector.authorization.BaseAuthorization;
 import org.apache.gravitino.exceptions.AuthorizationPluginException;
 import org.apache.gravitino.meta.AuditInfo;
 import org.apache.gravitino.meta.GroupEntity;
@@ -84,6 +85,13 @@ public abstract class RangerAuthorizationPlugin
 
   protected String metalake;
   protected final String rangerServiceName;
+
+  /** The stable entity ID of the metalake containing this catalog. */
+  protected final String metalakeId;
+
+  /** The stable entity ID of this catalog. */
+  protected final String catalogId;
+
   protected RangerClientExtension rangerClient;
   protected RangerHelper rangerHelper;
   @VisibleForTesting public final String rangerAdminName;
@@ -103,6 +111,10 @@ public abstract class RangerAuthorizationPlugin
     String password = 
config.get(RangerAuthorizationProperties.RANGER_PASSWORD);
 
     rangerServiceName = 
config.get(RangerAuthorizationProperties.RANGER_SERVICE_NAME);
+    metalakeId = config.get(BaseAuthorization.METALAKE_ID);
+    Preconditions.checkArgument(metalakeId != null, "Metalake ID is required");
+    catalogId = config.get(BaseAuthorization.CATALOG_ID);
+    Preconditions.checkArgument(catalogId != null, "Catalog ID is required");
     rangerClient = new RangerClientExtension(rangerUrl, authType, 
rangerAdminName, password);
 
     createRangerServiceIfNecessary(config, rangerServiceName);
@@ -518,33 +530,41 @@ public abstract class RangerAuthorizationPlugin
 
     List<AuthorizationSecurableObject> rangerSecurableObjects = 
translateOwner(metadataObject);
     String ownerRoleName;
+    String legacyOwnerRoleName;
     switch (metadataObject.type()) {
       case METALAKE:
       case CATALOG:
         // The metalake and catalog use role to manage the owner
         if (metadataObject.type() == MetadataObject.Type.METALAKE) {
-          ownerRoleName = RangerHelper.GRAVITINO_METALAKE_OWNER_ROLE;
+          ownerRoleName = 
RangerHelper.generateMetalakeOwnerRoleName(metalakeId);
+          legacyOwnerRoleName = RangerHelper.GRAVITINO_METALAKE_OWNER_ROLE;
         } else {
-          ownerRoleName = RangerHelper.GRAVITINO_CATALOG_OWNER_ROLE;
+          ownerRoleName = RangerHelper.generateCatalogOwnerRoleName(catalogId);
+          legacyOwnerRoleName = RangerHelper.GRAVITINO_CATALOG_OWNER_ROLE;
         }
         rangerHelper.createRangerRoleIfNotExists(ownerRoleName, true);
         
rangerHelper.createRangerRoleIfNotExists(RangerHelper.GRAVITINO_OWNER_ROLE, 
true);
-        try {
-          if (preOwnerUserName != null || preOwnerGroupName != null) {
-            GrantRevokeRoleRequest revokeRoleRequest =
-                rangerHelper.createGrantRevokeRoleRequest(
-                    ownerRoleName, preOwnerUserName, preOwnerGroupName);
+        if (preOwnerUserName != null || preOwnerGroupName != null) {
+          GrantRevokeRoleRequest revokeRoleRequest =
+              rangerHelper.createGrantRevokeRoleRequest(
+                  ownerRoleName, preOwnerUserName, preOwnerGroupName);
+          try {
             rangerClient.revokeRole(rangerServiceName, revokeRoleRequest);
+          } catch (RangerServiceException e) {
+            // Ignore exception, support idempotent operation
+            LOG.warn("Revoke owner role: {} failed!", ownerRoleName, e);
           }
-          if (newOwnerUserName != null || newOwnerGroupName != null) {
-            GrantRevokeRoleRequest grantRoleRequest =
-                rangerHelper.createGrantRevokeRoleRequest(
-                    ownerRoleName, newOwnerUserName, newOwnerGroupName);
+        }
+        if (newOwnerUserName != null || newOwnerGroupName != null) {
+          GrantRevokeRoleRequest grantRoleRequest =
+              rangerHelper.createGrantRevokeRoleRequest(
+                  ownerRoleName, newOwnerUserName, newOwnerGroupName);
+          try {
             rangerClient.grantRole(rangerServiceName, grantRoleRequest);
+          } catch (RangerServiceException e) {
+            // Ignore exception, support idempotent operation
+            LOG.warn("Grant owner role: {} failed!", ownerRoleName, e);
           }
-        } catch (RangerServiceException e) {
-          // Ignore exception, support idempotent operation
-          LOG.warn("Grant owner role: {} failed!", ownerRoleName, e);
         }
 
         rangerSecurableObjects.forEach(
@@ -555,7 +575,7 @@ public abstract class RangerAuthorizationPlugin
                   policy = addOwnerRoleToNewPolicy(rangerSecurableObject, 
ownerRoleName);
                   rangerClient.createPolicy(policy);
                 } else {
-                  rangerHelper.updatePolicyOwnerRole(policy, ownerRoleName);
+                  rangerHelper.updatePolicyOwnerRole(policy, ownerRoleName, 
legacyOwnerRoleName);
                   rangerClient.updatePolicy(policy.getId(), policy);
                 }
               } catch (RangerServiceException e) {
diff --git 
a/authorizations/authorization-ranger/src/main/java/org/apache/gravitino/authorization/ranger/RangerHelper.java
 
b/authorizations/authorization-ranger/src/main/java/org/apache/gravitino/authorization/ranger/RangerHelper.java
index 3c7517fe18..453f3dc489 100644
--- 
a/authorizations/authorization-ranger/src/main/java/org/apache/gravitino/authorization/ranger/RangerHelper.java
+++ 
b/authorizations/authorization-ranger/src/main/java/org/apache/gravitino/authorization/ranger/RangerHelper.java
@@ -228,6 +228,26 @@ public class RangerHelper {
     return GRAVITINO_ROLE_PREFIX + roleName;
   }
 
+  /**
+   * Generate the Ranger owner role name for a metalake.
+   *
+   * @param metalakeId The stable metalake entity ID
+   * @return The metalake-specific Ranger owner role name
+   */
+  public static String generateMetalakeOwnerRoleName(String metalakeId) {
+    return GRAVITINO_METALAKE_OWNER_ROLE + "_" + metalakeId;
+  }
+
+  /**
+   * Generate the Ranger owner role name for a catalog.
+   *
+   * @param catalogId The stable catalog entity ID
+   * @return The catalog-specific Ranger owner role name
+   */
+  public static String generateCatalogOwnerRoleName(String catalogId) {
+    return GRAVITINO_CATALOG_OWNER_ROLE + "_" + catalogId;
+  }
+
   protected GrantRevokeRoleRequest createGrantRevokeRoleRequest(
       String roleName, String userName, String groupName) {
     roleName = generateGravitinoRoleName(roleName);
@@ -259,19 +279,19 @@ public class RangerHelper {
     roleName = generateGravitinoRoleName(roleName);
     if (isOwnerRole) {
       Preconditions.checkArgument(
-          roleName.equalsIgnoreCase(GRAVITINO_METALAKE_OWNER_ROLE)
-              || roleName.equalsIgnoreCase(GRAVITINO_CATALOG_OWNER_ROLE)
+          isMetalakeOwnerRole(roleName)
+              || isCatalogOwnerRole(roleName)
               || roleName.equalsIgnoreCase(GRAVITINO_OWNER_ROLE),
           String.format(
-              "The role name should be %s or %s or %s",
+              "The role name should start with %s_ or %s_, or be %s",
               GRAVITINO_METALAKE_OWNER_ROLE, GRAVITINO_CATALOG_OWNER_ROLE, 
GRAVITINO_OWNER_ROLE));
     } else {
       Preconditions.checkArgument(
-          !roleName.equalsIgnoreCase(GRAVITINO_METALAKE_OWNER_ROLE)
-              && !roleName.equalsIgnoreCase(GRAVITINO_CATALOG_OWNER_ROLE)
+          !isMetalakeOwnerRole(roleName)
+              && !isCatalogOwnerRole(roleName)
               && !roleName.equalsIgnoreCase(GRAVITINO_OWNER_ROLE),
           String.format(
-              "The role name should not be %s or %s or %s",
+              "The role name should not start with %s_ or %s_, or be %s",
               GRAVITINO_METALAKE_OWNER_ROLE, GRAVITINO_CATALOG_OWNER_ROLE, 
GRAVITINO_OWNER_ROLE));
     }
 
@@ -384,7 +404,8 @@ public class RangerHelper {
             });
   }
 
-  protected void updatePolicyOwnerRole(RangerPolicy policy, String 
ownerRoleName) {
+  protected void updatePolicyOwnerRole(
+      RangerPolicy policy, String ownerRoleName, String legacyOwnerRoleName) {
     // Find matching policy items based on the owner's privileges
     List<RangerPolicy.RangerPolicyItem> matchPolicyItems =
         policy.getPolicyItems().stream()
@@ -401,9 +422,10 @@ public class RangerHelper {
                           });
                 })
             .collect(Collectors.toList());
-    // Add or remove the owner role in the policy item
+    // Replace the legacy shared owner role with the entity-specific owner 
role.
     matchPolicyItems.forEach(
         policyItem -> {
+          
policyItem.getRoles().removeIf(legacyOwnerRoleName::equalsIgnoreCase);
           addRoleToPolicyItemIfNoExists(policyItem, ownerRoleName);
         });
 
@@ -440,4 +462,24 @@ public class RangerHelper {
       policyItem.getRoles().add(gravitinoRoleName);
     }
   }
+
+  private static boolean isMetalakeOwnerRole(String roleName) {
+    return roleName.equalsIgnoreCase(GRAVITINO_METALAKE_OWNER_ROLE)
+        || roleName.regionMatches(
+            true,
+            0,
+            GRAVITINO_METALAKE_OWNER_ROLE + "_",
+            0,
+            GRAVITINO_METALAKE_OWNER_ROLE.length() + 1);
+  }
+
+  private static boolean isCatalogOwnerRole(String roleName) {
+    return roleName.equalsIgnoreCase(GRAVITINO_CATALOG_OWNER_ROLE)
+        || roleName.regionMatches(
+            true,
+            0,
+            GRAVITINO_CATALOG_OWNER_ROLE + "_",
+            0,
+            GRAVITINO_CATALOG_OWNER_ROLE.length() + 1);
+  }
 }
diff --git 
a/authorizations/authorization-ranger/src/test/java/org/apache/gravitino/authorization/ranger/TestRangerHelper.java
 
b/authorizations/authorization-ranger/src/test/java/org/apache/gravitino/authorization/ranger/TestRangerHelper.java
new file mode 100644
index 0000000000..09c985d3af
--- /dev/null
+++ 
b/authorizations/authorization-ranger/src/test/java/org/apache/gravitino/authorization/ranger/TestRangerHelper.java
@@ -0,0 +1,116 @@
+/*
+ * 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.authorization.ranger;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import java.util.Collections;
+import org.apache.ranger.RangerClient;
+import org.apache.ranger.plugin.model.RangerPolicy;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+public class TestRangerHelper {
+
+  @Test
+  public void testGenerateMetalakeOwnerRoleName() {
+    Assertions.assertEquals(
+        "GRAVITINO_METALAKE_OWNER_ROLE_1001", 
RangerHelper.generateMetalakeOwnerRoleName("1001"));
+    Assertions.assertNotEquals(
+        RangerHelper.generateMetalakeOwnerRoleName("1001"),
+        RangerHelper.generateMetalakeOwnerRoleName("1002"));
+  }
+
+  @Test
+  public void testGenerateCatalogOwnerRoleName() {
+    Assertions.assertEquals(
+        "GRAVITINO_CATALOG_OWNER_ROLE_1001", 
RangerHelper.generateCatalogOwnerRoleName("1001"));
+    Assertions.assertNotEquals(
+        RangerHelper.generateCatalogOwnerRoleName("1001"),
+        RangerHelper.generateCatalogOwnerRoleName("1002"));
+  }
+
+  @Test
+  public void testCreateCatalogSpecificOwnerRole() {
+    RangerHelper rangerHelper =
+        new RangerHelper(
+            Mockito.mock(RangerClient.class),
+            "admin",
+            "service",
+            ImmutableSet.of(),
+            ImmutableList.of());
+
+    Assertions.assertDoesNotThrow(
+        () ->
+            rangerHelper.createRangerRoleIfNotExists(
+                RangerHelper.generateMetalakeOwnerRoleName("1001"), true));
+    Assertions.assertDoesNotThrow(
+        () ->
+            rangerHelper.createRangerRoleIfNotExists(
+                RangerHelper.generateCatalogOwnerRoleName("1001"), true));
+  }
+
+  @Test
+  public void testMigrateLegacyOwnerRoleForTwoCatalogPolicies() {
+    RangerHelper rangerHelper =
+        new RangerHelper(
+            Mockito.mock(RangerClient.class),
+            "admin",
+            "service",
+            ImmutableSet.of(RangerPrivileges.RangerHadoopSQLPrivilege.SELECT),
+            ImmutableList.of());
+    RangerPolicy firstCatalogPolicy = createLegacyCatalogOwnerPolicy();
+    RangerPolicy secondCatalogPolicy = createLegacyCatalogOwnerPolicy();
+
+    String firstOwnerRole = RangerHelper.generateCatalogOwnerRoleName("1001");
+    String secondOwnerRole = RangerHelper.generateCatalogOwnerRoleName("1002");
+    rangerHelper.updatePolicyOwnerRole(
+        firstCatalogPolicy, firstOwnerRole, 
RangerHelper.GRAVITINO_CATALOG_OWNER_ROLE);
+    rangerHelper.updatePolicyOwnerRole(
+        secondCatalogPolicy, secondOwnerRole, 
RangerHelper.GRAVITINO_CATALOG_OWNER_ROLE);
+
+    Assertions.assertEquals(
+        Collections.singletonList(firstOwnerRole),
+        firstCatalogPolicy.getPolicyItems().get(0).getRoles());
+    Assertions.assertEquals(
+        Collections.singletonList(secondOwnerRole),
+        secondCatalogPolicy.getPolicyItems().get(0).getRoles());
+
+    // Reconciliation can be repeated safely.
+    rangerHelper.updatePolicyOwnerRole(
+        firstCatalogPolicy, firstOwnerRole, 
RangerHelper.GRAVITINO_CATALOG_OWNER_ROLE);
+    Assertions.assertEquals(
+        Collections.singletonList(firstOwnerRole),
+        firstCatalogPolicy.getPolicyItems().get(0).getRoles());
+  }
+
+  private RangerPolicy createLegacyCatalogOwnerPolicy() {
+    RangerPolicy policy = new RangerPolicy();
+    RangerPolicy.RangerPolicyItem policyItem = new 
RangerPolicy.RangerPolicyItem();
+    policyItem
+        .getAccesses()
+        .add(
+            new RangerPolicy.RangerPolicyItemAccess(
+                RangerPrivileges.RangerHadoopSQLPrivilege.SELECT.getName()));
+    policyItem.getRoles().add(RangerHelper.GRAVITINO_CATALOG_OWNER_ROLE);
+    policy.getPolicyItems().add(policyItem);
+    return policy;
+  }
+}
diff --git 
a/authorizations/authorization-ranger/src/test/java/org/apache/gravitino/authorization/ranger/integration/test/RangerHiveIT.java
 
b/authorizations/authorization-ranger/src/test/java/org/apache/gravitino/authorization/ranger/integration/test/RangerHiveIT.java
index 5c159c8f3d..5828d6cbbc 100644
--- 
a/authorizations/authorization-ranger/src/test/java/org/apache/gravitino/authorization/ranger/integration/test/RangerHiveIT.java
+++ 
b/authorizations/authorization-ranger/src/test/java/org/apache/gravitino/authorization/ranger/integration/test/RangerHiveIT.java
@@ -26,6 +26,7 @@ import static org.mockito.Mockito.when;
 
 import com.google.common.base.Joiner;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
@@ -50,6 +51,7 @@ import org.apache.gravitino.authorization.Role;
 import org.apache.gravitino.authorization.RoleChange;
 import org.apache.gravitino.authorization.SecurableObject;
 import org.apache.gravitino.authorization.SecurableObjects;
+import org.apache.gravitino.authorization.common.RangerAuthorizationProperties;
 import 
org.apache.gravitino.authorization.ranger.RangerAuthorizationHadoopSQLPlugin;
 import org.apache.gravitino.authorization.ranger.RangerClientExtension;
 import org.apache.gravitino.authorization.ranger.RangerHadoopSQLMetadataObject;
@@ -58,7 +60,9 @@ import org.apache.gravitino.authorization.ranger.RangerHelper;
 import org.apache.gravitino.authorization.ranger.RangerPrivileges;
 import org.apache.gravitino.authorization.ranger.reference.RangerDefines;
 import org.apache.gravitino.authorization.ranger.reference.VXUserList;
+import org.apache.gravitino.connector.authorization.BaseAuthorization;
 import org.apache.gravitino.exceptions.AuthorizationPluginException;
+import org.apache.gravitino.integration.test.container.RangerContainer;
 import org.apache.gravitino.integration.test.util.GravitinoITUtils;
 import org.apache.gravitino.meta.AuditInfo;
 import org.apache.gravitino.meta.GroupEntity;
@@ -67,6 +71,7 @@ import org.apache.gravitino.meta.UserEntity;
 import org.apache.ranger.RangerServiceException;
 import org.apache.ranger.plugin.model.RangerPolicy;
 import org.apache.ranger.plugin.model.RangerRole;
+import org.apache.ranger.plugin.util.GrantRevokeRoleRequest;
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Assertions;
@@ -1473,7 +1478,8 @@ public class RangerHiveIT {
                   null,
                   null,
                   null,
-                  
Lists.newArrayList(RangerHelper.GRAVITINO_METALAKE_OWNER_ROLE));
+                  Lists.newArrayList(
+                      
RangerHelper.generateMetalakeOwnerRoleName(RangerITEnv.METALAKE_ID)));
             });
 
     MetadataObject catalog =
@@ -1492,11 +1498,62 @@ public class RangerHiveIT {
                   null,
                   null,
                   Lists.newArrayList(
-                      RangerHelper.GRAVITINO_METALAKE_OWNER_ROLE,
-                      RangerHelper.GRAVITINO_CATALOG_OWNER_ROLE));
+                      
RangerHelper.generateMetalakeOwnerRoleName(RangerITEnv.METALAKE_ID),
+                      
RangerHelper.generateCatalogOwnerRoleName(RangerITEnv.CATALOG_ID)));
             });
   }
 
+  @Test
+  public void testMigrateLegacyOwnerRolesForCatalogsSharingRangerService()
+      throws RangerServiceException {
+    String suffix = GravitinoITUtils.genRandomName(currentFunName());
+    String firstCatalogId = Long.toString(System.nanoTime());
+    String secondCatalogId = firstCatalogId + "2";
+    RangerAuthorizationHadoopSQLPlugin firstPlugin = 
createRangerPlugin(firstCatalogId);
+    RangerAuthorizationHadoopSQLPlugin secondPlugin = 
createRangerPlugin(secondCatalogId);
+    MetadataObject firstCatalog =
+        MetadataObjects.parse("catalog-first-" + suffix, 
MetadataObject.Type.CATALOG);
+    MetadataObject secondCatalog =
+        MetadataObjects.parse("catalog-second-" + suffix, 
MetadataObject.Type.CATALOG);
+    Owner firstOwner = new MockOwner("owner-first-" + suffix, Owner.Type.USER);
+    Owner secondOwner = new MockOwner("owner-second-" + suffix, 
Owner.Type.USER);
+    String firstOwnerRole = 
RangerHelper.generateCatalogOwnerRoleName(firstCatalogId);
+    String secondOwnerRole = 
RangerHelper.generateCatalogOwnerRoleName(secondCatalogId);
+    String legacyOwnerRole = RangerHelper.GRAVITINO_CATALOG_OWNER_ROLE;
+
+    Assertions.assertTrue(firstPlugin.onOwnerSet(firstCatalog, null, 
firstOwner));
+    Assertions.assertTrue(secondPlugin.onOwnerSet(secondCatalog, null, 
secondOwner));
+
+    if (rangerHelper.getRangerRole(legacyOwnerRole) == null) {
+      rangerClient.createRole(
+          RangerITEnv.RANGER_HIVE_REPO_NAME,
+          new RangerRole(legacyOwnerRole, RangerHelper.MANAGED_BY_GRAVITINO, 
null, null, null));
+    }
+    GrantRevokeRoleRequest legacyOwnerGrant = new GrantRevokeRoleRequest();
+    legacyOwnerGrant.setUsers(Sets.newHashSet(firstOwner.name(), 
secondOwner.name()));
+    legacyOwnerGrant.setGroups(Collections.emptySet());
+    legacyOwnerGrant.setGrantor(firstPlugin.rangerAdminName);
+    legacyOwnerGrant.setTargetRoles(Sets.newHashSet(legacyOwnerRole));
+    rangerClient.grantRole(RangerITEnv.RANGER_HIVE_REPO_NAME, 
legacyOwnerGrant);
+
+    replacePolicyOwnerRole(firstPlugin, firstCatalog, firstOwnerRole, 
legacyOwnerRole);
+    replacePolicyOwnerRole(secondPlugin, secondCatalog, secondOwnerRole, 
legacyOwnerRole);
+    rangerClient.deleteRole(
+        firstOwnerRole, firstPlugin.rangerAdminName, 
RangerITEnv.RANGER_HIVE_REPO_NAME);
+    rangerClient.deleteRole(
+        secondOwnerRole, secondPlugin.rangerAdminName, 
RangerITEnv.RANGER_HIVE_REPO_NAME);
+
+    Assertions.assertTrue(firstPlugin.onOwnerSet(firstCatalog, firstOwner, 
firstOwner));
+    Assertions.assertTrue(secondPlugin.onOwnerSet(secondCatalog, secondOwner, 
secondOwner));
+    // Repeat reconciliation to verify idempotency.
+    Assertions.assertTrue(firstPlugin.onOwnerSet(firstCatalog, firstOwner, 
firstOwner));
+
+    verifyOwnerRole(firstPlugin, firstOwnerRole, firstOwner.name(), 
secondOwner.name());
+    verifyOwnerRole(secondPlugin, secondOwnerRole, secondOwner.name(), 
firstOwner.name());
+    verifyPolicyOwnerRole(firstPlugin, firstCatalog, firstOwnerRole, 
legacyOwnerRole);
+    verifyPolicyOwnerRole(secondPlugin, secondCatalog, secondOwnerRole, 
legacyOwnerRole);
+  }
+
   @Test
   public void testCreateUser() {
     UserEntity user =
@@ -2097,4 +2154,84 @@ public class RangerHiveIT {
         includeRoles,
         null);
   }
+
+  private RangerAuthorizationHadoopSQLPlugin createRangerPlugin(String 
catalogId) {
+    return new RangerAuthorizationHadoopSQLPlugin(
+        "metalake-" + currentFunName(),
+        ImmutableMap.of(
+            RangerAuthorizationProperties.RANGER_ADMIN_URL,
+            RangerITEnv.RANGER_ADMIN_URL,
+            RangerAuthorizationProperties.RANGER_AUTH_TYPE,
+            RangerContainer.authType,
+            RangerAuthorizationProperties.RANGER_USERNAME,
+            RangerContainer.rangerUserName,
+            RangerAuthorizationProperties.RANGER_PASSWORD,
+            RangerContainer.rangerPassword,
+            RangerAuthorizationProperties.RANGER_SERVICE_TYPE,
+            "HadoopSQL",
+            RangerAuthorizationProperties.RANGER_SERVICE_NAME,
+            RangerITEnv.RANGER_HIVE_REPO_NAME,
+            RangerAuthorizationProperties.RANGER_SERVICE_CREATE_IF_ABSENT,
+            "true",
+            BaseAuthorization.METALAKE_ID,
+            RangerITEnv.METALAKE_ID,
+            BaseAuthorization.CATALOG_ID,
+            catalogId));
+  }
+
+  private void replacePolicyOwnerRole(
+      RangerAuthorizationHadoopSQLPlugin plugin,
+      MetadataObject catalog,
+      String currentOwnerRole,
+      String legacyOwnerRole)
+      throws RangerServiceException {
+    for (AuthorizationSecurableObject securableObject : 
plugin.translateOwner(catalog)) {
+      RangerPolicy policy = plugin.findManagedPolicy(securableObject);
+      Assertions.assertNotNull(policy);
+      boolean replaced = false;
+      for (RangerPolicy.RangerPolicyItem policyItem : policy.getPolicyItems()) 
{
+        if (policyItem.getRoles().remove(currentOwnerRole)) {
+          if (!policyItem.getRoles().contains(legacyOwnerRole)) {
+            policyItem.getRoles().add(legacyOwnerRole);
+          }
+          replaced = true;
+        }
+      }
+      Assertions.assertTrue(replaced);
+      rangerClient.updatePolicy(policy.getId(), policy);
+    }
+  }
+
+  private void verifyPolicyOwnerRole(
+      RangerAuthorizationHadoopSQLPlugin plugin,
+      MetadataObject catalog,
+      String expectedOwnerRole,
+      String legacyOwnerRole) {
+    for (AuthorizationSecurableObject securableObject : 
plugin.translateOwner(catalog)) {
+      RangerPolicy policy = plugin.findManagedPolicy(securableObject);
+      Assertions.assertNotNull(policy);
+      boolean expectedRoleFound = false;
+      for (RangerPolicy.RangerPolicyItem policyItem : policy.getPolicyItems()) 
{
+        if (policyItem.getRoles().contains(expectedOwnerRole)) {
+          
Assertions.assertFalse(policyItem.getRoles().contains(legacyOwnerRole));
+          expectedRoleFound = true;
+        }
+      }
+      Assertions.assertTrue(expectedRoleFound);
+    }
+  }
+
+  private void verifyOwnerRole(
+      RangerAuthorizationHadoopSQLPlugin plugin,
+      String roleName,
+      String expectedOwner,
+      String unexpectedOwner)
+      throws RangerServiceException {
+    RangerRole role =
+        rangerClient.getRole(roleName, plugin.rangerAdminName, 
RangerITEnv.RANGER_HIVE_REPO_NAME);
+    List<String> users =
+        role.getUsers().stream().map(user -> 
user.getName()).collect(Collectors.toList());
+    Assertions.assertTrue(users.contains(expectedOwner));
+    Assertions.assertFalse(users.contains(unexpectedOwner));
+  }
 }
diff --git 
a/authorizations/authorization-ranger/src/test/java/org/apache/gravitino/authorization/ranger/integration/test/RangerITEnv.java
 
b/authorizations/authorization-ranger/src/test/java/org/apache/gravitino/authorization/ranger/integration/test/RangerITEnv.java
index 87f579dc44..997ba63aca 100644
--- 
a/authorizations/authorization-ranger/src/test/java/org/apache/gravitino/authorization/ranger/integration/test/RangerITEnv.java
+++ 
b/authorizations/authorization-ranger/src/test/java/org/apache/gravitino/authorization/ranger/integration/test/RangerITEnv.java
@@ -39,6 +39,7 @@ import 
org.apache.gravitino.authorization.ranger.RangerAuthorizationPlugin;
 import org.apache.gravitino.authorization.ranger.RangerHelper;
 import org.apache.gravitino.authorization.ranger.RangerPrivileges;
 import org.apache.gravitino.authorization.ranger.reference.RangerDefines;
+import org.apache.gravitino.connector.authorization.BaseAuthorization;
 import org.apache.gravitino.integration.test.container.ContainerSuite;
 import org.apache.gravitino.integration.test.container.HiveContainer;
 import org.apache.gravitino.integration.test.container.RangerContainer;
@@ -62,6 +63,8 @@ public class RangerITEnv {
   public static final String RANGER_HIVE_REPO_NAME = "hiveDev";
   private static final String RANGER_HIVE_TYPE = "hive";
   public static final String RANGER_HDFS_REPO_NAME = "hdfsDev";
+  public static final String METALAKE_ID = "1000";
+  public static final String CATALOG_ID = "1001";
   private static final String RANGER_HDFS_TYPE = "hdfs";
   public static RangerClient rangerClient;
   public static final String HADOOP_USER_NAME = "gravitino";
@@ -118,7 +121,11 @@ public class RangerITEnv {
                 RangerAuthorizationProperties.RANGER_SERVICE_NAME,
                 RangerITEnv.RANGER_HIVE_REPO_NAME,
                 RangerAuthorizationProperties.RANGER_SERVICE_CREATE_IF_ABSENT,
-                "true"));
+                "true",
+                BaseAuthorization.METALAKE_ID,
+                METALAKE_ID,
+                BaseAuthorization.CATALOG_ID,
+                CATALOG_ID));
 
     RangerAuthorizationHDFSPlugin spyRangerAuthorizationHDFSPlugin =
         Mockito.spy(
@@ -141,7 +148,11 @@ public class RangerITEnv {
                     RangerAuthorizationProperties.RANGER_SERVICE_NAME,
                     RangerITEnv.RANGER_HDFS_REPO_NAME,
                     
RangerAuthorizationProperties.RANGER_SERVICE_CREATE_IF_ABSENT,
-                    "true")));
+                    "true",
+                    BaseAuthorization.METALAKE_ID,
+                    METALAKE_ID,
+                    BaseAuthorization.CATALOG_ID,
+                    CATALOG_ID)));
     rangerAuthHDFSPlugin = spyRangerAuthorizationHDFSPlugin;
 
     rangerHelper =
diff --git 
a/core/src/main/java/org/apache/gravitino/catalog/CatalogManager.java 
b/core/src/main/java/org/apache/gravitino/catalog/CatalogManager.java
index fd0dcd2afb..644cb8a647 100644
--- a/core/src/main/java/org/apache/gravitino/catalog/CatalogManager.java
+++ b/core/src/main/java/org/apache/gravitino/catalog/CatalogManager.java
@@ -99,6 +99,7 @@ import org.apache.gravitino.lock.LockType;
 import org.apache.gravitino.lock.TreeLockUtils;
 import org.apache.gravitino.messaging.TopicCatalog;
 import org.apache.gravitino.meta.AuditInfo;
+import org.apache.gravitino.meta.BaseMetalake;
 import org.apache.gravitino.meta.CatalogEntity;
 import org.apache.gravitino.meta.SchemaEntity;
 import org.apache.gravitino.model.ModelCatalog;
@@ -1317,6 +1318,18 @@ public class CatalogManager implements 
CatalogDispatcher, Closeable {
   }
 
   private BaseCatalog<?> createBaseCatalog(IsolatedClassLoader classLoader, 
CatalogEntity entity) {
+    BaseMetalake metalakeEntity;
+    try {
+      metalakeEntity =
+          store.get(
+              NameIdentifier.of(entity.namespace().levels()),
+              EntityType.METALAKE,
+              BaseMetalake.class);
+    } catch (IOException e) {
+      throw new RuntimeException(
+          String.format("Failed to load metalake for catalog %s", 
entity.nameIdentifier()), e);
+    }
+
     // Load Catalog class instance
     BaseCatalog<?> catalog = createCatalogInstance(classLoader, 
entity.getProvider());
     // Resolve secret URNs to plaintext for connector init only; entity 
storage keeps URNs.
@@ -1324,7 +1337,7 @@ public class CatalogManager implements CatalogDispatcher, 
Closeable {
     catalog
         
.withCatalogConf(secretManager.toPlaintextProperties(entity.getProperties()))
         .withCatalogEntity(entity);
-    catalog.initAuthorizationPluginInstance(classLoader);
+    catalog.initAuthorizationPluginInstance(classLoader, metalakeEntity.id());
     return catalog;
   }
 
diff --git a/core/src/main/java/org/apache/gravitino/connector/BaseCatalog.java 
b/core/src/main/java/org/apache/gravitino/connector/BaseCatalog.java
index 029a0a7f32..0307cb8817 100644
--- a/core/src/main/java/org/apache/gravitino/connector/BaseCatalog.java
+++ b/core/src/main/java/org/apache/gravitino/connector/BaseCatalog.java
@@ -280,7 +280,13 @@ public abstract class BaseCatalog<T extends BaseCatalog>
     return authorizationPlugin;
   }
 
-  public void initAuthorizationPluginInstance(IsolatedClassLoader classLoader) 
{
+  /**
+   * Initializes the authorization plugin for this catalog.
+   *
+   * @param classLoader the catalog isolated class loader
+   * @param metalakeId the stable entity ID of the metalake containing this 
catalog
+   */
+  public void initAuthorizationPluginInstance(IsolatedClassLoader classLoader, 
long metalakeId) {
     if (authorizationPlugin == null) {
       synchronized (this) {
         if (authorizationPlugin == null) {
@@ -295,11 +301,15 @@ public abstract class BaseCatalog<T extends BaseCatalog>
           try (BaseAuthorization<?> authorization =
               BaseAuthorization.createAuthorization(classLoader, 
authorizationProvider)) {
 
+            Map<String, String> authorizationConfig = Maps.newHashMap(conf);
+            authorizationConfig.put(BaseAuthorization.METALAKE_ID, 
String.valueOf(metalakeId));
+            authorizationConfig.put(BaseAuthorization.CATALOG_ID, 
String.valueOf(entity().id()));
+
             authorizationPlugin =
                 classLoader.withClassLoader(
                     cl ->
                         authorization.newPlugin(
-                            entity.namespace().level(0), provider(), 
this.conf));
+                            entity.namespace().level(0), provider(), 
authorizationConfig));
 
           } catch (Exception e) {
             LOG.error("Failed to load authorization with class loader", e);
diff --git 
a/core/src/main/java/org/apache/gravitino/connector/authorization/BaseAuthorization.java
 
b/core/src/main/java/org/apache/gravitino/connector/authorization/BaseAuthorization.java
index fa90fbd201..a13cd7f0cb 100644
--- 
a/core/src/main/java/org/apache/gravitino/connector/authorization/BaseAuthorization.java
+++ 
b/core/src/main/java/org/apache/gravitino/connector/authorization/BaseAuthorization.java
@@ -45,6 +45,12 @@ import org.apache.gravitino.utils.IsolatedClassLoader;
 public abstract class BaseAuthorization<T extends BaseAuthorization>
     implements AuthorizationProvider, Closeable {
 
+  /** The internal property used to pass the stable metalake entity ID to 
authorization plugins. */
+  public static final String METALAKE_ID = "authorization.metalake.id";
+
+  /** The internal property used to pass the stable catalog entity ID to 
authorization plugins. */
+  public static final String CATALOG_ID = "authorization.catalog.id";
+
   /**
    * Creates a new instance of AuthorizationPlugin. <br>
    * The child class should implement this method to provide a specific 
AuthorizationPlugin instance
diff --git 
a/core/src/test/java/org/apache/gravitino/connector/authorization/TestAuthorization.java
 
b/core/src/test/java/org/apache/gravitino/connector/authorization/TestAuthorization.java
index 4ee37b4dde..35b13f7ec7 100644
--- 
a/core/src/test/java/org/apache/gravitino/connector/authorization/TestAuthorization.java
+++ 
b/core/src/test/java/org/apache/gravitino/connector/authorization/TestAuthorization.java
@@ -24,6 +24,7 @@ import java.util.Collections;
 import org.apache.gravitino.Catalog;
 import org.apache.gravitino.Namespace;
 import org.apache.gravitino.TestCatalog;
+import 
org.apache.gravitino.connector.authorization.ranger.TestRangerAuthorization;
 import 
org.apache.gravitino.connector.authorization.ranger.TestRangerAuthorizationHDFSPlugin;
 import 
org.apache.gravitino.connector.authorization.ranger.TestRangerAuthorizationHadoopSQLPlugin;
 import org.apache.gravitino.meta.AuditInfo;
@@ -34,6 +35,7 @@ import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 
 public class TestAuthorization {
+  private static final long METALAKE_ID = 10L;
   private static TestCatalog hiveCatalog;
   private static TestCatalog filesetCatalog;
 
@@ -64,7 +66,7 @@ public class TestAuthorization {
     IsolatedClassLoader isolatedClassLoader =
         new IsolatedClassLoader(
             Collections.emptyList(), Collections.emptyList(), 
Collections.emptyList());
-    hiveCatalog.initAuthorizationPluginInstance(isolatedClassLoader);
+    hiveCatalog.initAuthorizationPluginInstance(isolatedClassLoader, 
METALAKE_ID);
 
     CatalogEntity filesetEntity =
         CatalogEntity.builder()
@@ -85,13 +87,15 @@ public class TestAuthorization {
                     "authorization.ranger.service.type",
                     "HDFS"))
             .withCatalogEntity(filesetEntity);
-    filesetCatalog.initAuthorizationPluginInstance(isolatedClassLoader);
+    filesetCatalog.initAuthorizationPluginInstance(isolatedClassLoader, 
METALAKE_ID);
   }
 
   @Test
   public void testRangerHadoopSQLAuthorization() {
     AuthorizationPlugin rangerHiveAuthPlugin = 
hiveCatalog.getAuthorizationPlugin();
     Assertions.assertInstanceOf(TestRangerAuthorizationHadoopSQLPlugin.class, 
rangerHiveAuthPlugin);
+    Assertions.assertEquals("10", TestRangerAuthorization.hadoopSqlMetalakeId);
+    Assertions.assertEquals("1", TestRangerAuthorization.hadoopSqlCatalogId);
     TestRangerAuthorizationHadoopSQLPlugin testRangerAuthHadoopSQLPlugin =
         (TestRangerAuthorizationHadoopSQLPlugin) rangerHiveAuthPlugin;
     Assertions.assertFalse(testRangerAuthHadoopSQLPlugin.callOnCreateRole1);
@@ -103,6 +107,8 @@ public class TestAuthorization {
   public void testRangerHDFSAuthorization() {
     AuthorizationPlugin rangerHDFSAuthPlugin = 
filesetCatalog.getAuthorizationPlugin();
     Assertions.assertInstanceOf(TestRangerAuthorizationHDFSPlugin.class, 
rangerHDFSAuthPlugin);
+    Assertions.assertEquals("10", TestRangerAuthorization.hdfsMetalakeId);
+    Assertions.assertEquals("2", TestRangerAuthorization.hdfsCatalogId);
     TestRangerAuthorizationHDFSPlugin testRangerAuthHDFSPlugin =
         (TestRangerAuthorizationHDFSPlugin) rangerHDFSAuthPlugin;
     Assertions.assertFalse(testRangerAuthHDFSPlugin.callOnCreateRole2);
diff --git 
a/core/src/test/java/org/apache/gravitino/connector/authorization/ranger/TestRangerAuthorization.java
 
b/core/src/test/java/org/apache/gravitino/connector/authorization/ranger/TestRangerAuthorization.java
index 1709c90319..d7945b5444 100644
--- 
a/core/src/test/java/org/apache/gravitino/connector/authorization/ranger/TestRangerAuthorization.java
+++ 
b/core/src/test/java/org/apache/gravitino/connector/authorization/ranger/TestRangerAuthorization.java
@@ -24,6 +24,17 @@ import 
org.apache.gravitino.connector.authorization.AuthorizationPlugin;
 import org.apache.gravitino.connector.authorization.BaseAuthorization;
 
 public class TestRangerAuthorization extends 
BaseAuthorization<TestRangerAuthorization> {
+  /** Metalake ID received by the Hadoop SQL test plugin. */
+  public static String hadoopSqlMetalakeId;
+
+  /** Catalog ID received by the Hadoop SQL test plugin. */
+  public static String hadoopSqlCatalogId;
+
+  /** Metalake ID received by the HDFS test plugin. */
+  public static String hdfsMetalakeId;
+
+  /** Catalog ID received by the HDFS test plugin. */
+  public static String hdfsCatalogId;
 
   public TestRangerAuthorization() {}
 
@@ -41,8 +52,12 @@ public class TestRangerAuthorization extends 
BaseAuthorization<TestRangerAuthori
     String serviceType = 
properties.get("authorization.ranger.service.type").toUpperCase();
     switch (serviceType) {
       case "HADOOPSQL":
+        hadoopSqlMetalakeId = properties.get(BaseAuthorization.METALAKE_ID);
+        hadoopSqlCatalogId = properties.get(BaseAuthorization.CATALOG_ID);
         return new TestRangerAuthorizationHadoopSQLPlugin();
       case "HDFS":
+        hdfsMetalakeId = properties.get(BaseAuthorization.METALAKE_ID);
+        hdfsCatalogId = properties.get(BaseAuthorization.CATALOG_ID);
         return new TestRangerAuthorizationHDFSPlugin();
       default:
         throw new IllegalArgumentException("Unsupported service type: " + 
serviceType);
diff --git a/docs/security/authorization-pushdown.md 
b/docs/security/authorization-pushdown.md
index dea56c5d03..18b0b58be2 100644
--- a/docs/security/authorization-pushdown.md
+++ b/docs/security/authorization-pushdown.md
@@ -64,14 +64,55 @@ authorization.ranger.service.name=hiveRepo
 
 ### Roles Gravitino Creates
 
-Gravitino creates three roles in Ranger and manages their membership itself, 
so treat them as owned by Gravitino rather than editing them in the Ranger UI.
+Gravitino creates the following managed roles in Ranger and manages their 
membership itself, so treat them as owned by Gravitino rather than editing them 
in the Ranger UI.
 
 | Role                            | Purpose                                    
                                                      |
 
|---------------------------------|---------------------------------------------------------------------------------------------------|
-| `GRAVITINO_METALAKE_OWNER_ROLE` | Holds the users and groups that own the 
metalake, carrying owner privileges in Ranger policies    |
-| `GRAVITINO_CATALOG_OWNER_ROLE`  | Holds the users and groups that own the 
catalog, carrying owner privileges in Ranger policies     |
+| `GRAVITINO_METALAKE_OWNER_ROLE_<metalake_id>` | Holds the user or group that 
owns the identified metalake, carrying owner privileges in Ranger policies |
+| `GRAVITINO_CATALOG_OWNER_ROLE_<catalog_id>` | Holds the user or group that 
owns the identified catalog, carrying owner privileges in Ranger policies |
 | `GRAVITINO_OWNER_ROLE`          | Labels the policy items covering schema 
and table owner privileges, and holds no members          |
 
+### Migrate Owner Roles from 1.x to 2.0
+
+Gravitino 1.x used the shared Ranger roles `GRAVITINO_METALAKE_OWNER_ROLE` and
+`GRAVITINO_CATALOG_OWNER_ROLE`. If multiple catalogs wrote to the same Ranger 
service, membership
+in either shared role could give an owner access outside the object they 
owned. Gravitino 2.0 uses
+the ID-suffixed roles in the table above, but it does not migrate existing 
Ranger state
+automatically.
+
+After upgrading the Gravitino server, reconcile every existing metalake and 
Ranger-enabled catalog
+by setting its current owner again. Setting the same owner is safe and 
idempotent. It creates the
+ID-suffixed role, grants that role to the current owner, and replaces the 
legacy shared role in the
+matching Ranger policies. Get the current owner before sending the `PUT` 
request; do not infer it
+from membership in a legacy shared role because that role can contain owners 
of several catalogs.
+
+The following example reconciles the owner of catalog `sales` in metalake 
`prod`:
+
+```shell
+curl -s \
+  -H "Accept: application/vnd.gravitino.v1+json" \
+  "http://<gravitino-host>:8090/api/metalakes/prod/owners/CATALOG/sales"
+
+curl -X PUT \
+  -H "Content-Type: application/json" \
+  -d '{"name":"<current-owner-name>","type":"<USER-or-GROUP>"}' \
+  "http://<gravitino-host>:8090/api/metalakes/prod/owners/CATALOG/sales"
+```
+
+Repeat the same process for each catalog that has Ranger authorization 
enabled. Reconcile each
+metalake as well, using `METALAKE/<metalake-name>` in place of 
`CATALOG/<catalog-name>`. A metalake
+owner update is applied through every authorization-enabled catalog in that 
metalake.
+
+Keep the two legacy shared roles until all objects have been reconciled. Then 
use Ranger Admin to:
+
+1. Verify that no policy refers to the exact role names 
`GRAVITINO_METALAKE_OWNER_ROLE` or
+   `GRAVITINO_CATALOG_OWNER_ROLE`.
+2. Verify that every `GRAVITINO_METALAKE_OWNER_ROLE_<metalake_id>` and
+   `GRAVITINO_CATALOG_OWNER_ROLE_<catalog_id>` contains the expected owner, 
and test access for two
+   catalogs that share a Ranger service.
+3. Delete the two legacy shared roles. Do not delete the ID-suffixed roles or
+   `GRAVITINO_OWNER_ROLE`.
+
 ## Chaining Plugins
 
 One catalog often needs permissions applied in more than one place. A Hive 
catalog storing its data on HDFS needs both the table grant in the HadoopSQL 
service and the corresponding path grant in the HDFS service, or an engine 
reading the files directly bypasses the table permission.

Reply via email to