Repository: sentry Updated Branches: refs/heads/master b85b323fb -> 9fd29f9df
SENTRY-1480: A upgrade tool to migrate Solr/Sentry permissions. (Hrishikesh Gadre, reviewed by Kalyan Kumar Kalvagadda) Project: http://git-wip-us.apache.org/repos/asf/sentry/repo Commit: http://git-wip-us.apache.org/repos/asf/sentry/commit/9fd29f9d Tree: http://git-wip-us.apache.org/repos/asf/sentry/tree/9fd29f9d Diff: http://git-wip-us.apache.org/repos/asf/sentry/diff/9fd29f9d Branch: refs/heads/master Commit: 9fd29f9dfbe621c498461227472824a087845ac6 Parents: b85b323 Author: Kalyan Kumar Kalvagadda <[email protected]> Authored: Wed Nov 22 07:12:32 2017 -0600 Committer: Kalyan Kumar Kalvagadda <[email protected]> Committed: Wed Nov 22 07:12:32 2017 -0600 ---------------------------------------------------------------------- .../core/common/utils/PolicyFileConstants.java | 8 + .../sentry/core/common/utils/PolicyFiles.java | 48 +++ .../common/utils/StrictStringTokenizer.java | 59 +++ .../sentry/core/common/utils/Version.java | 239 ++++++++++++ .../sentry/policy/common/PrivilegeUtils.java | 11 +- .../tools/PermissionsMigrationToolCommon.java | 343 ++++++++++++++++++ .../tools/PermissionsMigrationToolSolr.java | 109 ++++++ .../tools/TestPermissionsMigrationToolSolr.java | 362 +++++++++++++++++++ 8 files changed, 1178 insertions(+), 1 deletion(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/sentry/blob/9fd29f9d/sentry-core/sentry-core-common/src/main/java/org/apache/sentry/core/common/utils/PolicyFileConstants.java ---------------------------------------------------------------------- diff --git a/sentry-core/sentry-core-common/src/main/java/org/apache/sentry/core/common/utils/PolicyFileConstants.java b/sentry-core/sentry-core-common/src/main/java/org/apache/sentry/core/common/utils/PolicyFileConstants.java index 6b625ff..216d861 100644 --- a/sentry-core/sentry-core-common/src/main/java/org/apache/sentry/core/common/utils/PolicyFileConstants.java +++ b/sentry-core/sentry-core-common/src/main/java/org/apache/sentry/core/common/utils/PolicyFileConstants.java @@ -16,6 +16,9 @@ */ package org.apache.sentry.core.common.utils; +import java.util.Arrays; +import java.util.List; + public class PolicyFileConstants { public static final String DATABASES = "databases"; public static final String GROUPS = "groups"; @@ -30,6 +33,11 @@ public class PolicyFileConstants { public static final String PRIVILEGE_ACTION_NAME = "action"; public static final String PRIVILEGE_GRANT_OPTION_NAME = "grantoption"; + /** + * This constant defines all possible section names in sentry ini file in the expected order + */ + public static final List<String> SECTION_NAMES = Arrays.asList(DATABASES, USERS, GROUPS, ROLES); + private PolicyFileConstants() { // Make constructor private to avoid instantiation } http://git-wip-us.apache.org/repos/asf/sentry/blob/9fd29f9d/sentry-core/sentry-core-common/src/main/java/org/apache/sentry/core/common/utils/PolicyFiles.java ---------------------------------------------------------------------- diff --git a/sentry-core/sentry-core-common/src/main/java/org/apache/sentry/core/common/utils/PolicyFiles.java b/sentry-core/sentry-core-common/src/main/java/org/apache/sentry/core/common/utils/PolicyFiles.java index ca2de7a..bba95e4 100644 --- a/sentry-core/sentry-core-common/src/main/java/org/apache/sentry/core/common/utils/PolicyFiles.java +++ b/sentry-core/sentry-core-common/src/main/java/org/apache/sentry/core/common/utils/PolicyFiles.java @@ -16,20 +16,27 @@ */ package org.apache.sentry.core.common.utils; +import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; import org.apache.hadoop.fs.FSDataOutputStream; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; +import org.apache.hadoop.io.IOUtils; import org.apache.shiro.config.Ini; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.base.Joiner; +import com.google.common.collect.Lists; import com.google.common.io.ByteStreams; import com.google.common.io.Resources; @@ -37,6 +44,8 @@ public final class PolicyFiles { private static final Logger LOGGER = LoggerFactory .getLogger(PolicyFiles.class); + private static final String NL = System.getProperty("line.separator", "\n"); + public static void copyToDir(File dest, String... resources) throws FileNotFoundException, IOException { @@ -91,6 +100,45 @@ public final class PolicyFiles { } } + /** + * Save the specified Sentry configuration file to the desired location + * + * @param iniFile The Sentry configuration ini file to be saved + * @param fileSystem The {@linkplain FileSystem} instance to be used + * @param path The path on the {@linkplain FileSystem} where the configuration file should be stored. + * @throws IOException in case of I/O errors + */ + public static void writeToPath (Ini iniFile, FileSystem fileSystem, Path path) throws IOException { + if (fileSystem.exists(path)) { + throw new IllegalArgumentException("The specified path " + path + " already exist!"); + } + + List<String> sectionStrs = new ArrayList<>(); + for (String sectionName : PolicyFileConstants.SECTION_NAMES) { + sectionStrs.add(toString(sectionName, iniFile.getSection(sectionName))); + } + + String contents = Joiner.on(NL).join(sectionStrs.iterator()); + try (OutputStream out = fileSystem.create(path)) { + ByteArrayInputStream in = new ByteArrayInputStream(contents.getBytes(StandardCharsets.UTF_8)); + IOUtils.copyBytes(in, out, fileSystem.getConf()); + } + } + + private static String toString(String name, Ini.Section mapping) { + if(mapping == null || mapping.isEmpty()) { + return ""; + } + Joiner kvJoiner = Joiner.on(" = "); + List<String> lines = Lists.newArrayList(); + lines.add(NL); + lines.add("[" + name + "]"); + for(String key : mapping.keySet()) { + lines.add(kvJoiner.join(key, mapping.get(key))); + } + return Joiner.on(NL).join(lines); + } + private PolicyFiles() { // Make constructor private to avoid instantiation } http://git-wip-us.apache.org/repos/asf/sentry/blob/9fd29f9d/sentry-core/sentry-core-common/src/main/java/org/apache/sentry/core/common/utils/StrictStringTokenizer.java ---------------------------------------------------------------------- diff --git a/sentry-core/sentry-core-common/src/main/java/org/apache/sentry/core/common/utils/StrictStringTokenizer.java b/sentry-core/sentry-core-common/src/main/java/org/apache/sentry/core/common/utils/StrictStringTokenizer.java new file mode 100644 index 0000000..eab6ffc --- /dev/null +++ b/sentry-core/sentry-core-common/src/main/java/org/apache/sentry/core/common/utils/StrictStringTokenizer.java @@ -0,0 +1,59 @@ +/* + * 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.core.common.utils; + + +/** + * Used for parsing Version strings so we don't have to + * use overkill String.split nor StringTokenizer (which silently + * skips empty tokens). + * Note - implementation of this class is copied from a similar + * functionality in Apache Lucene/Solr project. + **/ +final class StrictStringTokenizer { + + public StrictStringTokenizer(String s, char delimiter) { + this.s = s; + this.delimiter = delimiter; + } + + public String nextToken() { + if (pos < 0) { + throw new IllegalStateException("no more tokens"); + } + + int pos1 = s.indexOf(delimiter, pos); + String s1; + if (pos1 >= 0) { + s1 = s.substring(pos, pos1); + pos = pos1+1; + } else { + s1 = s.substring(pos); + pos=-1; + } + + return s1; + } + + public boolean hasMoreTokens() { + return pos >= 0; + } + + private final String s; + private final char delimiter; + private int pos; +} http://git-wip-us.apache.org/repos/asf/sentry/blob/9fd29f9d/sentry-core/sentry-core-common/src/main/java/org/apache/sentry/core/common/utils/Version.java ---------------------------------------------------------------------- diff --git a/sentry-core/sentry-core-common/src/main/java/org/apache/sentry/core/common/utils/Version.java b/sentry-core/sentry-core-common/src/main/java/org/apache/sentry/core/common/utils/Version.java new file mode 100644 index 0000000..1fc3fc1 --- /dev/null +++ b/sentry-core/sentry-core-common/src/main/java/org/apache/sentry/core/common/utils/Version.java @@ -0,0 +1,239 @@ +/* + * 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.core.common.utils; + +import java.text.ParseException; +import java.util.Locale; + +/** + * Use by certain classes to match version compatibility + * across releases of Sentry. Note - implementation of this + * class is copied from similar functionality in Apache Lucene/Solr project. + */ +public final class Version { + + public static final Version SENTRY_2_0_0 = new Version(2, 0, 0); + + // To add a new version: + // * Only add above this comment + + /** + * Parse a version number of the form {@code "major.minor.bugfix.prerelease"}. + * + * Part {@code ".bugfix"} and part {@code ".prerelease"} are optional. + * Note that this is forwards compatible: the parsed version does not have to exist as + * a constant. + */ + public static Version parse(String version) throws ParseException { + + StrictStringTokenizer tokens = new StrictStringTokenizer(version, '.'); + if (tokens.hasMoreTokens() == false) { + throw new ParseException("Version is not in form major.minor.bugfix(.prerelease) (got: " + + version + ")", 0); + } + + int major; + String token = tokens.nextToken(); + try { + major = Integer.parseInt(token); + } catch (NumberFormatException nfe) { + ParseException p = new ParseException("Failed to parse major version from \"" + token + + "\" (got: " + version + ")", 0); + p.initCause(nfe); + throw p; + } + + if (tokens.hasMoreTokens() == false) { + throw new ParseException("Version is not in form major.minor.bugfix(.prerelease) (got: " + + version + ")", 0); + } + + int minor; + token = tokens.nextToken(); + try { + minor = Integer.parseInt(token); + } catch (NumberFormatException nfe) { + ParseException p = new ParseException("Failed to parse minor version from \"" + token + + "\" (got: " + version + ")", 0); + p.initCause(nfe); + throw p; + } + + int bugfix = 0; + int prerelease = 0; + if (tokens.hasMoreTokens()) { + + token = tokens.nextToken(); + try { + bugfix = Integer.parseInt(token); + } catch (NumberFormatException nfe) { + ParseException p = new ParseException("Failed to parse bugfix version from \"" + token + + "\" (got: " + version + ")", 0); + p.initCause(nfe); + throw p; + } + + if (tokens.hasMoreTokens()) { + token = tokens.nextToken(); + try { + prerelease = Integer.parseInt(token); + } catch (NumberFormatException nfe) { + ParseException p = new ParseException("Failed to parse prerelease version from \"" + + token + "\" (got: " + version + ")", 0); + p.initCause(nfe); + throw p; + } + if (prerelease == 0) { + throw new ParseException("Invalid value " + prerelease + + " for prerelease; should be 1 or 2 (got: " + version + ")", 0); + } + + if (tokens.hasMoreTokens()) { + // Too many tokens! + throw new ParseException("Version is not in form major.minor.bugfix(.prerelease) (got: " + + version + ")", 0); + } + } + } + + try { + return new Version(major, minor, bugfix, prerelease); + } catch (IllegalArgumentException iae) { + ParseException pe = new ParseException("failed to parse version string \"" + version + + "\": " + iae.getMessage(), 0); + pe.initCause(iae); + throw pe; + } + } + + /** + * Parse the given version number as a constant or dot based version. + * <p>This method allows to use {@code "SENTRY_X_Y"} constant names, + * or version numbers in the format {@code "x.y.z"}. + * + * @lucene.internal + */ + public static Version parseLeniently(String version) throws ParseException { + String versionOrig = version; + version = version.toUpperCase(Locale.ROOT); + switch (version) { + default: + version = version + .replaceFirst("^SENTRY_(\\d+)_(\\d+)_(\\d+)$", "$1.$2.$3") + .replaceFirst("^SENTRY_(\\d+)_(\\d+)$", "$1.$2.0") + .replaceFirst("^SENTRY_(\\d)(\\d)$", "$1.$2.0"); + try { + return parse(version); + } catch (ParseException pe) { + ParseException pe2 = new ParseException( + "failed to parse lenient version string \"" + versionOrig + + "\": " + pe.getMessage(), 0); + pe2.initCause(pe); + throw pe2; + } + } + } + + /** Returns a new version based on raw numbers + * + * @lucene.internal */ + public static Version fromBits(int major, int minor, int bugfix) { + return new Version(major, minor, bugfix); + } + + /** Major version, the difference between stable and trunk */ + public final int major; + /** Minor version, incremented within the stable branch */ + public final int minor; + /** Bugfix number, incremented on release branches */ + public final int bugfix; + /** Prerelease version, currently 0 (alpha), 1 (beta), or 2 (final) */ + public final int prerelease; + + // stores the version pieces, with most significant pieces in high bits + // ie: | 1 byte | 1 byte | 1 byte | 2 bits | + // major minor bugfix prerelease + private final int encodedValue; + + private Version(int major, int minor, int bugfix) { + this(major, minor, bugfix, 0); + } + + private Version(int major, int minor, int bugfix, int prerelease) { + this.major = major; + this.minor = minor; + this.bugfix = bugfix; + this.prerelease = prerelease; + // NOTE: do not enforce major version so we remain future proof, except to + // make sure it fits in the 8 bits we encode it into: + if (major > 255 || major < 0) { + throw new IllegalArgumentException("Illegal major version: " + major); + } + if (minor > 255 || minor < 0) { + throw new IllegalArgumentException("Illegal minor version: " + minor); + } + if (bugfix > 255 || bugfix < 0) { + throw new IllegalArgumentException("Illegal bugfix version: " + bugfix); + } + if (prerelease > 2 || prerelease < 0) { + throw new IllegalArgumentException("Illegal prerelease version: " + prerelease); + } + if (prerelease != 0 && (minor != 0 || bugfix != 0)) { + throw new IllegalArgumentException( + "Prerelease version only supported with major release (got prerelease: " + + prerelease + ", minor: " + minor + ", bugfix: " + bugfix + ")"); + } + + encodedValue = major << 18 | minor << 10 | bugfix << 2 | prerelease; + + assert encodedIsValid(); + } + + /** + * Returns true if this version is the same or after the version from the argument. + */ + public boolean onOrAfter(Version other) { + return encodedValue >= other.encodedValue; + } + + @Override + public String toString() { + if (prerelease == 0) { + return "" + major + "." + minor + "." + bugfix; + } + return "" + major + "." + minor + "." + bugfix + "." + prerelease; + } + + @Override + public boolean equals(Object o) { + return o != null && o instanceof Version && ((Version)o).encodedValue == encodedValue; + } + + // Used only by assert: + private boolean encodedIsValid() { + assert major == ((encodedValue >>> 18) & 0xFF); + assert minor == ((encodedValue >>> 10) & 0xFF); + assert bugfix == ((encodedValue >>> 2) & 0xFF); + assert prerelease == (encodedValue & 0x03); + return true; + } + + @Override + public int hashCode() { + return encodedValue; + } +} http://git-wip-us.apache.org/repos/asf/sentry/blob/9fd29f9d/sentry-policy/sentry-policy-common/src/main/java/org/apache/sentry/policy/common/PrivilegeUtils.java ---------------------------------------------------------------------- diff --git a/sentry-policy/sentry-policy-common/src/main/java/org/apache/sentry/policy/common/PrivilegeUtils.java b/sentry-policy/sentry-policy-common/src/main/java/org/apache/sentry/policy/common/PrivilegeUtils.java index 6628a2f..2e1333e 100644 --- a/sentry-policy/sentry-policy-common/src/main/java/org/apache/sentry/policy/common/PrivilegeUtils.java +++ b/sentry-policy/sentry-policy-common/src/main/java/org/apache/sentry/policy/common/PrivilegeUtils.java @@ -16,15 +16,24 @@ */ package org.apache.sentry.policy.common; +import java.util.Collection; import java.util.Set; import org.apache.shiro.util.PermissionUtils; +import org.apache.shiro.util.StringUtils; public class PrivilegeUtils { public static Set<String> toPrivilegeStrings(String s) { return PermissionUtils.toPermissionStrings(s); } - + + /** + * Transform the specified {@linkplain Set} of privileges to a {@linkplain String} value. + */ + public static String fromPrivilegeStrings (Collection<String> s) { + return StringUtils.toDelimitedString(s, String.valueOf(StringUtils.DEFAULT_DELIMITER_CHAR)); + } + private PrivilegeUtils() { // Make constructor private to avoid instantiation } http://git-wip-us.apache.org/repos/asf/sentry/blob/9fd29f9d/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/PermissionsMigrationToolCommon.java ---------------------------------------------------------------------- diff --git a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/PermissionsMigrationToolCommon.java b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/PermissionsMigrationToolCommon.java new file mode 100644 index 0000000..8c2ec32 --- /dev/null +++ b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/PermissionsMigrationToolCommon.java @@ -0,0 +1,343 @@ +/** + * 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.generic.tools; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Optional; +import java.util.Set; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.GnuParser; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; +import org.apache.commons.cli.Parser; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.Path; +import org.apache.hadoop.security.UserGroupInformation; +import org.apache.sentry.core.common.ActiveRoleSet; +import org.apache.sentry.core.common.utils.PolicyFileConstants; +import org.apache.sentry.core.common.utils.PolicyFiles; +import org.apache.sentry.core.common.utils.Version; +import org.apache.sentry.policy.common.PrivilegeUtils; +import org.apache.sentry.provider.common.ProviderBackendContext; +import org.apache.sentry.provider.db.generic.service.thrift.SentryGenericServiceClient; +import org.apache.sentry.provider.db.generic.service.thrift.SentryGenericServiceClientFactory; +import org.apache.sentry.provider.db.generic.service.thrift.TSentryPrivilege; +import org.apache.sentry.provider.db.generic.service.thrift.TSentryRole; +import org.apache.sentry.provider.file.SimpleFileProviderBackend; +import org.apache.shiro.config.Ini; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.Sets; +import com.google.common.collect.Table; + +/** + * This class provides basic framework required to migrate permissions between different Sentry + * versions. Individual components (e.g. SOLR, KAFKA) needs to override the this class + * to provide component specific migration functionality. + */ +public abstract class PermissionsMigrationToolCommon { + private static final Logger LOGGER = LoggerFactory.getLogger(PermissionsMigrationToolCommon.class); + public static final String SOLR_SERVICE_NAME = "sentry.service.client.solr.service.name"; + + private Version sourceVersion; + private Optional<String> confPath = Optional.empty(); + private Optional<String> policyFile = Optional.empty(); + private Optional<String> outputFile = Optional.empty(); + private boolean dryRun = false; + + /** + * @return version of Sentry for which the privileges need to be migrated. + */ + public final Version getSourceVersion() { + return sourceVersion; + } + + /** + * This method returns the name of the component for the migration purpose. + * @param conf The Sentry configuration + * @return the name of the component + */ + protected abstract String getComponent(Configuration conf); + + + /** + * This method returns the name of the service name for the migration purpose. + * + * @param conf The Sentry configuration + * @return the name of the service + */ + protected abstract String getServiceName(Configuration conf); + + /** + * Migrate the privileges specified via <code>privileges</code>. + * + * @param privileges A collection of privileges to be migrated. + * @return A collection of migrated privileges + * An empty collection if migration is not necessary for the specified privileges. + */ + protected abstract Collection<String> transformPrivileges (Collection<String> privileges); + + /** + * parse arguments + * <pre> + * -s,--source Sentry source version + * -c,--sentry_conf <filepath> sentry config file path + * -p --policy_file <filepath> sentry (source) policy file path + * -o --output <filepath> sentry (target) policy file path + * -d --dry_run provides the output the migration for inspection without + * making any configuration changes. + * -h,--help print usage + * </pre> + * @param args + */ + protected boolean parseArgs(String [] args) { + Options options = new Options(); + + Option sourceVersionOpt = new Option("s", "source", true, "Source Sentry version"); + sourceVersionOpt.setRequired(true); + options.addOption(sourceVersionOpt); + + Option sentryConfPathOpt = new Option("c", "sentry_conf", true, + "sentry-site.xml file path (only required in case of Sentry service)"); + sentryConfPathOpt.setRequired(false); + options.addOption(sentryConfPathOpt); + + Option sentryPolicyFileOpt = new Option("p", "policy_file", true, + "sentry (source) policy file path (only in case of file based Sentry configuration)"); + sentryPolicyFileOpt.setRequired(false); + options.addOption(sentryPolicyFileOpt); + + Option sentryOutputFileOpt = new Option("o", "output", true, + "sentry (target) policy file path (only in case of file based Sentry configuration)"); + sentryOutputFileOpt.setRequired(false); + options.addOption(sentryOutputFileOpt); + + Option dryRunOpt = new Option("d", "dry_run", false, + "provides the output the migration for inspection without making actual configuration changes"); + dryRunOpt.setRequired(false); + options.addOption(dryRunOpt); + + // help option + Option helpOpt = new Option("h", "help", false, "Shell usage"); + helpOpt.setRequired(false); + options.addOption(helpOpt); + + // this Option is parsed first for help option + Options helpOptions = new Options(); + helpOptions.addOption(helpOpt); + + try { + Parser parser = new GnuParser(); + + // parse help option first + CommandLine cmd = parser.parse(helpOptions, args, true); + for (Option opt : cmd.getOptions()) { + if (opt.getOpt().equals("h")) { + // get the help option, print the usage and exit + usage(options); + return false; + } + } + + // without help option + cmd = parser.parse(options, args); + + String sourceVersionStr = null; + + for (Option opt : cmd.getOptions()) { + if (opt.getOpt().equals("s")) { + sourceVersionStr = opt.getValue(); + } else if (opt.getOpt().equals("c")) { + confPath = Optional.of(opt.getValue()); + } else if (opt.getOpt().equals("p")) { + policyFile = Optional.of(opt.getValue()); + } else if (opt.getOpt().equals("o")) { + outputFile = Optional.of(opt.getValue()); + } else if (opt.getOpt().equals("d")) { + dryRun = true; + } + } + + sourceVersion = Version.parse(sourceVersionStr); + + if (!(confPath.isPresent() || policyFile.isPresent())) { + System.out.println("Please select either file-based Sentry configuration (-p and -o flags)" + + " or Sentry service (-c flag) for migration."); + usage(options); + return false; + } + + if (confPath.isPresent() && (policyFile.isPresent() || outputFile.isPresent())) { + System.out.println("In order to migrate service based Sentry configuration," + + " do not specify either -p or -o parameters"); + usage(options); + return false; + } + + if (!confPath.isPresent() && (policyFile.isPresent() ^ outputFile.isPresent())) { + System.out.println("In order to migrate file based Sentry configuration," + + " please make sure to specify both -p and -o parameters."); + usage(options); + return false; + } + + } catch (ParseException | java.text.ParseException pe) { + System.out.println(pe.getMessage()); + usage(options); + return false; + } + return true; + } + + // print usage + private void usage(Options sentryOptions) { + HelpFormatter formatter = new HelpFormatter(); + formatter.printHelp("sentryMigrationTool", sentryOptions); + } + + public void run() throws Exception { + if (policyFile.isPresent()) { + migratePolicyFile(); + } else { + migrateSentryServiceConfig(); + } + } + + private void migrateSentryServiceConfig() throws Exception { + Configuration conf = getSentryConf(); + String component = getComponent(conf); + String serviceName = getServiceName(conf); + GenericPrivilegeConverter converter = new GenericPrivilegeConverter(component, serviceName, false); + + // instantiate a client for sentry service. This sets the ugi, so must + // be done before getting the ugi below. + try(SentryGenericServiceClient client = + SentryGenericServiceClientFactory.create(getSentryConf())) { + UserGroupInformation ugi = UserGroupInformation.getLoginUser(); + String requestorName = ugi.getShortUserName(); + + for (TSentryRole r : client.listAllRoles(requestorName, component)) { + for (TSentryPrivilege p : client.listAllPrivilegesByRoleName(requestorName, + r.getRoleName(), component, serviceName)) { + + Collection<String> privileges = Collections.singleton(converter.toString(p)); + Collection<String> migrated = transformPrivileges(privileges); + if (!migrated.isEmpty()) { + LOGGER.info("{} For role {} migrating privileges from {} to {}", getDryRunMessage(), r.getRoleName(), + privileges, migrated); + + if (!dryRun) { + Collection<TSentryPrivilege> tmp = new ArrayList<>(); + for (String perm : migrated) { + tmp.add(converter.fromString(perm)); + } + + /* + * Note that it is not possible to provide transactional (all-or-nothing) behavior for these configuration + * changes since the Sentry client/server protocol does not support. e.g. under certain failure conditions + * like crash of Sentry server or network disconnect between client/server, it is possible that the migration + * can not complete but can also not be rolled back. Hence this migration tool relies on the fact that privilege + * grant/revoke operations are idempotent and hence re-execution of the migration tool will fix any inconsistency + * due to such failures. + **/ + for (TSentryPrivilege x : tmp) { // grant new permissions + client.grantPrivilege(requestorName, r.getRoleName(), component, x); + } + + // Revoke old permission (only if not part of migrated permissions) + if (!tmp.contains(p)) { + client.revokePrivilege(requestorName, r.getRoleName(), component, p); + } + } + } + } + } + } + } + + private void migratePolicyFile () throws Exception { + Configuration conf = getSentryConf(); + Path sourceFile = new Path (policyFile.get()); + SimpleFileProviderBackend policyFileBackend = new SimpleFileProviderBackend(conf, sourceFile); + ProviderBackendContext ctx = new ProviderBackendContext(); + policyFileBackend.initialize(ctx); + + Set<String> roles = Sets.newHashSet(); + Table<String, String, Set<String>> groupRolePrivilegeTable = + policyFileBackend.getGroupRolePrivilegeTable(); + + Ini output = PolicyFiles.loadFromPath(sourceFile.getFileSystem(conf), sourceFile); + Ini.Section rolesSection = output.get(PolicyFileConstants.ROLES); + + for (String groupName : groupRolePrivilegeTable.rowKeySet()) { + for (String roleName : policyFileBackend.getRoles(Collections.singleton(groupName), ActiveRoleSet.ALL)) { + if (!roles.contains(roleName)) { + // Do the actual migration + Set<String> privileges = groupRolePrivilegeTable.get(groupName, roleName); + Collection<String> migrated = transformPrivileges(privileges); + + if (!migrated.isEmpty()) { + LOGGER.info("{} For role {} migrating privileges from {} to {}", getDryRunMessage(), + roleName, privileges, migrated); + if (!dryRun) { + rolesSection.put(roleName, PrivilegeUtils.fromPrivilegeStrings(migrated)); + } + } + + roles.add(roleName); + } + } + } + + if (!dryRun) { + Path targetFile = new Path (outputFile.get()); + PolicyFiles.writeToPath(output, targetFile.getFileSystem(conf), targetFile); + LOGGER.info("Successfully saved migrated Sentry policy file at {}", outputFile.get()); + } + } + + private String getDryRunMessage() { + return dryRun ? "[Dry Run]" : ""; + } + + private Configuration getSentryConf() { + Configuration conf = new Configuration(); + if (confPath.isPresent()) { + conf.addResource(new Path(confPath.get())); + } + return conf; + } + + @VisibleForTesting + public boolean executeConfigTool(String [] args) throws Exception { + boolean result = true; + if (parseArgs(args)) { + run(); + } else { + result = false; + } + return result; + } +} http://git-wip-us.apache.org/repos/asf/sentry/blob/9fd29f9d/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/PermissionsMigrationToolSolr.java ---------------------------------------------------------------------- diff --git a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/PermissionsMigrationToolSolr.java b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/PermissionsMigrationToolSolr.java new file mode 100644 index 0000000..5799993 --- /dev/null +++ b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/generic/tools/PermissionsMigrationToolSolr.java @@ -0,0 +1,109 @@ +/** + * 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.generic.tools; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import org.apache.hadoop.conf.Configuration; +import org.apache.sentry.core.common.utils.SentryConstants; +import org.apache.sentry.core.model.solr.validator.SolrPrivilegeValidator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class provides SOLR specific functionality required for migrating Sentry privileges. + */ +public class PermissionsMigrationToolSolr extends PermissionsMigrationToolCommon { + private static final Logger LOGGER = LoggerFactory.getLogger(PermissionsMigrationToolSolr.class); + + + @Override + protected String getComponent(Configuration conf) { + return "SOLR"; + } + + @Override + protected String getServiceName(Configuration conf) { + return conf.get(SOLR_SERVICE_NAME, "service1"); + } + + @Override + protected Collection<String> transformPrivileges(Collection<String> privileges) { + List<String> result = new ArrayList<>(); + boolean migrated = false; + + if (getSourceVersion().major == 1) { // Migrate only Sentry 1.x permissions + for (String p : privileges) { + SolrPrivilegeValidator v = new SolrPrivilegeValidator(); + v.validate(p, false); + + if ("collection".equalsIgnoreCase(v.getEntityType()) && "admin".equalsIgnoreCase(v.getEntityName())) { + result.add(getPermissionStr("admin", "collections", v.getActionName())); + result.add(getPermissionStr("admin", "cores", v.getActionName())); + migrated = true; + } else if ("collection".equalsIgnoreCase(v.getEntityType()) && "*".equals(v.getEntityName())) { + result.add(getPermissionStr("admin", "collections", v.getActionName())); + result.add(getPermissionStr("admin", "cores", v.getActionName())); + result.add(p); + migrated = true; + } else { + result.add(p); + } + } + } + + return migrated ? result : Collections.emptyList(); + } + + private String getPermissionStr (String entityType, String entityName, String action) { + StringBuilder builder = new StringBuilder(); + builder.append(entityType); + builder.append(SentryConstants.KV_SEPARATOR); + builder.append(entityName); + if (action != null) { + builder.append(SentryConstants.AUTHORIZABLE_SEPARATOR); + builder.append(SentryConstants.PRIVILEGE_NAME); + builder.append(SentryConstants.KV_SEPARATOR); + builder.append(action); + } + return builder.toString(); + } + + public static void main(String[] args) throws Exception { + PermissionsMigrationToolSolr solrTool = new PermissionsMigrationToolSolr(); + try { + solrTool.executeConfigTool(args); + } catch (Exception e) { + LOGGER.error(e.getMessage(), e); + Throwable current = e; + // find the first printable message; + while (current != null && current.getMessage() == null) { + current = current.getCause(); + } + String error = ""; + if (current != null && current.getMessage() != null) { + error = "Message: " + current.getMessage(); + } + System.out.println("The operation failed. " + error); + System.exit(1); + } + } +} http://git-wip-us.apache.org/repos/asf/sentry/blob/9fd29f9d/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/generic/tools/TestPermissionsMigrationToolSolr.java ---------------------------------------------------------------------- diff --git a/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/generic/tools/TestPermissionsMigrationToolSolr.java b/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/generic/tools/TestPermissionsMigrationToolSolr.java new file mode 100644 index 0000000..69c067f --- /dev/null +++ b/sentry-provider/sentry-provider-db/src/test/java/org/apache/sentry/provider/db/generic/tools/TestPermissionsMigrationToolSolr.java @@ -0,0 +1,362 @@ + /** + * 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.generic.tools; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertFalse; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.io.FileUtils; +import org.apache.sentry.core.common.exception.SentryUserException; +import org.apache.sentry.provider.common.ProviderBackendContext; +import org.apache.sentry.provider.db.generic.service.thrift.SentryGenericServiceIntegrationBase; +import org.apache.sentry.provider.db.generic.service.thrift.TAuthorizable; +import org.apache.sentry.provider.db.generic.service.thrift.TSentryPrivilege; +import org.apache.sentry.provider.db.generic.service.thrift.TSentryRole; +import org.apache.sentry.provider.file.PolicyFile; +import org.apache.sentry.provider.file.SimpleFileProviderBackend; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import com.google.common.collect.Sets; +import com.google.common.collect.Table; +import com.google.common.io.Files; + +public class TestPermissionsMigrationToolSolr extends SentryGenericServiceIntegrationBase { + private File confDir; + private File confPath; + private String requestorName = ""; + private String service = "service1"; + + @Before + public void prepareForTest() throws Exception { + confDir = Files.createTempDir(); + confPath = new File(confDir, "sentry-site.xml"); + if (confPath.createNewFile()) { + FileOutputStream to = new FileOutputStream(confPath); + conf.writeXml(to); + to.close(); + } + requestorName = clientUgi.getShortUserName();//System.getProperty("user.name", ""); + Set<String> requestorUserGroupNames = Sets.newHashSet(ADMIN_GROUP); + setLocalGroupMapping(requestorName, requestorUserGroupNames); + // add ADMIN_USER for the after() in SentryServiceIntegrationBase + setLocalGroupMapping(ADMIN_USER, requestorUserGroupNames); + setLocalGroupMapping("dev", Sets.newHashSet("dev_group")); + setLocalGroupMapping("user", Sets.newHashSet("user_group")); + writePolicyFile(); + } + + @After + public void clearTestData() throws Exception { + FileUtils.deleteQuietly(confDir); + + // clear roles and privileges + Set<TSentryRole> tRoles = client.listAllRoles(requestorName, SOLR); + for (TSentryRole tRole : tRoles) { + String role = tRole.getRoleName(); + Set<TSentryPrivilege> privileges = client.listAllPrivilegesByRoleName( + requestorName, role, SOLR, service); + for (TSentryPrivilege privilege : privileges) { + client.revokePrivilege(requestorName, role, SOLR, privilege); + } + client.dropRole(requestorName, role, SOLR); + } + } + + @Test + public void testPermissionsMigrationFromSentrySvc_v1() throws Exception { + initializeSentryService(); + + String[] args = { "-s", "1.8.0", "-c", confPath.getAbsolutePath()}; + PermissionsMigrationToolSolr sentryTool = new PermissionsMigrationToolSolr(); + sentryTool.executeConfigTool(args); + + Map<String, Set<String>> groupMapping = new HashMap<String, Set<String>>(); + groupMapping.put("admin_role", Sets.newHashSet("admin_group")); + groupMapping.put("dev_role", Sets.newHashSet("dev_group")); + groupMapping.put("user_role", Sets.newHashSet("user_group")); + + Map<String, Set<String>> privilegeMapping = new HashMap<String, Set<String>>(); + privilegeMapping.put("admin_role", + Sets.newHashSet("admin=collections->action=*", "admin=cores->action=*")); + privilegeMapping.put("dev_role", + Sets.newHashSet("collection=*->action=*", "admin=collections->action=*", "admin=cores->action=*")); + privilegeMapping.put("user_role", + Sets.newHashSet("collection=foo->action=*")); + + verifySentryServiceState(groupMapping, privilegeMapping); + } + + @Test + public void testPermissionsMigrationFromSentryPolicyFile_v1() throws Exception { + Path policyFilePath = initializeSentryPolicyFile(); + Path outputFilePath = Paths.get(confDir.getAbsolutePath(), "sentry-provider_migrated.ini"); + + String[] args = { "-s", "1.8.0", "-p", policyFilePath.toFile().getAbsolutePath(), + "-o", outputFilePath.toFile().getAbsolutePath() }; + PermissionsMigrationToolSolr sentryTool = new PermissionsMigrationToolSolr(); + assertTrue(sentryTool.executeConfigTool(args)); + + Set<String> groups = new HashSet<>(); + groups.add("admin_group"); + groups.add("dev_group"); + groups.add("user_group"); + + Map<String, Set<String>> privilegeMapping = new HashMap<String, Set<String>>(); + privilegeMapping.put("admin_role", + Sets.newHashSet("admin=collections->action=*", "admin=cores->action=*")); + privilegeMapping.put("dev_role", + Sets.newHashSet("collection=*->action=*", "admin=collections->action=*", "admin=cores->action=*")); + privilegeMapping.put("user_role", + Sets.newHashSet("collection=foo->action=*")); + + verifySentryPolicyFile(groups, privilegeMapping, outputFilePath); + } + + @Test + // For permissions created with Sentry 2.x, no migration necessary + public void testPermissionsMigrationFromSentrySvc_v2() throws Exception { + initializeSentryService(); + + String[] args = { "-s", "2.0.0", "-c", confPath.getAbsolutePath()}; + PermissionsMigrationToolSolr sentryTool = new PermissionsMigrationToolSolr(); + sentryTool.executeConfigTool(args); + + Map<String, Set<String>> groupMapping = new HashMap<String, Set<String>>(); + groupMapping.put("admin_role", Sets.newHashSet("admin_group")); + groupMapping.put("dev_role", Sets.newHashSet("dev_group")); + groupMapping.put("user_role", Sets.newHashSet("user_group")); + + Map<String, Set<String>> privilegeMapping = new HashMap<String, Set<String>>(); + privilegeMapping.put("admin_role", + Sets.newHashSet("collection=admin->action=*")); + privilegeMapping.put("dev_role", + Sets.newHashSet("collection=*->action=*")); + privilegeMapping.put("user_role", + Sets.newHashSet("collection=foo->action=*")); + + verifySentryServiceState(groupMapping, privilegeMapping); + } + + @Test + // For permissions created with Sentry 2.x, no migration necessary + public void testPermissionsMigrationFromSentryPolicyFile_v2() throws Exception { + Path policyFilePath = initializeSentryPolicyFile(); + Path outputFilePath = Paths.get(confDir.getAbsolutePath(), "sentry-provider_migrated.ini"); + + String[] args = { "-s", "2.0.0", "-p", policyFilePath.toFile().getAbsolutePath(), + "-o", outputFilePath.toFile().getAbsolutePath() }; + PermissionsMigrationToolSolr sentryTool = new PermissionsMigrationToolSolr(); + assertTrue(sentryTool.executeConfigTool(args)); + + Set<String> groups = new HashSet<>(); + groups.add("admin_group"); + groups.add("dev_group"); + groups.add("user_group"); + + Map<String, Set<String>> privilegeMapping = new HashMap<String, Set<String>>(); + privilegeMapping.put("admin_role", + Sets.newHashSet("collection=admin->action=*")); + privilegeMapping.put("dev_role", + Sets.newHashSet("collection=*->action=*")); + privilegeMapping.put("user_role", + Sets.newHashSet("collection=foo->action=*")); + + verifySentryPolicyFile(groups, privilegeMapping, outputFilePath); + } + + @Test + public void testDryRunOption() throws Exception { + initializeSentryService(); + + String[] args = { "-s", "1.8.0", "-c", confPath.getAbsolutePath(), "--dry_run"}; + PermissionsMigrationToolSolr sentryTool = new PermissionsMigrationToolSolr(); + sentryTool.executeConfigTool(args); + + Map<String, Set<String>> groupMapping = new HashMap<String, Set<String>>(); + groupMapping.put("admin_role", Sets.newHashSet("admin_group")); + groupMapping.put("dev_role", Sets.newHashSet("dev_group")); + groupMapping.put("user_role", Sets.newHashSet("user_group")); + + // No change in the privileges + Map<String, Set<String>> privilegeMapping = new HashMap<String, Set<String>>(); + privilegeMapping.put("admin_role", + Sets.newHashSet("collection=admin->action=*")); + privilegeMapping.put("dev_role", + Sets.newHashSet("collection=*->action=*")); + privilegeMapping.put("user_role", + Sets.newHashSet("collection=foo->action=*")); + + verifySentryServiceState(groupMapping, privilegeMapping); + } + + @Test + public void testInvalidToolArguments() throws Exception { + PermissionsMigrationToolSolr sentryTool = new PermissionsMigrationToolSolr(); + + { + String[] args = { "-c", confPath.getAbsolutePath()}; + assertFalse("The execution should have failed due to missing source version", + sentryTool.executeConfigTool(args)); + } + + { + String[] args = { "-s", "1.8.0" }; + sentryTool.executeConfigTool(args); + assertFalse("The execution should have failed due to missing Sentry config file" + + " (or policy file) path", + sentryTool.executeConfigTool(args)); + } + + { + String[] args = { "-s", "1.8.0", "-p", "/test/path" }; + sentryTool.executeConfigTool(args); + assertFalse("The execution should have failed due to missing Sentry config output file path", + sentryTool.executeConfigTool(args)); + } + + { + String[] args = { "-s", "1.8.0", "-c", "/test/path1", "-p", "/test/path2" }; + sentryTool.executeConfigTool(args); + assertFalse("The execution should have failed due to providing both Sentry config file" + + " as well as policy file params", + sentryTool.executeConfigTool(args)); + } + } + + private void initializeSentryService() throws SentryUserException { + // Define an admin role + client.createRoleIfNotExist(requestorName, "admin_role", SOLR); + client.grantRoleToGroups(requestorName, "admin_role", SOLR, Sets.newHashSet("admin_group")); + + // Define a developer role + client.createRoleIfNotExist(requestorName, "dev_role", SOLR); + client.grantRoleToGroups(requestorName, "dev_role", SOLR, Sets.newHashSet("dev_group")); + + // Define a user role + client.createRoleIfNotExist(requestorName, "user_role", SOLR); + client.grantRoleToGroups(requestorName, "user_role", SOLR, Sets.newHashSet("user_group")); + + // Grant permissions + client.grantPrivilege(requestorName, "admin_role", SOLR, + new TSentryPrivilege(SOLR, "service1", + Arrays.asList(new TAuthorizable("collection", "admin")), "*")); + client.grantPrivilege(requestorName, "dev_role", SOLR, + new TSentryPrivilege(SOLR, "service1", + Arrays.asList(new TAuthorizable("collection", "*")), "*")); + client.grantPrivilege(requestorName, "user_role", SOLR, + new TSentryPrivilege(SOLR, "service1", + Arrays.asList(new TAuthorizable("collection", "foo")), "*")); + } + + private void verifySentryServiceState(Map<String, Set<String>> groupMapping, + Map<String, Set<String>> privilegeMapping) throws SentryUserException { + // check roles + Set<TSentryRole> tRoles = client.listAllRoles(requestorName, SOLR); + assertEquals("Unexpected number of roles", groupMapping.keySet().size(), tRoles.size()); + Set<String> roles = new HashSet<String>(); + for (TSentryRole tRole : tRoles) { + roles.add(tRole.getRoleName()); + } + + for (String expectedRole : groupMapping.keySet()) { + assertTrue("Didn't find expected role: " + expectedRole, roles.contains(expectedRole)); + } + + // check groups + for (TSentryRole tRole : tRoles) { + Set<String> expectedGroups = groupMapping.get(tRole.getRoleName()); + assertEquals("Group size doesn't match for role: " + tRole.getRoleName(), + expectedGroups.size(), tRole.getGroups().size()); + assertTrue("Group does not contain all expected members for role: " + tRole.getRoleName(), + tRole.getGroups().containsAll(expectedGroups)); + } + + // check privileges + GenericPrivilegeConverter convert = new GenericPrivilegeConverter(SOLR, service); + for (String role : roles) { + Set<TSentryPrivilege> privileges = client.listAllPrivilegesByRoleName( + requestorName, role, SOLR, service); + Set<String> expectedPrivileges = privilegeMapping.get(role); + assertEquals("Privilege set size doesn't match for role: " + role + " Actual permissions : " + privileges, + expectedPrivileges.size(), privileges.size()); + + Set<String> privilegeStrs = new HashSet<String>(); + for (TSentryPrivilege privilege : privileges) { + privilegeStrs.add(convert.toString(privilege).toLowerCase()); + } + + for (String expectedPrivilege : expectedPrivileges) { + assertTrue("Did not find expected privilege: " + expectedPrivilege + " in " + privilegeStrs, + privilegeStrs.contains(expectedPrivilege)); + } + } + } + + private Path initializeSentryPolicyFile() throws Exception { + PolicyFile file = new PolicyFile(); + + file.addRolesToGroup("admin_group", "admin_role"); + file.addRolesToGroup("dev_group", "dev_role"); + file.addRolesToGroup("user_group", "user_role"); + + file.addPermissionsToRole("admin_role", "collection=admin->action=*"); + file.addPermissionsToRole("dev_role", "collection=*->action=*"); + file.addPermissionsToRole("user_role", "collection=foo->action=*"); + + Path policyFilePath = Paths.get(confDir.getAbsolutePath(), "sentry-provider.ini"); + file.write(policyFilePath.toFile()); + + return policyFilePath; + } + + private void verifySentryPolicyFile (Set<String> groups, Map<String, Set<String>> privilegeMapping, + Path policyFilePath) throws IOException { + SimpleFileProviderBackend policyFileBackend = new SimpleFileProviderBackend(conf, + new org.apache.hadoop.fs.Path(policyFilePath.toUri())); + policyFileBackend.initialize(new ProviderBackendContext()); + Table<String, String, Set<String>> groupRolePrivilegeTable = + policyFileBackend.getGroupRolePrivilegeTable(); + + assertEquals(groups, groupRolePrivilegeTable.rowKeySet()); + assertEquals(privilegeMapping.keySet(), groupRolePrivilegeTable.columnKeySet()); + + for (String groupName : groupRolePrivilegeTable.rowKeySet()) { + for (String roleName : groupRolePrivilegeTable.columnKeySet()) { + if (groupRolePrivilegeTable.contains(groupName, roleName)) { + Set<String> privileges = groupRolePrivilegeTable.get(groupName, roleName); + assertEquals(privilegeMapping.get(roleName), privileges); + } + } + } + } +}
