diff --git a/docs/src/main/sphinx/connector.rst b/docs/src/main/sphinx/connector.rst index 54cca76b38747..7eb9ed3e7f6bf 100644 --- a/docs/src/main/sphinx/connector.rst +++ b/docs/src/main/sphinx/connector.rst @@ -34,6 +34,7 @@ from different data sources. Pinot PostgreSQL Prometheus + Ranger Redis Redshift SingleStore (MemSQL) diff --git a/docs/src/main/sphinx/connector/ranger.rst b/docs/src/main/sphinx/connector/ranger.rst new file mode 100644 index 0000000000000..16b006d368537 --- /dev/null +++ b/docs/src/main/sphinx/connector/ranger.rst @@ -0,0 +1,212 @@ +======================= +Apache Ranger connector +======================= + +.. raw:: html + + + +https://ranger.apache.org/ + +Apache Ranger is a framework to enable comprehensive data security across many platforms. Trino is one of these platforms. + +Requirements +------------ + +To connect to Apache Ranger you need: + +* Apache Ranger installed, up and running. Compatible with Ranger v2.1.x, 2.2.x, 2.3.x +* Network access from the Trino coordinator to the Apache Ranger HTTPS port. + +Configuration +------------- + +The connector downloads the JSON policies from the configured Trino service in the Ranger UI editor. + +Example access-control-ranger.properties file: + +.. code-block:: text + + access-control.name=ranger + ranger.use_ugi=true + ranger.service_name=trino-dev-system + ranger.hadoop_config=/workspace/testing/trino-server-dev/etc/trino-ranger-site.xml + ranger.audit_resource=/workspace/testing/trino-server-dev/etc/trino-ranger-audit.xml + ranger.security_resource=/workspace/testing/trino-server-dev/etc/trino-ranger-security.xml + ranger.policy_manager_ssl_resource=/workspace/testing/trino-server-dev/etc/trino-ranger-policymgr-ssl.xml + +In the Trino config.properties file set the below so it properly points to the above configuration file + +.. code-block:: text + + ... + access-control.config-files=/etc/trino/access-control-ranger.properties + + +use_ugi (aka UserGroupInformation): Tells the plugin to map users and groups together. +Its much simpler to manage groups of users than individual users. Setting to true +is a requirement if you are going to use corporate AD/LDAP to manage access controls. + +service_name as defined in the Ranger UI + +hadoop_config (aka trino-ranger-site.xml): is the Ranger site file required to connect +to corporate AD/LDAP systems. When the user logs into trino, this is the file used to +connect to the AD/LDAP system and get a list of the users groups. + +audit_resource: Ranger can be configured to send reports to various systems. +At the moment this file is required for legacy reasons and should be cleaned up +in the figure. all DEFAULT settings is recommented. If you want auditing use the +trino-http-event-listener and post those events to the HTTP service of your choice. +We recommend using it to post to Kafla but its very flexible. + +security_resource (aka trino-ranger-security.xml): Configures the connectivity +between the trino-ranger plugin and the Apache Ranger running service. + +policy_manager_ssl_resource (aka trino-ranger-policymgr-ssl.xml): Used to setup +up 2 way SSL client/server validation + + +Full Enterprise ready Apache Ranger setup +----------------------------------------- + +Install and startup Ranger: + +* Quick and dirty setup for Ranger + + * git clone Ranger. I recommend using official tagged released. Here is an example: + + * git clone --recursive --branch release-ranger-2.2.0 https://github.com/apache/ranger + + * Build Ranger. Here is the commands I use + + .. code-block:: bash + + mvn -e -X clean package -pl '!plugin-kylin,!ranger-kylin-plugin-shim' -DskipTests + + * You are looking for all of the build targets under 'target/ranger-\*.tar.gz' + + * Copy the main Ranger file to your binary install directory and expand it. Lets call this directory $RANGER_HOME + + * The main service to install is the ranger-$RANGER_VERSION-admin.tar.gz + + * Ranger has 2 configuration files that generate the full ranger install + + * $RANGER_HOME/install.properties + + * You need to generate this config. There is already a sample install.properties extracted from the admin.tar.gz file + + * Most of the defaults work fine. The configs you want to pay attention to are + + * PYTHON_COMMAND_INVOKER, DB_FLAVOR, SQL_CONNECTOR_JAR, db_root_user, db_root_password, db_host, db_name, db_user, db_password, rangerAdmin_password, rangerTagsync_password, rangerUsersync_password, keyadmin_password + + * $RANGER_HOME/usersync/install.properties + + * In most enterprise environments, ActiveDirectory/LDAP are used to manage users and their roles. + + * I do NOT recommend trying to manage users inside of Apache Ranger UI. I would configure the usersync service to pull the groups from AD/LDAP and attach your Ranger SQL policies to those external AD/LDAP groups. + + * Most of the defaults work fine. The configs you want to pay attention to are + + * SYNC_SOURCE=(ldap), rangerUsersync_password (configured above), SYNC_INTERVAL, SYNC_LDAP_URL, SYNC_LDAP_BIND_DN, SYNC_LDAP_BIND_PASSWORD, SYNC_LDAP_DELTASYNC, SYNC_LDAP_SEARCH_BASE, SYNC_LDAP_USER_SEARCH_BASE, SYNC_LDAP_USER_SEARCH_SCOPE, SYNC_LDAP_USER_OBJECT_CLASS, SYNC_LDAP_USER_SEARCH_FILTER, SYNC_LDAP_USER_NAME_ATTRIBUTE, SYNC_LDAP_USER_GROUP_NAME_ATTRIBUTE, SYNC_LDAP_USERNAME_CASE_CONVERSION, SYNC_LDAP_GROUPNAME_CASE_CONVERSION + + * SYNC_INTERVAL specifies the time in minutes that you want Ranger to synchronize AD/LDAP. This can take some time so dont make it too short. 360 (6 hours) is a good number. + + * With both the $RANGER_HOME/install.properties and $RANGER_HOME/usersync/install.properties configured run the setup scripts + + * $RANGER_HOME/setup.sh + + * $RANGER_HOME/usersync/setup.sh + + * Now try and start the main ranger service. + + * $RANGER_HOME/ews/start-ranger-admin.sh + + * I highly recommend monitoring the ranger PID, /run/ranger/rangeradmin.pid, and restarting it of it should fail. If using Docker/kubernetes a simple bash script like so works well + + .. code-block:: bash + + ranger_admin_pid=`cat /run/ranger/rangeradmin.pid` > /dev/null 2>&1 + echo "${0##*/}:$LINENO: Waiting for ranger_admin_pid = $ranger_admin_pid" + while s=`ps -p $ranger_admin_pid -o s=` && [[ "$s" && "$s" != 'Z' ]]; do + sleep 1 + done + echo "${0##*/}:$LINENO: Ranger admin service exited!!!" + + * Now try and start the AD/LDAP usersync serivce + + * $RANGER_HOME/usersync/ranger-usersync-services.sh start + + * NOTE: If you are running multiple instances of the Ranger UI, you should only ever have 1 and only 1 AD/LDAP usersync service running. Your groups will not properly sync otherwise. + + * Now login to the ranger UI and configure the Trino service. + + * Example: + + * http://localhost:6080 + + * User: admin, Password: (defined above in rangerAdmin_password) + + * Create your Trino service. + + * The name is important and needs to be configured in the access-control-ranger.properties file. As of version 2.2.0, just use the Presto service type. Presto, also known as PrestoSQL, is the old name for Trino. + + * The defult policies that Ranger installs under your above service is WIDE open. Everything works for everyone. I will not document the full ranger setup. + + * Just remember, everything a JDBC driver sees, ranger also sees. This means even simple things like date_time functions will break in a fully locked down environment. + + +Connect Trino to Ranger +----------------------- + + * Connecting Trino to Ranger involved 5 files. + * Example files are checkin to the trino project under plugins/trino-ranger/conf/ + + * Main Trino config.properties + + * access-control.config-files=/usr/lib/trino/etc/access-control-ranger.properties + + * Ranger will lockdown EVERYTHING, no user can see another users queries. If you need a system wide user inside trino that can see the problems across the cluster you should add a access-control-file-based.properties to the above comma-separated list. + + * The access-control-ranger.properties file itself. Here is an example + + .. code-block:: text + + access-control.name=ranger + ranger.use_ugi=true + ranger.service_name=trino-dev-companyname-com + ranger.hadoop_config=/workspace/testing/trino-server-dev/etc/trino-ranger-site.xml + ranger.audit_resource=/workspace/testing/trino-server-dev/etc/ranger-trino-audit.xml + ranger.security_resource=/workspace/testing/trino-server-dev/etc/ranger-trino-security.xml + ranger.policy_manager_ssl_resource=/workspace/testing/trino-server-dev/etc/ranger-policymgr-ssl.xml + + * The ranger.service_name is the name of the service you created under the Ranger UI + + * ranger.hadoop_config= + + * Example file: trino-ranger-site.xml + + * Hopefully using hadoop is a temporary option. Hadoop is very heavy weight system just to load config files + + * This setup file, when a user logs in and executes the first SQL, it will pull the list of the users groups into Trino. This group list is used to match against the Ranger UI SQL policies you setup. + + * ranger.audit_resource + + * Example file: ranger-trino-site.xml + + * I dont recommend this but up to you. It was original Ranger features. As an alternative try the trino-http-event-listener and send every incoming SQL query to a kafka pipe. This way you can take the SQL contents and ingest into any system you want. Real time alerting is easier via using kafka pipes. + + * ranger.security_resource= + + * Example file: ranger-trino-security.xml + + * This is the main file that maps trino and ranger together. Only the 2 below are critical, the rest of the configurations the defaults are fine + + * ranger.plugin.trino.service.name is the same as the ranger.service_name entry. + + * ranger.plugin.trino.policy.rest.url is the URL of the Ranger admin service. + + * ranger.policy_manager_ssl_resource= + + * Example file: ranger-trino-security.xml + + * Defaults are fine. If you setup 2 way SSL verification then you will need to manage key expirations. diff --git a/docs/src/main/sphinx/static/img/apache_ranger.png b/docs/src/main/sphinx/static/img/apache_ranger.png new file mode 100755 index 0000000000000..c03333b632bde Binary files /dev/null and b/docs/src/main/sphinx/static/img/apache_ranger.png differ diff --git a/plugin/trino-ranger/README.md b/plugin/trino-ranger/README.md new file mode 100644 index 0000000000000..892fd191f6d7b --- /dev/null +++ b/plugin/trino-ranger/README.md @@ -0,0 +1,95 @@ +# Trino-Ranger plugin + +This plugin is designed to be build and run inside Trino. + +It works with vanilla Apache Ranger, version 2.2.1 and up. You dont need to customize Ranger at all. + +Here are the setup steps. + + +### Full Enterprise ready Apache Ranger setup + + * Install and startup Ranger + * Quick and dirty setup for Ranger + * git clone Ranger. I recommend using official tagged released. Here is an example: + * git clone --recursive --branch release-ranger-2.2.0 https://github.com/apache/ranger + * Build Ranger. Here is the commands I use + * mvn -e -X clean package -pl '!plugin-kylin,!ranger-kylin-plugin-shim' -DskipTests + * You are looking for all of the build targets under "target/ranger-*.tar.gz" + * Copy the main Ranger file to your binary install directory and expand it. Lets call this directory $RANGER_HOME + * The main service to install is the ranger-$RANGER_VERSION-admin.tar.gz + * Ranger has 2 configuration files that generate the full ranger install + * $RANGER_HOME/install.properties + * You need to generate this config. There is already a sample install.properties extracted from the admin.tar.gz file + * Most of the defaults work fine. The configs you want to pay attention to are + * PYTHON_COMMAND_INVOKER, DB_FLAVOR, SQL_CONNECTOR_JAR, db_root_user, db_root_password, db_host, db_name, db_user, db_password, rangerAdmin_password, rangerTagsync_password, rangerUsersync_password, keyadmin_password + * $RANGER_HOME/usersync/install.properties + * In most enterprise environments, ActiveDirectory/LDAP are used to manage users and their roles. + * I do NOT recommend trying to manage users inside of Apache Ranger UI. I would configure the usersync service to pull the groups from AD/LDAP and attach your Ranger SQL policies to those external AD/LDAP groups. + * Most of the defaults work fine. The configs you want to pay attention to are + * SYNC_SOURCE=(ldap), rangerUsersync_password (configured above), SYNC_INTERVAL, SYNC_LDAP_URL, SYNC_LDAP_BIND_DN, SYNC_LDAP_BIND_PASSWORD, SYNC_LDAP_DELTASYNC, SYNC_LDAP_SEARCH_BASE, SYNC_LDAP_USER_SEARCH_BASE, SYNC_LDAP_USER_SEARCH_SCOPE, SYNC_LDAP_USER_OBJECT_CLASS, SYNC_LDAP_USER_SEARCH_FILTER, SYNC_LDAP_USER_NAME_ATTRIBUTE, SYNC_LDAP_USER_GROUP_NAME_ATTRIBUTE, SYNC_LDAP_USERNAME_CASE_CONVERSION, SYNC_LDAP_GROUPNAME_CASE_CONVERSION + * SYNC_INTERVAL specifies the time in minutes that you want Ranger to synchronize AD/LDAP. This can take some time so dont make it too short. 360 (6 hours) is a good number. + * With both the $RANGER_HOME/install.properties and $RANGER_HOME/usersync/install.properties configured run the setup scripts + * $RANGER_HOME/setup.sh + * $RANGER_HOME/usersync/setup.sh + * Now try and start the main ranger service. + * $RANGER_HOME/ews/start-ranger-admin.sh + * I highly recommend monitoring the ranger PID, /run/ranger/rangeradmin.pid, and restarting it of it should fail. If using Docker/kubernetes a simple bash script like so works well + ``` + ranger_admin_pid=`cat /run/ranger/rangeradmin.pid` > /dev/null 2>&1 + + echo "${0##*/}:$LINENO: Waiting for ranger_admin_pid = $ranger_admin_pid" + + while s=`ps -p $ranger_admin_pid -o s=` && [[ "$s" && "$s" != 'Z' ]]; do + sleep 1 + done + + echo "${0##*/}:$LINENO: Ranger admin service exited!!!" + ``` + + * Now try and start the AD/LDAP usersync serivce + * $RANGER_HOME/usersync/ranger-usersync-services.sh start + * NOTE: If you are running multiple instances of the Ranger UI, you should only ever have 1 and only 1 AD/LDAP usersync service running. Your groups will not properly sync otherwise. + * Now login to the ranger UI and configure the Trino service. + * Example: + * http://localhost:6080 + * User: admin, Password: (defined above in rangerAdmin_password) + * Create your Trino service. + * The name is important and needs to be configured in the access-control-ranger.properties file. As of version 2.2.0, just use the Presto service type. Presto, also known as PrestoSQL, is the old name for Trino. + * The defult policies that Ranger installs under your above service is WIDE open. Everything works for everyone. I will not document the full ranger setup. + * Just remember, everything a JDBC driver sees, ranger also sees. This means even simple things like date_time functions will break in a fully locked down environment. + + +### Connect Trino to Ranger + + * Connecting Trino to Ranger involved 5 files. + * Main Trino config.properties + * access-control.config-files=/usr/lib/trino/etc/access-control-ranger.properties + * Ranger will lockdown EVERYTHING, no user can see another users queries. If you need a system wide user inside trino that can see the problems across the cluster you should add a access-control-file-based.properties to the above comma-separated list. + * The access-control-ranger.properties file itself. Here is an example + ``` + access-control.name=ranger + ranger.use_ugi=true + ranger.service_name=trino-dev-companyname-com + ranger.hadoop_config=/workspace/testing/trino-server-dev/etc/trino-ranger-site.xml + ranger.audit_resource=/workspace/testing/trino-server-dev/etc/ranger-trino-audit.xml + ranger.security_resource=/workspace/testing/trino-server-dev/etc/ranger-trino-security.xml + ranger.policy_manager_ssl_resource=/workspace/testing/trino-server-dev/etc/ranger-policymgr-ssl.xml + ``` + * The ranger.service_name is the name of the service you created under the Ranger UI + * ranger.hadoop_config= + * Example file: plugins/trino-ranger/conf/trino-ranger-site.xml + * Hopefully using hadoop is a temporary option. Hadoop is very heavy weight system just to load config files + * This setup file, when a user logs in and executes the first SQL, it will pull the list of the users groups into Trino. This group list is used to match against the Ranger UI SQL policies you setup. + * ranger.audit_resource + * Example file: plugins/trino-ranger/conf/ranger-trino-site.xml + * I dont recommend this but up to you. It was original Ranger features. As an alternative try the trino-http-event-listener and send every incoming SQL query to a kafka pipe. This way you can take the SQL contents and ingest into any system you want. Real time alerting is easier via using kafka pipes. + * ranger.security_resource= + * Example file: plugins/trino-ranger/conf/ranger-trino-security.xml + * This is the main file that maps trino and ranger together. Only the 2 below are critical, the rest of the configurations the defaults are fine + * ranger.plugin.trino.service.name is the same as the ranger.service_name entry. + * ranger.plugin.trino.policy.rest.url is the URL of the Ranger admin service. + * ranger.policy_manager_ssl_resource= + * Example file: plugins/trino-ranger/conf/ranger-trino-security.xml + * Defaults are fine. Java keystore is a pain but if you feel its necessary feel free to set it up. + diff --git a/plugin/trino-ranger/conf/ranger-policymgr-ssl.xml b/plugin/trino-ranger/conf/ranger-policymgr-ssl.xml new file mode 100644 index 0000000000000..21cea432afdce --- /dev/null +++ b/plugin/trino-ranger/conf/ranger-policymgr-ssl.xml @@ -0,0 +1,49 @@ + + + + + + + xasecure.policymgr.clientssl.keystore + trinoservice-clientcert.jks + + Java Keystore files + + + + xasecure.policymgr.clientssl.truststore + cacerts-xasecure.jks + + java truststore file + + + + xasecure.policymgr.clientssl.keystore.credential.file + jceks://file/tmp/keystore-trinoservice-ssl.jceks + + java keystore credential file + + + + xasecure.policymgr.clientssl.truststore.credential.file + jceks://file/tmp/truststore-trinoservice-ssl.jceks + + java truststore credential file + + + diff --git a/plugin/trino-ranger/conf/ranger-trino-audit.xml b/plugin/trino-ranger/conf/ranger-trino-audit.xml new file mode 100644 index 0000000000000..cd571185120df --- /dev/null +++ b/plugin/trino-ranger/conf/ranger-trino-audit.xml @@ -0,0 +1,216 @@ + + + + + + xasecure.audit.is.enabled + true + + + + + xasecure.audit.hdfs.is.enabled + false + + + + xasecure.audit.hdfs.is.async + true + + + + xasecure.audit.hdfs.async.max.queue.size + 1048576 + + + + xasecure.audit.hdfs.async.max.flush.interval.ms + 30000 + + + + xasecure.audit.hdfs.config.encoding + + + + + xasecure.audit.hdfs.config.destination.directory + hdfs://NAMENODE_HOST:8020/ranger/audit/%app-type%/%time:yyyyMMdd% + + + + xasecure.audit.hdfs.config.destination.file + %hostname%-audit.log + + + + xasecure.audit.hdfs.config.destination.flush.interval.seconds + 900 + + + + xasecure.audit.hdfs.config.destination.rollover.interval.seconds + 86400 + + + + xasecure.audit.hdfs.config.destination.open.retry.interval.seconds + 60 + + + + xasecure.audit.hdfs.config.local.buffer.directory + /var/log/trino/audit + + + + xasecure.audit.hdfs.config.local.buffer.file + %time:yyyyMMdd-HHmm.ss%.log + + + + xasecure.audit.hdfs.config.local.buffer.file.buffer.size.bytes + 8192 + + + + xasecure.audit.hdfs.config.local.buffer.flush.interval.seconds + 60 + + + + xasecure.audit.hdfs.config.local.buffer.rollover.interval.seconds + 600 + + + + xasecure.audit.hdfs.config.local.archive.directory + /var/log/trino/audit/archive + + + + xasecure.audit.hdfs.config.local.archive.max.file.count + 10 + + + + + + + xasecure.audit.log4j.is.enabled + false + + + + xasecure.audit.log4j.is.async + false + + + + xasecure.audit.log4j.async.max.queue.size + 10240 + + + + xasecure.audit.log4j.async.max.flush.interval.ms + 30000 + + + + + + xasecure.audit.trino.is.enabled + false + + + + xasecure.audit.trino.async.max.queue.size + 1 + + + + xasecure.audit.trino.async.max.flush.interval.ms + 1000 + + + + xasecure.audit.trino.broker_list + localhost:9092 + + + + xasecure.audit.trino.topic_name + ranger_audits + + + + + xasecure.audit.solr.is.enabled + false + + + + xasecure.audit.solr.async.max.queue.size + 1 + + + + xasecure.audit.solr.async.max.flush.interval.ms + 1000 + + + + xasecure.audit.solr.solr_url + http://localhost:6083/solr/ranger_audits + + diff --git a/plugin/trino-ranger/conf/ranger-trino-security.xml b/plugin/trino-ranger/conf/ranger-trino-security.xml new file mode 100644 index 0000000000000..1b2995a99c64e --- /dev/null +++ b/plugin/trino-ranger/conf/ranger-trino-security.xml @@ -0,0 +1,74 @@ + + + + + ranger.plugin.trino.service.name + trinoservice + + Name of the Ranger service containing policies for this Trino instance + + + + + ranger.plugin.trino.policy.source.impl + org.apache.ranger.admin.client.RangerAdminRESTClient + + Class to retrieve policies from the source + + + + + ranger.plugin.trino.policy.rest.url + http://localhost:6080 + + URL to Ranger Admin + + + + + ranger.plugin.trino.policy.rest.ssl.config.file + /etc/hadoop/conf/ranger-policymgr-ssl.xml + + Path to the file containing SSL details to contact Ranger Admin + + + + + ranger.plugin.trino.policy.pollIntervalMs + 30000 + + How often to poll for changes in policies? + + + + + ranger.plugin.trino.policy.rest.client.connection.timeoutMs + 30000 + + S3 Plugin RangerRestClient Connection Timeout in Milli Seconds + + + + + ranger.plugin.trino.policy.rest.client.read.timeoutMs + 30000 + + S3 Plugin RangerRestClient read Timeout in Milli Seconds + + + diff --git a/plugin/trino-ranger/conf/trino-ranger-site.xml b/plugin/trino-ranger/conf/trino-ranger-site.xml new file mode 100644 index 0000000000000..c4144fc64e6af --- /dev/null +++ b/plugin/trino-ranger/conf/trino-ranger-site.xml @@ -0,0 +1,64 @@ + + + + + + hadoop.security.group.mapping + org.apache.hadoop.security.LdapGroupsMapping + + + + hadoop.security.group.mapping.ldap.bind.user + cn=Manager,dc=hadoop,dc=apache,dc=org + + + + hadoop.security.group.mapping.ldap.bind.password + insert super secret password here + + + + hadoop.security.group.mapping.ldap.url + ldap://localhost:389/ + + + + hadoop.security.group.mapping.ldap.base + DC=whoknows,DC=company,DC=com + + + + hadoop.security.group.mapping.ldap.search.filter.user + (&(|(objectclass=person)(objectclass=applicationProcess))(cn={0})) + + + + hadoop.security.group.mapping.ldap.search.filter.group + (cn=your_group_name_pattern*) + + + + hadoop.security.group.mapping.ldap.search.attr.member + member + + + + hadoop.security.group.mapping.ldap.search.attr.group.name + cn + + diff --git a/plugin/trino-ranger/pom.xml b/plugin/trino-ranger/pom.xml new file mode 100644 index 0000000000000..5a510b963e696 --- /dev/null +++ b/plugin/trino-ranger/pom.xml @@ -0,0 +1,302 @@ + + + 4.0.0 + + + io.trino + trino-root + 391-SNAPSHOT + ../../pom.xml + + + trino-ranger + Trino - Apache Ranger plugin + trino-plugin + https://ranger.apache.org + + + ${project.parent.basedir} + 2.2.0 + 2.0.5 + 9.4.44.v20210927 + 2.15.0 + 1.7.30 + 1.6 + 3.3.0 + 9.4.44.v20210927 + 1.4 + 4.5.10 + 1.19.3 + 1.9.13 + 2.5 + 2.5.0 + 0.8.1 + 4.1.4 + 4.4.12 + 4.13.2 + 2.6 + 2.2.4 + 5.8.0 + + + + + + io.trino + trino-hadoop-toolkit + + + io.trino.hadoop + hadoop-apache + + + + + + io.airlift + bootstrap + ${dep.airlift.version} + + + + io.airlift + configuration + ${dep.airlift.version} + + + + io.airlift + log + ${dep.airlift.version} + + + + com.google.guava + guava + + + + com.google.inject + guice + + + + commons-lang + commons-lang + ${deps.commons-lang.version} + + + + javax.inject + javax.inject + + + + org.apache.hadoop + hadoop-common + ${dep.hadoop.version} + + + com.sun.jersey + jersey-core + + + com.sun.jersey + jersey-servlet + + + com.sun.jersey + jersey-json + + + com.sun.jersey + jersey-server + + + org.osgi + org.osgi.core + + + javax.ws.rs + jsr311-api + + + commons-logging + commons-logging + + + log4j + log4j + + + org.slf4j + slf4j-log4j12 + + + org.slf4j + slf4j-log4j12 + + + + + + org.apache.ranger + ranger-plugin-classloader + ${dep.ranger.version} + + + + org.apache.ranger + ranger-plugins-common + ${dep.ranger.version} + + + org.apache.logging.log4j + log4j-core + + + org.apache.logging.log4j + log4j-api + + + commons-logging + commons-logging + + + log4j + log4j + + + org.elasticsearch + jna + + + javax.ws.rs + jsr311-api + + + org.codehaus.woodstox + woodstox-core-asl + + + org.eclipse.persistence + eclipselink + + + org.eclipse.persistence + org.eclipse.persistence.moxy + + + org.eclipse.persistence + javax.persistence + + + + + + + io.airlift + log-manager + runtime + + + + com.google.code.gson + gson + ${deps.google-code-gson.version} + runtime + + + + io.trino + trino-spi + provided + + + + io.airlift + slice + provided + + + + com.fasterxml.jackson.core + jackson-annotations + provided + + + + org.openjdk.jol + jol-core + provided + + + + junit + junit + ${deps.junit.version} + test + + + + + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + + + + + org.apache.httpcomponents:httpclient + com.sun.jersey:jersey-json + com.sun.jersey:jersey-server + javax.servlet:servlet-api + org.slf4j:slf4j-api + com.carrotsearch:hppc + org.apache.httpcomponents:httpasyncclient + commons-cli:commons-cli + org.apache.httpcomponents:httpcore + org.codehaus.jackson:jackson-core-asl + org.codehaus.jackson:jackson-mapper-asl + org.apache.httpcomponents:httpcore-nio + net.java.dev.jna:jna-platform + com.sun.jersey:jersey-servlet + com.sun.jersey:jersey-core + org.osgi:org.osgi.core + org.eclipse.jetty:jetty-http + org.eclipse.jetty:jetty-io + org.apache.kafka:kafka-clients + commons-codec:commons-codec + + + + + + + org.basepom.maven + duplicate-finder-maven-plugin + + + .*build.properties + com.sun.jersey.* + readme.html + license.html + about.html + .*\.txt$ + + + + + + + diff --git a/plugin/trino-ranger/src/main/java/io/trino/plugin/ranger/RangerConfig.java b/plugin/trino-ranger/src/main/java/io/trino/plugin/ranger/RangerConfig.java new file mode 100644 index 0000000000000..f10b02b25339b --- /dev/null +++ b/plugin/trino-ranger/src/main/java/io/trino/plugin/ranger/RangerConfig.java @@ -0,0 +1,138 @@ +/* + * Licensed 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 io.trino.plugin.ranger; + +import io.airlift.configuration.Config; +import io.airlift.configuration.ConfigDescription; + +public class RangerConfig +{ + private String keytab; + private String principal; + private boolean useUgi; + private String serviceName; + private String hadoopConfigPath; + private String auditResourcesConfigPath; + private String securityResourcesConfigPath; + private String policyManagerSSLConfigPath; + + public RangerConfig() + { + useUgi = false; + } + + public String getKeytab() + { + return keytab; + } + + @Config("ranger.keytab") + @ConfigDescription("Keytab for authentication against Ranger") + public RangerConfig setKeytab(String keytab) + { + this.keytab = keytab; + return this; + } + + public String getPrincipal() + { + return principal; + } + + @Config("ranger.principal") + @ConfigDescription("Principal for authentication against Ranger with keytab") + public RangerConfig setPrincipal(String principal) + { + this.principal = principal; + return this; + } + + public boolean isUseUgi() + { + return useUgi; + } + + @Config("ranger.use_ugi") + @ConfigDescription("Use Hadoop User Group Information instead of Trino groups") + public RangerConfig setUseUgi(boolean useUgi) + { + this.useUgi = useUgi; + return this; + } + + @Config("ranger.service_name") + @ConfigDescription("The name of the service defined in the Ranger UI") + public RangerConfig setServiceName(String serviceName) + { + this.serviceName = serviceName; + return this; + } + + public String getServiceName() + { + return serviceName; + } + + @Config("ranger.hadoop_config") + @ConfigDescription("Path to hadoop configuration. Defaults to trino-ranger-site.xml in classpath") + public RangerConfig setHadoopConfigPath(String hadoopConfigPath) + { + this.hadoopConfigPath = hadoopConfigPath; + return this; + } + + public String getHadoopConfigPath() + { + return hadoopConfigPath; + } + + @Config("ranger.audit_resource") + @ConfigDescription("Full path to the audit config files (ranger.audit_resource)") + public RangerConfig setAuditResourcesConfigPath(String auditResourcesConfigPath) + { + this.auditResourcesConfigPath = auditResourcesConfigPath; + return this; + } + + public String getAuditResourcesConfigPath() + { + return auditResourcesConfigPath; + } + + @Config("ranger.security_resource") + @ConfigDescription("Full path to the security config file (ranger-trino-security.xml)") + public RangerConfig setSecurityResourcesConfigPath(String securityResourcesConfigPath) + { + this.securityResourcesConfigPath = securityResourcesConfigPath; + return this; + } + + public String getSecurityResourcesConfigPath() + { + return securityResourcesConfigPath; + } + + @Config("ranger.policy_manager_ssl_resource") + @ConfigDescription("Full path to the ranger policy manager config file (ranger-policymgr-ssl.xml)") + public RangerConfig setPolicyManagerSSLConfigPath(String policyManagerSSLConfigPath) + { + this.policyManagerSSLConfigPath = policyManagerSSLConfigPath; + return this; + } + + public String getPolicyManagerSSLConfigPath() + { + return policyManagerSSLConfigPath; + } +} diff --git a/plugin/trino-ranger/src/main/java/io/trino/plugin/ranger/RangerSystemAccessControl.java b/plugin/trino-ranger/src/main/java/io/trino/plugin/ranger/RangerSystemAccessControl.java new file mode 100644 index 0000000000000..f8b52fee65ca0 --- /dev/null +++ b/plugin/trino-ranger/src/main/java/io/trino/plugin/ranger/RangerSystemAccessControl.java @@ -0,0 +1,713 @@ +/* + * Licensed 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 io.trino.plugin.ranger; + +import io.trino.spi.connector.CatalogSchemaName; +import io.trino.spi.connector.CatalogSchemaRoutineName; +import io.trino.spi.connector.CatalogSchemaTableName; +import io.trino.spi.connector.SchemaTableName; +import io.trino.spi.security.Privilege; +import io.trino.spi.security.SystemAccessControl; +import io.trino.spi.security.SystemSecurityContext; +import io.trino.spi.security.TrinoPrincipal; +import io.trino.spi.security.ViewExpression; +import io.trino.spi.type.Type; +import org.apache.ranger.plugin.classloader.RangerPluginClassLoader; + +import javax.inject.Inject; + +import java.security.Principal; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +public class RangerSystemAccessControl + implements SystemAccessControl +{ + private static final String RANGER_PLUGIN_TYPE = "trino"; + // Apache Ranger will be the default authorizer if no authorizers match your catalog. + private static final String RANGER_TRINO_AUTHORIZER_IMPL_CLASSNAME = "io.trino.plugin.ranger.RangerSystemAccessControlImpl"; + + private final RangerPluginClassLoader rangerPluginClassLoader; + private final SystemAccessControl systemAccessControlImpl; + + @Inject + public RangerSystemAccessControl(RangerConfig config) + { + try { + rangerPluginClassLoader = RangerPluginClassLoader.getInstance(RANGER_PLUGIN_TYPE, this.getClass()); + + @SuppressWarnings("unchecked") + Class cls = (Class) Class.forName(RANGER_TRINO_AUTHORIZER_IMPL_CLASSNAME, true, rangerPluginClassLoader); + + activatePluginClassLoader(); + + Map configMap = new HashMap<>(); + if (config.getKeytab() != null && config.getPrincipal() != null) { + configMap.put("ranger.keytab", config.getKeytab()); + configMap.put("ranger.principal", config.getPrincipal()); + } + + configMap.put("ranger.use_ugi", Boolean.toString(config.isUseUgi())); + + if (config.getHadoopConfigPath() != null) { + configMap.put("ranger.hadoop_config", config.getHadoopConfigPath()); + } + + if (config.getAuditResourcesConfigPath() != null) { + configMap.put("ranger.audit_resource", config.getAuditResourcesConfigPath()); + } + + if (config.getSecurityResourcesConfigPath() != null) { + configMap.put("ranger.security_resource", config.getSecurityResourcesConfigPath()); + } + + if (config.getPolicyManagerSSLConfigPath() != null) { + configMap.put("ranger.policy_manager_ssl_resource", config.getPolicyManagerSSLConfigPath()); + } + + if (config.getServiceName() != null) { + configMap.put("ranger.service_name", config.getServiceName()); + } + + systemAccessControlImpl = cls.getDeclaredConstructor(Map.class).newInstance(configMap); + } + catch (Exception e) { + throw new RuntimeException(e); + } + finally { + deactivatePluginClassLoader(); + } + } + + @Override + public void checkCanSetSystemSessionProperty(SystemSecurityContext context, String propertyName) + { + try { + activatePluginClassLoader(); + systemAccessControlImpl.checkCanSetSystemSessionProperty(context, propertyName); + } + finally { + deactivatePluginClassLoader(); + } + } + + @Override + public void checkCanAccessCatalog(SystemSecurityContext context, String catalogName) + { + try { + activatePluginClassLoader(); + systemAccessControlImpl.checkCanAccessCatalog(context, catalogName); + } + finally { + deactivatePluginClassLoader(); + } + } + + @Override + public Set filterCatalogs(SystemSecurityContext context, Set catalogs) + { + Set filteredCatalogs; + try { + activatePluginClassLoader(); + filteredCatalogs = systemAccessControlImpl.filterCatalogs(context, catalogs); + } + finally { + deactivatePluginClassLoader(); + } + return filteredCatalogs; + } + + @Override + public void checkCanCreateSchema(SystemSecurityContext context, CatalogSchemaName schema) + { + try { + activatePluginClassLoader(); + systemAccessControlImpl.checkCanCreateSchema(context, schema); + } + finally { + deactivatePluginClassLoader(); + } + } + + @Override + public void checkCanDropSchema(SystemSecurityContext context, CatalogSchemaName schema) + { + try { + activatePluginClassLoader(); + systemAccessControlImpl.checkCanDropSchema(context, schema); + } + finally { + deactivatePluginClassLoader(); + } + } + + @Override + public void checkCanRenameSchema(SystemSecurityContext context, CatalogSchemaName schema, String newSchemaName) + { + try { + activatePluginClassLoader(); + systemAccessControlImpl.checkCanRenameSchema(context, schema, newSchemaName); + } + finally { + deactivatePluginClassLoader(); + } + } + + @Override + public void checkCanShowSchemas(SystemSecurityContext context, String catalogName) + { + try { + activatePluginClassLoader(); + systemAccessControlImpl.checkCanShowSchemas(context, catalogName); + } + finally { + deactivatePluginClassLoader(); + } + } + + @Override + public Set filterSchemas(SystemSecurityContext context, String catalogName, Set schemaNames) + { + Set filteredSchemas; + try { + activatePluginClassLoader(); + filteredSchemas = systemAccessControlImpl.filterSchemas(context, catalogName, schemaNames); + } + finally { + deactivatePluginClassLoader(); + } + return filteredSchemas; + } + + @Override + public void checkCanCreateTable(SystemSecurityContext context, CatalogSchemaTableName table, Map properties) + { + try { + activatePluginClassLoader(); + systemAccessControlImpl.checkCanCreateTable(context, table, properties); + } + finally { + deactivatePluginClassLoader(); + } + } + + @Override + public void checkCanDropTable(SystemSecurityContext context, CatalogSchemaTableName table) + { + try { + activatePluginClassLoader(); + systemAccessControlImpl.checkCanDropTable(context, table); + } + finally { + deactivatePluginClassLoader(); + } + } + + @Override + public void checkCanCreateMaterializedView(SystemSecurityContext context, CatalogSchemaTableName materializedView, + Map properties) + { + try { + activatePluginClassLoader(); + systemAccessControlImpl.checkCanCreateMaterializedView(context, materializedView, properties); + } + finally { + deactivatePluginClassLoader(); + } + } + + @Override + public void checkCanDropMaterializedView(SystemSecurityContext context, CatalogSchemaTableName materializedView) + { + try { + activatePluginClassLoader(); + systemAccessControlImpl.checkCanDropMaterializedView(context, materializedView); + } + finally { + deactivatePluginClassLoader(); + } + } + + @Override + public void checkCanRefreshMaterializedView(SystemSecurityContext context, CatalogSchemaTableName materializedView) + { + try { + activatePluginClassLoader(); + systemAccessControlImpl.checkCanRefreshMaterializedView(context, materializedView); + } + finally { + deactivatePluginClassLoader(); + } + } + + @Override + public void checkCanRenameMaterializedView(SystemSecurityContext context, CatalogSchemaTableName view, CatalogSchemaTableName newView) + { + try { + activatePluginClassLoader(); + systemAccessControlImpl.checkCanRenameMaterializedView(context, view, newView); + } + finally { + deactivatePluginClassLoader(); + } + } + + @Override + public void checkCanRenameTable(SystemSecurityContext context, CatalogSchemaTableName table, CatalogSchemaTableName newTable) + { + try { + activatePluginClassLoader(); + systemAccessControlImpl.checkCanRenameTable(context, table, newTable); + } + finally { + deactivatePluginClassLoader(); + } + } + + @Override + public Set filterTables(SystemSecurityContext context, String catalogName, Set tableNames) + { + Set filteredTableNames; + try { + activatePluginClassLoader(); + filteredTableNames = systemAccessControlImpl.filterTables(context, catalogName, tableNames); + } + finally { + deactivatePluginClassLoader(); + } + return filteredTableNames; + } + + @Override + public void checkCanAddColumn(SystemSecurityContext context, CatalogSchemaTableName table) + { + try { + activatePluginClassLoader(); + systemAccessControlImpl.checkCanAddColumn(context, table); + } + finally { + deactivatePluginClassLoader(); + } + } + + @Override + public void checkCanDropColumn(SystemSecurityContext context, CatalogSchemaTableName table) + { + try { + activatePluginClassLoader(); + systemAccessControlImpl.checkCanDropColumn(context, table); + } + finally { + deactivatePluginClassLoader(); + } + } + + @Override + public void checkCanRenameColumn(SystemSecurityContext context, CatalogSchemaTableName table) + { + try { + activatePluginClassLoader(); + systemAccessControlImpl.checkCanRenameColumn(context, table); + } + finally { + deactivatePluginClassLoader(); + } + } + + @Override + public void checkCanSelectFromColumns(SystemSecurityContext context, CatalogSchemaTableName table, Set columns) + { + try { + activatePluginClassLoader(); + systemAccessControlImpl.checkCanSelectFromColumns(context, table, columns); + } + finally { + deactivatePluginClassLoader(); + } + } + + @Override + public void checkCanInsertIntoTable(SystemSecurityContext context, CatalogSchemaTableName table) + { + try { + activatePluginClassLoader(); + systemAccessControlImpl.checkCanInsertIntoTable(context, table); + } + finally { + deactivatePluginClassLoader(); + } + } + + @Override + public void checkCanDeleteFromTable(SystemSecurityContext context, CatalogSchemaTableName table) + { + try { + activatePluginClassLoader(); + systemAccessControlImpl.checkCanDeleteFromTable(context, table); + } + finally { + deactivatePluginClassLoader(); + } + } + + @Override + public void checkCanCreateView(SystemSecurityContext context, CatalogSchemaTableName view) + { + try { + activatePluginClassLoader(); + systemAccessControlImpl.checkCanCreateView(context, view); + } + finally { + deactivatePluginClassLoader(); + } + } + + @Override + public void checkCanDropView(SystemSecurityContext context, CatalogSchemaTableName view) + { + try { + activatePluginClassLoader(); + systemAccessControlImpl.checkCanDropView(context, view); + } + finally { + deactivatePluginClassLoader(); + } + } + + @Override + public void checkCanCreateViewWithSelectFromColumns(SystemSecurityContext context, CatalogSchemaTableName table, Set columns) + { + try { + activatePluginClassLoader(); + systemAccessControlImpl.checkCanCreateViewWithSelectFromColumns(context, table, columns); + } + finally { + deactivatePluginClassLoader(); + } + } + + @Override + public void checkCanSetCatalogSessionProperty(SystemSecurityContext context, String catalogName, String propertyName) + { + try { + activatePluginClassLoader(); + systemAccessControlImpl.checkCanSetCatalogSessionProperty(context, catalogName, propertyName); + } + finally { + deactivatePluginClassLoader(); + } + } + + @Override + public void checkCanImpersonateUser(SystemSecurityContext context, String userName) + { + try { + activatePluginClassLoader(); + systemAccessControlImpl.checkCanImpersonateUser(context, userName); + } + finally { + deactivatePluginClassLoader(); + } + } + + @Override + public void checkCanExecuteQuery(SystemSecurityContext context) + { + try { + activatePluginClassLoader(); + systemAccessControlImpl.checkCanExecuteQuery(context); + } + finally { + deactivatePluginClassLoader(); + } + } + + @Override + public void checkCanViewQueryOwnedBy(SystemSecurityContext context, String queryOwner) + { + try { + activatePluginClassLoader(); + systemAccessControlImpl.checkCanViewQueryOwnedBy(context, queryOwner); + } + finally { + deactivatePluginClassLoader(); + } + } + + @Override + public Set filterViewQueryOwnedBy(SystemSecurityContext context, Set queryOwners) + { + Set filteredQueryOwners; + try { + activatePluginClassLoader(); + filteredQueryOwners = systemAccessControlImpl.filterViewQueryOwnedBy(context, queryOwners); + } + finally { + deactivatePluginClassLoader(); + } + return filteredQueryOwners; + } + + @Override + public void checkCanKillQueryOwnedBy(SystemSecurityContext context, String queryOwner) + { + try { + activatePluginClassLoader(); + systemAccessControlImpl.checkCanKillQueryOwnedBy(context, queryOwner); + } + finally { + deactivatePluginClassLoader(); + } + } + + @Override + public void checkCanShowCreateTable(SystemSecurityContext context, CatalogSchemaTableName table) + { + try { + activatePluginClassLoader(); + systemAccessControlImpl.checkCanShowCreateTable(context, table); + } + finally { + deactivatePluginClassLoader(); + } + } + + @Override + public void checkCanSetTableComment(SystemSecurityContext context, CatalogSchemaTableName table) + { + try { + activatePluginClassLoader(); + systemAccessControlImpl.checkCanSetTableComment(context, table); + } + finally { + deactivatePluginClassLoader(); + } + } + + @Override + public void checkCanExecuteTableProcedure(SystemSecurityContext context, CatalogSchemaTableName table, + String procedure) + { + try { + activatePluginClassLoader(); + systemAccessControlImpl.checkCanExecuteTableProcedure(context, table, procedure); + } + finally { + deactivatePluginClassLoader(); + } + } + + @Override + public void checkCanShowTables(SystemSecurityContext context, CatalogSchemaName schema) + { + try { + activatePluginClassLoader(); + systemAccessControlImpl.checkCanShowTables(context, schema); + } + finally { + deactivatePluginClassLoader(); + } + } + + @Override + public void checkCanShowColumns(SystemSecurityContext context, CatalogSchemaTableName table) + { + try { + activatePluginClassLoader(); + systemAccessControlImpl.checkCanShowColumns(context, table); + } + finally { + deactivatePluginClassLoader(); + } + } + + @Override + public Set filterColumns(SystemSecurityContext context, CatalogSchemaTableName table, Set columns) + { + Set filteredColumns; + try { + activatePluginClassLoader(); + filteredColumns = systemAccessControlImpl.filterColumns(context, table, columns); + } + finally { + deactivatePluginClassLoader(); + } + return filteredColumns; + } + + @Override + public void checkCanRenameView(SystemSecurityContext context, CatalogSchemaTableName view, CatalogSchemaTableName newView) + { + try { + activatePluginClassLoader(); + systemAccessControlImpl.checkCanRenameView(context, view, newView); + } + finally { + deactivatePluginClassLoader(); + } + } + + @Override + public void checkCanGrantTablePrivilege(SystemSecurityContext context, Privilege privilege, CatalogSchemaTableName table, TrinoPrincipal grantee, boolean withGrantOption) + { + try { + activatePluginClassLoader(); + systemAccessControlImpl.checkCanGrantTablePrivilege(context, privilege, table, grantee, withGrantOption); + } + finally { + deactivatePluginClassLoader(); + } + } + + @Override + public void checkCanRevokeTablePrivilege(SystemSecurityContext context, Privilege privilege, CatalogSchemaTableName table, TrinoPrincipal revokee, boolean grantOptionFor) + { + try { + activatePluginClassLoader(); + systemAccessControlImpl.checkCanRevokeTablePrivilege(context, privilege, table, revokee, grantOptionFor); + } + finally { + deactivatePluginClassLoader(); + } + } + +/* Erik Anderson, commented out because interface is currently broken + @Override + public void checkCanShowRoles(SystemSecurityContext context, String catalogName) { + try { + activatePluginClassLoader(); + systemAccessControlImpl.checkCanShowRoles(context, catalogName); + } finally { + deactivatePluginClassLoader(); + } + } +*/ + + @Override + public List getRowFilters(SystemSecurityContext context, CatalogSchemaTableName tableName) + { + List viewExpressions; + try { + activatePluginClassLoader(); + viewExpressions = systemAccessControlImpl.getRowFilters(context, tableName); + } + finally { + deactivatePluginClassLoader(); + } + return viewExpressions; + } + + @Override + public List getColumnMasks(SystemSecurityContext context, CatalogSchemaTableName tableName, String columnName, Type type) + { + List viewExpressions; + try { + activatePluginClassLoader(); + viewExpressions = systemAccessControlImpl.getColumnMasks(context, tableName, columnName, type); + } + finally { + deactivatePluginClassLoader(); + } + return viewExpressions; + } + + @Override + public void checkCanSetUser(Optional principal, String userName) + { + try { + activatePluginClassLoader(); + systemAccessControlImpl.checkCanSetUser(principal, userName); + } + finally { + deactivatePluginClassLoader(); + } + } + + @Override + public void checkCanGrantExecuteFunctionPrivilege(SystemSecurityContext context, String functionName, TrinoPrincipal grantee, boolean grantOption) + { + try { + activatePluginClassLoader(); + systemAccessControlImpl.checkCanGrantExecuteFunctionPrivilege(context, functionName, grantee, grantOption); + } + finally { + deactivatePluginClassLoader(); + } + } + + @Override + public void checkCanSetSchemaAuthorization(SystemSecurityContext context, CatalogSchemaName schema, TrinoPrincipal principal) + { + try { + activatePluginClassLoader(); + systemAccessControlImpl.checkCanSetSchemaAuthorization(context, schema, principal); + } + finally { + deactivatePluginClassLoader(); + } + } + + @Override + public void checkCanShowCreateSchema(SystemSecurityContext context, CatalogSchemaName schemaName) + { + try { + activatePluginClassLoader(); + systemAccessControlImpl.checkCanShowCreateSchema(context, schemaName); + } + finally { + deactivatePluginClassLoader(); + } + } + + @Override + public void checkCanExecuteProcedure(SystemSecurityContext systemSecurityContext, CatalogSchemaRoutineName procedure) + { + try { + activatePluginClassLoader(); + systemAccessControlImpl.checkCanExecuteProcedure(systemSecurityContext, procedure); + } + finally { + deactivatePluginClassLoader(); + } + } + + @Override + public void checkCanExecuteFunction(SystemSecurityContext systemSecurityContext, String functionName) + { + try { + activatePluginClassLoader(); + systemAccessControlImpl.checkCanExecuteFunction(systemSecurityContext, functionName); + } + finally { + deactivatePluginClassLoader(); + } + } + + private void activatePluginClassLoader() + { + if (rangerPluginClassLoader != null) { + rangerPluginClassLoader.activate(); + } + } + + private void deactivatePluginClassLoader() + { + if (rangerPluginClassLoader != null) { + rangerPluginClassLoader.deactivate(); + } + } +} diff --git a/plugin/trino-ranger/src/main/java/io/trino/plugin/ranger/RangerSystemAccessControlFactory.java b/plugin/trino-ranger/src/main/java/io/trino/plugin/ranger/RangerSystemAccessControlFactory.java new file mode 100644 index 0000000000000..5fc9034657f3f --- /dev/null +++ b/plugin/trino-ranger/src/main/java/io/trino/plugin/ranger/RangerSystemAccessControlFactory.java @@ -0,0 +1,63 @@ +/* + * Licensed 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 io.trino.plugin.ranger; + +import com.google.inject.Injector; +import com.google.inject.Scopes; +import io.airlift.bootstrap.Bootstrap; +import io.trino.spi.security.SystemAccessControl; +import io.trino.spi.security.SystemAccessControlFactory; + +import java.util.Map; + +import static com.google.common.base.Throwables.throwIfUnchecked; +import static io.airlift.configuration.ConfigBinder.configBinder; +import static java.util.Objects.requireNonNull; + +public class RangerSystemAccessControlFactory + implements SystemAccessControlFactory +{ + private static final String NAME = "ranger"; + + @Override + public String getName() + { + return NAME; + } + + @Override + public SystemAccessControl create(Map config) + { + requireNonNull(config, "config is null"); + + try { + Bootstrap app = new Bootstrap( + binder -> { + configBinder(binder).bindConfig(RangerConfig.class); + binder.bind(RangerSystemAccessControl.class).in(Scopes.SINGLETON); + }); + + Injector injector = app + .doNotInitializeLogging() + .setRequiredConfigurationProperties(config) + .initialize(); + + return injector.getInstance(RangerSystemAccessControl.class); + } + catch (Exception e) { + throwIfUnchecked(e); + throw new RuntimeException(e); + } + } +} diff --git a/plugin/trino-ranger/src/main/java/io/trino/plugin/ranger/RangerSystemAccessControlImpl.java b/plugin/trino-ranger/src/main/java/io/trino/plugin/ranger/RangerSystemAccessControlImpl.java new file mode 100644 index 0000000000000..da7152c4d5053 --- /dev/null +++ b/plugin/trino-ranger/src/main/java/io/trino/plugin/ranger/RangerSystemAccessControlImpl.java @@ -0,0 +1,1006 @@ +/* + * Licensed 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 io.trino.plugin.ranger; + +import io.airlift.log.Logger; +import io.trino.spi.TrinoException; +import io.trino.spi.connector.CatalogSchemaName; +import io.trino.spi.connector.CatalogSchemaRoutineName; +import io.trino.spi.connector.CatalogSchemaTableName; +import io.trino.spi.connector.SchemaTableName; +import io.trino.spi.security.AccessDeniedException; +import io.trino.spi.security.Privilege; +import io.trino.spi.security.SystemAccessControl; +import io.trino.spi.security.SystemSecurityContext; +import io.trino.spi.security.TrinoPrincipal; +import io.trino.spi.security.ViewExpression; +import io.trino.spi.type.Type; +import org.apache.commons.lang.StringUtils; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.security.UserGroupInformation; +import org.apache.ranger.authorization.hadoop.config.RangerConfiguration; +import org.apache.ranger.plugin.audit.RangerDefaultAuditHandler; +import org.apache.ranger.plugin.model.RangerPolicy; +import org.apache.ranger.plugin.model.RangerServiceDef; +import org.apache.ranger.plugin.policyengine.RangerAccessResult; +import org.apache.ranger.plugin.service.RangerBasePlugin; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.security.Principal; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import static io.trino.hadoop.ConfigurationInstantiator.newEmptyConfiguration; +import static io.trino.spi.StandardErrorCode.CONFIGURATION_INVALID; + +public class RangerSystemAccessControlImpl + implements SystemAccessControl +{ + // User group information + private boolean useUgi; + // These strings hold the full path to the .xml configuration files + private String auditConfig; + private String hadoopConfig; + private String policyManagerSSLConfig; + private String securityConfig; + + public static final String RANGER_CONFIG_KEYTAB = "ranger.keytab"; + public static final String RANGER_CONFIG_PRINCIPAL = "ranger.principal"; + public static final String RANGER_CONFIG_USE_UGI = "ranger.use_ugi"; + public static final String RANGER_CONFIG_HADOOP_CONFIG = "ranger.hadoop_config"; + public static final String RANGER_AUDIT_CONFIG = "ranger.audit_resource"; + public static final String RANGER_SECURITY_CONFIG = "ranger.security_resource"; + public static final String RANGER_POLICY_MANAGER_SSL_CONFIG = "ranger.policy_manager_ssl_resource"; + public static final String RANGER_TRINO_DEFAULT_HADOOP_CONF = "trino-ranger-site.xml"; + public static final String RANGER_TRINO_SERVICETYPE = "trino"; + public static final String RANGER_TRINO_APPID = "trino"; + public static final String RANGER_SERVICE_NAME = "ranger.service_name"; + + private static final Logger LOG = Logger.get(RangerSystemAccessControl.class); + + private final RangerBasePlugin rangerPlugin; + + public RangerSystemAccessControlImpl(Map config) + { + super(); + useUgi = false; + if (config.get(RANGER_SERVICE_NAME) != null) { + rangerPlugin = new RangerBasePlugin(RANGER_TRINO_SERVICETYPE, config.get(RANGER_SERVICE_NAME), RANGER_TRINO_APPID); + } + else { + throw invalidRangerConfigEntry(config, RANGER_SERVICE_NAME); + } + + Configuration hadoopConf = newEmptyConfiguration(); + if (config.get(RANGER_CONFIG_HADOOP_CONFIG) != null) { + hadoopConfig = config.get(RANGER_CONFIG_HADOOP_CONFIG); + URL url = getFileLocation(config.get(RANGER_CONFIG_HADOOP_CONFIG), Optional.of(RANGER_CONFIG_HADOOP_CONFIG)); + if (url == null) { + throw invalidRangerConfigFile(hadoopConfig); + } + hadoopConf.addResource(url); + // This resource is more for Hadoop but lets add to the general book keeping config. Use false so parsing + // isnt restricted to the directory and subdirectory where the .jar is installed. + rangerPlugin.getConfig().addResource(url); + } + else { + throw invalidRangerConfigEntry(config, RANGER_CONFIG_HADOOP_CONFIG); + } + + UserGroupInformation.setConfiguration(hadoopConf); + + if (config.get(RANGER_CONFIG_KEYTAB) != null && config.get(RANGER_CONFIG_PRINCIPAL) != null) { + String keytab = config.get(RANGER_CONFIG_KEYTAB); + String principal = config.get(RANGER_CONFIG_PRINCIPAL); + + LOG.info("Performing kerberos login with principal " + principal + " and keytab " + keytab); + + try { + UserGroupInformation.loginUserFromKeytab(principal, keytab); + } + catch (IOException ioe) { + LOG.error(ioe, "Kerberos login failed"); + throw new RuntimeException(ioe); + } + } + + if (config.getOrDefault(RANGER_CONFIG_USE_UGI, "false").equalsIgnoreCase("true")) { + useUgi = true; + } + + if (config.get(RANGER_AUDIT_CONFIG) != null) { + auditConfig = config.get(RANGER_AUDIT_CONFIG); + URL url = getFileLocation(auditConfig, Optional.of(RANGER_AUDIT_CONFIG)); + if (url == null) { + throw invalidRangerConfigFile(auditConfig); + } + // Add the resources. Make sure we use false or the parsing will be restricted to the .class directory. + rangerPlugin.getConfig().addResource(url, false); + } + else { + throw invalidRangerConfigEntry(config, RANGER_AUDIT_CONFIG); + } + + if (config.get(RANGER_SECURITY_CONFIG) != null) { + securityConfig = config.get(RANGER_SECURITY_CONFIG); + URL url = getFileLocation(securityConfig, Optional.of(RANGER_SECURITY_CONFIG)); + if (url == null) { + throw invalidRangerConfigFile(securityConfig); + } + // Add the resources. Make sure we use false or the parsing will be restricted to the .class install directory. + rangerPlugin.getConfig().addResource(url, false); + } + else { + throw invalidRangerConfigEntry(config, RANGER_SECURITY_CONFIG); + } + + if (config.get(RANGER_POLICY_MANAGER_SSL_CONFIG) != null) { + policyManagerSSLConfig = config.get(RANGER_POLICY_MANAGER_SSL_CONFIG); + URL url = getFileLocation(policyManagerSSLConfig, Optional.of(RANGER_AUDIT_CONFIG)); + if (url == null) { + throw invalidRangerConfigFile(policyManagerSSLConfig); + } + // Add the resources. Make sure we use false or the parsing will be restricted to the .class install directory. + rangerPlugin.getConfig().addResource(url, false); + } + else { + throw invalidRangerConfigEntry(config, RANGER_POLICY_MANAGER_SSL_CONFIG); + } + + // init() will initialize the loading of the configurations from the above added resources + // Also lauches the RangerRESTClient which reads policies from Ranger + rangerPlugin.init(); + rangerPlugin.setResultProcessor(new RangerDefaultAuditHandler()); + } + + /** + * FILTERING AND DATA MASKING + **/ + private RangerAccessResult getDataMaskResult(RangerTrinoAccessRequest request) + { + if (LOG.isDebugEnabled()) { + LOG.debug("==> getDataMaskResult(request=" + request + ")"); + } + + RangerAccessResult ret = rangerPlugin.evalDataMaskPolicies(request, null); + + if (LOG.isDebugEnabled()) { + LOG.debug("<== getDataMaskResult(request=" + request + "): ret=" + ret); + } + + return ret; + } + + private RangerAccessResult getRowFilterResult(RangerTrinoAccessRequest request) + { + if (LOG.isDebugEnabled()) { + LOG.debug("==> getRowFilterResult(request=" + request + ")"); + } + + RangerAccessResult ret = rangerPlugin.evalRowFilterPolicies(request, null); + + if (LOG.isDebugEnabled()) { + LOG.debug("<== getRowFilterResult(request=" + request + "): ret=" + ret); + } + + return ret; + } + + private boolean isDataMaskEnabled(RangerAccessResult result) + { + return result != null && result.isMaskEnabled(); + } + + private boolean isRowFilterEnabled(RangerAccessResult result) + { + return result != null && result.isRowFilterEnabled(); + } + + @Override + public List getRowFilters(SystemSecurityContext context, CatalogSchemaTableName tableName) + { + RangerTrinoAccessRequest request = createAccessRequest(createResource(tableName), context, TrinoAccessType.SELECT); + RangerAccessResult result = getRowFilterResult(request); + + ViewExpression viewExpression = null; + if (isRowFilterEnabled(result)) { + String filter = result.getFilterExpr(); + + viewExpression = new ViewExpression( + context.getIdentity().getUser(), + Optional.of(tableName.getCatalogName()), + Optional.of(tableName.getSchemaTableName().getSchemaName()), + filter); + } + + return viewExpression == null ? Collections.emptyList() : List.of(viewExpression); + } + + @Override + public List getColumnMasks(SystemSecurityContext context, CatalogSchemaTableName tableName, String columnName, Type type) + { + RangerTrinoAccessRequest request = createAccessRequest( + createResource(tableName.getCatalogName(), tableName.getSchemaTableName().getSchemaName(), + tableName.getSchemaTableName().getTableName(), Optional.of(columnName)), + context, TrinoAccessType.SELECT); + RangerAccessResult result = getDataMaskResult(request); + + ViewExpression viewExpression = null; + if (isDataMaskEnabled(result)) { + String maskType = result.getMaskType(); + RangerServiceDef.RangerDataMaskTypeDef maskTypeDef = result.getMaskTypeDef(); + String transformer = null; + + if (maskTypeDef != null) { + transformer = maskTypeDef.getTransformer(); + } + + if (StringUtils.equalsIgnoreCase(maskType, RangerPolicy.MASK_TYPE_NULL)) { + transformer = "NULL"; + } + else if (StringUtils.equalsIgnoreCase(maskType, RangerPolicy.MASK_TYPE_CUSTOM)) { + String maskedValue = result.getMaskedValue(); + + if (maskedValue == null) { + transformer = "NULL"; + } + else { + transformer = maskedValue; + } + } + + if (StringUtils.isNotEmpty(transformer)) { + transformer = transformer.replace("{col}", columnName).replace("{type}", type.getDisplayName()); + } + + viewExpression = new ViewExpression( + context.getIdentity().getUser(), + Optional.of(tableName.getCatalogName()), + Optional.of(tableName.getSchemaTableName().getSchemaName()), + transformer); + + if (LOG.isDebugEnabled()) { + LOG.debug("getColumnMask: user: %s, catalog: %s, schema: %s, transformer: %s"); + } + } + + return viewExpression == null ? Collections.emptyList() : List.of(viewExpression); + } + + @Override + public Set filterCatalogs(SystemSecurityContext context, Set catalogs) + { + LOG.debug("==> RangerSystemAccessControl.filterCatalogs(" + catalogs + ")"); + Set filteredCatalogs = new HashSet<>(catalogs.size()); + for (String catalog : catalogs) { + if (hasPermission(createResource(catalog), context, TrinoAccessType.SELECT)) { + filteredCatalogs.add(catalog); + } + } + return filteredCatalogs; + } + + @Override + public Set filterSchemas(SystemSecurityContext context, String catalogName, Set schemaNames) + { + LOG.debug("==> RangerSystemAccessControl.filterSchemas(" + catalogName + ")"); + Set filteredSchemaNames = new HashSet<>(schemaNames.size()); + for (String schemaName : schemaNames) { + if (hasPermission(createResource(catalogName, schemaName), context, TrinoAccessType.SELECT)) { + filteredSchemaNames.add(schemaName); + } + } + return filteredSchemaNames; + } + + @Override + public Set filterTables(SystemSecurityContext context, String catalogName, Set tableNames) + { + LOG.debug("==> RangerSystemAccessControl.filterTables(" + catalogName + ")"); + Set filteredTableNames = new HashSet<>(tableNames.size()); + for (SchemaTableName tableName : tableNames) { + RangerTrinoResource res = createResource(catalogName, tableName.getSchemaName(), tableName.getTableName()); + if (hasPermission(res, context, TrinoAccessType.SELECT)) { + filteredTableNames.add(tableName); + } + } + return filteredTableNames; + } + + /** PERMISSION CHECKS ORDERED BY SYSTEM, CATALOG, SCHEMA, TABLE, VIEW, COLUMN, QUERY, FUNCTIONS, PROCEDURES **/ + + /** + * SYSTEM + **/ + + @Override + public void checkCanSetSystemSessionProperty(SystemSecurityContext context, String propertyName) + { + if (!hasPermission(createSystemPropertyResource(propertyName), context, TrinoAccessType.ALTER)) { + LOG.debug("RangerSystemAccessControl.checkCanSetSystemSessionProperty denied"); + AccessDeniedException.denySetSystemSessionProperty(propertyName); + } + } + + @Override + public void checkCanImpersonateUser(SystemSecurityContext context, String userName) + { + if (!hasPermission(createUserResource(userName), context, TrinoAccessType.IMPERSONATE)) { + LOG.debug("RangerSystemAccessControl.checkCanImpersonateUser(" + userName + ") denied"); + AccessDeniedException.denyImpersonateUser(context.getIdentity().getUser(), userName); + } + } + + @Override + public void checkCanSetUser(Optional principal, String userName) + { + // pass as it is deprecated + } + + /** + * CATALOG + **/ + @Override + public void checkCanSetCatalogSessionProperty(SystemSecurityContext context, String catalogName, String propertyName) + { + if (!hasPermission(createCatalogSessionResource(catalogName, propertyName), context, TrinoAccessType.ALTER)) { + LOG.debug("RangerSystemAccessControl.checkCanSetCatalogSessionProperty(" + catalogName + ") denied"); + AccessDeniedException.denySetCatalogSessionProperty(catalogName, propertyName); + } + } + +/* Erik Anderson, commented out because interface is currently broken + @Override + public void checkCanShowRoles(SystemSecurityContext context, String catalogName) { + if (!hasPermission(createResource(catalogName), context, TrinoAccessType.SHOW)) { + LOG.debug("RangerSystemAccessControl.checkCanShowRoles(" + catalogName + ") denied"); + AccessDeniedException.denyShowRoles(catalogName); + } + } +*/ + + @Override + public void checkCanAccessCatalog(SystemSecurityContext context, String catalogName) + { + if (!hasPermission(createResource(catalogName), context, TrinoAccessType.USE)) { + LOG.debug("RangerSystemAccessControl.checkCanAccessCatalog(" + catalogName + ") denied"); + AccessDeniedException.denyCatalogAccess(catalogName); + } + } + + @Override + public void checkCanShowSchemas(SystemSecurityContext context, String catalogName) + { + if (!hasPermission(createResource(catalogName), context, TrinoAccessType.SHOW)) { + LOG.debug("RangerSystemAccessControl.checkCanShowSchemas(" + catalogName + ") denied"); + AccessDeniedException.denyShowSchemas(catalogName); + } + } + + /** + * SCHEMA + **/ + + @Override + public void checkCanSetSchemaAuthorization(SystemSecurityContext context, CatalogSchemaName schema, TrinoPrincipal principal) + { + if (!hasPermission(createResource(schema.getCatalogName(), schema.getSchemaName()), context, TrinoAccessType.GRANT)) { + LOG.debug("RangerSystemAccessControl.checkCanSetSchemaAuthorization(" + schema.getSchemaName() + ") denied"); + AccessDeniedException.denySetSchemaAuthorization(schema.getSchemaName(), principal); + } + } + + @Override + public void checkCanShowCreateSchema(SystemSecurityContext context, CatalogSchemaName schema) + { + if (!hasPermission(createResource(schema.getCatalogName(), schema.getSchemaName()), context, TrinoAccessType.SHOW)) { + LOG.debug("RangerSystemAccessControl.checkCanShowCreateSchema(" + schema.getSchemaName() + ") denied"); + AccessDeniedException.denyShowCreateSchema(schema.getSchemaName()); + } + } + + /** + * Create schema is evaluated on the level of the Catalog. This means that it is assumed you have permission + * to create a schema when you have create rights on the catalog level + */ + @Override + public void checkCanCreateSchema(SystemSecurityContext context, CatalogSchemaName schema) + { + if (!hasPermission(createResource(schema.getCatalogName()), context, TrinoAccessType.CREATE)) { + LOG.debug("RangerSystemAccessControl.checkCanCreateSchema(" + schema.getSchemaName() + ") denied"); + AccessDeniedException.denyCreateSchema(schema.getSchemaName()); + } + } + + /** + * This is evaluated against the schema name as ownership information is not available + */ + @Override + public void checkCanDropSchema(SystemSecurityContext context, CatalogSchemaName schema) + { + if (!hasPermission(createResource(schema.getCatalogName(), schema.getSchemaName()), context, TrinoAccessType.DROP)) { + LOG.debug("RangerSystemAccessControl.checkCanDropSchema(" + schema.getSchemaName() + ") denied"); + AccessDeniedException.denyDropSchema(schema.getSchemaName()); + } + } + + /** + * This is evaluated against the schema name as ownership information is not available + */ + @Override + public void checkCanRenameSchema(SystemSecurityContext context, CatalogSchemaName schema, String newSchemaName) + { + RangerTrinoResource res = createResource(schema.getCatalogName(), schema.getSchemaName()); + if (!hasPermission(res, context, TrinoAccessType.ALTER)) { + LOG.debug("RangerSystemAccessControl.checkCanRenameSchema(" + schema.getSchemaName() + ") denied"); + AccessDeniedException.denyRenameSchema(schema.getSchemaName(), newSchemaName); + } + } + + /** + * TABLE + **/ + + @Override + public void checkCanShowTables(SystemSecurityContext context, CatalogSchemaName schema) + { + if (!hasPermission(createResource(schema), context, TrinoAccessType.SHOW)) { + LOG.debug("RangerSystemAccessControl.checkCanShowTables(" + schema + ") denied"); + AccessDeniedException.denyShowTables(schema.toString()); + } + } + + @Override + public void checkCanShowCreateTable(SystemSecurityContext context, CatalogSchemaTableName table) + { + if (!hasPermission(createResource(table), context, TrinoAccessType.SHOW)) { + LOG.debug("RangerSystemAccessControl.checkCanShowTables(" + table + ") denied"); + AccessDeniedException.denyShowCreateTable(table.toString()); + } + } + + /** + * check if materialized view can be created + */ + @Override + public void checkCanCreateMaterializedView(SystemSecurityContext context, CatalogSchemaTableName materializedView, + Map properties) + { + if (!hasPermission(createResource(materializedView), context, TrinoAccessType.CREATE)) { + LOG.debug("RangerSystemAccessControl.checkCanCreateMaterializedView( " + materializedView.getSchemaTableName().getTableName() + ") denied"); + AccessDeniedException.denyCreateMaterializedView(materializedView.getSchemaTableName().getTableName()); + } + } + + @Override + public void checkCanDropMaterializedView(SystemSecurityContext context, CatalogSchemaTableName materializedView) + { + if (!hasPermission(createResource(materializedView), context, TrinoAccessType.DROP)) { + LOG.debug("RangerSystemAccessControl.checkCanDropMaterializedView(" + materializedView.getSchemaTableName().getTableName() + ") denied"); + AccessDeniedException.denyDropMaterializedView(materializedView.getSchemaTableName().getTableName()); + } + } + + @Override + public void checkCanRefreshMaterializedView(SystemSecurityContext context, CatalogSchemaTableName materializedView) + { + if (!hasPermission(createResource(materializedView), context, TrinoAccessType.ALTER)) { + LOG.debug("RangerSystemAccessControl.checkCanRefreshMaterializedView\n(" + materializedView.getSchemaTableName().getTableName() + ") denied"); + AccessDeniedException.denyRefreshMaterializedView(materializedView.getSchemaTableName().getTableName()); + } + } + + @Override + public void checkCanRenameMaterializedView(SystemSecurityContext context, CatalogSchemaTableName view, CatalogSchemaTableName newView) + { + if (!hasPermission(createResource(view), context, TrinoAccessType.ALTER)) { + LOG.debug("RangerSystemAccessControl.checkCanRenameMaterializedView\n(" + view.getSchemaTableName().getTableName() + ") denied"); + AccessDeniedException.denyRenameMaterializedView(view.getSchemaTableName().getTableName(), + newView.getSchemaTableName().getTableName()); + } + } + + /** + * Create table is verified on schema level + */ + @Override + public void checkCanCreateTable(SystemSecurityContext context, CatalogSchemaTableName table, Map properties) + { + if (!hasPermission(createResource(table.getCatalogName(), table.getSchemaTableName().getSchemaName()), context, TrinoAccessType.CREATE)) { + LOG.debug("RangerSystemAccessControl.checkCanCreateTable(" + table.getSchemaTableName().getTableName() + ") denied"); + AccessDeniedException.denyCreateTable(table.getSchemaTableName().getTableName()); + } + } + + /** + * This is evaluated against the table name as ownership information is not available + */ + @Override + public void checkCanDropTable(SystemSecurityContext context, CatalogSchemaTableName table) + { + if (!hasPermission(createResource(table), context, TrinoAccessType.DROP)) { + LOG.debug("RangerSystemAccessControl.checkCanDropTable(" + table.getSchemaTableName().getTableName() + ") denied"); + AccessDeniedException.denyDropTable(table.getSchemaTableName().getTableName()); + } + } + + /** + * This is evaluated against the table name as ownership information is not available + */ + @Override + public void checkCanRenameTable(SystemSecurityContext context, CatalogSchemaTableName table, CatalogSchemaTableName newTable) + { + RangerTrinoResource res = createResource(table); + if (!hasPermission(res, context, TrinoAccessType.ALTER)) { + LOG.debug("RangerSystemAccessControl.checkCanRenameTable(" + table.getSchemaTableName().getTableName() + ") denied"); + AccessDeniedException.denyRenameTable(table.getSchemaTableName().getTableName(), newTable.getSchemaTableName().getTableName()); + } + } + + @Override + public void checkCanInsertIntoTable(SystemSecurityContext context, CatalogSchemaTableName table) + { + RangerTrinoResource res = createResource(table); + if (!hasPermission(res, context, TrinoAccessType.INSERT)) { + LOG.debug("RangerSystemAccessControl.checkCanInsertIntoTable(" + table.getSchemaTableName().getTableName() + ") denied"); + AccessDeniedException.denyInsertTable(table.getSchemaTableName().getTableName()); + } + } + + @Override + public void checkCanDeleteFromTable(SystemSecurityContext context, CatalogSchemaTableName table) + { + if (!hasPermission(createResource(table), context, TrinoAccessType.DELETE)) { + LOG.debug("RangerSystemAccessControl.checkCanDeleteFromTable(" + table.getSchemaTableName().getTableName() + ") denied"); + AccessDeniedException.denyDeleteTable(table.getSchemaTableName().getTableName()); + } + } + + @Override + public void checkCanGrantTablePrivilege(SystemSecurityContext context, Privilege privilege, CatalogSchemaTableName table, TrinoPrincipal grantee, boolean withGrantOption) + { + if (!hasPermission(createResource(table), context, TrinoAccessType.GRANT)) { + LOG.debug("RangerSystemAccessControl.checkCanGrantTablePrivilege(" + table + ") denied"); + AccessDeniedException.denyGrantTablePrivilege(privilege.toString(), table.toString()); + } + } + + @Override + public void checkCanRevokeTablePrivilege(SystemSecurityContext context, Privilege privilege, CatalogSchemaTableName table, TrinoPrincipal revokee, boolean grantOptionFor) + { + if (!hasPermission(createResource(table), context, TrinoAccessType.REVOKE)) { + LOG.debug("RangerSystemAccessControl.checkCanRevokeTablePrivilege(" + table + ") denied"); + AccessDeniedException.denyRevokeTablePrivilege(privilege.toString(), table.toString()); + } + } + + @Override + public void checkCanSetTableComment(SystemSecurityContext context, CatalogSchemaTableName table) + { + if (!hasPermission(createResource(table), context, TrinoAccessType.ALTER)) { + LOG.debug("RangerSystemAccessControl.checkCanSetTableComment(" + table + ") denied"); + AccessDeniedException.denyCommentTable(table.toString()); + } + } + + @Override + public void checkCanExecuteTableProcedure(SystemSecurityContext context, CatalogSchemaTableName table, + String procedure) + { + if (!hasPermission(createResource(table), context, TrinoAccessType.EXECUTE)) { + LOG.debug("RangerSystemAccessControl.checkCanExecuteTableProcedure(" + table + ") denied"); + AccessDeniedException.denyExecuteTableProcedure(table.toString(), procedure); + } + } + + /** + * Create view is verified on schema level + */ + @Override + public void checkCanCreateView(SystemSecurityContext context, CatalogSchemaTableName view) + { + if (!hasPermission(createResource(view.getCatalogName(), view.getSchemaTableName().getSchemaName()), context, TrinoAccessType.CREATE)) { + LOG.debug("RangerSystemAccessControl.checkCanCreateView(" + view.getSchemaTableName().getTableName() + ") denied"); + AccessDeniedException.denyCreateView(view.getSchemaTableName().getTableName()); + } + } + + /** + * This is evaluated against the table name as ownership information is not available + */ + @Override + public void checkCanDropView(SystemSecurityContext context, CatalogSchemaTableName view) + { + if (!hasPermission(createResource(view), context, TrinoAccessType.DROP)) { + LOG.debug("RangerSystemAccessControl.checkCanDropView(" + view.getSchemaTableName().getTableName() + ") denied"); + AccessDeniedException.denyDropView(view.getSchemaTableName().getTableName()); + } + } + + /** + * This check equals the check for checkCanCreateView + */ + @Override + public void checkCanCreateViewWithSelectFromColumns(SystemSecurityContext context, CatalogSchemaTableName table, Set columns) + { + try { + checkCanCreateView(context, table); + } + catch (AccessDeniedException ade) { + LOG.debug("RangerSystemAccessControl.checkCanCreateViewWithSelectFromColumns(" + table.getSchemaTableName().getTableName() + ") denied"); + AccessDeniedException.denyCreateViewWithSelect(table.getSchemaTableName().getTableName(), context.getIdentity()); + } + } + + /** + * This is evaluated against the table name as ownership information is not available + */ + @Override + public void checkCanRenameView(SystemSecurityContext context, CatalogSchemaTableName view, CatalogSchemaTableName newView) + { + if (!hasPermission(createResource(view), context, TrinoAccessType.ALTER)) { + LOG.debug("RangerSystemAccessControl.checkCanRenameView(" + view + ") denied"); + AccessDeniedException.denyRenameView(view.toString(), newView.toString()); + } + } + + /** COLUMN **/ + + /** + * This is evaluated on table level + */ + @Override + public void checkCanAddColumn(SystemSecurityContext context, CatalogSchemaTableName table) + { + RangerTrinoResource res = createResource(table); + if (!hasPermission(res, context, TrinoAccessType.ALTER)) { + AccessDeniedException.denyAddColumn(table.getSchemaTableName().getTableName()); + } + } + + /** + * This is evaluated on table level + */ + @Override + public void checkCanDropColumn(SystemSecurityContext context, CatalogSchemaTableName table) + { + RangerTrinoResource res = createResource(table); + if (!hasPermission(res, context, TrinoAccessType.DROP)) { + LOG.debug("RangerSystemAccessControl.checkCanDropColumn(" + table.getSchemaTableName().getTableName() + ") denied"); + AccessDeniedException.denyDropColumn(table.getSchemaTableName().getTableName()); + } + } + + /** + * This is evaluated on table level + */ + @Override + public void checkCanRenameColumn(SystemSecurityContext context, CatalogSchemaTableName table) + { + RangerTrinoResource res = createResource(table); + if (!hasPermission(res, context, TrinoAccessType.ALTER)) { + LOG.debug("RangerSystemAccessControl.checkCanRenameColumn(" + table.getSchemaTableName().getTableName() + ") denied"); + AccessDeniedException.denyRenameColumn(table.getSchemaTableName().getTableName()); + } + } + + /** + * This is evaluated on table level + */ + @Override + public void checkCanShowColumns(SystemSecurityContext context, CatalogSchemaTableName table) + { + if (!hasPermission(createResource(table), context, TrinoAccessType.SHOW)) { + LOG.debug("RangerSystemAccessControl.checkCanShowTables(" + table + ") denied"); + AccessDeniedException.denyShowColumns(table.toString()); + } + } + + @Override + public void checkCanSelectFromColumns(SystemSecurityContext context, CatalogSchemaTableName table, Set columns) + { + for (RangerTrinoResource res : createResource(table, columns)) { + if (!hasPermission(res, context, TrinoAccessType.SELECT)) { + LOG.debug("RangerSystemAccessControl.checkCanSelectFromColumns(" + table.getSchemaTableName().getTableName() + ") denied"); + AccessDeniedException.denySelectColumns(table.getSchemaTableName().getTableName(), columns); + } + } + } + + /** + * This is a NOOP, no filtering is applied + */ + @Override + public Set filterColumns(SystemSecurityContext context, CatalogSchemaTableName table, Set columns) + { + return columns; + } + + /** QUERY **/ + + /** + * This is a NOOP. Everyone can execute a query + * + * @param context + */ + @Override + public void checkCanExecuteQuery(SystemSecurityContext context) + { + } + + @Override + public void checkCanViewQueryOwnedBy(SystemSecurityContext context, String queryOwner) + { + if (!hasPermission(createUserResource(queryOwner), context, TrinoAccessType.IMPERSONATE)) { + LOG.debug("RangerSystemAccessControl.checkCanViewQueryOwnedBy(" + queryOwner + ") denied"); + AccessDeniedException.denyImpersonateUser(context.getIdentity().getUser(), queryOwner); + } + } + + /** + * This is a NOOP, no filtering is applied + */ + @Override + public Set filterViewQueryOwnedBy(SystemSecurityContext context, Set queryOwners) + { + return queryOwners; + } + + @Override + public void checkCanKillQueryOwnedBy(SystemSecurityContext context, String queryOwner) + { + if (!hasPermission(createUserResource(queryOwner), context, TrinoAccessType.IMPERSONATE)) { + LOG.debug("RangerSystemAccessControl.checkCanKillQueryOwnedBy(" + queryOwner + ") denied"); + AccessDeniedException.denyImpersonateUser(context.getIdentity().getUser(), queryOwner); + } + } + + /** + * FUNCTIONS + **/ + @Override + public void checkCanGrantExecuteFunctionPrivilege(SystemSecurityContext context, String function, TrinoPrincipal grantee, boolean grantOption) + { + if (!hasPermission(createFunctionResource(function), context, TrinoAccessType.GRANT)) { + LOG.debug("RangerSystemAccessControl.checkCanGrantExecuteFunctionPrivilege(" + function + ") denied"); + AccessDeniedException.denyGrantExecuteFunctionPrivilege(function, context.getIdentity(), grantee.getName()); + } + } + + @Override + public void checkCanExecuteFunction(SystemSecurityContext context, String function) + { + if (!hasPermission(createFunctionResource(function), context, TrinoAccessType.EXECUTE)) { + LOG.debug("RangerSystemAccessControl.checkCanExecuteFunction(" + function + ") denied"); + AccessDeniedException.denyExecuteFunction(function); + } + } + + /** + * PROCEDURES + **/ + @Override + public void checkCanExecuteProcedure(SystemSecurityContext context, CatalogSchemaRoutineName procedure) + { + if (!hasPermission(createProcedureResource(procedure), context, TrinoAccessType.EXECUTE)) { + LOG.debug("RangerSystemAccessControl.checkCanExecuteFunction(" + procedure.getSchemaRoutineName().getRoutineName() + ") denied"); + AccessDeniedException.denyExecuteProcedure(procedure.getSchemaRoutineName().getRoutineName()); + } + } + + /** + * HELPER FUNCTIONS + **/ + + private RangerTrinoAccessRequest createAccessRequest(RangerTrinoResource resource, SystemSecurityContext context, TrinoAccessType accessType) + { + String userName = null; + Set userGroups = null; + + if (useUgi) { + UserGroupInformation ugi = UserGroupInformation.createRemoteUser(context.getIdentity().getUser()); + + userName = ugi.getShortUserName(); + String[] groups = ugi != null ? ugi.getGroupNames() : null; + + if (groups != null && groups.length > 0) { + userGroups = new HashSet<>(Arrays.asList(groups)); + } + } + else { + userName = context.getIdentity().getUser(); + userGroups = context.getIdentity().getGroups(); + } + + RangerTrinoAccessRequest request = new RangerTrinoAccessRequest( + resource, + userName, + userGroups, + accessType); + + return request; + } + + private boolean hasPermission(RangerTrinoResource resource, SystemSecurityContext context, TrinoAccessType accessType) + { + boolean ret = false; + + RangerTrinoAccessRequest request = createAccessRequest(resource, context, accessType); + + RangerAccessResult result = rangerPlugin.isAccessAllowed(request); + if (result != null && result.getIsAllowed()) { + ret = true; + } + + return ret; + } + + private static RangerTrinoResource createUserResource(String userName) + { + RangerTrinoResource res = new RangerTrinoResource(); + res.setValue(RangerTrinoResource.KEY_USER, userName); + + return res; + } + + private static RangerTrinoResource createFunctionResource(String function) + { + RangerTrinoResource res = new RangerTrinoResource(); + res.setValue(RangerTrinoResource.KEY_FUNCTION, function); + + return res; + } + + private static RangerTrinoResource createProcedureResource(CatalogSchemaRoutineName procedure) + { + RangerTrinoResource res = new RangerTrinoResource(); + res.setValue(RangerTrinoResource.KEY_CATALOG, procedure.getCatalogName()); + res.setValue(RangerTrinoResource.KEY_SCHEMA, procedure.getSchemaRoutineName().getSchemaName()); + res.setValue(RangerTrinoResource.KEY_PROCEDURE, procedure.getSchemaRoutineName().getRoutineName()); + + return res; + } + + private static RangerTrinoResource createCatalogSessionResource(String catalogName, String propertyName) + { + RangerTrinoResource res = new RangerTrinoResource(); + res.setValue(RangerTrinoResource.KEY_CATALOG, catalogName); + res.setValue(RangerTrinoResource.KEY_SESSION_PROPERTY, propertyName); + + return res; + } + + private static RangerTrinoResource createSystemPropertyResource(String property) + { + RangerTrinoResource res = new RangerTrinoResource(); + res.setValue(RangerTrinoResource.KEY_SYSTEM_PROPERTY, property); + + return res; + } + + private static RangerTrinoResource createResource(CatalogSchemaName catalogSchemaName) + { + return createResource(catalogSchemaName.getCatalogName(), catalogSchemaName.getSchemaName()); + } + + private static RangerTrinoResource createResource(CatalogSchemaTableName catalogSchemaTableName) + { + return createResource(catalogSchemaTableName.getCatalogName(), + catalogSchemaTableName.getSchemaTableName().getSchemaName(), + catalogSchemaTableName.getSchemaTableName().getTableName()); + } + + private static RangerTrinoResource createResource(String catalogName) + { + return new RangerTrinoResource(catalogName, Optional.empty(), Optional.empty()); + } + + private static RangerTrinoResource createResource(String catalogName, String schemaName) + { + return new RangerTrinoResource(catalogName, Optional.of(schemaName), Optional.empty()); + } + + private static RangerTrinoResource createResource(String catalogName, String schemaName, final String tableName) + { + return new RangerTrinoResource(catalogName, Optional.of(schemaName), Optional.of(tableName)); + } + + private static RangerTrinoResource createResource(String catalogName, String schemaName, final String tableName, final Optional column) + { + return new RangerTrinoResource(catalogName, Optional.of(schemaName), Optional.of(tableName), column); + } + + private static List createResource(CatalogSchemaTableName table, Set columns) + { + List colRequests = new ArrayList<>(); + + if (columns.size() > 0) { + for (String column : columns) { + RangerTrinoResource rangerTrinoResource = createResource(table.getCatalogName(), + table.getSchemaTableName().getSchemaName(), + table.getSchemaTableName().getTableName(), Optional.of(column)); + colRequests.add(rangerTrinoResource); + } + } + else { + colRequests.add(createResource(table.getCatalogName(), + table.getSchemaTableName().getSchemaName(), + table.getSchemaTableName().getTableName(), Optional.empty())); + } + return colRequests; + } + + private URL getFileLocation(String fileName, Optional configEntry) + { + URL lurl = null; + + if (LOG.isDebugEnabled()) { + if (configEntry.isPresent()) { + LOG.debug("Trying to load config(" + configEntry.get() + " from " + fileName + " (cannot be null)"); + } + else { + LOG.debug("Trying to load config from " + fileName + " (cannot be null)"); + } + } + + if (!StringUtils.isEmpty(fileName)) { + lurl = RangerConfiguration.class.getClassLoader().getResource(fileName); + + if (lurl == null) { + lurl = RangerConfiguration.class.getClassLoader().getResource("/" + fileName); + } + + if (lurl == null) { + File f = new File(fileName); + if (f.exists()) { + try { + lurl = f.toURI().toURL(); + } + catch (MalformedURLException e) { + LOG.error("Unable to load the resource name [" + fileName + "]. Ignoring the resource:" + f.getPath()); + } + } + else { + if (LOG.isDebugEnabled()) { + LOG.debug("Conf file path " + fileName + " does not exists"); + } + } + } + } + return lurl; + } + + private static TrinoException invalidRangerConfigFile(String fileName) + { + return new TrinoException( + CONFIGURATION_INVALID, + String.format("%s is not a valid file", fileName)); + } + + private static TrinoException invalidRangerConfigEntry(Map config, String configEntry) + { + return new TrinoException( + CONFIGURATION_INVALID, + String.format("%s must be specified in the ranger configurations. Value was '%s'", configEntry, config.get(configEntry))); + } +} diff --git a/plugin/trino-ranger/src/main/java/io/trino/plugin/ranger/RangerTrinoAccessRequest.java b/plugin/trino-ranger/src/main/java/io/trino/plugin/ranger/RangerTrinoAccessRequest.java new file mode 100644 index 0000000000000..aa2557a7da9e2 --- /dev/null +++ b/plugin/trino-ranger/src/main/java/io/trino/plugin/ranger/RangerTrinoAccessRequest.java @@ -0,0 +1,34 @@ +/* + * Licensed 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 io.trino.plugin.ranger; + +import org.apache.ranger.plugin.policyengine.RangerAccessRequestImpl; + +import java.util.Date; +import java.util.Set; + +import static java.util.Locale.ENGLISH; + +class RangerTrinoAccessRequest + extends RangerAccessRequestImpl +{ + public RangerTrinoAccessRequest(RangerTrinoResource resource, + String user, + Set userGroups, + TrinoAccessType trinoAccessType) + { + super(resource, trinoAccessType.name().toLowerCase(ENGLISH), user, userGroups, null); + setAccessTime(new Date()); + } +} diff --git a/plugin/trino-ranger/src/main/java/io/trino/plugin/ranger/RangerTrinoResource.java b/plugin/trino-ranger/src/main/java/io/trino/plugin/ranger/RangerTrinoResource.java new file mode 100644 index 0000000000000..82b11ad0b592e --- /dev/null +++ b/plugin/trino-ranger/src/main/java/io/trino/plugin/ranger/RangerTrinoResource.java @@ -0,0 +1,92 @@ +/* + * Licensed 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 io.trino.plugin.ranger; + +import io.trino.spi.connector.SchemaTableName; +import org.apache.commons.lang.StringUtils; +import org.apache.ranger.plugin.policyengine.RangerAccessResourceImpl; + +import java.util.Optional; + +class RangerTrinoResource + extends RangerAccessResourceImpl +{ + public static final String KEY_CATALOG = "catalog"; + public static final String KEY_SCHEMA = "schema"; + public static final String KEY_TABLE = "table"; + public static final String KEY_COLUMN = "column"; + public static final String KEY_USER = "trinouser"; + public static final String KEY_FUNCTION = "function"; + public static final String KEY_PROCEDURE = "procedure"; + public static final String KEY_SYSTEM_PROPERTY = "systemproperty"; + public static final String KEY_SESSION_PROPERTY = "sessionproperty"; + + public RangerTrinoResource() + { + } + + public RangerTrinoResource(String catalogName, Optional schema, Optional table) + { + setValue(KEY_CATALOG, catalogName); + if (schema.isPresent()) { + setValue(KEY_SCHEMA, schema.get()); + } + if (table.isPresent()) { + setValue(KEY_TABLE, table.get()); + } + } + + public RangerTrinoResource(String catalogName, Optional schema, Optional table, Optional column) + { + setValue(KEY_CATALOG, catalogName); + if (schema.isPresent()) { + setValue(KEY_SCHEMA, schema.get()); + } + if (table.isPresent()) { + setValue(KEY_TABLE, table.get()); + } + if (column.isPresent()) { + setValue(KEY_COLUMN, column.get()); + } + } + + public String getCatalogName() + { + return (String) getValue(KEY_CATALOG); + } + + public String getTable() + { + return (String) getValue(KEY_TABLE); + } + + public String getCatalog() + { + return (String) getValue(KEY_CATALOG); + } + + public String getSchema() + { + return (String) getValue(KEY_SCHEMA); + } + + public Optional getSchemaTable() + { + final String schema = getSchema(); + if (StringUtils.isNotEmpty(schema)) { + return Optional.of(new SchemaTableName(schema, Optional.ofNullable(getTable()).orElse("*"))); + } + return Optional.empty(); + } +} diff --git a/plugin/trino-ranger/src/main/java/io/trino/plugin/ranger/TrinoAccessType.java b/plugin/trino-ranger/src/main/java/io/trino/plugin/ranger/TrinoAccessType.java new file mode 100644 index 0000000000000..a0b08a0f3016c --- /dev/null +++ b/plugin/trino-ranger/src/main/java/io/trino/plugin/ranger/TrinoAccessType.java @@ -0,0 +1,19 @@ +/* + * Licensed 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 io.trino.plugin.ranger; + +public enum TrinoAccessType +{ + CREATE, DROP, SELECT, INSERT, DELETE, USE, ALTER, ALL, GRANT, REVOKE, SHOW, IMPERSONATE, EXECUTE +} diff --git a/plugin/trino-ranger/src/main/java/io/trino/plugin/ranger/TrinoRangerPlugin.java b/plugin/trino-ranger/src/main/java/io/trino/plugin/ranger/TrinoRangerPlugin.java new file mode 100644 index 0000000000000..e52c1e35b16a9 --- /dev/null +++ b/plugin/trino-ranger/src/main/java/io/trino/plugin/ranger/TrinoRangerPlugin.java @@ -0,0 +1,32 @@ +/* + * Licensed 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 io.trino.plugin.ranger; + +import io.trino.spi.Plugin; +import io.trino.spi.security.SystemAccessControlFactory; + +import java.util.ArrayList; + +public class TrinoRangerPlugin + implements Plugin +{ + @Override + public Iterable getSystemAccessControlFactories() + { + ArrayList list = new ArrayList<>(); + SystemAccessControlFactory factory = new RangerSystemAccessControlFactory(); + list.add(factory); + return list; + } +} diff --git a/plugin/trino-ranger/src/test/java/io/trino/plugin/ranger/RangerAdminClientImpl.java b/plugin/trino-ranger/src/test/java/io/trino/plugin/ranger/RangerAdminClientImpl.java new file mode 100644 index 0000000000000..39f85ffa1aa19 --- /dev/null +++ b/plugin/trino-ranger/src/test/java/io/trino/plugin/ranger/RangerAdminClientImpl.java @@ -0,0 +1,43 @@ +/* + * Licensed 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 io.trino.plugin.ranger; + +import org.apache.ranger.admin.client.AbstractRangerAdminClient; +import org.apache.ranger.plugin.util.ServicePolicies; + +import java.io.File; +import java.nio.file.FileSystems; +import java.nio.file.Files; + +import static java.nio.charset.StandardCharsets.UTF_8; + +public class RangerAdminClientImpl + extends AbstractRangerAdminClient +{ + private static final String cacheFilename = "trino-policies.json"; + + @Override + public ServicePolicies getServicePoliciesIfUpdated(long lastKnownVersion, long lastActivationTimeInMillis) throws Exception + { + String basedir = System.getProperty("basedir"); + if (basedir == null) { + basedir = new File(".").getCanonicalPath(); + } + + java.nio.file.Path cachePath = FileSystems.getDefault().getPath(basedir, "/src/test/resources/" + cacheFilename); + byte[] cacheBytes = Files.readAllBytes(cachePath); + + return gson.fromJson(new String(cacheBytes, UTF_8), ServicePolicies.class); + } +} diff --git a/plugin/trino-ranger/src/test/java/io/trino/plugin/ranger/RangerSystemAccessControlTest.java b/plugin/trino-ranger/src/test/java/io/trino/plugin/ranger/RangerSystemAccessControlTest.java new file mode 100644 index 0000000000000..3dbfe44521d05 --- /dev/null +++ b/plugin/trino-ranger/src/test/java/io/trino/plugin/ranger/RangerSystemAccessControlTest.java @@ -0,0 +1,196 @@ +/* + * Licensed 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 io.trino.plugin.ranger; + +import com.google.common.collect.ImmutableSet; +import io.trino.spi.connector.CatalogSchemaName; +import io.trino.spi.connector.CatalogSchemaRoutineName; +import io.trino.spi.connector.CatalogSchemaTableName; +import io.trino.spi.connector.SchemaTableName; +import io.trino.spi.security.AccessDeniedException; +import io.trino.spi.security.Identity; +import io.trino.spi.security.SystemSecurityContext; +import io.trino.spi.security.TrinoPrincipal; +import io.trino.spi.security.ViewExpression; +import io.trino.spi.type.VarcharType; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +import javax.security.auth.kerberos.KerberosPrincipal; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import static io.trino.spi.security.PrincipalType.USER; +import static io.trino.spi.security.Privilege.SELECT; + +public class RangerSystemAccessControlTest +{ + static RangerSystemAccessControlImpl accessControlManager; + + private static final Identity alice = Identity.ofUser("alice"); + private static final Identity admin = Identity.ofUser("admin"); + //private static final Identity aliceWithGroups = Identity.from(alice).withGroups(new HashSet(Arrays.asList("users", "friends"))).build(); + //private static final Identity kerberosValidAlice = Identity.from(alice).withPrincipal(new KerberosPrincipal("alice/example.com@EXAMPLE.COM")).build(); + //private static final Identity kerberosValidNonAsciiUser = Identity.forUser("\u0194\u0194\u0194").withPrincipal(new KerberosPrincipal("\u0194\u0194\u0194/example.com@EXAMPLE.COM")).build(); + private static final Identity kerberosInvalidAlice = Identity.from(alice).withPrincipal(new KerberosPrincipal("mallory/example.com@EXAMPLE.COM")).build(); + private static final Identity bob = Identity.ofUser("bob"); + //private static final Identity nonAsciiUser = Identity.ofUser("\u0194\u0194\u0194"); + + private static final Set allCatalogs = ImmutableSet.of("open-to-all", "all-allowed", "alice-catalog"); + private static final Set queryOwners = ImmutableSet.of("bob", "alice", "frank"); + private static final String aliceCatalog = "alice-catalog"; + private static final CatalogSchemaName aliceSchema = new CatalogSchemaName("alice-catalog", "schema"); + private static final CatalogSchemaTableName aliceTable = new CatalogSchemaTableName("alice-catalog", "schema", "table"); + private static final CatalogSchemaTableName aliceView = new CatalogSchemaTableName("alice-catalog", "schema", "view"); + + private static final CatalogSchemaRoutineName aliceProcedure = new CatalogSchemaRoutineName("alice-catalog", "schema", "procedure"); + private static final String functionName = new String("function"); + + @BeforeClass + public static void setUpBeforeClass() throws Exception + { + Map config = new HashMap<>(); + accessControlManager = new RangerSystemAccessControlImpl(config); + } + + @Test + @SuppressWarnings("PMD") + public void testCanSetUserOperations() + { + try { + accessControlManager.checkCanImpersonateUser(context(alice), bob.getUser()); + throw new AssertionError("expected AccessDeniedExeption"); + } + catch (AccessDeniedException expected) { + } + + accessControlManager.checkCanImpersonateUser(context(admin), bob.getUser()); + + try { + accessControlManager.checkCanImpersonateUser(context(kerberosInvalidAlice), bob.getUser()); + throw new AssertionError("expected AccessDeniedExeption"); + } + catch (AccessDeniedException expected) { + } + } + + @Test + public void testCatalogOperations() + { + Assert.assertEquals(accessControlManager.filterCatalogs(context(alice), allCatalogs), allCatalogs); + Set bobCatalogs = ImmutableSet.of("open-to-all", "all-allowed"); + Assert.assertEquals(accessControlManager.filterCatalogs(context(bob), allCatalogs), bobCatalogs); + //Set nonAsciiUserCatalogs = ImmutableSet.of("open-to-all", "all-allowed", "\u0200\u0200\u0200"); + //assertEquals(accessControlManager.filterCatalogs(context(nonAsciiUser), allCatalogs), nonAsciiUserCatalogs); + } + + @Test + @SuppressWarnings("PMD") + public void testSchemaOperations() + { + Set aliceSchemas = ImmutableSet.of("schema"); + Assert.assertEquals(accessControlManager.filterSchemas(context(alice), aliceCatalog, aliceSchemas), aliceSchemas); + Assert.assertEquals(accessControlManager.filterSchemas(context(bob), "alice-catalog", aliceSchemas), ImmutableSet.of()); + + accessControlManager.checkCanCreateSchema(context(alice), aliceSchema); + accessControlManager.checkCanDropSchema(context(alice), aliceSchema); + accessControlManager.checkCanRenameSchema(context(alice), aliceSchema, "new-schema"); + accessControlManager.checkCanShowSchemas(context(alice), aliceCatalog); + + try { + accessControlManager.checkCanCreateSchema(context(bob), aliceSchema); + } + catch (AccessDeniedException expected) { + } + + accessControlManager.checkCanSetSchemaAuthorization(context(alice), aliceSchema, new TrinoPrincipal(USER, "principal")); + accessControlManager.checkCanShowCreateSchema(context(alice), aliceSchema); + } + + @Test + @SuppressWarnings("PMD") + public void testTableOperations() + { + Set aliceTables = ImmutableSet.of(new SchemaTableName("schema", "table")); + Assert.assertEquals(accessControlManager.filterTables(context(alice), aliceCatalog, aliceTables), aliceTables); + Assert.assertEquals(accessControlManager.filterTables(context(bob), "alice-catalog", aliceTables), ImmutableSet.of()); + + accessControlManager.checkCanCreateTable(context(alice), aliceTable, Map.of()); + accessControlManager.checkCanDropTable(context(alice), aliceTable); + accessControlManager.checkCanSelectFromColumns(context(alice), aliceTable, ImmutableSet.of()); + accessControlManager.checkCanInsertIntoTable(context(alice), aliceTable); + accessControlManager.checkCanDeleteFromTable(context(alice), aliceTable); + accessControlManager.checkCanRenameColumn(context(alice), aliceTable); + + try { + accessControlManager.checkCanCreateTable(context(bob), aliceTable, Map.of()); + } + catch (AccessDeniedException expected) { + } + } + + @Test + @SuppressWarnings("PMD") + public void testViewOperations() + { + accessControlManager.checkCanCreateView(context(alice), aliceView); + accessControlManager.checkCanDropView(context(alice), aliceView); + accessControlManager.checkCanSelectFromColumns(context(alice), aliceView, ImmutableSet.of()); + accessControlManager.checkCanCreateViewWithSelectFromColumns(context(alice), aliceTable, ImmutableSet.of()); + accessControlManager.checkCanCreateViewWithSelectFromColumns(context(alice), aliceView, ImmutableSet.of()); + accessControlManager.checkCanSetCatalogSessionProperty(context(alice), aliceCatalog, "property"); + accessControlManager.checkCanGrantTablePrivilege(context(alice), SELECT, aliceTable, new TrinoPrincipal(USER, "grantee"), true); + accessControlManager.checkCanRevokeTablePrivilege(context(alice), SELECT, aliceTable, new TrinoPrincipal(USER, "revokee"), true); + + try { + accessControlManager.checkCanCreateView(context(bob), aliceView); + } + catch (AccessDeniedException expected) { + } + } + + @Test + @SuppressWarnings("PMD") + public void testMisc() + { + Assert.assertEquals(accessControlManager.filterViewQueryOwnedBy(context(alice), queryOwners), queryOwners); + + // check {type} / {col} replacement + final VarcharType varcharType = VarcharType.createVarcharType(20); + + List ret = accessControlManager.getColumnMasks(context(alice), aliceTable, "cast_me", varcharType); + Assert.assertNotNull(ret.get(0)); + Assert.assertEquals(ret.get(0).getExpression(), "cast cast_me as varchar(20)"); + + ret = accessControlManager.getColumnMasks(context(alice), aliceTable, "do-not-cast-me", varcharType); + Assert.assertEquals(ret.size(), 0); + + ret = accessControlManager.getRowFilters(context(alice), aliceTable); + Assert.assertEquals(ret.size(), 0); + + accessControlManager.checkCanExecuteFunction(context(alice), functionName); + accessControlManager.checkCanGrantExecuteFunctionPrivilege(context(alice), functionName, new TrinoPrincipal(USER, "grantee"), true); + accessControlManager.checkCanExecuteProcedure(context(alice), aliceProcedure); + } + + private SystemSecurityContext context(Identity id) + { + return new SystemSecurityContext(id, Optional.empty()); + } +} diff --git a/plugin/trino-ranger/src/test/resources/log4j.properties b/plugin/trino-ranger/src/test/resources/log4j.properties new file mode 100644 index 0000000000000..fd5ca2a5e455c --- /dev/null +++ b/plugin/trino-ranger/src/test/resources/log4j.properties @@ -0,0 +1,26 @@ +# +# 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. +# + +# Root logger option +log4j.rootLogger=INFO, stdout + +# Direct log messages to stdout +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.Target=System.out +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{10}:%L - %m%n diff --git a/plugin/trino-ranger/src/test/resources/ranger-trino-security.xml b/plugin/trino-ranger/src/test/resources/ranger-trino-security.xml new file mode 100644 index 0000000000000..ecc467b32b437 --- /dev/null +++ b/plugin/trino-ranger/src/test/resources/ranger-trino-security.xml @@ -0,0 +1,52 @@ + + + + + + ranger.plugin.trino.service.name + cl1_trino + + Name of the Ranger service containing policies for this SampleApp instance + + + + + ranger.plugin.trino.policy.source.impl + org.apache.ranger.authorization.trino.authorizer.RangerAdminClientImpl + + Policy source. + + + + + ranger.plugin.trino.policy.pollIntervalMs + 30000 + + How often to poll for changes in policies? + + + + + ranger.plugin.trino.policy.cache.dir + ${project.build.directory} + + Directory where Ranger policies are cached after successful retrieval from the source + + + + diff --git a/plugin/trino-ranger/src/test/resources/trino-policies.json b/plugin/trino-ranger/src/test/resources/trino-policies.json new file mode 100644 index 0000000000000..737e380b1a32f --- /dev/null +++ b/plugin/trino-ranger/src/test/resources/trino-policies.json @@ -0,0 +1,1214 @@ +{ + "serviceName": "cl1_trino", + "serviceId": 16, + "policyUpdateTime": "20180304-09:49:38.000-+0000", + "policies": [ + { + "service": "cl1_trino", + "name": "checkCanImpersonateUser", + "policyType": 0, + "policyPriority": 0, + "description": "", + "isAuditEnabled": true, + "resources": { + "trinouser": { + "values": [ + "bob" + ], + "isExcludes": false, + "isRecursive": false + } + }, + "policyItems": [ + { + "accesses": [ + { + "type": "impersonate", + "isAllowed": true + } + ], + "users": [ + "admin" + ], + "groups": [], + "roles": [], + "conditions": [], + "delegateAdmin": false + } + ], + "denyPolicyItems": [], + "allowExceptions": [], + "denyExceptions": [], + "dataMaskPolicyItems": [], + "rowFilterPolicyItems": [], + "serviceType": "trino", + "options": {}, + "validitySchedules": [], + "policyLabels": [], + "zoneName": "", + "isDenyAllElse": false, + "id": 51, + "guid": "7ab96b62-6fd3-4193-bf49-af462c25784d", + "isEnabled": true, + "version": 1 + }, + { + "service": "cl1_trino", + "name": "checkFunction", + "policyType": 0, + "policyPriority": 0, + "description": "", + "isAuditEnabled": true, + "resources": { + "function": { + "values": [ + "function" + ], + "isExcludes": false, + "isRecursive": false + } + }, + "policyItems": [ + { + "accesses": [ + { + "type": "execute", + "isAllowed": true + }, + { + "type": "grant", + "isAllowed": true + } + ], + "users": [ + "alice" + ], + "groups": [], + "roles": [], + "conditions": [], + "delegateAdmin": false + } + ], + "denyPolicyItems": [], + "allowExceptions": [], + "denyExceptions": [], + "dataMaskPolicyItems": [], + "rowFilterPolicyItems": [], + "serviceType": "trino", + "options": {}, + "validitySchedules": [], + "policyLabels": [], + "zoneName": "", + "isDenyAllElse": false, + "id": 51, + "guid": "7ab96b62-6fd3-4193-bf49-af462c25784d", + "isEnabled": true, + "version": 1 + }, + { + "service": "cl1_trino", + "name": "alice-schema", + "policyType": 0, + "policyPriority": 0, + "description": "", + "isAuditEnabled": true, + "resources": { + "schema": { + "values": [ + "schema" + ], + "isExcludes": false, + "isRecursive": false + }, + "catalog": { + "values": [ + "alice-catalog" + ], + "isExcludes": false, + "isRecursive": false + } + }, + "policyItems": [ + { + "accesses": [ + { + "type": "select", + "isAllowed": true + }, + { + "type": "insert", + "isAllowed": true + }, + { + "type": "create", + "isAllowed": true + }, + { + "type": "drop", + "isAllowed": true + }, + { + "type": "alter", + "isAllowed": true + }, + { + "type": "show", + "isAllowed": true + }, + { + "type": "grant", + "isAllowed": true + } + ], + "users": [ + "alice" + ], + "groups": [], + "roles": [], + "conditions": [], + "delegateAdmin": false + } + ], + "denyPolicyItems": [], + "allowExceptions": [], + "denyExceptions": [], + "dataMaskPolicyItems": [], + "rowFilterPolicyItems": [], + "serviceType": "trino", + "options": {}, + "validitySchedules": [], + "policyLabels": [], + "zoneName": "", + "isDenyAllElse": false, + "id": 52, + "guid": "11b10138-34c3-4e11-8beb-56a10334a375", + "isEnabled": true, + "version": 1 + }, + { + "service": "cl1_trino", + "name": "alice-catalog", + "policyType": 0, + "policyPriority": 0, + "description": "", + "isAuditEnabled": true, + "resources": { + "catalog": { + "values": [ + "alice-catalog" + ], + "isExcludes": false, + "isRecursive": false + } + }, + "policyItems": [ + { + "accesses": [ + { + "type": "select", + "isAllowed": true + }, + { + "type": "insert", + "isAllowed": true + }, + { + "type": "create", + "isAllowed": true + }, + { + "type": "drop", + "isAllowed": true + }, + { + "type": "use", + "isAllowed": true + }, + { + "type": "alter", + "isAllowed": true + }, + { + "type": "show", + "isAllowed": true + } + ], + "users": [ + "alice" + ], + "groups": [], + "roles": [], + "conditions": [], + "delegateAdmin": false + } + ], + "denyPolicyItems": [], + "allowExceptions": [], + "denyExceptions": [], + "dataMaskPolicyItems": [], + "rowFilterPolicyItems": [], + "serviceType": "trino", + "options": {}, + "validitySchedules": [], + "policyLabels": [], + "zoneName": "", + "isDenyAllElse": false, + "id": 53, + "guid": "60207d91-7fd7-424e-8e6f-88d803297b6a", + "isEnabled": true, + "version": 2 + }, + { + "service": "cl1_trino", + "name": "alice-table", + "policyType": 0, + "policyPriority": 0, + "description": "", + "isAuditEnabled": true, + "resources": { + "schema": { + "values": [ + "schema" + ], + "isExcludes": false, + "isRecursive": false + }, + "catalog": { + "values": [ + "alice-catalog" + ], + "isExcludes": false, + "isRecursive": false + }, + "table": { + "values": [ + "table", + "alice" + ], + "isExcludes": false, + "isRecursive": false + } + }, + "policyItems": [ + { + "accesses": [ + { + "type": "select", + "isAllowed": true + }, + { + "type": "insert", + "isAllowed": true + }, + { + "type": "drop", + "isAllowed": true + }, + { + "type": "delete", + "isAllowed": true + }, + { + "type": "alter", + "isAllowed": true + }, + { + "type": "grant", + "isAllowed": true + }, + { + "type": "revoke", + "isAllowed": true + } + ], + "users": [ + "alice" + ], + "groups": [], + "roles": [], + "conditions": [], + "delegateAdmin": false + } + ], + "denyPolicyItems": [], + "allowExceptions": [], + "denyExceptions": [], + "dataMaskPolicyItems": [], + "rowFilterPolicyItems": [], + "serviceType": "trino", + "options": {}, + "validitySchedules": [], + "policyLabels": [], + "zoneName": "", + "isDenyAllElse": false, + "id": 55, + "guid": "b47e1c19-a05f-41f8-94ef-f86c14076ad9", + "isEnabled": true, + "version": 2 + }, + { + "service": "cl1_trino", + "name": "alice-procedure", + "policyType": 0, + "policyPriority": 0, + "description": "", + "isAuditEnabled": true, + "resources": { + "schema": { + "values": [ + "schema" + ], + "isExcludes": false, + "isRecursive": false + }, + "catalog": { + "values": [ + "alice-catalog" + ], + "isExcludes": false, + "isRecursive": false + }, + "procedure": { + "values": [ + "procedure" + ], + "isExcludes": false, + "isRecursive": false + } + }, + "policyItems": [ + { + "accesses": [ + { + "type": "execute", + "isAllowed": true + } + ], + "users": [ + "alice" + ], + "groups": [], + "roles": [], + "conditions": [], + "delegateAdmin": false + } + ], + "denyPolicyItems": [], + "allowExceptions": [], + "denyExceptions": [], + "dataMaskPolicyItems": [], + "rowFilterPolicyItems": [], + "serviceType": "trino", + "options": {}, + "validitySchedules": [], + "policyLabels": [], + "zoneName": "", + "isDenyAllElse": false, + "id": 55, + "guid": "b47e1c19-a05f-41f8-94ef-f86c14076ad9", + "isEnabled": true, + "version": 2 + }, + { + "service": "cl1_trino", + "name": "alice-view", + "policyType": 0, + "policyPriority": 0, + "description": "", + "isAuditEnabled": true, + "resources": { + "schema": { + "values": [ + "schema" + ], + "isExcludes": false, + "isRecursive": false + }, + "catalog": { + "values": [ + "alice-catalog" + ], + "isExcludes": false, + "isRecursive": false + }, + "table": { + "values": [ + "view" + ], + "isExcludes": false, + "isRecursive": false + } + }, + "policyItems": [ + { + "accesses": [ + { + "type": "select", + "isAllowed": true + }, + { + "type": "drop", + "isAllowed": true + } + ], + "users": [ + "alice" + ], + "groups": [], + "roles": [], + "conditions": [], + "delegateAdmin": false + } + ], + "denyPolicyItems": [], + "allowExceptions": [], + "denyExceptions": [], + "dataMaskPolicyItems": [], + "rowFilterPolicyItems": [], + "serviceType": "trino", + "options": {}, + "validitySchedules": [], + "policyLabels": [], + "zoneName": "", + "isDenyAllElse": false, + "id": 56, + "guid": "91335d40-0bcf-4515-89ed-74531df970c7", + "isEnabled": true, + "version": 1 + }, + { + "service": "cl1_trino", + "name": "alice-session-property", + "policyType": 0, + "policyPriority": 0, + "description": "", + "isAuditEnabled": true, + "resources": { + "sessionproperty": { + "values": [ + "property" + ], + "isExcludes": false, + "isRecursive": false + }, + "catalog": { + "values": [ + "alice-catalog" + ], + "isExcludes": false, + "isRecursive": false + } + }, + "policyItems": [ + { + "accesses": [ + { + "type": "show", + "isAllowed": true + }, + { + "type": "alter", + "isAllowed": true + } + ], + "users": [ + "alice" + ], + "groups": [], + "roles": [], + "conditions": [], + "delegateAdmin": false + } + ], + "denyPolicyItems": [], + "allowExceptions": [], + "denyExceptions": [], + "dataMaskPolicyItems": [], + "rowFilterPolicyItems": [], + "serviceType": "trino", + "options": {}, + "validitySchedules": [], + "policyLabels": [], + "zoneName": "", + "isDenyAllElse": false, + "id": 57, + "guid": "b5a30229-89b2-456a-83d4-7d64e8b8b6bf", + "isEnabled": true, + "version": 1 + }, + { + "service": "cl1_trino", + "name": "open-to-all", + "policyType": 0, + "policyPriority": 0, + "description": "", + "isAuditEnabled": true, + "resources": { + "catalog": { + "values": [ + "open-to-all", + "all-allowed" + ], + "isExcludes": false, + "isRecursive": false + } + }, + "policyItems": [ + { + "accesses": [ + { + "type": "select", + "isAllowed": true + } + ], + "users": [ + "{USER}" + ], + "groups": [], + "roles": [], + "conditions": [], + "delegateAdmin": false + } + ], + "denyPolicyItems": [], + "allowExceptions": [], + "denyExceptions": [], + "dataMaskPolicyItems": [], + "rowFilterPolicyItems": [], + "serviceType": "trino", + "options": {}, + "validitySchedules": [], + "policyLabels": [], + "zoneName": "", + "isDenyAllElse": false, + "id": 67, + "guid": "370d3e54-0428-4fcb-b0b5-ad1f5dfdd7db", + "isEnabled": true, + "version": 1 + }, + { + "service": "cl1_trino", + "name": "test-mask", + "policyType": 1, + "policyPriority": 0, + "description": "", + "isAuditEnabled": true, + "resources": { + "schema": { + "values": [ + "schema" + ], + "isExcludes": false, + "isRecursive": false + }, + "catalog": { + "values": [ + "alice-catalog" + ], + "isExcludes": false, + "isRecursive": false + }, + "column": { + "values": [ + "only_first_4" + ], + "isExcludes": false, + "isRecursive": false + }, + "table": { + "values": [ + "table" + ], + "isExcludes": false, + "isRecursive": false + } + }, + "policyItems": [], + "denyPolicyItems": [], + "allowExceptions": [], + "denyExceptions": [], + "dataMaskPolicyItems": [ + { + "dataMaskInfo": { + "dataMaskType": "MASK_SHOW_FIRST_4" + }, + "accesses": [ + { + "type": "select", + "isAllowed": true + } + ], + "users": [ + "{USER}" + ], + "groups": [], + "roles": [], + "conditions": [], + "delegateAdmin": false + } + ], + "rowFilterPolicyItems": [], + "serviceType": "trino", + "options": {}, + "validitySchedules": [], + "policyLabels": [], + "zoneName": "", + "isDenyAllElse": false, + "id": 68, + "guid": "6ec12d33-4d5d-46f0-9a05-b31d78281b02", + "isEnabled": true, + "version": 1 + }, + { + "service": "cl1_trino", + "name": "test-mask-cast", + "policyType": 1, + "policyPriority": 0, + "description": "", + "isAuditEnabled": true, + "resources": { + "schema": { + "values": [ + "schema" + ], + "isExcludes": false, + "isRecursive": false + }, + "catalog": { + "values": [ + "alice-catalog" + ], + "isExcludes": false, + "isRecursive": false + }, + "column": { + "values": [ + "cast_me" + ], + "isExcludes": false, + "isRecursive": false + }, + "table": { + "values": [ + "table" + ], + "isExcludes": false, + "isRecursive": false + } + }, + "policyItems": [], + "denyPolicyItems": [], + "allowExceptions": [], + "denyExceptions": [], + "dataMaskPolicyItems": [ + { + "dataMaskInfo": { + "dataMaskType": "CUSTOM", + "valueExpr": "cast {col} as {type}" + }, + "accesses": [ + { + "type": "select", + "isAllowed": true + } + ], + "users": [ + "{USER}" + ], + "groups": [], + "roles": [], + "conditions": [], + "delegateAdmin": false + } + ], + "rowFilterPolicyItems": [], + "serviceType": "trino", + "options": {}, + "validitySchedules": [], + "policyLabels": [], + "zoneName": "", + "isDenyAllElse": false, + "id": 69, + "guid": "50e855fb-8dc2-42cd-99d3-16e8df8de774", + "isEnabled": true, + "version": 1 + } + ], + "startIndex": 0, + "pageSize": 0, + "totalCount": 0, + "resultSize": 0, + "queryTimeMS": 1585212824007, + "serviceDef": { + "id": 18, + "name": "trino", + "displayName": "trino", + "implClass": "org.apache.ranger.services.trino.RangerServiceTrino", + "label": "Trino", + "description": "Trino", + "guid": "0fd0b92e-46f9-4b25-ad46-89733872e8fb", + "resources": [ + { + "itemId": 1, + "name": "catalog", + "type": "string", + "level": 10, + "parent": "", + "mandatory": true, + "isValidLeaf": true, + "lookupSupported": true, + "recursiveSupported": false, + "excludesSupported": true, + "matcher": "org.apache.ranger.plugin.resourcematcher.RangerDefaultResourceMatcher", + "matcherOptions": { + "wildCard": true, + "ignoreCase": true + }, + "validationRegEx": "", + "validationMessage": "", + "uiHint": "", + "label": "Trino Catalog", + "description": "Trino Catalog" + }, + { + "itemId": 2, + "name": "schema", + "type": "string", + "level": 20, + "parent": "catalog", + "mandatory": true, + "isValidLeaf": true, + "lookupSupported": true, + "recursiveSupported": false, + "excludesSupported": true, + "matcher": "org.apache.ranger.plugin.resourcematcher.RangerDefaultResourceMatcher", + "matcherOptions": { + "wildCard": true, + "ignoreCase": true + }, + "validationRegEx": "", + "validationMessage": "", + "uiHint": "", + "label": "Trino Schema", + "description": "Trino Schema" + }, + { + "itemId": 3, + "name": "table", + "type": "string", + "level": 30, + "parent": "schema", + "mandatory": true, + "isValidLeaf": true, + "lookupSupported": true, + "recursiveSupported": false, + "excludesSupported": true, + "matcher": "org.apache.ranger.plugin.resourcematcher.RangerDefaultResourceMatcher", + "matcherOptions": { + "wildCard": true, + "ignoreCase": true + }, + "validationRegEx": "", + "validationMessage": "", + "uiHint": "", + "label": "Trino Table", + "description": "Trino Table" + }, + { + "itemId": 4, + "name": "column", + "type": "string", + "level": 40, + "parent": "table", + "mandatory": true, + "lookupSupported": true, + "recursiveSupported": false, + "excludesSupported": true, + "matcher": "org.apache.ranger.plugin.resourcematcher.RangerDefaultResourceMatcher", + "matcherOptions": { + "wildCard": true, + "ignoreCase": true + }, + "validationRegEx": "", + "validationMessage": "", + "uiHint": "", + "label": "Trino Column", + "description": "Trino Column" + }, + { + "itemId": 5, + "name": "trinouser", + "type": "string", + "level": 10, + "parent": "", + "mandatory": true, + "lookupSupported": false, + "recursiveSupported": false, + "excludesSupported": false, + "matcher": "org.apache.ranger.plugin.resourcematcher.RangerDefaultResourceMatcher", + "matcherOptions": { + "wildCard": true, + "ignoreCase": true + }, + "validationRegEx": "", + "validationMessage": "", + "uiHint": "", + "label": "Trino User", + "description": "Trino User", + "accessTypeRestrictions": ["impersonate"] + }, + { + "itemId": 6, + "name": "systemproperty", + "type": "string", + "level": 10, + "parent": "", + "mandatory": true, + "lookupSupported": false, + "recursiveSupported": false, + "excludesSupported": false, + "matcher": "org.apache.ranger.plugin.resourcematcher.RangerDefaultResourceMatcher", + "matcherOptions": { + "wildCard": true, + "ignoreCase": true + }, + "validationRegEx": "", + "validationMessage": "", + "uiHint": "", + "label": "System Property", + "description": "Trino System Property", + "accessTypeRestrictions": ["alter"] + }, + { + "itemId": 7, + "name": "sessionproperty", + "type": "string", + "level": 20, + "parent": "catalog", + "mandatory": true, + "lookupSupported": false, + "recursiveSupported": false, + "excludesSupported": false, + "matcher": "org.apache.ranger.plugin.resourcematcher.RangerDefaultResourceMatcher", + "matcherOptions": { + "wildCard": true, + "ignoreCase": true + }, + "validationRegEx": "", + "validationMessage": "", + "uiHint": "", + "label": "Catalog Session Property", + "description": "Trino Catalog Session Property", + "accessTypeRestrictions": ["alter"] + }, + { + "itemId": 8, + "name": "function", + "type": "string", + "level": 10, + "parent": "", + "mandatory": true, + "lookupSupported": false, + "recursiveSupported": false, + "excludesSupported": false, + "matcher": "org.apache.ranger.plugin.resourcematcher.RangerDefaultResourceMatcher", + "matcherOptions": { + "wildCard": true, + "ignoreCase": true + }, + "validationRegEx": "", + "validationMessage": "", + "uiHint": "", + "label": "Trino Function", + "description": "Trino Function", + "accessTypeRestrictions": ["execute", "grant"] + }, + { + "itemId": 9, + "name": "procedure", + "type": "string", + "level": 30, + "parent": "schema", + "mandatory": true, + "lookupSupported": false, + "recursiveSupported": false, + "excludesSupported": false, + "matcher": "org.apache.ranger.plugin.resourcematcher.RangerDefaultResourceMatcher", + "matcherOptions": { + "wildCard": true, + "ignoreCase": true + }, + "validationRegEx": "", + "validationMessage": "", + "uiHint": "", + "label": "Schema Procedure", + "description": "Schema Procedure", + "accessTypeRestrictions": ["execute", "grant"] + } + ], + "accessTypes": [ + { + "itemId": 1, + "name": "select", + "label": "Select" + }, + { + "itemId": 2, + "name": "insert", + "label": "Insert" + }, + { + "itemId": 3, + "name": "create", + "label": "Create" + }, + { + "itemId": 4, + "name": "drop", + "label": "Drop" + }, + { + "itemId": 5, + "name": "delete", + "label": "Delete" + }, + { + "itemId": 6, + "name": "use", + "label": "Use" + }, + { + "itemId": 7, + "name": "alter", + "label": "Alter" + }, + { + "itemId": 8, + "name": "grant", + "label": "Grant" + }, + { + "itemId": 9, + "name": "revoke", + "label": "Revoke" + }, + { + "itemId": 10, + "name": "show", + "label": "Show" + }, + { + "itemId": 11, + "name": "impersonate", + "label": "Impersonate" + }, + { + "itemId": 12, + "name": "execute", + "label": "execute" + }, + { + "itemId": 13, + "name": "all", + "label": "All", + "impliedGrants": [ + "select", + "insert", + "create", + "delete", + "drop", + "use", + "alter", + "grant", + "revoke", + "show", + "impersonate", + "execute" + ] + } + ], + "configs": [ + { + "itemId": 1, + "name": "username", + "type": "string", + "mandatory": true, + "validationRegEx": "", + "validationMessage": "", + "uiHint": "", + "label": "Username" + }, + { + "itemId": 2, + "name": "password", + "type": "password", + "mandatory": false, + "validationRegEx": "", + "validationMessage": "", + "uiHint": "", + "label": "Password" + }, + { + "itemId": 3, + "name": "jdbc.driverClassName", + "type": "string", + "mandatory": true, + "validationRegEx": "", + "validationMessage": "", + "uiHint": "", + "defaultValue": "io.trino.jdbc.TrinoDriver" + }, + { + "itemId": 4, + "name": "jdbc.url", + "type": "string", + "mandatory": true, + "defaultValue": "", + "validationRegEx": "", + "validationMessage": "", + "uiHint": "" + } + ], + "enums": [ + ], + "contextEnrichers": [ + ], + "policyConditions": + [ + ], + "dataMaskDef": { + "accessTypes": [ + { + "name": "select" + } + ], + "resources": [ + { + "name": "catalog", + "matcherOptions": { + "wildCard": "true" + }, + "lookupSupported": true, + "uiHint":"{ \"singleValue\":true }" + }, + { + "name": "schema", + "matcherOptions": { + "wildCard": "true" + }, + "lookupSupported": true, + "uiHint":"{ \"singleValue\":true }" + }, + { + "name": "table", + "matcherOptions": { + "wildCard": "true" + }, + "lookupSupported": true, + "uiHint":"{ \"singleValue\":true }" + }, + { + "name": "column", + "matcherOptions": { + "wildCard": "true" + }, + "lookupSupported": true, + "uiHint":"{ \"singleValue\":true }" + } + ], + "maskTypes": [ + { + "itemId": 1, + "name": "MASK", + "label": "Redact", + "description": "Replace lowercase with 'x', uppercase with 'X', digits with '0'", + "transformer": "cast(regexp_replace(regexp_replace(regexp_replace({col},'([A-Z])', 'X'),'([a-z])','x'),'([0-9])','0') as {type})", + "dataMaskOptions": { + } + }, + { + "itemId": 2, + "name": "MASK_SHOW_LAST_4", + "label": "Partial mask: show last 4", + "description": "Show last 4 characters; replace rest with 'X'", + "transformer": "cast(regexp_replace({col}, '(.*)(.{4}$)', x -> regexp_replace(x[1], '.', 'X') || x[2]) as {type})" + }, + { + "itemId": 3, + "name": "MASK_SHOW_FIRST_4", + "label": "Partial mask: show first 4", + "description": "Show first 4 characters; replace rest with 'x'", + "transformer": "cast(regexp_replace({col}, '(^.{4})(.*)', x -> x[1] || regexp_replace(x[2], '.', 'X')) as {type})" + }, + { + "itemId": 4, + "name": "MASK_HASH", + "label": "Hash", + "description": "Hash the value of a varchar with sha256", + "transformer": "cast(to_hex(sha256(to_utf8({col}))) as {type})" + }, + { + "itemId": 5, + "name": "MASK_NULL", + "label": "Nullify", + "description": "Replace with NULL" + }, + { + "itemId": 6, + "name": "MASK_NONE", + "label": "Unmasked (retain original value)", + "description": "No masking" + }, + { + "itemId": 12, + "name": "MASK_DATE_SHOW_YEAR", + "label": "Date: show only year", + "description": "Date: show only year", + "transformer": "date_trunc('year', {col})" + }, + { + "itemId": 13, + "name": "CUSTOM", + "label": "Custom", + "description": "Custom" + } + ] + }, + "rowFilterDef": { + "accessTypes": [ + { + "name": "select" + } + ], + "resources": [ + { + "name": "catalog", + "matcherOptions": { + "wildCard": "true" + }, + "lookupSupported": true, + "mandatory": true, + "uiHint": "{ \"singleValue\":true }" + }, + { + "name": "schema", + "matcherOptions": { + "wildCard": "true" + }, + "lookupSupported": true, + "mandatory": true, + "uiHint": "{ \"singleValue\":true }" + }, + { + "name": "table", + "matcherOptions": { + "wildCard": "true" + }, + "lookupSupported": true, + "mandatory": true, + "uiHint": "{ \"singleValue\":true }" + } + ] + } + } +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 260543eee4aff..74e87f64d0705 100644 --- a/pom.xml +++ b/pom.xml @@ -145,6 +145,7 @@ plugin/trino-pinot plugin/trino-postgresql plugin/trino-prometheus + plugin/trino-ranger plugin/trino-raptor-legacy plugin/trino-redis plugin/trino-redshift @@ -474,6 +475,12 @@ ${project.version} + + io.trino + trino-ranger + ${project.version} + + io.trino trino-raptor-legacy