Repository: sentry Updated Branches: refs/heads/master 9fd29f9df -> ef81e0907
SENTRY-1812: Provide interactive Sentry CLI Project: http://git-wip-us.apache.org/repos/asf/sentry/repo Commit: http://git-wip-us.apache.org/repos/asf/sentry/commit/44c5d9f4 Tree: http://git-wip-us.apache.org/repos/asf/sentry/tree/44c5d9f4 Diff: http://git-wip-us.apache.org/repos/asf/sentry/diff/44c5d9f4 Branch: refs/heads/master Commit: 44c5d9f4a744bd71047bab773cdb783ee3b89b44 Parents: da1863f Author: Alexander Kolbasov <[email protected]> Authored: Wed Sep 27 13:28:46 2017 -0500 Committer: Alexander Kolbasov <[email protected]> Committed: Wed Sep 27 13:28:46 2017 -0500 ---------------------------------------------------------------------- .../db/service/thrift/TestSentryMetrics.java | 96 ++++++ sentry-tools/pom.xml | 63 ++++ .../org/apache/sentry/shell/GroupShell.java | 65 ++++ .../org/apache/sentry/shell/PrivsShell.java | 73 ++++ .../org/apache/sentry/shell/RolesShell.java | 72 ++++ .../java/org/apache/sentry/shell/SentryCli.java | 205 ++++++++++++ .../java/org/apache/sentry/shell/ShellUtil.java | 335 +++++++++++++++++++ .../org/apache/sentry/shell/TopLevelShell.java | 161 +++++++++ 8 files changed, 1070 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/sentry/blob/44c5d9f4/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/service/thrift/TestSentryMetrics.java ---------------------------------------------------------------------- diff --git a/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/service/thrift/TestSentryMetrics.java b/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/service/thrift/TestSentryMetrics.java new file mode 100644 index 0000000..b9c63ff --- /dev/null +++ b/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/service/thrift/TestSentryMetrics.java @@ -0,0 +1,96 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sentry.provider.db.service.thrift; + +import com.codahale.metrics.Counter; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import org.apache.hadoop.conf.Configuration; +import org.apache.sentry.service.thrift.ServiceConstants; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; + +import static java.lang.Thread.sleep; + +public class TestSentryMetrics { + private static SentryMetrics metrics = SentryMetrics.getInstance(); + private final static Configuration conf = new Configuration(); + private static File jsonReportFile; + + @BeforeClass + public static void setUp() throws Exception { + jsonReportFile = File.createTempFile("TestMetrics", ".json"); + String jsonFile = jsonReportFile.getAbsolutePath(); + conf.set(ServiceConstants.ServerConfig.SENTRY_JSON_REPORTER_FILE, jsonFile); + conf.setInt(ServiceConstants.ServerConfig.SENTRY_REPORTER_INTERVAL_SEC, 1); + conf.set(ServiceConstants.ServerConfig.SENTRY_REPORTER, "JSON"); + metrics.initReporting(conf); + } + + @AfterClass + public static void cleanup() { + System.out.println(jsonReportFile); + jsonReportFile.delete(); + } + + + /** + * Test JSON reporter. + * <ul> + * <li>increment the counter value</li> + * <li>wait a bit for the new repor to be written</li> + * <li>read the value from JSON file</li> + * <li>verify that the value matches expectation</li> + * </ul> + * This check is repeated a few times to verify that the values are updated over time. + * @throws Exception if fails to read counter value + */ + @Test + public void testJsonReporter() throws Exception { + int runs = 5; + String counterName = "cnt"; + Counter counter = metrics.getCounter(counterName); + for (int i = 0; i < runs; i++) { + counter.inc(); + sleep(1500); + Assert.assertEquals(i + 1, getCounterValue(counterName)); + } + + } + + /** + * Read counter value from JSON metric report + * @param name counter name + * @return counter value + * @throws FileNotFoundException if file doesn't exist + */ + private int getCounterValue(String name) throws FileNotFoundException { + JsonParser parser = new JsonParser(); + JsonElement element = parser.parse(new FileReader(jsonReportFile.getAbsolutePath())); + JsonObject jobj = element.getAsJsonObject(); + jobj = jobj.getAsJsonObject("counters").getAsJsonObject(name); + return jobj.get("count").getAsInt(); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/sentry/blob/44c5d9f4/sentry-tools/pom.xml ---------------------------------------------------------------------- diff --git a/sentry-tools/pom.xml b/sentry-tools/pom.xml new file mode 100644 index 0000000..ed0fb92 --- /dev/null +++ b/sentry-tools/pom.xml @@ -0,0 +1,63 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +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. +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <parent> + <artifactId>sentry</artifactId> + <groupId>org.apache.sentry</groupId> + <version>2.0.0-SNAPSHOT</version> + </parent> + <modelVersion>4.0.0</modelVersion> + + <artifactId>sentry-tools</artifactId> + + <dependencies> + <dependency> + <groupId>log4j</groupId> + <artifactId>log4j</artifactId> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-log4j12</artifactId> + </dependency> + <dependency> + <groupId>commons-cli</groupId> + <artifactId>commons-cli</artifactId> + </dependency> + <dependency> + <groupId>com.budhash.cliche</groupId> + <artifactId>cliche-shell</artifactId> + <version>0.9.3</version> + </dependency> + <dependency> + <groupId>org.apache.sentry</groupId> + <artifactId>sentry-provider-db</artifactId> + </dependency> + </dependencies> + + <build> + <sourceDirectory>${basedir}/src/main/java</sourceDirectory> + </build> + + +</project> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/sentry/blob/44c5d9f4/sentry-tools/src/main/java/org/apache/sentry/shell/GroupShell.java ---------------------------------------------------------------------- diff --git a/sentry-tools/src/main/java/org/apache/sentry/shell/GroupShell.java b/sentry-tools/src/main/java/org/apache/sentry/shell/GroupShell.java new file mode 100644 index 0000000..3fc7a31 --- /dev/null +++ b/sentry-tools/src/main/java/org/apache/sentry/shell/GroupShell.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.sentry.shell; + +import com.budhash.cliche.Command; +import com.budhash.cliche.Shell; +import com.budhash.cliche.ShellDependent; +import org.apache.sentry.provider.db.service.thrift.SentryPolicyServiceClient; + +import java.util.List; + +/** + * Sentry group manipulation for CLI + */ +public class GroupShell implements ShellDependent { + @Command + public List<String> list() { + return tools.listGroups(); + } + + @Command(abbrev = "lr", header = "[groups]", + description = "list groups and their roles") + public List<String> listRoles() { + return tools.listGroupRoles(); + } + + @Command(description = "Grant role to groups") + public void grant(String roleName, String ...groups) { + tools.grantGroupsToRole(roleName, groups); + } + + @Command(description = "Revoke role from groups") + public void revoke(String roleName, String ...groups) { + tools.revokeGroupsFromRole(roleName, groups); + } + + private final ShellUtil tools; + Shell shell; + + + public GroupShell(SentryPolicyServiceClient sentryClient, String authUser) { + this.tools = new ShellUtil(sentryClient, authUser); + } + + @Override + public void cliSetShell(Shell theShell) { + this.shell = theShell; + } +} http://git-wip-us.apache.org/repos/asf/sentry/blob/44c5d9f4/sentry-tools/src/main/java/org/apache/sentry/shell/PrivsShell.java ---------------------------------------------------------------------- diff --git a/sentry-tools/src/main/java/org/apache/sentry/shell/PrivsShell.java b/sentry-tools/src/main/java/org/apache/sentry/shell/PrivsShell.java new file mode 100644 index 0000000..9d8b9d9 --- /dev/null +++ b/sentry-tools/src/main/java/org/apache/sentry/shell/PrivsShell.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.sentry.shell; + +import com.budhash.cliche.Command; +import com.budhash.cliche.Param; +import com.budhash.cliche.Shell; +import com.budhash.cliche.ShellDependent; +import org.apache.sentry.provider.db.service.thrift.SentryPolicyServiceClient; + +import java.util.List; + +public class PrivsShell implements ShellDependent { + private final ShellUtil tools; + Shell shell; + + @Command(description = "Grant privilege to role") + public void grant( + @Param(name = "roleName") + String roleName, + @Param(name = "privilege", + description = "privilege string, e.g. server=s1->db=foo") + String privilege) { + tools.grantPrivilegeToRole(roleName, privilege); + } + + @Command + public String list() { + return tools.listPrivileges(); + } + + @Command + public List<String> list( + @Param(name = "roleName") + String roleName) { + return tools.listPrivileges(roleName); + } + + @Command + public void revoke( + @Param(name = "roleName") + String roleName, + @Param(name = "privilege", + description = "privilege string, e.g. server=s1->db=foo") + String privilege) { + tools.revokePrivilegeFromRole(roleName, privilege); + } + + public PrivsShell(SentryPolicyServiceClient sentryClient, String authUser) { + this.tools = new ShellUtil(sentryClient, authUser); + } + + @Override + public void cliSetShell(Shell theShell) { + this.shell = theShell; + } +} http://git-wip-us.apache.org/repos/asf/sentry/blob/44c5d9f4/sentry-tools/src/main/java/org/apache/sentry/shell/RolesShell.java ---------------------------------------------------------------------- diff --git a/sentry-tools/src/main/java/org/apache/sentry/shell/RolesShell.java b/sentry-tools/src/main/java/org/apache/sentry/shell/RolesShell.java new file mode 100644 index 0000000..9ac6637 --- /dev/null +++ b/sentry-tools/src/main/java/org/apache/sentry/shell/RolesShell.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.sentry.shell; + +import com.budhash.cliche.Command; +import com.budhash.cliche.Param; +import com.budhash.cliche.Shell; +import com.budhash.cliche.ShellDependent; +import org.apache.sentry.provider.db.service.thrift.SentryPolicyServiceClient; + +import java.util.List; + +/** + * Sentry roles manipulation for CLI. + */ +public class RolesShell implements ShellDependent { + @Command(description = "List sentry roles. shows all available roles.") + public List<String> list() { + return tools.listRoles(); + } + + @Command(description = "List sentry roles by group") + public List<String> list( + @Param(name = "groupName", description = "group name for roles") + String group) { + return tools.listRoles(group); + } + + @Command(description = "Create Sentry role(s).") + public void create( + @Param(name = "roleName", description = "name of role to create") + String ...roles) { + tools.createRoles(roles); + } + + @Command(description = "remove Sentry role(s).") + public void remove( + @Param(name = "roleName ...", description = "role names to remove") + String ...roles) { + tools.removeRoles(roles); + } + + + @Override + public void cliSetShell(Shell theShell) { + this.shell = theShell; + } + + private final ShellUtil tools; + Shell shell; + + public RolesShell(SentryPolicyServiceClient sentryClient, String authUser) { + this.tools = new ShellUtil(sentryClient, authUser); + } + +} http://git-wip-us.apache.org/repos/asf/sentry/blob/44c5d9f4/sentry-tools/src/main/java/org/apache/sentry/shell/SentryCli.java ---------------------------------------------------------------------- diff --git a/sentry-tools/src/main/java/org/apache/sentry/shell/SentryCli.java b/sentry-tools/src/main/java/org/apache/sentry/shell/SentryCli.java new file mode 100644 index 0000000..180d240 --- /dev/null +++ b/sentry-tools/src/main/java/org/apache/sentry/shell/SentryCli.java @@ -0,0 +1,205 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.sentry.shell; + +import org.apache.commons.cli.*; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.Path; +import org.apache.hadoop.security.UserGroupInformation; +import org.apache.log4j.PropertyConfigurator; +import org.apache.sentry.provider.db.service.thrift.SentryPolicyServiceClient; +import org.apache.sentry.service.thrift.SentryServiceClientFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Map; +import java.util.Properties; + +import static org.apache.sentry.service.thrift.ServiceConstants.ClientConfig.SERVER_RPC_ADDRESS; +import static org.apache.sentry.service.thrift.ServiceConstants.ServerConfig.SECURITY_MODE; +import static org.apache.sentry.service.thrift.ServiceConstants.ServerConfig.SECURITY_MODE_NONE; + +/** + * Sentry interactive tool + */ +public class SentryCli { + private static final Logger log = LoggerFactory.getLogger(SentryCli.class.getName()); + private static final String LOG4J_CONF = "log4jConf"; + private final String[] args; + private Options options = new Options(); + private CommandLine cmd; + + private static final String localhost = "localhost"; + private static final String defaultPort = "8038"; + + + private static final String configOpt = "config"; + private static final String userOpt = "user"; + private static final String hostOpt = "host"; + + private static final String configEnv = "SENTRY_CONFIG"; + private static final String hostEnv = "SENTRY_HOST"; + private static final String userEnv = "SENTRY_USER"; + + + private SentryPolicyServiceClient sentryClient; + + public SentryPolicyServiceClient getSentryClient() { + return sentryClient; + } + + public String getRequestorName() { + return requestorName; + } + + private String requestorName; + + public static void main(String[] args) { + SentryCli cli = new SentryCli(args); + // Create interactive shell and run it + TopLevelShell shell = new TopLevelShell(cli.getSentryClient(), + cli.getRequestorName()); + shell.run(); + } + + /** + * Construct SentryCli from arguments + * @param args command-line arguments + */ + public SentryCli(String[] args) { + this.args = args; + options.addOption("h", "help", false, "show help"); + // file path of sentry-site + options.addOption("U", userOpt, true, "auth user"); + options.addOption("H", hostOpt, true, "host address"); + options.addOption("c", configOpt, true, "sentry configuration"); + options.addOption("L", LOG4J_CONF, true, "Location of log4j properties file"); + CommandLineParser parser = new GnuParser(); + try { + this.cmd = parser.parse(options, args); + } catch (ParseException e) { + help(); + } + if (cmd.hasOption("h")) { + help(); + } + init(); + } + + /** + * Parse command-line arguments. + */ + public void parse() { + CommandLineParser parser = new GnuParser(); + try { + cmd = parser.parse(options, args); + if (cmd.hasOption("h")) { + help(); + } + } catch (ParseException e) { + log.warn("error in parsing expression", e); + help(); + System.exit(1); + } + } + + /** + * Initialize CLI + */ + private void init() { + Map<String, String> env = System.getenv(); + String log4jconf = cmd.getOptionValue(LOG4J_CONF); + if (log4jconf != null && log4jconf.length() > 0) { + Properties log4jProperties = new Properties(); + + // Firstly load log properties from properties file + FileInputStream istream = null; + try { + istream = new FileInputStream(log4jconf); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + try { + log4jProperties.load(istream); + istream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + + PropertyConfigurator.configure(log4jProperties); + } + + String host = cmd.getOptionValue(hostOpt); + if (host == null) { + host = env.get(hostEnv); + } + + String pathConf = cmd.getOptionValue(configOpt); + if (pathConf == null) { + pathConf = env.get(configEnv); + } + if (host == null && pathConf == null) { + host = localhost + ":" + defaultPort; + } + + Configuration conf = new Configuration(); + + if (pathConf != null) { + conf.addResource(new Path(pathConf)); + } else { + conf.set(SECURITY_MODE, SECURITY_MODE_NONE); + } + + if (host != null) { + conf.set(SERVER_RPC_ADDRESS, host); + } + + requestorName = cmd.getOptionValue(userOpt); + if (requestorName == null) { + requestorName = env.get(userEnv); + } + if (requestorName == null) { + + UserGroupInformation ugi = null; + try { + ugi = UserGroupInformation.getLoginUser(); + } catch (IOException e) { + e.printStackTrace(); + } + requestorName = ugi.getShortUserName(); + } + + try { + sentryClient = SentryServiceClientFactory.create(conf); + } catch (Exception e) { + System.out.println("Failed to connect to Sentry server: " + e.toString()); + } + } + + private void help() { + // This prints out some help + HelpFormatter formater = new HelpFormatter(); + formater.printHelp("sentrycli", options); + System.exit(0); + } + +} http://git-wip-us.apache.org/repos/asf/sentry/blob/44c5d9f4/sentry-tools/src/main/java/org/apache/sentry/shell/ShellUtil.java ---------------------------------------------------------------------- diff --git a/sentry-tools/src/main/java/org/apache/sentry/shell/ShellUtil.java b/sentry-tools/src/main/java/org/apache/sentry/shell/ShellUtil.java new file mode 100644 index 0000000..007975c --- /dev/null +++ b/sentry-tools/src/main/java/org/apache/sentry/shell/ShellUtil.java @@ -0,0 +1,335 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.sentry.shell; + +import com.google.common.collect.Sets; +import org.apache.commons.lang.StringUtils; +import org.apache.sentry.core.common.exception.SentryUserException; +import org.apache.sentry.provider.db.service.thrift.*; +import org.apache.sentry.service.thrift.ServiceConstants; + +import java.util.*; + +import static org.apache.sentry.service.thrift.SentryServiceUtil.convertTSentryPrivilegeToStr; +import static org.apache.sentry.service.thrift.SentryServiceUtil.convertToTSentryPrivilege; + +/** + * ShellUtil implements actual commands + */ +class ShellUtil { + + List<String> listRoles() { + List<String> roles = null; + try { + return getRoles(); + } catch (SentryUserException e) { + System.out.println("Error listing roles: " + e.toString()); + } + return new LinkedList<>(); + } + + List<String> listRoles(String group) { + Set<TSentryRole> roles = null; + try { + roles = sentryClient.listRolesByGroupName(authUser, group); + } catch (SentryUserException e) { + System.out.println("Error listing roles: " + e.toString()); + } + List<String> result = new ArrayList<>(); + if (roles == null || roles.isEmpty()) { + return result; + } + + for(TSentryRole role: roles) { + result.add(role.getRoleName()); + } + + Collections.sort(result); + return result; + } + + void createRoles(String ...roles) { + for (String role: roles) { + try { + sentryClient.createRole(authUser, role); + } catch (SentryUserException e) { + System.out.printf("failed to create role %s: %s\n", + role, e.toString()); + } + } + } + + void removeRoles(String ...roles) { + for (String role: roles) { + try { + sentryClient.dropRole(authUser, role); + } catch (SentryUserException e) { + System.out.printf("failed to remove role %s: %s\n", + role, e.toString()); + } + } + } + + List<String> listGroups() { + Set<TSentryRole> roles = null; + + try { + roles = sentryClient.listRoles(authUser); + } catch (SentryUserException e) { + System.out.println("Error reading roles: " + e.toString()); + } + + if (roles == null || roles.isEmpty()) { + return new ArrayList<>(); + } + + // Set of all group names + Set<String> groupNames = new HashSet<>(); + + // Get all group names + for (TSentryRole role: roles) { + for (TSentryGroup group: role.getGroups()) { + groupNames.add(group.getGroupName()); + } + } + + List<String> result = new ArrayList<>(groupNames); + + Collections.sort(result); + return result; + } + + List<String> listGroupRoles() { + Set<TSentryRole> roles = null; + + try { + roles = sentryClient.listRoles(authUser); + } catch (SentryUserException e) { + System.out.println("Error reading roles: " + e.toString()); + } + + if (roles == null || roles.isEmpty()) { + return new ArrayList<>(); + } + + // Set of all group names + Set<String> groupNames = new HashSet<>(); + + // Map group to set of roles + Map<String, Set<String>> groupInfo = new HashMap<>(); + + // Get all group names + for (TSentryRole role: roles) { + for (TSentryGroup group: role.getGroups()) { + String groupName = group.getGroupName(); + groupNames.add(groupName); + Set<String> groupRoles = groupInfo.get(groupName); + if (groupRoles != null) { + // Add a new or existing role + groupRoles.add(role.getRoleName()); + continue; + } + // Never seen this group before + groupRoles = new HashSet<>(); + groupRoles.add(role.getRoleName()); + groupInfo.put(groupName, groupRoles); + } + } + + List<String> groups = new ArrayList<>(groupNames); + Collections.sort(groups); + + // Produce printable result as + // group1 = role1, role2, ... + // group2 = ... + List<String> result = new LinkedList<>(); + for(String groupName: groups) { + result.add(groupName + " = " + + StringUtils.join(groupInfo.get(groupName), ", ")); + } + return result; + } + + void grantGroupsToRole(String roleName, String ...groups) { + try { + sentryClient.grantRoleToGroups(authUser, roleName, Sets.newHashSet(groups)); + } catch (SentryUserException e) { + System.out.printf("Failed to gran role %s to groups: %s\n", + roleName, e.toString()); + } + } + + void revokeGroupsFromRole(String roleName, String ...groups) { + try { + sentryClient.revokeRoleFromGroups(authUser, roleName, Sets.newHashSet(groups)); + } catch (SentryUserException e) { + System.out.printf("Failed to revoke role %s to groups: %s\n", + roleName, e.toString()); + } + } + + void grantPrivilegeToRole(String roleName, String privilege) { + TSentryPrivilege tPriv = convertToTSentryPrivilege(privilege); + boolean grantOption = tPriv.getGrantOption().equals(TSentryGrantOption.TRUE); + try { + if (ServiceConstants.PrivilegeScope.SERVER.toString().equals(tPriv.getPrivilegeScope())) { + sentryClient.grantServerPrivilege(authUser, roleName, tPriv.getServerName(), + tPriv.getAction(), grantOption); + return; + } + if (ServiceConstants.PrivilegeScope.DATABASE.toString().equals(tPriv.getPrivilegeScope())) { + sentryClient.grantDatabasePrivilege(authUser, roleName, tPriv.getServerName(), + tPriv.getDbName(), tPriv.getAction(), grantOption); + return; + } + if (ServiceConstants.PrivilegeScope.TABLE.toString().equals(tPriv.getPrivilegeScope())) { + sentryClient.grantTablePrivilege(authUser, roleName, tPriv.getServerName(), + tPriv.getDbName(), tPriv.getTableName(), + tPriv.getAction(), grantOption); + return; + } + if (ServiceConstants.PrivilegeScope.COLUMN.toString().equals(tPriv.getPrivilegeScope())) { + sentryClient.grantColumnPrivilege(authUser, roleName, tPriv.getServerName(), + tPriv.getDbName(), tPriv.getTableName(), + tPriv.getColumnName(), tPriv.getAction(), grantOption); + return; + } + if (ServiceConstants.PrivilegeScope.URI.toString().equals(tPriv.getPrivilegeScope())) { + sentryClient.grantURIPrivilege(authUser, roleName, tPriv.getServerName(), + tPriv.getURI(), grantOption); + return; + } + } catch (SentryUserException e) { + System.out.println("Error granting privilege: " + e.toString()); + } + } + + List<String> listPrivileges(String roleName) { + Set<TSentryPrivilege> privileges = null; + try { + privileges = sentryClient + .listAllPrivilegesByRoleName(authUser, roleName); + } catch (SentryUserException e) { + System.out.println("Failed to list privileges: " + e.toString()); + } + + if (privileges == null || privileges.isEmpty()) { + return new ArrayList<>(); + } + + List<String> result = new LinkedList<>(); + for (TSentryPrivilege privilege : privileges) { + String privilegeStr = convertTSentryPrivilegeToStr(privilege); + if (privilegeStr.isEmpty()) { + continue; + } + result.add(privilegeStr); + } + return result; + } + + /** + * List all privileges + * @return string with privilege info for all roles + */ + String listPrivileges() { + List<String> roles = null; + try { + roles = getRoles(); + } catch (SentryUserException e) { + System.out.println("failed to get role names: " + e.toString()); + } + + if (roles == null || roles.isEmpty()) { + return ""; + } + + StringBuilder result = new StringBuilder(); + for (String role: roles) { + List<String> privs = listPrivileges(role); + if (privs.isEmpty()) { + continue; + } + result.append(role).append(" = "); + result.append(StringUtils.join(listPrivileges(role), ",\n\t")); + result.append('\n'); + } + return result.toString(); + } + + void revokePrivilegeFromRole(String roleName, String privilegeStr) { + TSentryPrivilege tSentryPrivilege = convertToTSentryPrivilege(privilegeStr); + boolean grantOption = tSentryPrivilege.getGrantOption().equals(TSentryGrantOption.TRUE) ? true : false; + + try { + if (ServiceConstants.PrivilegeScope.SERVER.toString().equals(tSentryPrivilege.getPrivilegeScope())) { + sentryClient.revokeServerPrivilege(authUser, roleName, tSentryPrivilege.getServerName(), + grantOption); + return; + } + if (ServiceConstants.PrivilegeScope.DATABASE.toString().equals(tSentryPrivilege.getPrivilegeScope())) { + sentryClient.revokeDatabasePrivilege(authUser, roleName, tSentryPrivilege.getServerName(), + tSentryPrivilege.getDbName(), tSentryPrivilege.getAction(), grantOption); + return; + } + if (ServiceConstants.PrivilegeScope.TABLE.toString().equals(tSentryPrivilege.getPrivilegeScope())) { + sentryClient.revokeTablePrivilege(authUser, roleName, tSentryPrivilege.getServerName(), + tSentryPrivilege.getDbName(), tSentryPrivilege.getTableName(), + tSentryPrivilege.getAction(), grantOption); + return; + } + if (ServiceConstants.PrivilegeScope.COLUMN.toString().equals(tSentryPrivilege.getPrivilegeScope())) { + sentryClient.revokeColumnPrivilege(authUser, roleName, tSentryPrivilege.getServerName(), + tSentryPrivilege.getDbName(), tSentryPrivilege.getTableName(), + tSentryPrivilege.getColumnName(), tSentryPrivilege.getAction(), grantOption); + return; + } + if (ServiceConstants.PrivilegeScope.URI.toString().equals(tSentryPrivilege.getPrivilegeScope())) { + sentryClient.revokeURIPrivilege(authUser, roleName, tSentryPrivilege.getServerName(), + tSentryPrivilege.getURI(), grantOption); + return; + } + } catch (SentryUserException e) { + System.out.println("failed to revoke privilege: " + e.toString()); + } + } + + + private List<String>getRoles() throws SentryUserException { + // Collect role names + Set<TSentryRole> roles = null; + roles = sentryClient.listRoles(authUser); + List<String> roleNames = new ArrayList<>(); + for(TSentryRole role: roles) { + roleNames.add(role.getRoleName()); + } + + Collections.sort(roleNames); + return roleNames; + } + + ShellUtil(SentryPolicyServiceClient sentryClient, String authUser) { + this.sentryClient = sentryClient; + this.authUser = authUser; + } + + private final SentryPolicyServiceClient sentryClient; + private final String authUser; + +} http://git-wip-us.apache.org/repos/asf/sentry/blob/44c5d9f4/sentry-tools/src/main/java/org/apache/sentry/shell/TopLevelShell.java ---------------------------------------------------------------------- diff --git a/sentry-tools/src/main/java/org/apache/sentry/shell/TopLevelShell.java b/sentry-tools/src/main/java/org/apache/sentry/shell/TopLevelShell.java new file mode 100644 index 0000000..ef5313a --- /dev/null +++ b/sentry-tools/src/main/java/org/apache/sentry/shell/TopLevelShell.java @@ -0,0 +1,161 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.sentry.shell; + +import com.budhash.cliche.*; +import org.apache.sentry.provider.db.service.thrift.SentryPolicyServiceClient; + +import java.io.IOException; +import java.util.List; + +/** + * Top level commands + */ +public class TopLevelShell implements ShellDependent, Runnable { + + private final Shell topShell; + private final ShellUtil tools; + private Shell shell; // top level shell object + + private final String authUser; + private final SentryPolicyServiceClient sentryClient; + + TopLevelShell(SentryPolicyServiceClient sentryClient, + String authUser) { + this.authUser = authUser; + this.sentryClient = sentryClient; + this.tools = new ShellUtil(sentryClient, authUser); + topShell = ShellFactory.createConsoleShell("sentry", + "sentry shell\n" + + "Enter ?l to list available commands.", + this); + } + + @Command(description="list, create and remove roles") + public void roles() throws IOException { + ShellFactory.createSubshell("roles", shell, "roles commands", + new RolesShell(sentryClient, authUser)).commandLoop(); + } + + @Command(description = "list, create and remove groups") + public void groups() throws IOException { + ShellFactory.createSubshell("groups", shell, "groups commands", + new GroupShell(sentryClient, authUser)).commandLoop(); + } + + @Command(description = "list, create and remove privileges") + public void privileges() throws IOException { + ShellFactory.createSubshell("privileges", shell, "privileges commands", + new PrivsShell(sentryClient, authUser)).commandLoop(); + } + + @Command(description = "List sentry roles. shows all available roles.") + public List<String> listRoles() { + return tools.listRoles(); + } + + @Command(description = "List sentry roles by group") + public List<String> listRoles( + @Param(name = "groupName") + String group) { + return tools.listRoles(group); + } + + @Command(abbrev = "lg", header = "[groups]", + description = "list groups and their roles") + public List<String> listGroups() { + return tools.listGroupRoles(); + } + + @Command(description = "Grant role to groups") + public void grantRole( + @Param(name = "roleName") + String roleName, + @Param(name = "group...") String ...groups) { + tools.grantGroupsToRole(roleName, groups); + } + + @Command(abbrev = "grm", + description = "Revoke role from groups") + public void revokeRole( + @Param(name = "roleName") + String roleName, + @Param(name = "group...") + String ...groups) { + tools.revokeGroupsFromRole(roleName, groups); + } + + @Command(description = "Create Sentry role(s).") + public void createRole( + @Param(name = "roleName", description = "name of role to create") + String ...roles) { + tools.createRoles(roles); + } + + @Command(abbrev = "rm", description = "remove Sentry role(s).") + public void removeRole( + @Param(name = "roleName ...", description = "role names to remove") + String ...roles) { + tools.removeRoles(roles); + } + + @Command(description = "list Sentry privileges") + public String listPrivileges() { + return tools.listPrivileges(); + } + + @Command(description = "list Sentry privileges") + public List<String> listPrivileges( + @Param(name = "roleName") + String roleName) { + return tools.listPrivileges(roleName); + } + + @Command(description = "Grant privilege to role") + public void grantPrivilege( + @Param(name = "roleName") + String roleName, + @Param(name = "privilege", description = "privilege string, e.g. server=s1->db=foo") + String privilege) { + tools.grantPrivilegeToRole(roleName, privilege); + } + + @Command + public void revokePrivilege( + @Param(name = "roleName") + String roleName, + @Param(name = "privilege", description = "privilege string, e.g. server=s1->db=foo") + String privilege) { + tools.revokePrivilegeFromRole(roleName, privilege); + } + + @Override + public void cliSetShell(Shell theShell) { + this.shell = theShell; + } + + @Override + public void run() { + try { + this.topShell.commandLoop(); + } catch (IOException e) { + System.out.println("error: " + e.toString()); + } + } +}
