diff --git a/api/src/main/java/org/apache/gravitino/authorization/Privilege.java b/api/src/main/java/org/apache/gravitino/authorization/Privilege.java index 3ca4107a12d..866cf7d23a2 100644 --- a/api/src/main/java/org/apache/gravitino/authorization/Privilege.java +++ b/api/src/main/java/org/apache/gravitino/authorization/Privilege.java @@ -61,7 +61,7 @@ enum Name { USE_SCHEMA(0L, 1L << 4), /** The privilege to create a table. */ CREATE_TABLE(0L, 1L << 5), - /** The privilege to execute SQL `ALTER`, `INSERT`, `UPDATE`, or `DELETE` for a table. */ + /** The privilege to write data to a table or modify the table schema. */ MODIFY_TABLE(0L, 1L << 6), /** The privilege to select data from a table. */ SELECT_TABLE(0L, 1L << 7), diff --git a/api/src/main/java/org/apache/gravitino/authorization/Privileges.java b/api/src/main/java/org/apache/gravitino/authorization/Privileges.java index 5255bce1c5a..b0f9e8fcc43 100644 --- a/api/src/main/java/org/apache/gravitino/authorization/Privileges.java +++ b/api/src/main/java/org/apache/gravitino/authorization/Privileges.java @@ -407,7 +407,7 @@ public boolean canBindTo(MetadataObject.Type type) { } } - /** The privilege to execute SQL `ALTER`, `INSERT`, `UPDATE`, or `DELETE` for a table. */ + /** The privilege to write data to a table or modify the table schema. */ public static class ModifyTable extends GenericPrivilege { private static final ModifyTable ALLOW_INSTANCE = new ModifyTable(Condition.ALLOW, Name.MODIFY_TABLE); diff --git a/authorizations/authorization-ranger/build.gradle.kts b/authorizations/authorization-ranger/build.gradle.kts index 66341d9b00a..93d90cd4fdb 100644 --- a/authorizations/authorization-ranger/build.gradle.kts +++ b/authorizations/authorization-ranger/build.gradle.kts @@ -27,6 +27,8 @@ plugins { val scalaVersion: String = project.properties["scalaVersion"] as? String ?: extra["defaultScalaVersion"].toString() val sparkVersion: String = libs.versions.spark35.get() val kyuubiVersion: String = libs.versions.kyuubi4spark35.get() +val sparkMajorVersion: String = sparkVersion.substringBeforeLast(".") +val icebergVersion: String = libs.versions.iceberg4spark.get() dependencies { implementation(project(":api")) { @@ -97,6 +99,7 @@ dependencies { exclude("javax.servlet", "servlet-api") exclude("io.netty") } + testImplementation("org.apache.iceberg:iceberg-spark-runtime-${sparkMajorVersion}_$scalaVersion:$icebergVersion") } tasks { @@ -126,7 +129,7 @@ tasks { tasks.test { doFirst { - environment("HADOOP_USER_NAME", "test") + environment("HADOOP_USER_NAME", "gravitino") } dependsOn(":catalogs:catalog-hive:jar", ":catalogs:catalog-hive:runtimeJars") diff --git a/authorizations/authorization-ranger/src/main/java/org/apache/gravitino/authorization/ranger/RangerAuthorization.java b/authorizations/authorization-ranger/src/main/java/org/apache/gravitino/authorization/ranger/RangerAuthorization.java index 3fb74f2887e..9f8b42b0633 100644 --- a/authorizations/authorization-ranger/src/main/java/org/apache/gravitino/authorization/ranger/RangerAuthorization.java +++ b/authorizations/authorization-ranger/src/main/java/org/apache/gravitino/authorization/ranger/RangerAuthorization.java @@ -33,7 +33,8 @@ public String shortName() { protected AuthorizationPlugin newPlugin(String catalogProvider, Map config) { switch (catalogProvider) { case "hive": - return RangerAuthorizationHivePlugin.getInstance(config); + case "lakehouse-iceberg": + return RangerAuthorizationHadoopSQLPlugin.getInstance(config); default: throw new IllegalArgumentException("Unknown catalog provider: " + catalogProvider); } diff --git a/authorizations/authorization-ranger/src/main/java/org/apache/gravitino/authorization/ranger/RangerAuthorizationHivePlugin.java b/authorizations/authorization-ranger/src/main/java/org/apache/gravitino/authorization/ranger/RangerAuthorizationHadoopSQLPlugin.java similarity index 96% rename from authorizations/authorization-ranger/src/main/java/org/apache/gravitino/authorization/ranger/RangerAuthorizationHivePlugin.java rename to authorizations/authorization-ranger/src/main/java/org/apache/gravitino/authorization/ranger/RangerAuthorizationHadoopSQLPlugin.java index 12072d7f688..f75485aedbb 100644 --- a/authorizations/authorization-ranger/src/main/java/org/apache/gravitino/authorization/ranger/RangerAuthorizationHivePlugin.java +++ b/authorizations/authorization-ranger/src/main/java/org/apache/gravitino/authorization/ranger/RangerAuthorizationHadoopSQLPlugin.java @@ -41,19 +41,21 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class RangerAuthorizationHivePlugin extends RangerAuthorizationPlugin { - private static final Logger LOG = LoggerFactory.getLogger(RangerAuthorizationHivePlugin.class); - private static volatile RangerAuthorizationHivePlugin instance = null; +public class RangerAuthorizationHadoopSQLPlugin extends RangerAuthorizationPlugin { + private static final Logger LOG = + LoggerFactory.getLogger(RangerAuthorizationHadoopSQLPlugin.class); + private static volatile RangerAuthorizationHadoopSQLPlugin instance = null; - private RangerAuthorizationHivePlugin(Map config) { + private RangerAuthorizationHadoopSQLPlugin(Map config) { super(config); } - public static synchronized RangerAuthorizationHivePlugin getInstance(Map config) { + public static synchronized RangerAuthorizationHadoopSQLPlugin getInstance( + Map config) { if (instance == null) { - synchronized (RangerAuthorizationHivePlugin.class) { + synchronized (RangerAuthorizationHadoopSQLPlugin.class) { if (instance == null) { - instance = new RangerAuthorizationHivePlugin(config); + instance = new RangerAuthorizationHadoopSQLPlugin(config); } } } diff --git a/authorizations/authorization-ranger/src/test/java/org/apache/gravitino/authorization/ranger/integration/test/RangerBaseE2EIT.java b/authorizations/authorization-ranger/src/test/java/org/apache/gravitino/authorization/ranger/integration/test/RangerBaseE2EIT.java new file mode 100644 index 00000000000..50a7d2394ae --- /dev/null +++ b/authorizations/authorization-ranger/src/test/java/org/apache/gravitino/authorization/ranger/integration/test/RangerBaseE2EIT.java @@ -0,0 +1,986 @@ +/* + * 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.integration.test; + +import static org.apache.gravitino.authorization.ranger.integration.test.RangerITEnv.currentFunName; + +import com.google.common.collect.Lists; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import org.apache.commons.io.FileUtils; +import org.apache.gravitino.Catalog; +import org.apache.gravitino.MetadataObject; +import org.apache.gravitino.MetadataObjects; +import org.apache.gravitino.NameIdentifier; +import org.apache.gravitino.auth.AuthConstants; +import org.apache.gravitino.authorization.Owner; +import org.apache.gravitino.authorization.Privileges; +import org.apache.gravitino.authorization.SecurableObject; +import org.apache.gravitino.authorization.SecurableObjects; +import org.apache.gravitino.client.GravitinoMetalake; +import org.apache.gravitino.integration.test.util.BaseIT; +import org.apache.gravitino.integration.test.util.GravitinoITUtils; +import org.apache.gravitino.rel.TableChange; +import org.apache.kyuubi.plugin.spark.authz.AccessControlException; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Row; +import org.apache.spark.sql.SparkSession; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public abstract class RangerBaseE2EIT extends BaseIT { + private static final Logger LOG = LoggerFactory.getLogger(RangerBaseE2EIT.class); + public static final String catalogName = GravitinoITUtils.genRandomName("catalog").toLowerCase(); + public static final String schemaName = GravitinoITUtils.genRandomName("schema").toLowerCase(); + + public static final String tableName = GravitinoITUtils.genRandomName("table").toLowerCase(); + + public static String metalakeName; + protected static GravitinoMetalake metalake; + protected static Catalog catalog; + protected static String HIVE_METASTORE_URIS; + protected static String RANGER_ADMIN_URL = null; + + protected static SparkSession sparkSession = null; + protected static final String HADOOP_USER_NAME = "HADOOP_USER_NAME"; + + protected static final String SQL_SHOW_DATABASES = + String.format("SHOW DATABASES like '%s'", schemaName); + + protected static final String SQL_CREATE_SCHEMA = String.format("CREATE DATABASE %s", schemaName); + + protected static final String SQL_DROP_SCHEMA = String.format("DROP DATABASE %s", schemaName); + + protected static final String SQL_USE_SCHEMA = String.format("USE SCHEMA %s", schemaName); + + protected static final String SQL_CREATE_TABLE = + String.format("CREATE TABLE %s (a int, b string, c string)", tableName); + + protected static final String SQL_INSERT_TABLE = + String.format("INSERT INTO %s (a, b, c) VALUES (1, 'a', 'b')", tableName); + + protected static final String SQL_SELECT_TABLE = String.format("SELECT * FROM %s", tableName); + + protected static final String SQL_UPDATE_TABLE = + String.format("UPDATE %s SET b = 'b', c = 'c' WHERE a = 1", tableName); + + protected static final String SQL_DELETE_TABLE = + String.format("DELETE FROM %s WHERE a = 1", tableName); + + protected static final String SQL_ALTER_TABLE = + String.format("ALTER TABLE %s ADD COLUMN d string", tableName); + + protected static final String SQL_ALTER_TABLE_BACK = + String.format("ALTER TABLE %s DROP COLUMN d", tableName); + protected static final String SQL_RENAME_TABLE = + String.format("ALTER TABLE %s RENAME TO new_table", tableName); + + protected static final String SQL_RENAME_BACK_TABLE = + String.format("ALTER TABLE new_table RENAME TO %s", tableName); + + protected static final String SQL_DROP_TABLE = String.format("DROP TABLE %s", tableName); + + protected static void generateRangerSparkSecurityXML() throws IOException { + String templatePath = + String.join( + File.separator, + System.getenv("GRAVITINO_ROOT_DIR"), + "authorizations", + "authorization-ranger", + "src", + "test", + "resources", + "ranger-spark-security.xml.template"); + String xmlPath = + String.join( + File.separator, + System.getenv("GRAVITINO_ROOT_DIR"), + "authorizations", + "authorization-ranger", + "build", + "resources", + "test", + "ranger-spark-security.xml"); + + String templateContext = + FileUtils.readFileToString(new File(templatePath), StandardCharsets.UTF_8); + templateContext = + templateContext + .replace("__REPLACE__RANGER_ADMIN_URL", RANGER_ADMIN_URL) + .replace("__REPLACE__RANGER_HIVE_REPO_NAME", RangerITEnv.RANGER_HIVE_REPO_NAME); + FileUtils.writeStringToFile(new File(xmlPath), templateContext, StandardCharsets.UTF_8); + } + + protected void cleanIT() { + if (client != null) { + Arrays.stream(catalog.asSchemas().listSchemas()) + .filter(schema -> !schema.equals("default")) + .forEach( + (schema -> { + catalog.asSchemas().dropSchema(schema, false); + })); + Arrays.stream(metalake.listCatalogs()) + .forEach((catalogName -> metalake.dropCatalog(catalogName, true))); + client.disableMetalake(metalakeName); + client.dropMetalake(metalakeName); + } + if (sparkSession != null) { + sparkSession.close(); + } + try { + closer.close(); + } catch (Exception e) { + LOG.error("Failed to close CloseableGroup", e); + } + client = null; + RangerITEnv.cleanup(); + } + + protected void createMetalake() { + GravitinoMetalake[] gravitinoMetalakes = client.listMetalakes(); + Assertions.assertEquals(0, gravitinoMetalakes.length); + + client.createMetalake(metalakeName, "comment", Collections.emptyMap()); + GravitinoMetalake loadMetalake = client.loadMetalake(metalakeName); + Assertions.assertEquals(metalakeName, loadMetalake.name()); + + metalake = loadMetalake; + } + + protected static void waitForUpdatingPolicies() throws InterruptedException { + // After Ranger authorization, Must wait a period of time for the Ranger Spark plugin to update + // the policy Sleep time must be greater than the policy update interval + // (ranger.plugin.spark.policy.pollIntervalMs) in the + // `resources/ranger-spark-security.xml.template` + Thread.sleep(1000L); + } + + protected abstract void checkTableAllPrivilegesExceptForCreating(); + + protected abstract void checkUpdateSQLWithReadWritePrivileges(); + + protected abstract void checkUpdateSQLWithReadPrivileges(); + + protected abstract void checkUpdateSQLWithWritePrivileges(); + + protected abstract void checkDeleteSQLWithReadWritePrivileges(); + + protected abstract void checkDeleteSQLWithReadPrivileges(); + + protected abstract void checkDeleteSQLWithWritePrivileges(); + + protected abstract void useCatalog() throws InterruptedException; + + protected abstract void checkHaveNoPrivileges(); + + protected abstract void testAlterTable(); + + @Test + void testCreateSchema() throws InterruptedException { + // Choose a catalog + useCatalog(); + + // First, fail to create the schema + Assertions.assertThrows( + AccessControlException.class, () -> sparkSession.sql(SQL_CREATE_SCHEMA)); + + // Second, grant the `CREATE_SCHEMA` role + String userName1 = System.getenv(HADOOP_USER_NAME); + String roleName = currentFunName(); + SecurableObject securableObject = + SecurableObjects.ofMetalake( + metalakeName, Lists.newArrayList(Privileges.CreateSchema.allow())); + metalake.createRole(roleName, Collections.emptyMap(), Lists.newArrayList(securableObject)); + metalake.grantRolesToUser(Lists.newArrayList(roleName), userName1); + waitForUpdatingPolicies(); + + // Third, succeed to create the schema + sparkSession.sql(SQL_CREATE_SCHEMA); + + // Fourth, fail to create the table + Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_CREATE_TABLE)); + + // Clean up + catalog.asSchemas().dropSchema(schemaName, false); + metalake.deleteRole(roleName); + } + + @Test + void testCreateTable() throws InterruptedException { + // Choose a catalog + useCatalog(); + + // First, create a role for creating a database and grant role to the user + String createSchemaRole = currentFunName(); + SecurableObject securableObject = + SecurableObjects.ofMetalake( + metalakeName, + Lists.newArrayList(Privileges.UseSchema.allow(), Privileges.CreateSchema.allow())); + String userName1 = System.getenv(HADOOP_USER_NAME); + metalake.createRole( + createSchemaRole, Collections.emptyMap(), Lists.newArrayList(securableObject)); + metalake.grantRolesToUser(Lists.newArrayList(createSchemaRole), userName1); + waitForUpdatingPolicies(); + // Second, create a schema + sparkSession.sql(SQL_CREATE_SCHEMA); + + // Third, fail to create a table + sparkSession.sql(SQL_USE_SCHEMA); + Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_CREATE_TABLE)); + + // Fourth, create a role for creating a table and grant to the user + String createTableRole = currentFunName() + "2"; + securableObject = + SecurableObjects.ofMetalake( + metalakeName, Lists.newArrayList(Privileges.CreateTable.allow())); + metalake.createRole( + createTableRole, Collections.emptyMap(), Lists.newArrayList(securableObject)); + metalake.grantRolesToUser(Lists.newArrayList(createTableRole), userName1); + waitForUpdatingPolicies(); + + // Fifth, succeed to create a table + sparkSession.sql(SQL_CREATE_TABLE); + + // Sixth, fail to read and write a table + Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_INSERT_TABLE)); + Assertions.assertThrows( + AccessControlException.class, () -> sparkSession.sql(SQL_SELECT_TABLE).collectAsList()); + + // Clean up + catalog.asTableCatalog().dropTable(NameIdentifier.of(schemaName, tableName)); + catalog.asSchemas().dropSchema(schemaName, false); + metalake.deleteRole(createTableRole); + metalake.deleteRole(createSchemaRole); + } + + @Test + void testReadWriteTableWithMetalakeLevelRole() throws InterruptedException { + // Choose a catalog + useCatalog(); + + // First, create a role for creating a database and grant role to the user + String readWriteRole = currentFunName(); + SecurableObject securableObject = + SecurableObjects.ofMetalake( + metalakeName, + Lists.newArrayList( + Privileges.UseSchema.allow(), + Privileges.CreateSchema.allow(), + Privileges.CreateTable.allow(), + Privileges.SelectTable.allow(), + Privileges.ModifyTable.allow())); + String userName1 = System.getenv(HADOOP_USER_NAME); + metalake.createRole(readWriteRole, Collections.emptyMap(), Lists.newArrayList(securableObject)); + metalake.grantRolesToUser(Lists.newArrayList(readWriteRole), userName1); + waitForUpdatingPolicies(); + // Second, create a schema + sparkSession.sql(SQL_CREATE_SCHEMA); + + // Third, create a table + sparkSession.sql(SQL_USE_SCHEMA); + sparkSession.sql(SQL_CREATE_TABLE); + + // case 1: Succeed to insert data into table + sparkSession.sql(SQL_INSERT_TABLE); + + // case 2: Succeed to select data from the table + sparkSession.sql(SQL_SELECT_TABLE).collectAsList(); + + // case 3: Update data in the table + checkUpdateSQLWithReadWritePrivileges(); + + // case 4: Delete data from the table. + checkDeleteSQLWithReadWritePrivileges(); + + // case 5: Succeed to alter the table + testAlterTable(); + + // case 6: Fail to drop the table + Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_DROP_TABLE)); + + // case 7: If we don't have the role, we can't insert and select from data. + metalake.deleteRole(readWriteRole); + waitForUpdatingPolicies(); + checkHaveNoPrivileges(); + + // Clean up + catalog.asTableCatalog().dropTable(NameIdentifier.of(schemaName, tableName)); + catalog.asSchemas().dropSchema(schemaName, false); + } + + @Test + void testReadWriteTableWithTableLevelRole() throws InterruptedException { + // Choose a catalog + useCatalog(); + + // First, create a role for creating a database and grant role to the user + String roleName = currentFunName(); + SecurableObject securableObject = + SecurableObjects.ofMetalake( + metalakeName, + Lists.newArrayList( + Privileges.UseSchema.allow(), + Privileges.CreateSchema.allow(), + Privileges.CreateTable.allow())); + String userName1 = System.getenv(HADOOP_USER_NAME); + metalake.createRole(roleName, Collections.emptyMap(), Lists.newArrayList(securableObject)); + metalake.grantRolesToUser(Lists.newArrayList(roleName), userName1); + waitForUpdatingPolicies(); + // Second, create a schema + sparkSession.sql(SQL_CREATE_SCHEMA); + + // Third, create a table + sparkSession.sql(SQL_USE_SCHEMA); + sparkSession.sql(SQL_CREATE_TABLE); + + // Fourth, revoke and grant a table level role + metalake.deleteRole(roleName); + securableObject = + SecurableObjects.parse( + String.format("%s.%s.%s", catalogName, schemaName, tableName), + MetadataObject.Type.TABLE, + Lists.newArrayList(Privileges.ModifyTable.allow(), Privileges.SelectTable.allow())); + metalake.createRole(roleName, Collections.emptyMap(), Lists.newArrayList(securableObject)); + metalake.grantRolesToUser(Lists.newArrayList(roleName), userName1); + waitForUpdatingPolicies(); + + // case 1: Succeed to insert data into table + sparkSession.sql(SQL_INSERT_TABLE); + + // case 2: Succeed to select data from the table + sparkSession.sql(SQL_SELECT_TABLE).collectAsList(); + + // case 3: Update data in the table. + checkUpdateSQLWithReadWritePrivileges(); + + // case 4: Delete data from the table. + checkDeleteSQLWithReadWritePrivileges(); + + // case 5: Succeed to alter the table + testAlterTable(); + + // case 6: Fail to drop the table + Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_DROP_TABLE)); + + // case 7: If we don't have the role, we can't insert and select from data. + metalake.deleteRole(roleName); + waitForUpdatingPolicies(); + checkHaveNoPrivileges(); + + // Clean up + catalog.asTableCatalog().dropTable(NameIdentifier.of(schemaName, tableName)); + catalog.asSchemas().dropSchema(schemaName, false); + } + + @Test + void testReadOnlyTable() throws InterruptedException { + // Choose a catalog + useCatalog(); + + // First, create a role for creating a database and grant role to the user + String readOnlyRole = currentFunName(); + SecurableObject securableObject = + SecurableObjects.ofMetalake( + metalakeName, + Lists.newArrayList( + Privileges.UseSchema.allow(), + Privileges.CreateSchema.allow(), + Privileges.CreateTable.allow(), + Privileges.SelectTable.allow())); + String userName1 = System.getenv(HADOOP_USER_NAME); + metalake.createRole(readOnlyRole, Collections.emptyMap(), Lists.newArrayList(securableObject)); + metalake.grantRolesToUser(Lists.newArrayList(readOnlyRole), userName1); + waitForUpdatingPolicies(); + // Second, create a schema + sparkSession.sql(SQL_CREATE_SCHEMA); + + // Third, create a table + sparkSession.sql(SQL_USE_SCHEMA); + sparkSession.sql(SQL_CREATE_TABLE); + + // case 1: Fail to insert data into table + Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_INSERT_TABLE)); + + // case 2: Succeed to select data from the table + sparkSession.sql(SQL_SELECT_TABLE).collectAsList(); + + // case 3: Update data in the table + checkUpdateSQLWithReadPrivileges(); + + // case 4: Delete data from the table + checkDeleteSQLWithReadPrivileges(); + + // case 5: Fail to alter the table + Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_ALTER_TABLE)); + + // case 6: Fail to drop the table + Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_DROP_TABLE)); + + // case 7: If we don't have the role, we can't insert and select from data. + metalake.deleteRole(readOnlyRole); + waitForUpdatingPolicies(); + checkHaveNoPrivileges(); + + // Clean up + catalog.asTableCatalog().dropTable(NameIdentifier.of(schemaName, tableName)); + catalog.asSchemas().dropSchema(schemaName, false); + } + + @Test + void testWriteOnlyTable() throws InterruptedException { + // Choose a catalog + useCatalog(); + + // First, create a role for creating a database and grant role to the user + String writeOnlyRole = currentFunName(); + SecurableObject securableObject = + SecurableObjects.ofMetalake( + metalakeName, + Lists.newArrayList( + Privileges.UseSchema.allow(), + Privileges.CreateSchema.allow(), + Privileges.CreateTable.allow(), + Privileges.ModifyTable.allow())); + String userName1 = System.getenv(HADOOP_USER_NAME); + metalake.createRole(writeOnlyRole, Collections.emptyMap(), Lists.newArrayList(securableObject)); + metalake.grantRolesToUser(Lists.newArrayList(writeOnlyRole), userName1); + waitForUpdatingPolicies(); + // Second, create a schema + sparkSession.sql(SQL_CREATE_SCHEMA); + + // Third, create a table + sparkSession.sql(SQL_USE_SCHEMA); + sparkSession.sql(SQL_CREATE_TABLE); + + // case 1: Succeed to insert data into the table + sparkSession.sql(SQL_INSERT_TABLE); + + // case 2: Fail to select data from the table + Assertions.assertThrows( + AccessControlException.class, () -> sparkSession.sql(SQL_SELECT_TABLE).collectAsList()); + + // case 3: Update data in the table + checkUpdateSQLWithWritePrivileges(); + + // case 4: Delete data from the table + checkDeleteSQLWithWritePrivileges(); + + // case 5: Succeed to alter the table + testAlterTable(); + + // case 6: Fail to drop the table + Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_DROP_TABLE)); + + // case 7: If we don't have the role, we can't insert and select from data. + metalake.deleteRole(writeOnlyRole); + waitForUpdatingPolicies(); + checkHaveNoPrivileges(); + + // Clean up + catalog.asTableCatalog().dropTable(NameIdentifier.of(schemaName, tableName)); + catalog.asSchemas().dropSchema(schemaName, false); + } + + @Test + void testCreateAllPrivilegesRole() throws InterruptedException { + // Choose a catalog + useCatalog(); + + // Create a role + String roleName = currentFunName(); + SecurableObject securableObject = + SecurableObjects.ofMetalake( + metalakeName, + Lists.newArrayList( + Privileges.CreateCatalog.allow(), + Privileges.UseCatalog.allow(), + Privileges.UseSchema.allow(), + Privileges.CreateSchema.allow(), + Privileges.CreateFileset.allow(), + Privileges.ReadFileset.allow(), + Privileges.WriteFileset.allow(), + Privileges.CreateTopic.allow(), + Privileges.ConsumeTopic.allow(), + Privileges.ProduceTopic.allow(), + Privileges.CreateTable.allow(), + Privileges.SelectTable.allow(), + Privileges.ModifyTable.allow(), + Privileges.ManageUsers.allow(), + Privileges.ManageGroups.allow(), + Privileges.CreateRole.allow())); + metalake.createRole(roleName, Collections.emptyMap(), Lists.newArrayList(securableObject)); + + // Granted this role to the spark execution user `HADOOP_USER_NAME` + String userName1 = System.getenv(HADOOP_USER_NAME); + metalake.grantRolesToUser(Lists.newArrayList(roleName), userName1); + + waitForUpdatingPolicies(); + + // Test to create the schema + sparkSession.sql(SQL_CREATE_SCHEMA); + + // Test to create a table + sparkSession.sql(SQL_USE_SCHEMA); + sparkSession.sql(SQL_CREATE_TABLE); + + // Clean up + catalog.asTableCatalog().dropTable(NameIdentifier.of(schemaName, tableName)); + catalog.asSchemas().dropSchema(schemaName, false); + metalake.deleteRole(roleName); + } + + @Test + void testDeleteAndRecreateRole() throws InterruptedException { + // Choose a catalog + useCatalog(); + + // Fail to create schema + Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_DROP_SCHEMA)); + + // Create a role with CREATE_SCHEMA privilege + String roleName = currentFunName(); + SecurableObject securableObject = + SecurableObjects.parse( + String.format("%s", catalogName), + MetadataObject.Type.CATALOG, + Lists.newArrayList(Privileges.UseCatalog.allow(), Privileges.CreateSchema.allow())); + metalake.createRole(roleName, Collections.emptyMap(), Lists.newArrayList(securableObject)); + + // Granted this role to the spark execution user `HADOOP_USER_NAME` + String userName1 = System.getenv(HADOOP_USER_NAME); + metalake.grantRolesToUser(Lists.newArrayList(roleName), userName1); + waitForUpdatingPolicies(); + + // Succeed to create the schema + sparkSession.sql(SQL_CREATE_SCHEMA); + catalog.asSchemas().dropSchema(schemaName, false); + + // Delete the role + metalake.deleteRole(roleName); + waitForUpdatingPolicies(); + + // Fail to create the schema + Assertions.assertThrows( + AccessControlException.class, () -> sparkSession.sql(SQL_CREATE_SCHEMA)); + + // Create the role again + metalake.createRole(roleName, Collections.emptyMap(), Lists.newArrayList(securableObject)); + + // Grant the role again + metalake.grantRolesToUser(Lists.newArrayList(roleName), userName1); + waitForUpdatingPolicies(); + + // Succeed to create the schema + sparkSession.sql(SQL_CREATE_SCHEMA); + + // Clean up + catalog.asSchemas().dropSchema(schemaName, false); + metalake.deleteRole(roleName); + } + + @Test + void testDeleteAndRecreateMetadataObject() throws InterruptedException { + // Choose a catalog + useCatalog(); + + // Create a role with CREATE_SCHEMA privilege + String roleName = currentFunName(); + SecurableObject securableObject = + SecurableObjects.parse( + String.format("%s", catalogName), + MetadataObject.Type.CATALOG, + Lists.newArrayList(Privileges.CreateSchema.allow())); + metalake.createRole(roleName, Collections.emptyMap(), Lists.newArrayList(securableObject)); + + // Granted this role to the spark execution user `HADOOP_USER_NAME` + String userName1 = System.getenv(HADOOP_USER_NAME); + metalake.grantRolesToUser(Lists.newArrayList(roleName), userName1); + waitForUpdatingPolicies(); + + // Create a schema + sparkSession.sql(SQL_CREATE_SCHEMA); + + Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_DROP_SCHEMA)); + + // Set owner + MetadataObject schemaObject = + MetadataObjects.of(catalogName, schemaName, MetadataObject.Type.SCHEMA); + metalake.setOwner(schemaObject, userName1, Owner.Type.USER); + waitForUpdatingPolicies(); + + // Delete a schema + sparkSession.sql(SQL_DROP_SCHEMA); + catalog.asSchemas().dropSchema(schemaName, false); + waitForUpdatingPolicies(); + + // Recreate a schema + sparkSession.sql(SQL_CREATE_SCHEMA); + + Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_DROP_SCHEMA)); + + // Set owner + schemaObject = MetadataObjects.of(catalogName, schemaName, MetadataObject.Type.SCHEMA); + metalake.setOwner(schemaObject, userName1, Owner.Type.USER); + waitForUpdatingPolicies(); + sparkSession.sql(SQL_DROP_SCHEMA); + + // Delete the role and fail to create schema + metalake.deleteRole(roleName); + waitForUpdatingPolicies(); + + sparkSession.sql(SQL_CREATE_SCHEMA); + + // Clean up + catalog.asSchemas().dropSchema(schemaName, false); + } + + @Test + void testRenameMetadataObject() throws InterruptedException { + // Choose a catalog + useCatalog(); + + // Create a role with CREATE_SCHEMA and CREATE_TABLE privilege + String roleName = currentFunName(); + SecurableObject securableObject = + SecurableObjects.parse( + String.format("%s", catalogName), + MetadataObject.Type.CATALOG, + Lists.newArrayList( + Privileges.UseCatalog.allow(), + Privileges.CreateSchema.allow(), + Privileges.CreateTable.allow(), + Privileges.ModifyTable.allow())); + metalake.createRole(roleName, Collections.emptyMap(), Lists.newArrayList(securableObject)); + // Granted this role to the spark execution user `HADOOP_USER_NAME` + String userName1 = System.getenv(HADOOP_USER_NAME); + metalake.grantRolesToUser(Lists.newArrayList(roleName), userName1); + + waitForUpdatingPolicies(); + + // Create a schema and a table + sparkSession.sql(SQL_CREATE_SCHEMA); + sparkSession.sql(SQL_USE_SCHEMA); + sparkSession.sql(SQL_CREATE_TABLE); + + // Rename a table and rename back + sparkSession.sql(SQL_RENAME_TABLE); + sparkSession.sql(SQL_RENAME_BACK_TABLE); + + // Clean up + catalog.asTableCatalog().dropTable(NameIdentifier.of(schemaName, tableName)); + catalog.asSchemas().dropSchema(schemaName, false); + metalake.deleteRole(roleName); + } + + @Test + void testRenameMetadataObjectPrivilege() throws InterruptedException { + // Choose a catalog + useCatalog(); + + // Create a role with CREATE_SCHEMA and CREATE_TABLE privilege + String roleName = currentFunName(); + SecurableObject securableObject = + SecurableObjects.parse( + String.format("%s", catalogName), + MetadataObject.Type.CATALOG, + Lists.newArrayList( + Privileges.UseCatalog.allow(), + Privileges.CreateSchema.allow(), + Privileges.CreateTable.allow(), + Privileges.ModifyTable.allow())); + metalake.createRole(roleName, Collections.emptyMap(), Lists.newArrayList(securableObject)); + // Granted this role to the spark execution user `HADOOP_USER_NAME` + String userName1 = System.getenv(HADOOP_USER_NAME); + metalake.grantRolesToUser(Lists.newArrayList(roleName), userName1); + + waitForUpdatingPolicies(); + + // Create a schema and a table + sparkSession.sql(SQL_CREATE_SCHEMA); + sparkSession.sql(SQL_USE_SCHEMA); + sparkSession.sql(SQL_CREATE_TABLE); + + // Rename a table and rename back + catalog + .asTableCatalog() + .alterTable(NameIdentifier.of(schemaName, tableName), TableChange.rename("new_table")); + + // Succeed to insert data + sparkSession.sql("INSERT INTO new_table (a, b, c) VALUES (1, 'a', 'b')"); + + catalog + .asTableCatalog() + .alterTable(NameIdentifier.of(schemaName, "new_table"), TableChange.rename(tableName)); + + // Succeed to insert data + sparkSession.sql(SQL_INSERT_TABLE); + + // Clean up + catalog.asTableCatalog().dropTable(NameIdentifier.of(schemaName, tableName)); + catalog.asSchemas().dropSchema(schemaName, false); + metalake.deleteRole(roleName); + } + + @Test + void testChangeOwner() throws InterruptedException { + // Choose a catalog + useCatalog(); + + // Create a schema and a table + String helperRole = currentFunName(); + SecurableObject securableObject = + SecurableObjects.ofMetalake( + metalakeName, + Lists.newArrayList( + Privileges.UseSchema.allow(), + Privileges.CreateSchema.allow(), + Privileges.CreateTable.allow(), + Privileges.ModifyTable.allow())); + String userName1 = System.getenv(HADOOP_USER_NAME); + metalake.createRole(helperRole, Collections.emptyMap(), Lists.newArrayList(securableObject)); + metalake.grantRolesToUser(Lists.newArrayList(helperRole), userName1); + waitForUpdatingPolicies(); + + // Create a schema and a table + sparkSession.sql(SQL_CREATE_SCHEMA); + sparkSession.sql(SQL_USE_SCHEMA); + sparkSession.sql(SQL_CREATE_TABLE); + sparkSession.sql(SQL_INSERT_TABLE); + + metalake.revokeRolesFromUser(Lists.newArrayList(helperRole), userName1); + metalake.deleteRole(helperRole); + waitForUpdatingPolicies(); + + checkHaveNoPrivileges(); + + // case 2. user is the table owner + MetadataObject tableObject = + MetadataObjects.of( + Lists.newArrayList(catalogName, schemaName, tableName), MetadataObject.Type.TABLE); + metalake.setOwner(tableObject, userName1, Owner.Type.USER); + waitForUpdatingPolicies(); + + // Owner has all the privileges except for creating table + checkTableAllPrivilegesExceptForCreating(); + + // Delete Gravitino's meta data + catalog.asTableCatalog().dropTable(NameIdentifier.of(schemaName, tableName)); + waitForUpdatingPolicies(); + + // Fail to create the table + Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_CREATE_TABLE)); + + // case 3. user is the schema owner + MetadataObject schemaObject = + MetadataObjects.of(catalogName, schemaName, MetadataObject.Type.SCHEMA); + metalake.setOwner(schemaObject, userName1, Owner.Type.USER); + waitForUpdatingPolicies(); + + // Succeed to create a table + sparkSession.sql(SQL_CREATE_TABLE); + + // Succeed to check other table privileges + checkTableAllPrivilegesExceptForCreating(); + + // Succeed to drop schema + sparkSession.sql(SQL_DROP_SCHEMA); + catalog.asSchemas().dropSchema(schemaName, false); + waitForUpdatingPolicies(); + + // Fail to create schema + Assertions.assertThrows( + AccessControlException.class, () -> sparkSession.sql(SQL_CREATE_SCHEMA)); + + // case 4. user is the catalog owner + MetadataObject catalogObject = + MetadataObjects.of(null, catalogName, MetadataObject.Type.CATALOG); + metalake.setOwner(catalogObject, userName1, Owner.Type.USER); + waitForUpdatingPolicies(); + + // Succeed to create a schema + sparkSession.sql(SQL_CREATE_SCHEMA); + + // Succeed to create a table + sparkSession.sql(SQL_CREATE_TABLE); + + // Succeed to check other table privileges + checkTableAllPrivilegesExceptForCreating(); + + // Succeed to drop schema + sparkSession.sql(SQL_DROP_SCHEMA); + catalog.asSchemas().dropSchema(schemaName, false); + waitForUpdatingPolicies(); + + metalake.setOwner(catalogObject, AuthConstants.ANONYMOUS_USER, Owner.Type.USER); + // case 5. user is the metalake owner + MetadataObject metalakeObject = + MetadataObjects.of(null, metalakeName, MetadataObject.Type.METALAKE); + metalake.setOwner(metalakeObject, userName1, Owner.Type.USER); + waitForUpdatingPolicies(); + + // Succeed to create a schema + sparkSession.sql(SQL_CREATE_SCHEMA); + + // Succeed to create a table + sparkSession.sql(SQL_CREATE_TABLE); + + // Succeed to check other table privileges + checkTableAllPrivilegesExceptForCreating(); + + // Succeed to drop schema + sparkSession.sql(SQL_DROP_SCHEMA); + + // Clean up + catalog.asTableCatalog().dropTable(NameIdentifier.of(schemaName, tableName)); + catalog.asSchemas().dropSchema(schemaName, false); + } + + @Test + void testAllowUseSchemaPrivilege() throws InterruptedException { + // Choose a catalog + useCatalog(); + + // Create a role with CREATE_SCHEMA privilege + String roleName = currentFunName(); + SecurableObject securableObject = + SecurableObjects.parse( + String.format("%s", catalogName), + MetadataObject.Type.CATALOG, + Lists.newArrayList(Privileges.CreateSchema.allow())); + metalake.createRole(roleName, Collections.emptyMap(), Lists.newArrayList(securableObject)); + + // Granted this role to the spark execution user `HADOOP_USER_NAME` + String userName1 = System.getenv(HADOOP_USER_NAME); + metalake.grantRolesToUser(Lists.newArrayList(roleName), userName1); + waitForUpdatingPolicies(); + + // create a schema use Gravitino client + sparkSession.sql(SQL_CREATE_SCHEMA); + + // Revoke the privilege of creating schema + MetadataObject catalogObject = + MetadataObjects.of(null, catalogName, MetadataObject.Type.CATALOG); + metalake.revokePrivilegesFromRole( + roleName, catalogObject, Lists.newArrayList(Privileges.CreateSchema.allow())); + waitForUpdatingPolicies(); + + // Use Spark to show this databases(schema) + Dataset dataset1 = sparkSession.sql(SQL_SHOW_DATABASES); + dataset1.show(); + List rows1 = dataset1.collectAsList(); + // The schema should not be shown, because the user does not have the permission + Assertions.assertEquals( + 0, rows1.stream().filter(row -> row.getString(0).equals(schemaName)).count()); + + // Grant the privilege of using schema + MetadataObject schemaObject = + MetadataObjects.of(catalogName, schemaName, MetadataObject.Type.SCHEMA); + metalake.grantPrivilegesToRole( + roleName, schemaObject, Lists.newArrayList(Privileges.UseSchema.allow())); + waitForUpdatingPolicies(); + + // Use Spark to show this databases(schema) again + Dataset dataset2 = sparkSession.sql(SQL_SHOW_DATABASES); + dataset2.show(100, 100); + List rows2 = dataset2.collectAsList(); + rows2.stream() + .filter(row -> row.getString(0).equals(schemaName)) + .findFirst() + .orElseThrow(() -> new IllegalStateException("Database not found: " + schemaName)); + // The schema should be shown, because the user has the permission + Assertions.assertEquals( + 1, rows2.stream().filter(row -> row.getString(0).equals(schemaName)).count()); + + // Clean up + catalog.asTableCatalog().dropTable(NameIdentifier.of(schemaName, tableName)); + catalog.asSchemas().dropSchema(schemaName, false); + metalake.revokeRolesFromUser(Lists.newArrayList(roleName), userName1); + metalake.deleteRole(roleName); + } + + @Test + void testDenyPrivileges() throws InterruptedException { + // Choose a catalog + useCatalog(); + + // Create a schema + catalog.asSchemas().createSchema(schemaName, "test", Collections.emptyMap()); + + // Create a role with CREATE_SCHEMA privilege + String roleName = currentFunName(); + SecurableObject allowObject = + SecurableObjects.parse( + String.format("%s", catalogName), + MetadataObject.Type.CATALOG, + Lists.newArrayList(Privileges.UseSchema.allow(), Privileges.CreateTable.allow())); + SecurableObject denyObject = + SecurableObjects.parse( + String.format("%s.%s", catalogName, schemaName), + MetadataObject.Type.SCHEMA, + Lists.newArrayList(Privileges.CreateTable.deny())); + // Create a role, catalog allows to create a table, schema denies to create a table + metalake.createRole( + roleName, Collections.emptyMap(), Lists.newArrayList(allowObject, denyObject)); + + // Granted this role to the spark execution user `HADOOP_USER_NAME` + String userName1 = System.getenv(HADOOP_USER_NAME); + metalake.grantRolesToUser(Lists.newArrayList(roleName), userName1); + waitForUpdatingPolicies(); + + // Fail to create a table + sparkSession.sql(SQL_USE_SCHEMA); + Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_CREATE_TABLE)); + + // Delete the role + metalake.deleteRole(roleName); + + // Create another role, but catalog denies to create a table, schema allows to create a table + allowObject = + SecurableObjects.parse( + String.format("%s", catalogName), + MetadataObject.Type.CATALOG, + Lists.newArrayList(Privileges.CreateTable.deny())); + denyObject = + SecurableObjects.parse( + String.format("%s.%s", catalogName, schemaName), + MetadataObject.Type.SCHEMA, + Lists.newArrayList(Privileges.CreateTable.allow())); + metalake.createRole( + roleName, Collections.emptyMap(), Lists.newArrayList(allowObject, denyObject)); + + // Granted this role to the spark execution user `HADOOP_USER_NAME` + userName1 = System.getenv(HADOOP_USER_NAME); + metalake.grantRolesToUser(Lists.newArrayList(roleName), userName1); + + waitForUpdatingPolicies(); + + // Fail to create a table + Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_CREATE_TABLE)); + + // Clean up + catalog.asSchemas().dropSchema(schemaName, false); + metalake.deleteRole(roleName); + } +} diff --git a/authorizations/authorization-ranger/src/test/java/org/apache/gravitino/authorization/ranger/integration/test/RangerHiveE2EIT.java b/authorizations/authorization-ranger/src/test/java/org/apache/gravitino/authorization/ranger/integration/test/RangerHiveE2EIT.java index 409ddf48e22..7e3096a61ae 100644 --- a/authorizations/authorization-ranger/src/test/java/org/apache/gravitino/authorization/ranger/integration/test/RangerHiveE2EIT.java +++ b/authorizations/authorization-ranger/src/test/java/org/apache/gravitino/authorization/ranger/integration/test/RangerHiveE2EIT.java @@ -19,7 +19,6 @@ package org.apache.gravitino.authorization.ranger.integration.test; import static org.apache.gravitino.Catalog.AUTHORIZATION_PROVIDER; -import static org.apache.gravitino.authorization.ranger.integration.test.RangerITEnv.currentFunName; import static org.apache.gravitino.catalog.hive.HiveConstants.IMPERSONATION_ENABLE; import static org.apache.gravitino.connector.AuthorizationPropertiesMeta.RANGER_AUTH_TYPE; import static org.apache.gravitino.connector.AuthorizationPropertiesMeta.RANGER_PASSWORD; @@ -28,106 +27,37 @@ import static org.apache.gravitino.integration.test.container.RangerContainer.RANGER_SERVER_PORT; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Lists; import com.google.common.collect.Maps; -import java.io.File; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; import java.util.Map; -import org.apache.commons.io.FileUtils; import org.apache.gravitino.Catalog; import org.apache.gravitino.Configs; -import org.apache.gravitino.MetadataObject; -import org.apache.gravitino.MetadataObjects; -import org.apache.gravitino.NameIdentifier; import org.apache.gravitino.auth.AuthConstants; import org.apache.gravitino.auth.AuthenticatorType; -import org.apache.gravitino.authorization.Owner; -import org.apache.gravitino.authorization.Privileges; -import org.apache.gravitino.authorization.SecurableObject; -import org.apache.gravitino.authorization.SecurableObjects; import org.apache.gravitino.catalog.hive.HiveConstants; -import org.apache.gravitino.client.GravitinoMetalake; import org.apache.gravitino.connector.AuthorizationPropertiesMeta; import org.apache.gravitino.integration.test.container.HiveContainer; import org.apache.gravitino.integration.test.container.RangerContainer; -import org.apache.gravitino.integration.test.util.BaseIT; import org.apache.gravitino.integration.test.util.GravitinoITUtils; -import org.apache.gravitino.rel.TableChange; import org.apache.kyuubi.plugin.spark.authz.AccessControlException; import org.apache.spark.SparkUnsupportedOperationException; import org.apache.spark.sql.AnalysisException; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; import org.apache.spark.sql.SparkSession; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @Tag("gravitino-docker-test") -public class RangerHiveE2EIT extends BaseIT { +public class RangerHiveE2EIT extends RangerBaseE2EIT { private static final Logger LOG = LoggerFactory.getLogger(RangerHiveE2EIT.class); - public static final String metalakeName = - GravitinoITUtils.genRandomName("metalake").toLowerCase(); - public static final String catalogName = GravitinoITUtils.genRandomName("catalog").toLowerCase(); - public static final String schemaName = GravitinoITUtils.genRandomName("schema").toLowerCase(); - - public static final String tableName = GravitinoITUtils.genRandomName("table").toLowerCase(); - - private static GravitinoMetalake metalake; - private static Catalog catalog; private static final String provider = "hive"; - private static String HIVE_METASTORE_URIS; - - private static SparkSession sparkSession = null; - private static final String HADOOP_USER_NAME = "HADOOP_USER_NAME"; - - private static final String SQL_SHOW_DATABASES = - String.format("SHOW DATABASES like '%s'", schemaName); - - private static final String SQL_CREATE_SCHEMA = String.format("CREATE DATABASE %s", schemaName); - - private static final String SQL_DROP_SCHEMA = String.format("DROP DATABASE %s", schemaName); - - private static final String SQL_USE_SCHEMA = String.format("USE SCHEMA %s", schemaName); - - private static final String SQL_CREATE_TABLE = - String.format("CREATE TABLE %s (a int, b string, c string)", tableName); - - private static final String SQL_INSERT_TABLE = - String.format("INSERT INTO %s (a, b, c) VALUES (1, 'a', 'b')", tableName); - - private static final String SQL_SELECT_TABLE = String.format("SELECT * FROM %s", tableName); - - private static final String SQL_UPDATE_TABLE = - String.format("UPDATE %s SET b = 'b', c = 'c' WHERE a = 1", tableName); - - private static final String SQL_DELETE_TABLE = - String.format("DELETE FROM %s WHERE a = 1", tableName); - - private static final String SQL_ALTER_TABLE = - String.format("ALTER TABLE %s ADD COLUMN d string", tableName); - - private static final String SQL_RENAME_TABLE = - String.format("ALTER TABLE %s RENAME TO new_table", tableName); - - private static final String SQL_RENAME_BACK_TABLE = - String.format("ALTER TABLE new_table RENAME TO %s", tableName); - - private static final String SQL_DROP_TABLE = String.format("DROP TABLE %s", tableName); - - private static String RANGER_ADMIN_URL = null; @BeforeAll public void startIntegrationTest() throws Exception { + metalakeName = GravitinoITUtils.genRandomName("metalake").toLowerCase(); // Enable Gravitino Authorization mode Map configs = Maps.newHashMap(); configs.put(Configs.ENABLE_AUTHORIZATION.getKey(), String.valueOf(true)); @@ -175,184 +105,22 @@ public void startIntegrationTest() throws Exception { createMetalake(); createCatalog(); - metalake.addUser("test"); - } - - private static void generateRangerSparkSecurityXML() throws IOException { - String templatePath = - String.join( - File.separator, - System.getenv("GRAVITINO_ROOT_DIR"), - "authorizations", - "authorization-ranger", - "src", - "test", - "resources", - "ranger-spark-security.xml.template"); - String xmlPath = - String.join( - File.separator, - System.getenv("GRAVITINO_ROOT_DIR"), - "authorizations", - "authorization-ranger", - "build", - "resources", - "test", - "ranger-spark-security.xml"); - - String templateContext = - FileUtils.readFileToString(new File(templatePath), StandardCharsets.UTF_8); - templateContext = - templateContext - .replace("__REPLACE__RANGER_ADMIN_URL", RANGER_ADMIN_URL) - .replace("__REPLACE__RANGER_HIVE_REPO_NAME", RangerITEnv.RANGER_HIVE_REPO_NAME); - FileUtils.writeStringToFile(new File(xmlPath), templateContext, StandardCharsets.UTF_8); + RangerITEnv.cleanup(); + metalake.addUser(System.getenv(HADOOP_USER_NAME)); } @AfterAll public void stop() { - if (client != null) { - Arrays.stream(catalog.asSchemas().listSchemas()) - .filter(schema -> !schema.equals("default")) - .forEach( - (schema -> { - catalog.asSchemas().dropSchema(schema, true); - })); - Arrays.stream(metalake.listCatalogs()) - .forEach((catalogName -> metalake.dropCatalog(catalogName, true))); - client.disableMetalake(metalakeName); - client.dropMetalake(metalakeName); - } - if (sparkSession != null) { - sparkSession.close(); - } - try { - closer.close(); - } catch (Exception e) { - LOG.error("Failed to close CloseableGroup", e); - } - client = null; - RangerITEnv.cleanup(); + cleanIT(); } - @Test - void testCreateSchema() throws InterruptedException { - // First, fail to create the schema - Assertions.assertThrows( - AccessControlException.class, () -> sparkSession.sql(SQL_CREATE_SCHEMA)); - - // Second, grant the `CREATE_SCHEMA` role - String userName1 = System.getenv(HADOOP_USER_NAME); - String roleName = currentFunName(); - SecurableObject securableObject = - SecurableObjects.ofMetalake( - metalakeName, Lists.newArrayList(Privileges.CreateSchema.allow())); - metalake.createRole(roleName, Collections.emptyMap(), Lists.newArrayList(securableObject)); - metalake.grantRolesToUser(Lists.newArrayList(roleName), userName1); - waitForUpdatingPolicies(); - - // Third, succeed to create the schema - sparkSession.sql(SQL_CREATE_SCHEMA); - - // Fourth, fail to create the table - Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_CREATE_TABLE)); - - // Clean up - catalog.asSchemas().dropSchema(schemaName, true); - metalake.deleteRole(roleName); + @Override + protected void useCatalog() throws InterruptedException { + // Do nothing, default catalog is ok for Hive. } - @Test - void testCreateTable() throws InterruptedException { - // First, create a role for creating a database and grant role to the user - String createSchemaRole = currentFunName(); - SecurableObject securableObject = - SecurableObjects.ofMetalake( - metalakeName, - Lists.newArrayList(Privileges.UseSchema.allow(), Privileges.CreateSchema.allow())); - String userName1 = System.getenv(HADOOP_USER_NAME); - metalake.createRole( - createSchemaRole, Collections.emptyMap(), Lists.newArrayList(securableObject)); - metalake.grantRolesToUser(Lists.newArrayList(createSchemaRole), userName1); - waitForUpdatingPolicies(); - // Second, create a schema - sparkSession.sql(SQL_CREATE_SCHEMA); - - // Third, fail to create a table - sparkSession.sql(SQL_USE_SCHEMA); - Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_CREATE_TABLE)); - - // Fourth, create a role for creating a table and grant to the user - String createTableRole = currentFunName() + "2"; - securableObject = - SecurableObjects.ofMetalake( - metalakeName, Lists.newArrayList(Privileges.CreateTable.allow())); - metalake.createRole( - createTableRole, Collections.emptyMap(), Lists.newArrayList(securableObject)); - metalake.grantRolesToUser(Lists.newArrayList(createTableRole), userName1); - waitForUpdatingPolicies(); - - // Fifth, succeed to create a table - sparkSession.sql(SQL_CREATE_TABLE); - - // Sixth, fail to read and write a table - Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_INSERT_TABLE)); - Assertions.assertThrows( - AccessControlException.class, () -> sparkSession.sql(SQL_SELECT_TABLE).collectAsList()); - - // Clean up - catalog.asTableCatalog().dropTable(NameIdentifier.of(schemaName, tableName)); - catalog.asSchemas().dropSchema(schemaName, true); - metalake.deleteRole(createTableRole); - metalake.deleteRole(createSchemaRole); - } - - @Test - void testReadWriteTableWithMetalakeLevelRole() throws InterruptedException { - // First, create a role for creating a database and grant role to the user - String readWriteRole = currentFunName(); - SecurableObject securableObject = - SecurableObjects.ofMetalake( - metalakeName, - Lists.newArrayList( - Privileges.UseSchema.allow(), - Privileges.CreateSchema.allow(), - Privileges.CreateTable.allow(), - Privileges.SelectTable.allow(), - Privileges.ModifyTable.allow())); - String userName1 = System.getenv(HADOOP_USER_NAME); - metalake.createRole(readWriteRole, Collections.emptyMap(), Lists.newArrayList(securableObject)); - metalake.grantRolesToUser(Lists.newArrayList(readWriteRole), userName1); - waitForUpdatingPolicies(); - // Second, create a schema - sparkSession.sql(SQL_CREATE_SCHEMA); - - // Third, create a table - sparkSession.sql(SQL_USE_SCHEMA); - sparkSession.sql(SQL_CREATE_TABLE); - - // case 1: Succeed to insert data into table - sparkSession.sql(SQL_INSERT_TABLE); - - // case 2: Succeed to select data from the table - sparkSession.sql(SQL_SELECT_TABLE).collectAsList(); - - // case 3: Fail to update data in the table, Because Hive doesn't support. - Assertions.assertThrows( - SparkUnsupportedOperationException.class, () -> sparkSession.sql(SQL_UPDATE_TABLE)); - - // case 4: Fail to delete data from the table, Because Hive doesn't support. - Assertions.assertThrows(AnalysisException.class, () -> sparkSession.sql(SQL_DELETE_TABLE)); - - // case 5: Succeed to alter the table - sparkSession.sql(SQL_ALTER_TABLE); - - // case 6: Fail to drop the table - Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_DROP_TABLE)); - - // case 7: If we don't have the role, we can't insert and select from data. - metalake.deleteRole(readWriteRole); - waitForUpdatingPolicies(); + @Override + protected void checkHaveNoPrivileges() { Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_INSERT_TABLE)); Assertions.assertThrows( AccessControlException.class, () -> sparkSession.sql(SQL_SELECT_TABLE).collectAsList()); @@ -362,688 +130,44 @@ void testReadWriteTableWithMetalakeLevelRole() throws InterruptedException { Assertions.assertThrows( AccessControlException.class, () -> sparkSession.sql(SQL_CREATE_SCHEMA)); Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_CREATE_TABLE)); - - // Clean up - catalog.asTableCatalog().dropTable(NameIdentifier.of(schemaName, tableName)); - catalog.asSchemas().dropSchema(schemaName, true); } - @Test - void testReadWriteTableWithTableLevelRole() throws InterruptedException { - // First, create a role for creating a database and grant role to the user - String roleName = currentFunName(); - SecurableObject securableObject = - SecurableObjects.ofMetalake( - metalakeName, - Lists.newArrayList( - Privileges.UseSchema.allow(), - Privileges.CreateSchema.allow(), - Privileges.CreateTable.allow())); - String userName1 = System.getenv(HADOOP_USER_NAME); - metalake.createRole(roleName, Collections.emptyMap(), Lists.newArrayList(securableObject)); - metalake.grantRolesToUser(Lists.newArrayList(roleName), userName1); - waitForUpdatingPolicies(); - // Second, create a schema - sparkSession.sql(SQL_CREATE_SCHEMA); - - // Third, create a table - sparkSession.sql(SQL_USE_SCHEMA); - sparkSession.sql(SQL_CREATE_TABLE); - - // Fourth, revoke and grant a table level role - metalake.deleteRole(roleName); - securableObject = - SecurableObjects.parse( - String.format("%s.%s.%s", catalogName, schemaName, tableName), - MetadataObject.Type.TABLE, - Lists.newArrayList(Privileges.ModifyTable.allow(), Privileges.SelectTable.allow())); - metalake.createRole(roleName, Collections.emptyMap(), Lists.newArrayList(securableObject)); - metalake.grantRolesToUser(Lists.newArrayList(roleName), userName1); - waitForUpdatingPolicies(); - - // case 1: Succeed to insert data into table - sparkSession.sql(SQL_INSERT_TABLE); - - // case 2: Succeed to select data from the table - sparkSession.sql(SQL_SELECT_TABLE).collectAsList(); - - // case 3: Fail to update data in the table, Because Hive doesn't support. + @Override + protected void checkUpdateSQLWithReadWritePrivileges() { Assertions.assertThrows( SparkUnsupportedOperationException.class, () -> sparkSession.sql(SQL_UPDATE_TABLE)); - - // case 4: Fail to delete data from the table, Because Hive doesn't support. - Assertions.assertThrows(AnalysisException.class, () -> sparkSession.sql(SQL_DELETE_TABLE)); - - // case 5: Succeed to alter the table - sparkSession.sql(SQL_ALTER_TABLE); - - // case 6: Fail to drop the table - Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_DROP_TABLE)); - - // case 7: If we don't have the role, we can't insert and select from data. - metalake.deleteRole(roleName); - waitForUpdatingPolicies(); - Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_INSERT_TABLE)); - Assertions.assertThrows( - AccessControlException.class, () -> sparkSession.sql(SQL_SELECT_TABLE).collectAsList()); - Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_DROP_TABLE)); - Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_DROP_SCHEMA)); - Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_USE_SCHEMA)); - Assertions.assertThrows( - AccessControlException.class, () -> sparkSession.sql(SQL_CREATE_SCHEMA)); - Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_CREATE_TABLE)); - - // Clean up - catalog.asTableCatalog().dropTable(NameIdentifier.of(schemaName, tableName)); - catalog.asSchemas().dropSchema(schemaName, true); } - @Test - void testReadOnlyTable() throws InterruptedException { - // First, create a role for creating a database and grant role to the user - String readOnlyRole = currentFunName(); - SecurableObject securableObject = - SecurableObjects.ofMetalake( - metalakeName, - Lists.newArrayList( - Privileges.UseSchema.allow(), - Privileges.CreateSchema.allow(), - Privileges.CreateTable.allow(), - Privileges.SelectTable.allow())); - String userName1 = System.getenv(HADOOP_USER_NAME); - metalake.createRole(readOnlyRole, Collections.emptyMap(), Lists.newArrayList(securableObject)); - metalake.grantRolesToUser(Lists.newArrayList(readOnlyRole), userName1); - waitForUpdatingPolicies(); - // Second, create a schema - sparkSession.sql(SQL_CREATE_SCHEMA); - - // Third, create a table - sparkSession.sql(SQL_USE_SCHEMA); - sparkSession.sql(SQL_CREATE_TABLE); - - // case 1: Fail to insert data into table - Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_INSERT_TABLE)); - - // case 2: Succeed to select data from the table - sparkSession.sql(SQL_SELECT_TABLE).collectAsList(); - - // case 3: Fail to alter data in the table + @Override + protected void checkUpdateSQLWithReadPrivileges() { Assertions.assertThrows( SparkUnsupportedOperationException.class, () -> sparkSession.sql(SQL_UPDATE_TABLE)); - - // case 4: Fail to delete data from the table - Assertions.assertThrows(AnalysisException.class, () -> sparkSession.sql(SQL_DELETE_TABLE)); - - // case 5: Fail to alter the table - Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_ALTER_TABLE)); - - // case 6: Fail to drop the table - Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_DROP_TABLE)); - - // case 7: If we don't have the role, we can't insert and select from data. - metalake.deleteRole(readOnlyRole); - waitForUpdatingPolicies(); - Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_INSERT_TABLE)); - Assertions.assertThrows( - AccessControlException.class, () -> sparkSession.sql(SQL_SELECT_TABLE).collectAsList()); - Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_DROP_TABLE)); - Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_DROP_SCHEMA)); - Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_USE_SCHEMA)); - Assertions.assertThrows( - AccessControlException.class, () -> sparkSession.sql(SQL_CREATE_SCHEMA)); - Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_CREATE_TABLE)); - - // Clean up - catalog.asTableCatalog().dropTable(NameIdentifier.of(schemaName, tableName)); - catalog.asSchemas().dropSchema(schemaName, true); } - @Test - void testWriteOnlyTable() throws InterruptedException { - // First, create a role for creating a database and grant role to the user - String writeOnlyRole = currentFunName(); - SecurableObject securableObject = - SecurableObjects.ofMetalake( - metalakeName, - Lists.newArrayList( - Privileges.UseSchema.allow(), - Privileges.CreateSchema.allow(), - Privileges.CreateTable.allow(), - Privileges.ModifyTable.allow())); - String userName1 = System.getenv(HADOOP_USER_NAME); - metalake.createRole(writeOnlyRole, Collections.emptyMap(), Lists.newArrayList(securableObject)); - metalake.grantRolesToUser(Lists.newArrayList(writeOnlyRole), userName1); - waitForUpdatingPolicies(); - // Second, create a schema - sparkSession.sql(SQL_CREATE_SCHEMA); - - // Third, create a table - sparkSession.sql(SQL_USE_SCHEMA); - sparkSession.sql(SQL_CREATE_TABLE); - - // case 1: Succeed to insert data into the table - sparkSession.sql(SQL_INSERT_TABLE); - - // case 2: Fail to select data from the table - Assertions.assertThrows( - AccessControlException.class, () -> sparkSession.sql(SQL_SELECT_TABLE).collectAsList()); - - // case 3: Succeed to update data in the table + @Override + protected void checkUpdateSQLWithWritePrivileges() { Assertions.assertThrows( SparkUnsupportedOperationException.class, () -> sparkSession.sql(SQL_UPDATE_TABLE)); - - // case 4: Succeed to delete data from the table - Assertions.assertThrows(AnalysisException.class, () -> sparkSession.sql(SQL_DELETE_TABLE)); - - // case 5: Succeed to alter the table - sparkSession.sql(SQL_ALTER_TABLE); - - // case 6: Fail to drop the table - Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_DROP_TABLE)); - - // case 7: If we don't have the role, we can't insert and select from data. - metalake.deleteRole(writeOnlyRole); - waitForUpdatingPolicies(); - Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_INSERT_TABLE)); - Assertions.assertThrows( - AccessControlException.class, () -> sparkSession.sql(SQL_SELECT_TABLE).collectAsList()); - Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_DROP_TABLE)); - Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_DROP_SCHEMA)); - Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_USE_SCHEMA)); - Assertions.assertThrows( - AccessControlException.class, () -> sparkSession.sql(SQL_CREATE_SCHEMA)); - Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_CREATE_TABLE)); - - // Clean up - catalog.asTableCatalog().dropTable(NameIdentifier.of(schemaName, tableName)); - catalog.asSchemas().dropSchema(schemaName, true); - } - - @Test - void testCreateAllPrivilegesRole() throws InterruptedException { - String roleName = currentFunName(); - SecurableObject securableObject = - SecurableObjects.ofMetalake( - metalakeName, - Lists.newArrayList( - Privileges.CreateCatalog.allow(), - Privileges.UseCatalog.allow(), - Privileges.UseSchema.allow(), - Privileges.CreateSchema.allow(), - Privileges.CreateFileset.allow(), - Privileges.ReadFileset.allow(), - Privileges.WriteFileset.allow(), - Privileges.CreateTopic.allow(), - Privileges.ConsumeTopic.allow(), - Privileges.ProduceTopic.allow(), - Privileges.CreateTable.allow(), - Privileges.SelectTable.allow(), - Privileges.ModifyTable.allow(), - Privileges.ManageUsers.allow(), - Privileges.ManageGroups.allow(), - Privileges.CreateRole.allow())); - metalake.createRole(roleName, Collections.emptyMap(), Lists.newArrayList(securableObject)); - - // Granted this role to the spark execution user `HADOOP_USER_NAME` - String userName1 = System.getenv(HADOOP_USER_NAME); - metalake.grantRolesToUser(Lists.newArrayList(roleName), userName1); - - waitForUpdatingPolicies(); - - // Test to create the schema - sparkSession.sql(SQL_CREATE_SCHEMA); - - // Test to create a table - sparkSession.sql(SQL_USE_SCHEMA); - sparkSession.sql(SQL_CREATE_TABLE); - - // Clean up - catalog.asTableCatalog().dropTable(NameIdentifier.of(schemaName, tableName)); - catalog.asSchemas().dropSchema(schemaName, true); - metalake.deleteRole(roleName); } - @Test - void testDeleteAndRecreateRole() throws InterruptedException { - // Fail to create schema - Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_DROP_SCHEMA)); - - // Create a role with CREATE_SCHEMA privilege - String roleName = currentFunName(); - SecurableObject securableObject = - SecurableObjects.parse( - String.format("%s", catalogName), - MetadataObject.Type.CATALOG, - Lists.newArrayList(Privileges.UseCatalog.allow(), Privileges.CreateSchema.allow())); - metalake.createRole(roleName, Collections.emptyMap(), Lists.newArrayList(securableObject)); - - // Granted this role to the spark execution user `HADOOP_USER_NAME` - String userName1 = System.getenv(HADOOP_USER_NAME); - metalake.grantRolesToUser(Lists.newArrayList(roleName), userName1); - waitForUpdatingPolicies(); - - // Succeed to create the schema - sparkSession.sql(SQL_CREATE_SCHEMA); - catalog.asSchemas().dropSchema(schemaName, true); - - // Delete the role - metalake.deleteRole(roleName); - waitForUpdatingPolicies(); - - // Fail to create the schema - Assertions.assertThrows( - AccessControlException.class, () -> sparkSession.sql(SQL_CREATE_SCHEMA)); - - // Create the role again - metalake.createRole(roleName, Collections.emptyMap(), Lists.newArrayList(securableObject)); - - // Grant the role again - metalake.grantRolesToUser(Lists.newArrayList(roleName), userName1); - waitForUpdatingPolicies(); - - // Succeed to create the schema - sparkSession.sql(SQL_CREATE_SCHEMA); - - // Clean up - catalog.asSchemas().dropSchema(schemaName, true); - metalake.deleteRole(roleName); - } - - @Test - void testDeleteAndRecreateMetadataObject() throws InterruptedException { - // Create a role with CREATE_SCHEMA privilege - String roleName = currentFunName(); - SecurableObject securableObject = - SecurableObjects.parse( - String.format("%s", catalogName), - MetadataObject.Type.CATALOG, - Lists.newArrayList(Privileges.CreateSchema.allow())); - metalake.createRole(roleName, Collections.emptyMap(), Lists.newArrayList(securableObject)); - - // Granted this role to the spark execution user `HADOOP_USER_NAME` - String userName1 = System.getenv(HADOOP_USER_NAME); - metalake.grantRolesToUser(Lists.newArrayList(roleName), userName1); - waitForUpdatingPolicies(); - - // Create a schema - sparkSession.sql(SQL_CREATE_SCHEMA); - - Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_DROP_SCHEMA)); - - // Set owner - MetadataObject schemaObject = - MetadataObjects.of(catalogName, schemaName, MetadataObject.Type.SCHEMA); - metalake.setOwner(schemaObject, userName1, Owner.Type.USER); - waitForUpdatingPolicies(); - - // Delete a schema - sparkSession.sql(SQL_DROP_SCHEMA); - catalog.asSchemas().dropSchema(schemaName, true); - waitForUpdatingPolicies(); - - // Recreate a schema - sparkSession.sql(SQL_CREATE_SCHEMA); - - Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_DROP_SCHEMA)); - - // Set owner - schemaObject = MetadataObjects.of(catalogName, schemaName, MetadataObject.Type.SCHEMA); - metalake.setOwner(schemaObject, userName1, Owner.Type.USER); - waitForUpdatingPolicies(); - sparkSession.sql(SQL_DROP_SCHEMA); - - // Delete the role and fail to create schema - metalake.deleteRole(roleName); - waitForUpdatingPolicies(); - - sparkSession.sql(SQL_CREATE_SCHEMA); - - // Clean up - catalog.asSchemas().dropSchema(schemaName, true); - } - - @Test - void testRenameMetadataObject() throws InterruptedException { - // Create a role with CREATE_SCHEMA and CREATE_TABLE privilege - String roleName = currentFunName(); - SecurableObject securableObject = - SecurableObjects.parse( - String.format("%s", catalogName), - MetadataObject.Type.CATALOG, - Lists.newArrayList( - Privileges.UseCatalog.allow(), - Privileges.CreateSchema.allow(), - Privileges.CreateTable.allow(), - Privileges.ModifyTable.allow())); - metalake.createRole(roleName, Collections.emptyMap(), Lists.newArrayList(securableObject)); - // Granted this role to the spark execution user `HADOOP_USER_NAME` - String userName1 = System.getenv(HADOOP_USER_NAME); - metalake.grantRolesToUser(Lists.newArrayList(roleName), userName1); - - waitForUpdatingPolicies(); - - // Create a schema and a table - sparkSession.sql(SQL_CREATE_SCHEMA); - sparkSession.sql(SQL_USE_SCHEMA); - sparkSession.sql(SQL_CREATE_TABLE); - - // Rename a table and rename back - sparkSession.sql(SQL_RENAME_TABLE); - sparkSession.sql(SQL_RENAME_BACK_TABLE); - - // Clean up - catalog.asTableCatalog().dropTable(NameIdentifier.of(schemaName, tableName)); - catalog.asSchemas().dropSchema(schemaName, true); - metalake.deleteRole(roleName); - } - - @Test - void testRenameMetadataObjectPrivilege() throws InterruptedException { - // Create a role with CREATE_SCHEMA and CREATE_TABLE privilege - String roleName = currentFunName(); - SecurableObject securableObject = - SecurableObjects.parse( - String.format("%s", catalogName), - MetadataObject.Type.CATALOG, - Lists.newArrayList( - Privileges.UseCatalog.allow(), - Privileges.CreateSchema.allow(), - Privileges.CreateTable.allow(), - Privileges.ModifyTable.allow())); - metalake.createRole(roleName, Collections.emptyMap(), Lists.newArrayList(securableObject)); - // Granted this role to the spark execution user `HADOOP_USER_NAME` - String userName1 = System.getenv(HADOOP_USER_NAME); - metalake.grantRolesToUser(Lists.newArrayList(roleName), userName1); - - waitForUpdatingPolicies(); - - // Create a schema and a table - sparkSession.sql(SQL_CREATE_SCHEMA); - sparkSession.sql(SQL_USE_SCHEMA); - sparkSession.sql(SQL_CREATE_TABLE); - - // Rename a table and rename back - catalog - .asTableCatalog() - .alterTable(NameIdentifier.of(schemaName, tableName), TableChange.rename("new_table")); - - // Succeed to insert data - sparkSession.sql("INSERT INTO new_table (a, b, c) VALUES (1, 'a', 'b')"); - - catalog - .asTableCatalog() - .alterTable(NameIdentifier.of(schemaName, "new_table"), TableChange.rename(tableName)); - - // Succeed to insert data - sparkSession.sql(SQL_INSERT_TABLE); - - // Clean up - catalog.asTableCatalog().dropTable(NameIdentifier.of(schemaName, tableName)); - catalog.asSchemas().dropSchema(schemaName, true); - metalake.deleteRole(roleName); - } - - @Test - void testChangeOwner() throws InterruptedException { - // Create a schema and a table - String helperRole = currentFunName(); - SecurableObject securableObject = - SecurableObjects.ofMetalake( - metalakeName, - Lists.newArrayList( - Privileges.UseSchema.allow(), - Privileges.CreateSchema.allow(), - Privileges.CreateTable.allow(), - Privileges.ModifyTable.allow())); - String userName1 = System.getenv(HADOOP_USER_NAME); - metalake.createRole(helperRole, Collections.emptyMap(), Lists.newArrayList(securableObject)); - metalake.grantRolesToUser(Lists.newArrayList(helperRole), userName1); - waitForUpdatingPolicies(); - - // Create a schema and a table - sparkSession.sql(SQL_CREATE_SCHEMA); - sparkSession.sql(SQL_USE_SCHEMA); - sparkSession.sql(SQL_CREATE_TABLE); - sparkSession.sql(SQL_INSERT_TABLE); - - metalake.revokeRolesFromUser(Lists.newArrayList(helperRole), userName1); - metalake.deleteRole(helperRole); - waitForUpdatingPolicies(); - - // case 1. Have none of privileges of the table - - // - a. Fail to insert data into the table - Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_INSERT_TABLE)); - - // - b. Fail to select data from the table - Assertions.assertThrows( - AccessControlException.class, () -> sparkSession.sql(SQL_SELECT_TABLE).collectAsList()); - - // - c: Fail to update data in the table - Assertions.assertThrows( - SparkUnsupportedOperationException.class, () -> sparkSession.sql(SQL_UPDATE_TABLE)); - - // - d: Fail to delete data from the table + @Override + protected void checkDeleteSQLWithReadWritePrivileges() { Assertions.assertThrows(AnalysisException.class, () -> sparkSession.sql(SQL_DELETE_TABLE)); - - // - e: Fail to alter the table - Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_ALTER_TABLE)); - - // - f: Fail to drop the table - Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_DROP_TABLE)); - - // case 2. user is the table owner - MetadataObject tableObject = - MetadataObjects.of( - Lists.newArrayList(catalogName, schemaName, tableName), MetadataObject.Type.TABLE); - metalake.setOwner(tableObject, userName1, Owner.Type.USER); - waitForUpdatingPolicies(); - - // Owner has all the privileges except for creating table - checkTableAllPrivilegesExceptForCreating(); - - // Delete Gravitino's meta data - catalog.asTableCatalog().dropTable(NameIdentifier.of(schemaName, tableName)); - waitForUpdatingPolicies(); - - // Fail to create the table - Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_CREATE_TABLE)); - - // case 3. user is the schema owner - MetadataObject schemaObject = - MetadataObjects.of(catalogName, schemaName, MetadataObject.Type.SCHEMA); - metalake.setOwner(schemaObject, userName1, Owner.Type.USER); - waitForUpdatingPolicies(); - - // Succeed to create a table - sparkSession.sql(SQL_CREATE_TABLE); - - // Succeed to check other table privileges - checkTableAllPrivilegesExceptForCreating(); - - // Succeed to drop schema - sparkSession.sql(SQL_DROP_SCHEMA); - catalog.asSchemas().dropSchema(schemaName, true); - waitForUpdatingPolicies(); - - // Fail to create schema - Assertions.assertThrows( - AccessControlException.class, () -> sparkSession.sql(SQL_CREATE_SCHEMA)); - - // case 4. user is the catalog owner - MetadataObject catalogObject = - MetadataObjects.of(null, catalogName, MetadataObject.Type.CATALOG); - metalake.setOwner(catalogObject, userName1, Owner.Type.USER); - waitForUpdatingPolicies(); - - // Succeed to create a schema - sparkSession.sql(SQL_CREATE_SCHEMA); - - // Succeed to create a table - sparkSession.sql(SQL_CREATE_TABLE); - - // Succeed to check other table privileges - checkTableAllPrivilegesExceptForCreating(); - - // Succeed to drop schema - sparkSession.sql(SQL_DROP_SCHEMA); - catalog.asSchemas().dropSchema(schemaName, true); - waitForUpdatingPolicies(); - - metalake.setOwner(catalogObject, AuthConstants.ANONYMOUS_USER, Owner.Type.USER); - // case 5. user is the metalake owner - MetadataObject metalakeObject = - MetadataObjects.of(null, metalakeName, MetadataObject.Type.METALAKE); - metalake.setOwner(metalakeObject, userName1, Owner.Type.USER); - waitForUpdatingPolicies(); - - // Succeed to create a schema - sparkSession.sql(SQL_CREATE_SCHEMA); - - // Succeed to create a table - sparkSession.sql(SQL_CREATE_TABLE); - - // Succeed to check other table privileges - checkTableAllPrivilegesExceptForCreating(); - - // Succeed to drop schema - sparkSession.sql(SQL_DROP_SCHEMA); - - // Clean up - catalog.asTableCatalog().dropTable(NameIdentifier.of(schemaName, tableName)); - catalog.asSchemas().dropSchema(schemaName, true); } - @Test - void testAllowUseSchemaPrivilege() throws InterruptedException { - // Create a role with CREATE_SCHEMA privilege - String roleName = currentFunName(); - SecurableObject securableObject = - SecurableObjects.parse( - String.format("%s", catalogName), - MetadataObject.Type.CATALOG, - Lists.newArrayList(Privileges.CreateSchema.allow())); - metalake.createRole(roleName, Collections.emptyMap(), Lists.newArrayList(securableObject)); - - // Granted this role to the spark execution user `HADOOP_USER_NAME` - String userName1 = System.getenv(HADOOP_USER_NAME); - metalake.grantRolesToUser(Lists.newArrayList(roleName), userName1); - waitForUpdatingPolicies(); - - // create a schema use Gravitino client - sparkSession.sql(SQL_CREATE_SCHEMA); - - // Revoke the privilege of creating schema - MetadataObject catalogObject = - MetadataObjects.of(null, catalogName, MetadataObject.Type.CATALOG); - metalake.revokePrivilegesFromRole( - roleName, catalogObject, Lists.newArrayList(Privileges.CreateSchema.allow())); - waitForUpdatingPolicies(); - - // Use Spark to show this databases(schema) - Dataset dataset1 = sparkSession.sql(SQL_SHOW_DATABASES); - dataset1.show(); - List rows1 = dataset1.collectAsList(); - // The schema should not be shown, because the user does not have the permission - Assertions.assertEquals( - 0, rows1.stream().filter(row -> row.getString(0).equals(schemaName)).count()); - - // Grant the privilege of using schema - MetadataObject schemaObject = - MetadataObjects.of(catalogName, schemaName, MetadataObject.Type.SCHEMA); - metalake.grantPrivilegesToRole( - roleName, schemaObject, Lists.newArrayList(Privileges.UseSchema.allow())); - waitForUpdatingPolicies(); - - // Use Spark to show this databases(schema) again - Dataset dataset2 = sparkSession.sql(SQL_SHOW_DATABASES); - dataset2.show(100, 100); - List rows2 = dataset2.collectAsList(); - rows2.stream() - .filter(row -> row.getString(0).equals(schemaName)) - .findFirst() - .orElseThrow(() -> new IllegalStateException("Database not found: " + schemaName)); - // The schema should be shown, because the user has the permission - Assertions.assertEquals( - 1, rows2.stream().filter(row -> row.getString(0).equals(schemaName)).count()); - - // Clean up - catalog.asTableCatalog().dropTable(NameIdentifier.of(schemaName, tableName)); - catalog.asSchemas().dropSchema(schemaName, true); - metalake.revokeRolesFromUser(Lists.newArrayList(roleName), userName1); - metalake.deleteRole(roleName); + @Override + protected void checkDeleteSQLWithReadPrivileges() { + Assertions.assertThrows(AnalysisException.class, () -> sparkSession.sql(SQL_DELETE_TABLE)); } - @Test - void testDenyPrivileges() throws InterruptedException { - // Create a schema - catalog.asSchemas().createSchema(schemaName, "test", Collections.emptyMap()); - - // Create a role with CREATE_SCHEMA privilege - String roleName = currentFunName(); - SecurableObject allowObject = - SecurableObjects.parse( - String.format("%s", catalogName), - MetadataObject.Type.CATALOG, - Lists.newArrayList(Privileges.UseSchema.allow(), Privileges.CreateTable.allow())); - SecurableObject denyObject = - SecurableObjects.parse( - String.format("%s.%s", catalogName, schemaName), - MetadataObject.Type.SCHEMA, - Lists.newArrayList(Privileges.CreateTable.deny())); - // Create a role, catalog allows to create a table, schema denies to create a table - metalake.createRole( - roleName, Collections.emptyMap(), Lists.newArrayList(allowObject, denyObject)); - - // Granted this role to the spark execution user `HADOOP_USER_NAME` - String userName1 = System.getenv(HADOOP_USER_NAME); - metalake.grantRolesToUser(Lists.newArrayList(roleName), userName1); - waitForUpdatingPolicies(); - - // Fail to create a table - sparkSession.sql(SQL_USE_SCHEMA); - Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_CREATE_TABLE)); - - // Delete the role - metalake.deleteRole(roleName); - - // Create another role, but catalog denies to create a table, schema allows to create a table - allowObject = - SecurableObjects.parse( - String.format("%s", catalogName), - MetadataObject.Type.CATALOG, - Lists.newArrayList(Privileges.CreateTable.deny())); - denyObject = - SecurableObjects.parse( - String.format("%s.%s", catalogName, schemaName), - MetadataObject.Type.SCHEMA, - Lists.newArrayList(Privileges.CreateTable.allow())); - metalake.createRole( - roleName, Collections.emptyMap(), Lists.newArrayList(allowObject, denyObject)); - - // Granted this role to the spark execution user `HADOOP_USER_NAME` - userName1 = System.getenv(HADOOP_USER_NAME); - metalake.grantRolesToUser(Lists.newArrayList(roleName), userName1); - - waitForUpdatingPolicies(); - - // Fail to create a table - Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_CREATE_TABLE)); - - // Clean up - catalog.asSchemas().dropSchema(schemaName, true); - metalake.deleteRole(roleName); + @Override + protected void checkDeleteSQLWithWritePrivileges() { + Assertions.assertThrows(AnalysisException.class, () -> sparkSession.sql(SQL_DELETE_TABLE)); } - private void createMetalake() { - GravitinoMetalake[] gravitinoMetalakes = client.listMetalakes(); - Assertions.assertEquals(0, gravitinoMetalakes.length); - - client.createMetalake(metalakeName, "comment", Collections.emptyMap()); - GravitinoMetalake loadMetalake = client.loadMetalake(metalakeName); - Assertions.assertEquals(metalakeName, loadMetalake.name()); - - metalake = loadMetalake; + @Override + protected void testAlterTable() { + sparkSession.sql(SQL_ALTER_TABLE); } private static void createCatalog() { @@ -1071,7 +195,7 @@ private static void createCatalog() { LOG.info("Catalog created: {}", catalog); } - private void checkTableAllPrivilegesExceptForCreating() { + protected void checkTableAllPrivilegesExceptForCreating() { // - a. Succeed to insert data into the table sparkSession.sql(SQL_INSERT_TABLE); @@ -1091,12 +215,4 @@ private void checkTableAllPrivilegesExceptForCreating() { // - f: Succeed to drop the table sparkSession.sql(SQL_DROP_TABLE); } - - private static void waitForUpdatingPolicies() throws InterruptedException { - // After Ranger authorization, Must wait a period of time for the Ranger Spark plugin to update - // the policy Sleep time must be greater than the policy update interval - // (ranger.plugin.spark.policy.pollIntervalMs) in the - // `resources/ranger-spark-security.xml.template` - Thread.sleep(1000L); - } } 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 fdc2d8fab18..31ae3974d29 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 @@ -30,7 +30,7 @@ import java.util.stream.Collectors; import org.apache.gravitino.authorization.Privilege; import org.apache.gravitino.authorization.Role; -import org.apache.gravitino.authorization.ranger.RangerAuthorizationHivePlugin; +import org.apache.gravitino.authorization.ranger.RangerAuthorizationHadoopSQLPlugin; import org.apache.gravitino.authorization.ranger.RangerAuthorizationPlugin; import org.apache.gravitino.authorization.ranger.RangerHelper; import org.apache.gravitino.authorization.ranger.RangerPrivileges; @@ -89,7 +89,7 @@ public static void init() { rangerClient = containerSuite.getRangerContainer().rangerClient; rangerAuthHivePlugin = - RangerAuthorizationHivePlugin.getInstance( + RangerAuthorizationHadoopSQLPlugin.getInstance( ImmutableMap.of( AuthorizationPropertiesMeta.RANGER_ADMIN_URL, String.format( diff --git a/authorizations/authorization-ranger/src/test/java/org/apache/gravitino/authorization/ranger/integration/test/RangerIcebergE2EIT.java b/authorizations/authorization-ranger/src/test/java/org/apache/gravitino/authorization/ranger/integration/test/RangerIcebergE2EIT.java new file mode 100644 index 00000000000..648a9c4d709 --- /dev/null +++ b/authorizations/authorization-ranger/src/test/java/org/apache/gravitino/authorization/ranger/integration/test/RangerIcebergE2EIT.java @@ -0,0 +1,236 @@ +/* + * 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.integration.test; + +import static org.apache.gravitino.Catalog.AUTHORIZATION_PROVIDER; +import static org.apache.gravitino.authorization.ranger.integration.test.RangerITEnv.currentFunName; +import static org.apache.gravitino.catalog.hive.HiveConstants.IMPERSONATION_ENABLE; +import static org.apache.gravitino.connector.AuthorizationPropertiesMeta.RANGER_AUTH_TYPE; +import static org.apache.gravitino.connector.AuthorizationPropertiesMeta.RANGER_PASSWORD; +import static org.apache.gravitino.connector.AuthorizationPropertiesMeta.RANGER_SERVICE_NAME; +import static org.apache.gravitino.connector.AuthorizationPropertiesMeta.RANGER_USERNAME; +import static org.apache.gravitino.integration.test.container.RangerContainer.RANGER_SERVER_PORT; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import java.util.Collections; +import java.util.Map; +import org.apache.gravitino.Catalog; +import org.apache.gravitino.Configs; +import org.apache.gravitino.auth.AuthConstants; +import org.apache.gravitino.auth.AuthenticatorType; +import org.apache.gravitino.authorization.Privileges; +import org.apache.gravitino.authorization.SecurableObject; +import org.apache.gravitino.authorization.SecurableObjects; +import org.apache.gravitino.catalog.lakehouse.iceberg.IcebergConstants; +import org.apache.gravitino.connector.AuthorizationPropertiesMeta; +import org.apache.gravitino.integration.test.container.HiveContainer; +import org.apache.gravitino.integration.test.container.RangerContainer; +import org.apache.gravitino.integration.test.util.GravitinoITUtils; +import org.apache.kyuubi.plugin.spark.authz.AccessControlException; +import org.apache.spark.sql.SparkSession; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Tag("gravitino-docker-test") +public class RangerIcebergE2EIT extends RangerBaseE2EIT { + private static final Logger LOG = LoggerFactory.getLogger(RangerIcebergE2EIT.class); + private static final String SQL_USE_CATALOG = "USE iceberg"; + private static final String provider = "lakehouse-iceberg"; + + @BeforeAll + public void startIntegrationTest() throws Exception { + metalakeName = GravitinoITUtils.genRandomName("metalake").toLowerCase(); + // Enable Gravitino Authorization mode + Map configs = Maps.newHashMap(); + configs.put(Configs.ENABLE_AUTHORIZATION.getKey(), String.valueOf(true)); + configs.put(Configs.SERVICE_ADMINS.getKey(), RangerITEnv.HADOOP_USER_NAME); + configs.put(Configs.AUTHENTICATORS.getKey(), AuthenticatorType.SIMPLE.name().toLowerCase()); + configs.put("SimpleAuthUserName", AuthConstants.ANONYMOUS_USER); + registerCustomConfigs(configs); + super.startIntegrationTest(); + + RangerITEnv.init(); + RangerITEnv.startHiveRangerContainer(); + + RANGER_ADMIN_URL = + String.format( + "http://%s:%d", + containerSuite.getRangerContainer().getContainerIpAddress(), RANGER_SERVER_PORT); + + HIVE_METASTORE_URIS = + String.format( + "thrift://%s:%d", + containerSuite.getHiveRangerContainer().getContainerIpAddress(), + HiveContainer.HIVE_METASTORE_PORT); + + generateRangerSparkSecurityXML(); + + sparkSession = + SparkSession.builder() + .master("local[1]") + .appName("Ranger Hive E2E integration test") + .config("spark.sql.catalog.iceberg", "org.apache.iceberg.spark.SparkCatalog") + .config("spark.sql.catalog.iceberg.type", "hive") + .config("spark.sql.catalog.iceberg.uri", HIVE_METASTORE_URIS) + .config("spark.sql.catalog.iceberg.cache-enabled", "false") + .config( + "spark.sql.extensions", + "org.apache.kyuubi.plugin.spark.authz.ranger.RangerSparkExtension," + + "org.apache.iceberg.spark.extensions.IcebergSparkSessionExtensions") + .enableHiveSupport() + .getOrCreate(); + + createMetalake(); + createCatalog(); + + metalake.addUser(System.getenv(HADOOP_USER_NAME)); + + RangerITEnv.cleanup(); + waitForUpdatingPolicies(); + } + + @AfterAll + public void stop() { + cleanIT(); + } + + @Override + protected void checkUpdateSQLWithReadWritePrivileges() { + sparkSession.sql(SQL_UPDATE_TABLE); + } + + @Override + protected void checkUpdateSQLWithReadPrivileges() { + Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_UPDATE_TABLE)); + } + + @Override + protected void checkUpdateSQLWithWritePrivileges() { + Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_UPDATE_TABLE)); + } + + @Override + protected void checkDeleteSQLWithReadWritePrivileges() { + sparkSession.sql(SQL_DELETE_TABLE); + } + + @Override + protected void checkDeleteSQLWithReadPrivileges() { + Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_DELETE_TABLE)); + } + + @Override + protected void checkDeleteSQLWithWritePrivileges() { + Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_DELETE_TABLE)); + } + + @Override + protected void checkHaveNoPrivileges() { + Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_INSERT_TABLE)); + Assertions.assertThrows( + AccessControlException.class, () -> sparkSession.sql(SQL_SELECT_TABLE).collectAsList()); + Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_DROP_TABLE)); + Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_DROP_SCHEMA)); + Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_USE_SCHEMA)); + Assertions.assertThrows( + AccessControlException.class, () -> sparkSession.sql(SQL_CREATE_SCHEMA)); + Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_CREATE_TABLE)); + Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_DELETE_TABLE)); + Assertions.assertThrows(AccessControlException.class, () -> sparkSession.sql(SQL_UPDATE_TABLE)); + } + + @Override + protected void testAlterTable() { + sparkSession.sql(SQL_ALTER_TABLE); + sparkSession.sql(SQL_ALTER_TABLE_BACK); + } + + private static void createCatalog() { + Map properties = + ImmutableMap.of( + IcebergConstants.URI, + HIVE_METASTORE_URIS, + IcebergConstants.CATALOG_BACKEND, + "hive", + IcebergConstants.WAREHOUSE, + String.format( + "hdfs://%s:%d/user/hive/warehouse", + containerSuite.getHiveRangerContainer().getContainerIpAddress(), + HiveContainer.HDFS_DEFAULTFS_PORT), + IMPERSONATION_ENABLE, + "true", + AUTHORIZATION_PROVIDER, + "ranger", + RANGER_SERVICE_NAME, + RangerITEnv.RANGER_HIVE_REPO_NAME, + AuthorizationPropertiesMeta.RANGER_ADMIN_URL, + RANGER_ADMIN_URL, + RANGER_AUTH_TYPE, + RangerContainer.authType, + RANGER_USERNAME, + RangerContainer.rangerUserName, + RANGER_PASSWORD, + RangerContainer.rangerPassword); + + metalake.createCatalog(catalogName, Catalog.Type.RELATIONAL, provider, "comment", properties); + catalog = metalake.loadCatalog(catalogName); + LOG.info("Catalog created: {}", catalog); + } + + @Override + protected void useCatalog() throws InterruptedException { + String userName1 = System.getenv(HADOOP_USER_NAME); + String roleName = currentFunName(); + SecurableObject securableObject = + SecurableObjects.ofMetalake( + metalakeName, Lists.newArrayList(Privileges.UseCatalog.allow())); + metalake.createRole(roleName, Collections.emptyMap(), Lists.newArrayList(securableObject)); + metalake.grantRolesToUser(Lists.newArrayList(roleName), userName1); + waitForUpdatingPolicies(); + sparkSession.sql(SQL_USE_CATALOG); + metalake.deleteRole(roleName); + waitForUpdatingPolicies(); + } + + protected void checkTableAllPrivilegesExceptForCreating() { + // - a. Succeed to insert data into the table + sparkSession.sql(SQL_INSERT_TABLE); + + // - b. Succeed to select data from the table + sparkSession.sql(SQL_SELECT_TABLE).collectAsList(); + + // - c: Succeed to update data in the table. + sparkSession.sql(SQL_UPDATE_TABLE); + + // - d: Succeed to delete data from the table. + sparkSession.sql(SQL_DELETE_TABLE); + + // - e: Succeed to alter the table + sparkSession.sql(SQL_ALTER_TABLE); + + // - f: Succeed to drop the table + sparkSession.sql(SQL_DROP_TABLE); + } +} diff --git a/docs/security/access-control.md b/docs/security/access-control.md index afdf921bf47..6b47a254154 100644 --- a/docs/security/access-control.md +++ b/docs/security/access-control.md @@ -202,11 +202,11 @@ and `USE_SCHEMA` privileges on its parent schema. ### Table privileges -| Name | Supports Securable Object | Operation | -|--------------|-----------------------------------|------------------------------------------------| -| CREATE_TABLE | Metalake, Catalog, Schema | Create a table | -| MODIFY_TABLE | Metalake, Catalog, Schema, Table | Use the SQL `UPDATE`,`DELETE`,`INSERT` a table | -| SELECT_TABLE | Metalake, Catalog, Schema, Table | Use the SQL `SELECT` data from a table | +| Name | Supports Securable Object | Operation | +|--------------|-----------------------------------|--------------------------------------------------| +| CREATE_TABLE | Metalake, Catalog, Schema | Create a table | +| MODIFY_TABLE | Metalake, Catalog, Schema, Table | Write data to a table or modify the table schema | +| SELECT_TABLE | Metalake, Catalog, Schema, Table | Select data from a table | ### Topic privileges diff --git a/docs/security/authorization-pushdown.md b/docs/security/authorization-pushdown.md index 148e76b5f81..2d93305f5ec 100644 --- a/docs/security/authorization-pushdown.md +++ b/docs/security/authorization-pushdown.md @@ -43,6 +43,36 @@ authorization.ranger.password=PWD123 authorization.ranger.service.name=hiveRepo ``` +### Authorization Iceberg with Ranger properties + +In order to use the Authorization Ranger Iceberg Plugin, you need to configure the following properties and [Lakehouse_Iceberg catalog properties](../lakehouse-iceberg-catalog.md#catalog-properties): + +| Property Name | Description | Default Value | Required | Since Version | +|-------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------|---------------|----------|------------------| +| `authorization-provider` | Providers to use to implement authorization plugin such as `ranger`. | (none) | No | 0.8.0-incubating | +| `authorization.ranger.admin.url` | The Apache Ranger web URIs. | (none) | No | 0.8.0-incubating | +| `authorization.ranger.auth.type` | The Apache Ranger authentication type `simple` or `kerberos`. | `simple` | No | 0.8.0-incubating | +| `authorization.ranger.username` | The Apache Ranger admin web login username (auth type=simple), or kerberos principal(auth type=kerberos), Need have Ranger administrator permission. | (none) | No | 0.8.0-incubating | +| `authorization.ranger.password` | The Apache Ranger admin web login user password (auth type=simple), or path of the keytab file(auth type=kerberos) | (none) | No | 0.8.0-incubating | +| `authorization.ranger.service.name` | The Apache Ranger service name. | (none) | No | 0.8.0-incubating | + +Once you have used the correct configuration, you can perform authorization operations by calling Gravitino [authorization RESTful API](https://gravitino.apache.org/docs/latest/api/rest/grant-roles-to-a-user). + +#### Example of using the Authorization Ranger Iceberg Plugin + +Suppose you have an Apache Hive service in your datacenter and have created a `icebergRepo` in Apache Ranger to manage its permissions. +The Ranger service is accessible at `172.0.0.100:6080`, with the username `Jack` and the password `PWD123`. +To add this Hive service to Gravitino using the Hive catalog, you'll need to configure the following parameters. + +```properties +authorization-provider=ranger +authorization.ranger.admin.url=172.0.0.100:6080 +authorization.ranger.auth.type=simple +authorization.ranger.username=Jack +authorization.ranger.password=PWD123 +authorization.ranger.service.name=icebergRepo +``` + :::caution -Gravitino 0.6.0 only supports the authorization Apache Ranger Hive service and more data source authorization is under development. +Gravitino 0.8.0 only supports the authorization Apache Ranger Hive service and Apache Iceberg service. More data source authorization is under development. ::: \ No newline at end of file