blob: b04669128378345ae0fd4b1e6ef4459e65bc7f08 [file] [log] [blame]
// 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.impala.authorization;
import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTH_TO_LOCAL;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.hadoop.conf.Configuration;
import org.apache.hive.service.rpc.thrift.TGetColumnsReq;
import org.apache.hive.service.rpc.thrift.TGetCrossReferenceReq;
import org.apache.hive.service.rpc.thrift.TGetPrimaryKeysReq;
import org.apache.hive.service.rpc.thrift.TGetSchemasReq;
import org.apache.hive.service.rpc.thrift.TGetTablesReq;
import org.apache.impala.analysis.AnalysisContext;
import org.apache.impala.authorization.sentry.SentryAuthorizationConfig;
import org.apache.impala.authorization.sentry.SentryAuthorizationFactory;
import org.apache.impala.catalog.CatalogException;
import org.apache.impala.catalog.FeDb;
import org.apache.impala.catalog.Role;
import org.apache.impala.common.AnalysisException;
import org.apache.impala.common.FrontendTestBase;
import org.apache.impala.common.ImpalaException;
import org.apache.impala.common.InternalException;
import org.apache.impala.common.RuntimeEnv;
import org.apache.impala.testutil.TestSentryGroupMapper;
import org.apache.impala.testutil.TestSentryResourceAuthorizationProvider;
import org.apache.impala.service.Frontend;
import org.apache.impala.testutil.ImpaladTestCatalog;
import org.apache.impala.thrift.TMetadataOpRequest;
import org.apache.impala.thrift.TMetadataOpcode;
import org.apache.impala.thrift.TNetworkAddress;
import org.apache.impala.thrift.TPrincipalType;
import org.apache.impala.thrift.TPrivilege;
import org.apache.impala.thrift.TPrivilegeLevel;
import org.apache.impala.thrift.TPrivilegeScope;
import org.apache.impala.thrift.TResultSet;
import org.apache.impala.thrift.TSessionState;
import org.apache.impala.util.PatternMatcher;
import org.apache.sentry.provider.common.ResourceAuthorizationProvider;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
public class AuthorizationTest extends FrontendTestBase {
public static final User USER = new User(System.getProperty("user.name"));
// Tables in functional that the current user and 'test_user' have table- or
// column-level SELECT or INSERT permission. I.e. that should be returned by
// 'SHOW TABLES'.
private static final List<String> FUNCTIONAL_VISIBLE_TABLES = Lists.newArrayList(
"alltypes", "alltypesagg", "alltypeserror", "alltypessmall", "alltypestiny",
"child_table", "parent_table", "parent_table_2");
private static final SentryAuthorizationConfig AUTHZ_CONFIG =
SentryAuthorizationConfig.createHadoopGroupAuthConfig("server1",
System.getenv("IMPALA_HOME") + "/fe/src/test/resources/sentry-site.xml");
private static final ImpaladTestCatalog AUTHZ_CATALOG =
new ImpaladTestCatalog(new SentryAuthorizationFactory(AUTHZ_CONFIG));
private static final AuthorizationFactory AUTHZ_FACTORY =
new SentryAuthorizationFactory(AUTHZ_CONFIG);
private static final Frontend AUTHZ_FE;
static {
try {
AUTHZ_FE = new Frontend(AUTHZ_FACTORY, AUTHZ_CATALOG);
} catch (ImpalaException e) {
throw new RuntimeException(e);
}
}
private final AnalysisContext authzCtx;
public AuthorizationTest() throws ImpalaException {
authzCtx = createAnalysisCtx(AUTHZ_FACTORY, USER.getName());
setupImpalaCatalog(AUTHZ_CATALOG);
}
@BeforeClass
public static void setUp() throws ImpalaException {
RuntimeEnv.INSTANCE.setTestEnv(true);
}
@AfterClass
public static void cleanUp() throws ImpalaException {
RuntimeEnv.INSTANCE.reset();
}
private static void setupImpalaCatalog(ImpaladTestCatalog catalog)
throws ImpalaException {
// Create admin user
String role_ = "admin";
TPrivilege privilege = new TPrivilege(TPrivilegeLevel.ALL, TPrivilegeScope.SERVER,
false);
Role role = catalog.addRole(role_);
addRolePrivilege(catalog, privilege, role);
catalog.addRoleGrantGroup(role_, TestSentryGroupMapper.SERVER_ADMIN);
// Create test users
Set<String> groups = new HashSet<>(Arrays.asList(
USER.getName(),
TestSentryGroupMapper.AUTH_TO_LOCAL,
TestSentryGroupMapper.TEST_USER));
role_ = "testRole";
role = catalog.addRole(role_);
// Insert on functional.alltypes
privilege = new TPrivilege(TPrivilegeLevel.INSERT, TPrivilegeScope.TABLE, false);
privilege.setDb_name("functional");
privilege.setTable_name("alltypes");
addRolePrivilege(catalog, privilege, role);
// All on functional.[alltypesagg, alltypeserror, alltypestiny]
List<String> tables = Arrays.asList(
"alltypesagg",
"alltypeserror",
"alltypestiny");
for (String table : tables) {
privilege = new TPrivilege(TPrivilegeLevel.ALL, TPrivilegeScope.TABLE, false);
privilege.setDb_name("functional");
privilege.setTable_name(table);
addRolePrivilege(catalog, privilege, role);
}
// Columns [id, int_col, year] on functional.alltypessmall
List<String> columns = Arrays.asList("id", "int_col", "year");
for (String column : columns) {
privilege = new TPrivilege(TPrivilegeLevel.SELECT, TPrivilegeScope.COLUMN, false);
privilege.setDb_name("functional");
privilege.setTable_name("alltypessmall");
privilege.setColumn_name(column);
addRolePrivilege(catalog, privilege, role);
}
// Select on tpcds
privilege = new TPrivilege(TPrivilegeLevel.SELECT, TPrivilegeScope.DATABASE, false);
privilege.setDb_name("tpcds");
addRolePrivilege(catalog, privilege, role);
// Select on tpch
privilege = new TPrivilege(TPrivilegeLevel.SELECT, TPrivilegeScope.DATABASE, false);
privilege.setDb_name("tpch");
addRolePrivilege(catalog, privilege, role);
// Select on parent_table_2
privilege = new TPrivilege(TPrivilegeLevel.SELECT, TPrivilegeScope.TABLE, false);
privilege.setDb_name("functional");
privilege.setTable_name("parent_table_2");
addRolePrivilege(catalog, privilege, role);
// Add privilege on "year" for parent table.
privilege = new TPrivilege(TPrivilegeLevel.SELECT, TPrivilegeScope.COLUMN, false);
privilege.setDb_name("functional");
privilege.setTable_name("parent_table");
privilege.setColumn_name("year");
addRolePrivilege(catalog, privilege, role);
// Add SELECT privilege on child_table
privilege = new TPrivilege(TPrivilegeLevel.SELECT, TPrivilegeScope.TABLE, false);
privilege.setDb_name("functional");
privilege.setTable_name("child_table");
addRolePrivilege(catalog, privilege, role);
for (String group: groups) {
catalog.addRoleGrantGroup(role_, group);
}
}
private static void addRolePrivilege(ImpaladTestCatalog catalog, TPrivilege privilege,
Role role) throws CatalogException {
privilege.setServer_name("server1");
privilege.setPrincipal_type(TPrincipalType.ROLE);
privilege.setPrincipal_id(role.getId());
catalog.addRolePrivilege(role.getName(), privilege);
}
@After
public void TestTPCHCleanup() throws AuthorizationException, AnalysisException {
// Failure to cleanup TPCH can cause:
// TestDropDatabase(org.apache.impala.analysis.AuthorizationTest):
// Cannot drop non-empty database: tpch
if (catalog_.getDb("tpch").numFunctions() != 0) {
fail("Failed to clean up functions in tpch.");
}
}
@Test
public void TestShowDbResultsFiltered() throws ImpalaException {
// These are the only dbs that should show up because they are the only
// dbs the user has any permissions on.
List<String> expectedDbs = Lists.newArrayList("default", "functional", "tpcds",
"tpch");
List<? extends FeDb> dbs = AUTHZ_FE.getDbs(
PatternMatcher.createHivePatternMatcher("*"), USER);
assertEquals(expectedDbs, extractDbNames(dbs));
dbs = AUTHZ_FE.getDbs(PatternMatcher.MATCHER_MATCH_ALL, USER);
assertEquals(expectedDbs, extractDbNames(dbs));
dbs = AUTHZ_FE.getDbs(PatternMatcher.createHivePatternMatcher(null), USER);
assertEquals(expectedDbs, extractDbNames(dbs));
dbs = AUTHZ_FE.getDbs(PatternMatcher.MATCHER_MATCH_NONE, USER);
assertEquals(Collections.emptyList(), extractDbNames(dbs));
dbs = AUTHZ_FE.getDbs(PatternMatcher.createHivePatternMatcher(""), USER);
assertEquals(Collections.emptyList(), extractDbNames(dbs));
dbs = AUTHZ_FE.getDbs(PatternMatcher.createHivePatternMatcher("functional_rc"), USER);
assertEquals(Collections.emptyList(), extractDbNames(dbs));
dbs = AUTHZ_FE.getDbs(PatternMatcher.createHivePatternMatcher("tp*|de*"), USER);
expectedDbs = Lists.newArrayList("default", "tpcds", "tpch");
assertEquals(expectedDbs, extractDbNames(dbs));
}
private List<String> extractDbNames(List<? extends FeDb> dbs) {
List<String> names = Lists.newArrayListWithCapacity(dbs.size());
for (FeDb db: dbs) names.add(db.getName());
return names;
}
@Test
public void TestShowTableResultsFiltered() throws ImpalaException {
List<String> tables = AUTHZ_FE.getTableNames("functional",
PatternMatcher.createHivePatternMatcher("*"), USER);
Assert.assertEquals(FUNCTIONAL_VISIBLE_TABLES, tables);
tables = AUTHZ_FE.getTableNames("functional",
PatternMatcher.MATCHER_MATCH_ALL, USER);
Assert.assertEquals(FUNCTIONAL_VISIBLE_TABLES, tables);
tables = AUTHZ_FE.getTableNames("functional",
PatternMatcher.createHivePatternMatcher(null), USER);
Assert.assertEquals(FUNCTIONAL_VISIBLE_TABLES, tables);
tables = AUTHZ_FE.getTableNames("functional",
PatternMatcher.MATCHER_MATCH_NONE, USER);
Assert.assertEquals(Collections.emptyList(), tables);
tables = AUTHZ_FE.getTableNames("functional",
PatternMatcher.createHivePatternMatcher(""), USER);
Assert.assertEquals(Collections.emptyList(), tables);
tables = AUTHZ_FE.getTableNames("functional",
PatternMatcher.createHivePatternMatcher("alltypes*"), USER);
List<String> expectedTables = Lists.newArrayList(
"alltypes", "alltypesagg", "alltypeserror", "alltypessmall", "alltypestiny");
Assert.assertEquals(expectedTables, tables);
}
@Test
public void TestHs2GetTables() throws ImpalaException {
TMetadataOpRequest req = new TMetadataOpRequest();
req.setSession(createSessionState("default", USER));
req.opcode = TMetadataOpcode.GET_TABLES;
req.get_tables_req = new TGetTablesReq();
req.get_tables_req.setSchemaName("functional");
// Get all tables
req.get_tables_req.setTableName("%");
TResultSet resp = AUTHZ_FE.execHiveServer2MetadataOp(req);
assertEquals(FUNCTIONAL_VISIBLE_TABLES.size(), resp.rows.size());
for (int i = 0; i < resp.rows.size(); ++i) {
assertEquals(FUNCTIONAL_VISIBLE_TABLES.get(i),
resp.rows.get(i).colVals.get(2).string_val.toLowerCase());
}
// Pattern "" and null is the same as "%" to match all
req.get_tables_req.setTableName("");
resp = AUTHZ_FE.execHiveServer2MetadataOp(req);
assertEquals(FUNCTIONAL_VISIBLE_TABLES.size(), resp.rows.size());
for (int i = 0; i < resp.rows.size(); ++i) {
assertEquals(FUNCTIONAL_VISIBLE_TABLES.get(i),
resp.rows.get(i).colVals.get(2).string_val.toLowerCase());
}
req.get_tables_req.setTableName(null);
resp = AUTHZ_FE.execHiveServer2MetadataOp(req);
assertEquals(FUNCTIONAL_VISIBLE_TABLES.size(), resp.rows.size());
for (int i = 0; i < resp.rows.size(); ++i) {
assertEquals(FUNCTIONAL_VISIBLE_TABLES.get(i),
resp.rows.get(i).colVals.get(2).string_val.toLowerCase());
}
// Pattern ".*" matches all and "." is a wildcard that matches any single character
req.get_tables_req.setTableName(".*");
resp = AUTHZ_FE.execHiveServer2MetadataOp(req);
assertEquals(FUNCTIONAL_VISIBLE_TABLES.size(), resp.rows.size());
for (int i = 0; i < resp.rows.size(); ++i) {
assertEquals(FUNCTIONAL_VISIBLE_TABLES.get(i),
resp.rows.get(i).colVals.get(2).string_val.toLowerCase());
}
req.get_tables_req.setTableName("alltypesag.");
resp = AUTHZ_FE.execHiveServer2MetadataOp(req);
List<String> expectedTblNames = Lists.newArrayList("alltypesagg");
assertEquals(expectedTblNames.size(), resp.rows.size());
for (int i = 0; i < resp.rows.size(); ++i) {
assertEquals(expectedTblNames.get(i),
resp.rows.get(i).colVals.get(2).string_val.toLowerCase());
}
// "_" is a wildcard for a single character
req.get_tables_req.setTableName("alltypesag_");
resp = AUTHZ_FE.execHiveServer2MetadataOp(req);
expectedTblNames = Lists.newArrayList("alltypesagg");
assertEquals(expectedTblNames.size(), resp.rows.size());
for (int i = 0; i < resp.rows.size(); ++i) {
assertEquals(expectedTblNames.get(i),
resp.rows.get(i).colVals.get(2).string_val.toLowerCase());
}
// Get all tables of tpcds
final int numTpcdsTables = 24;
req.get_tables_req.setSchemaName("tpcds");
req.get_tables_req.setTableName("%");
resp = AUTHZ_FE.execHiveServer2MetadataOp(req);
assertEquals(numTpcdsTables, resp.rows.size());
}
@Test
public void TestHs2GetPrimaryKeys() throws ImpalaException {
// Only one of the pk columns has privileges. This should not return anything.
TMetadataOpRequest req = new TMetadataOpRequest();
req.setSession(createSessionState("default", USER));
req.opcode = TMetadataOpcode.GET_PRIMARY_KEYS;
req.get_primary_keys_req = new TGetPrimaryKeysReq();
req.get_primary_keys_req.setSchemaName("functional");
req.get_primary_keys_req.setTableName("parent_table");
TResultSet resp = AUTHZ_FE.execHiveServer2MetadataOp(req);
assertEquals(0, resp.rows.size());
// PK column has privileges, should return it.
req = new TMetadataOpRequest();
req.setSession(createSessionState("default", USER));
req.opcode = TMetadataOpcode.GET_PRIMARY_KEYS;
req.get_primary_keys_req = new TGetPrimaryKeysReq();
req.get_primary_keys_req.setSchemaName("functional");
req.get_primary_keys_req.setTableName("parent_table_2");
resp = AUTHZ_FE.execHiveServer2MetadataOp(req);
assertEquals(1, resp.rows.size());
}
@Test
public void TestHs2GetCrossReference() throws ImpalaException {
// On parent_table, only one of the pk columns has privileges for USER, hence the
// entire SQLPrimaryKey sequence will be filtered out.
TMetadataOpRequest req = new TMetadataOpRequest();
req.setSession(createSessionState("default", USER));
req.opcode = TMetadataOpcode.GET_CROSS_REFERENCE;
req.get_cross_reference_req = new TGetCrossReferenceReq();
req.get_cross_reference_req.setForeignSchemaName("functional");
req.get_cross_reference_req.setForeignTableName("child_table");
TResultSet resp = AUTHZ_FE.execHiveServer2MetadataOp(req);
// Returns just 1 SQLForeignKeys instead of 3.
assertEquals(1, resp.rows.size());
}
@Test
public void TestHs2GetSchema() throws ImpalaException {
TMetadataOpRequest req = new TMetadataOpRequest();
req.setSession(createSessionState("default", USER));
req.opcode = TMetadataOpcode.GET_SCHEMAS;
req.get_schemas_req = new TGetSchemasReq();
// Get all schema (databases).
req.get_schemas_req.setSchemaName("%");
TResultSet resp = AUTHZ_FE.execHiveServer2MetadataOp(req);
List<String> expectedDbs = Lists.newArrayList("default", "functional", "tpcds",
"tpch");
assertEquals(expectedDbs.size(), resp.rows.size());
for (int i = 0; i < resp.rows.size(); ++i) {
assertEquals(expectedDbs.get(i),
resp.rows.get(i).colVals.get(0).string_val.toLowerCase());
}
// Pattern "" and null is the same as "%" to match all
req.get_schemas_req.setSchemaName("");
resp = AUTHZ_FE.execHiveServer2MetadataOp(req);
assertEquals(expectedDbs.size(), resp.rows.size());
for (int i = 0; i < resp.rows.size(); ++i) {
assertEquals(expectedDbs.get(i),
resp.rows.get(i).colVals.get(0).string_val.toLowerCase());
}
req.get_schemas_req.setSchemaName(null);
resp = AUTHZ_FE.execHiveServer2MetadataOp(req);
assertEquals(expectedDbs.size(), resp.rows.size());
for (int i = 0; i < resp.rows.size(); ++i) {
assertEquals(expectedDbs.get(i),
resp.rows.get(i).colVals.get(0).string_val.toLowerCase());
}
// Pattern ".*" matches all and "." is a wildcard that matches any single character
req.get_schemas_req.setSchemaName(".*");
resp = AUTHZ_FE.execHiveServer2MetadataOp(req);
assertEquals(expectedDbs.size(), resp.rows.size());
for (int i = 0; i < resp.rows.size(); ++i) {
assertEquals(expectedDbs.get(i),
resp.rows.get(i).colVals.get(0).string_val.toLowerCase());
}
req.get_schemas_req.setSchemaName("defaul.");
resp = AUTHZ_FE.execHiveServer2MetadataOp(req);
expectedDbs = Lists.newArrayList("default");
assertEquals(expectedDbs.size(), resp.rows.size());
for (int i = 0; i < resp.rows.size(); ++i) {
assertEquals(expectedDbs.get(i),
resp.rows.get(i).colVals.get(0).string_val.toLowerCase());
}
// "_" is a wildcard that matches any single character
req.get_schemas_req.setSchemaName("defaul_");
resp = AUTHZ_FE.execHiveServer2MetadataOp(req);
expectedDbs = Lists.newArrayList("default");
assertEquals(expectedDbs.size(), resp.rows.size());
for (int i = 0; i < resp.rows.size(); ++i) {
assertEquals(expectedDbs.get(i),
resp.rows.get(i).colVals.get(0).string_val.toLowerCase());
}
}
@Test
public void TestHs2GetColumns() throws ImpalaException {
// It should return one column: alltypes.string_col.
TMetadataOpRequest req = new TMetadataOpRequest();
req.opcode = TMetadataOpcode.GET_COLUMNS;
req.setSession(createSessionState("default", USER));
req.get_columns_req = new TGetColumnsReq();
req.get_columns_req.setSchemaName("functional");
req.get_columns_req.setTableName("alltypes");
req.get_columns_req.setColumnName("stri%");
TResultSet resp = AUTHZ_FE.execHiveServer2MetadataOp(req);
assertEquals(1, resp.rows.size());
// User does not have permission to access the table, no results should be returned.
req.get_columns_req.setTableName("alltypesnopart");
resp = AUTHZ_FE.execHiveServer2MetadataOp(req);
assertEquals(0, resp.rows.size());
// User does not have permission to access db or table, no results should be
// returned.
req.get_columns_req.setSchemaName("functional_seq_gzip");
req.get_columns_req.setTableName("alltypes");
resp = AUTHZ_FE.execHiveServer2MetadataOp(req);
assertEquals(0, resp.rows.size());
// User has SELECT privileges on all table columns but no table-level privileges.
req.get_columns_req.setSchemaName("functional");
req.get_columns_req.setTableName("alltypestiny");
req.get_columns_req.setColumnName("%");
resp = AUTHZ_FE.execHiveServer2MetadataOp(req);
assertEquals(13, resp.rows.size());
// User has SELECT privileges on some columns but no table-level privileges.
req.get_columns_req.setSchemaName("functional");
req.get_columns_req.setTableName("alltypessmall");
req.get_columns_req.setColumnName("%");
resp = AUTHZ_FE.execHiveServer2MetadataOp(req);
String[] expectedColumnNames = {"id", "int_col", "year"};
assertEquals(expectedColumnNames.length, resp.rows.size());
for (int i = 0; i < resp.rows.size(); ++i) {
assertEquals(expectedColumnNames[i],
resp.rows.get(i).colVals.get(3).string_val.toLowerCase());
}
}
@Test
public void TestShortUsernameUsed() throws Exception {
// Different long variations of the same username.
List<User> users = Lists.newArrayList(
// Historical note: Hadoop 2 accepts kerberos names missing a realm, but insists
// on having a terminating '@' even when the default realm is intended. Hadoop 3
// now has more normal name conventions, where to specify the default realm,
// everything after and including the '@' character is omitted.
new User(USER.getName() + "/abc.host.com"),
new User(USER.getName() + "/abc.host.com@REAL.COM"),
new User(USER.getName() + "@REAL.COM"));
for (User user: users) {
AnalysisContext ctx = createAnalysisCtx(
new SentryAuthorizationFactory(AUTHZ_CONFIG), user.getName());
// Can select from table that user has privileges on.
AuthzOk(ctx, "select * from functional.alltypesagg");
// Unqualified table name.
AuthzError(ctx, "select * from alltypes",
"User '%s' does not have privileges to execute 'SELECT' on: default.alltypes");
}
// If the first character is '/', the short username should be the same as
// the full username.
assertEquals("/" + USER.getName(), new User("/" + USER.getName()).getShortName());
}
@Test
public void TestShortUsernameWithAuthToLocal() throws ImpalaException {
// We load the auth_to_local configs from core-site.xml in the test mode even if
// kerberos is disabled. We use the following configuration for tests
// <property>
// <name>hadoop.security.auth_to_local</name>
// <value>RULE:[2:$1@$0](authtest@REALM.COM)s/(.*)@REALM.COM/auth_to_local_user/
// RULE:[1:$1]
// RULE:[2:$1]
// DEFAULT
// </value>
// </property>
SentryAuthorizationConfig authzConfig = new SentryAuthorizationConfig("server1",
AuthorizationTest.AUTHZ_CONFIG.getSentryConfig().getConfigFile(),
TestSentryResourceAuthorizationProvider.class.getName());
SentryAuthorizationFactory authzFactory = new SentryAuthorizationFactory(
authzConfig);
try (ImpaladTestCatalog catalog = new ImpaladTestCatalog(authzFactory)) {
setupImpalaCatalog(catalog);
// This test relies on the auth_to_local rule -
// "RULE:[2:$1@$0](authtest@REALM.COM)s/(.*)@REALM.COM/auth_to_local_user/"
// which converts any principal of type authtest/<hostname>@REALM.COM to user
// auth_to_local_user. We have a sentry privilege in place that grants 'SELECT'
// privilege on tpcds.* to user auth_to_local_user. To test the integration, we try
// running the query with authtest/hostname@REALM.COM user which is converted to
// user 'auth_to_local_user' and the authz should pass.
User.setRulesForTesting(
new Configuration().get(HADOOP_SECURITY_AUTH_TO_LOCAL, "DEFAULT"));
User user = new User("authtest/hostname@REALM.COM");
AnalysisContext ctx = createAnalysisCtx(authzFactory, user.getName());
Frontend fe = new Frontend(authzFactory, catalog);
// Can select from table that user has privileges on.
AuthzOk(fe, ctx, "select * from tpcds.customer");
// Does not have privileges to execute a query
AuthzError(fe, ctx, "select * from functional.alltypes",
"User '%s' does not have privileges to execute 'SELECT' on: " +
"functional.alltypes");
// Unit tests for User#getShortName()
// Different auth_to_local rules to apply on the username.
List<String> rules = Lists.newArrayList(
// Expects user@REALM and returns 'usera' (appends a in the end)
"RULE:[1:$1@$0](.*@REALM.COM)s/(.*)@REALM.COM/$1a/g",
// Same as above but expects user/host@REALM
"RULE:[2:$1@$0](.*@REALM.COM)s/(.*)@REALM.COM/$1a/g");
// Map from the user to the expected getShortName() output after applying
// the corresponding rule from 'rules' list.
List<Map.Entry<User, String>> users = Lists.newArrayList(
Maps.immutableEntry(new User(USER.getName() + "@REALM.COM"), USER.getName()
+ "a"),
Maps.immutableEntry(new User(USER.getName() + "/abc.host.com@REALM.COM"),
USER.getName() + "a"));
for (int idx = 0; idx < users.size(); ++idx) {
Map.Entry<User, String> userObj = users.get(idx);
assertEquals(userObj.getKey().getShortNameForTesting(rules.get(idx)),
userObj.getValue());
}
// Test malformed rules. RuleParser throws an IllegalArgumentException.
String malformedRule = "{((()";
try {
user.getShortNameForTesting(malformedRule);
} catch (IllegalArgumentException e) {
Assert.assertTrue(e.getMessage().contains("Invalid rule"));
return;
}
Assert.fail("No exception caught while using getShortName() on a malformed rule");
}
}
@Test
public void TestConfigValidation() throws InternalException {
String sentryConfig = AUTHZ_CONFIG.getSentryConfig().getConfigFile();
// Valid configs pass validation.
SentryAuthorizationConfig config =
SentryAuthorizationConfig.createHadoopGroupAuthConfig("server1", sentryConfig);
Assert.assertTrue(config.isEnabled());
config = SentryAuthorizationConfig.createHadoopGroupAuthConfig("server1",
sentryConfig);
Assert.assertTrue(config.isEnabled());
// Invalid configs
// No sentry configuration file.
try {
config = SentryAuthorizationConfig.createHadoopGroupAuthConfig("server1", null);
Assert.assertTrue(config.isEnabled());
} catch (Exception e) {
Assert.assertEquals("A valid path to a sentry-site.xml config " +
"file must be set using --sentry_config to enable authorization.",
e.getMessage());
}
// Empty / null server name.
try {
config = SentryAuthorizationConfig.createHadoopGroupAuthConfig("", sentryConfig);
Assert.assertTrue(config.isEnabled());
fail("Expected configuration to fail.");
} catch (IllegalArgumentException e) {
Assert.assertEquals(
"Authorization is enabled but the server name is null or empty. Set the " +
"server name using the impalad --server_name flag.",
e.getMessage());
}
try {
config = SentryAuthorizationConfig.createHadoopGroupAuthConfig(null, sentryConfig);
Assert.assertTrue(config.isEnabled());
fail("Expected configuration to fail.");
} catch (IllegalArgumentException e) {
Assert.assertEquals(
"Authorization is enabled but the server name is null or empty. Set the " +
"server name using the impalad --server_name flag.",
e.getMessage());
}
// Sentry config file does not exist.
try {
config = SentryAuthorizationConfig.createHadoopGroupAuthConfig("server1",
"/path/does/not/exist.xml");
Assert.assertTrue(config.isEnabled());
fail("Expected configuration to fail.");
} catch (Exception e) {
Assert.assertEquals(
"Sentry configuration file does not exist: \"/path/does/not/exist.xml\"",
e.getMessage());
}
// Invalid ResourcePolicyProvider class name.
try {
config = new SentryAuthorizationConfig("server1", sentryConfig,
"ClassDoesNotExist");
Assert.assertTrue(config.isEnabled());
fail("Expected configuration to fail.");
} catch (IllegalArgumentException e) {
Assert.assertEquals(
"The authorization policy provider class 'ClassDoesNotExist' was not found.",
e.getMessage());
}
// Valid class name, but class is not derived from ResourcePolicyProvider
try {
config = new SentryAuthorizationConfig("server1", sentryConfig,
this.getClass().getName());
Assert.assertTrue(config.isEnabled()); fail("Expected configuration to fail.");
} catch (IllegalArgumentException e) {
Assert.assertEquals(String.format("The authorization policy " +
"provider class '%s' must be a subclass of '%s'.", this.getClass().getName(),
ResourceAuthorizationProvider.class.getName()),
e.getMessage());
}
// Config validations skipped if authorization disabled
config = new SentryAuthorizationConfig("", "", "");
Assert.assertFalse(config.isEnabled());
config = new SentryAuthorizationConfig(null, "", null);
Assert.assertFalse(config.isEnabled());
config = new SentryAuthorizationConfig(null, null, null);
Assert.assertFalse(config.isEnabled());
}
@Test
public void TestLocalGroupPolicyProvider() throws ImpalaException {
// Use an authorization configuration that uses the
// CustomClusterResourceAuthorizationProvider.
SentryAuthorizationConfig authzConfig = new SentryAuthorizationConfig("server1",
AuthorizationTest.AUTHZ_CONFIG.getSentryConfig().getConfigFile(),
TestSentryResourceAuthorizationProvider.class.getName());
SentryAuthorizationFactory authzFactory = new SentryAuthorizationFactory(authzConfig);
try (ImpaladTestCatalog catalog = new ImpaladTestCatalog(authzFactory)) {
setupImpalaCatalog(catalog);
// Create an analysis context + FE with the test user
// (as defined in the policy file)
User user = new User("test_user");
AnalysisContext ctx = createAnalysisCtx(authzFactory, user.getName());
Frontend fe = new Frontend(authzFactory, catalog);
// Can select from table that user has privileges on.
AuthzOk(fe, ctx, "select * from functional.alltypesagg");
// Does not have privileges to execute a query
AuthzError(fe, ctx, "select * from functional.alltypes",
"User '%s' does not have privileges to execute 'SELECT' on: " +
"functional.alltypes");
// Verify with the admin user
user = new User("admin_user");
ctx = createAnalysisCtx(authzFactory, user.getName());
fe = new Frontend(authzFactory, catalog);
// Admin user should have privileges to do anything
AuthzOk(fe, ctx, "select * from functional.alltypesagg");
AuthzOk(fe, ctx, "select * from functional.alltypes");
AuthzOk(fe, ctx, "invalidate metadata");
AuthzOk(fe, ctx, "create external table tpch.kudu_tbl stored as kudu " +
"TBLPROPERTIES ('kudu.master_addresses'='127.0.0.1', 'kudu.table_name'='tbl')");
}
}
private void TestWithIncorrectConfig(AuthorizationConfig authzConfig, User user)
throws ImpalaException {
SentryAuthorizationFactory authzFactory = new SentryAuthorizationFactory(authzConfig);
Frontend fe = new Frontend(authzFactory, AUTHZ_CATALOG);
AnalysisContext ctx = createAnalysisCtx(authzFactory, user.getName());
AuthzError(fe, ctx, "select * from functional.alltypesagg",
"User '%s' does not have privileges to execute 'SELECT' on: " +
"functional.alltypesagg");
AuthzError(fe, ctx, "drop table tpch.lineitem",
"User '%s' does not have privileges to execute 'DROP' on: tpch.lineitem");
AuthzError(fe, ctx, "show tables in functional",
"User '%s' does not have privileges to access: functional.*");
}
private void AuthzOk(String stmt) throws ImpalaException {
AuthzOk(authzCtx, stmt);
}
private void AuthzOk(AnalysisContext context, String stmt) throws ImpalaException {
AuthzOk(AUTHZ_FE, context, stmt);
}
private void AuthzOk(Frontend fe, AnalysisContext context, String stmt)
throws ImpalaException {
parseAndAnalyze(stmt, context, fe);
}
/**
* Verifies that a given statement fails authorization and the expected error
* string matches.
*/
private void AuthzError(String stmt, String expectedErrorString)
throws ImpalaException {
AuthzError(authzCtx, stmt, expectedErrorString);
}
private void AuthzError(AnalysisContext ctx, String stmt, String expectedErrorString)
throws ImpalaException {
AuthzError(AUTHZ_FE, ctx, stmt, expectedErrorString);
}
private void AuthzError(Frontend fe, AnalysisContext ctx,
String stmt, String expectedErrorString)
throws ImpalaException {
Preconditions.checkNotNull(expectedErrorString);
try {
parseAndAnalyze(stmt, ctx, fe);
} catch (AuthorizationException e) {
// Insert the username into the error.
expectedErrorString = String.format(expectedErrorString, ctx.getUser());
String errorString = e.getMessage();
Assert.assertTrue(
"got error:\n" + errorString + "\nexpected:\n" + expectedErrorString,
errorString.startsWith(expectedErrorString));
return;
}
fail("Stmt didn't result in authorization error: " + stmt);
}
private static TSessionState createSessionState(String defaultDb, User user) {
return new TSessionState(null, null,
defaultDb, user.getName(), new TNetworkAddress("", 0));
}
}