| /* |
| * 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.drill.exec.impersonation; |
| |
| import org.apache.drill.categories.SecurityTest; |
| import org.apache.drill.categories.SlowTest; |
| import org.apache.drill.common.config.DrillProperties; |
| import org.apache.drill.exec.ExecConstants; |
| import org.apache.drill.exec.dotdrill.DotDrillType; |
| import org.apache.drill.exec.proto.UserBitShared; |
| import org.apache.drill.exec.store.dfs.WorkspaceConfig; |
| import org.apache.drill.shaded.guava.com.google.common.collect.Maps; |
| import org.apache.drill.test.ClientFixture; |
| import org.apache.drill.test.ClusterFixtureBuilder; |
| import org.apache.hadoop.fs.FileStatus; |
| import org.apache.hadoop.fs.Path; |
| import org.apache.hadoop.fs.permission.FsPermission; |
| import org.junit.Test; |
| import org.junit.experimental.categories.Category; |
| |
| import java.util.Map; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.apache.drill.exec.rpc.user.security.testing.UserAuthenticatorTestImpl.PROCESS_USER; |
| import static org.apache.drill.exec.rpc.user.security.testing.UserAuthenticatorTestImpl.PROCESS_USER_PASSWORD; |
| import static org.apache.drill.exec.rpc.user.security.testing.UserAuthenticatorTestImpl.TYPE; |
| |
| @Category({SlowTest.class, SecurityTest.class}) |
| public class TestInboundImpersonation extends BaseTestImpersonation { |
| |
| public static final String OWNER = org1Users[0]; |
| public static final String OWNER_PASSWORD = "owner"; |
| |
| public static final String TARGET_NAME = org1Users[1]; |
| public static final String TARGET_PASSWORD = "target"; |
| |
| public static final String DATA_GROUP = org1Groups[0]; |
| |
| public static final String PROXY_NAME = org1Users[2]; |
| public static final String PROXY_PASSWORD = "proxy"; |
| |
| // Because cluster fixture save client properties even client closed, |
| // Use a dedicated cluster fixture for each test case so that the client fixture has clean properties. |
| @Test |
| public void selectChainedView() throws Exception { |
| startMiniDfsCluster(TestInboundImpersonation.class.getSimpleName()); |
| ClusterFixtureBuilder builder = new ClusterFixtureBuilder(dirTestWatcher) |
| .configProperty(ExecConstants.USER_AUTHENTICATION_ENABLED, true) |
| .configProperty(ExecConstants.USER_AUTHENTICATOR_IMPL, TYPE) |
| .configProperty(ExecConstants.IMPERSONATION_ENABLED, true) |
| .configClientProperty(DrillProperties.USER, PROXY_NAME) |
| .configClientProperty(DrillProperties.PASSWORD, PROXY_PASSWORD); |
| |
| startCluster(builder); |
| addMiniDfsBasedStorage(createTestWorkspaces()); |
| createTestData(); |
| // Authorize PROXY_NAME to impersonate TARGET_NAME |
| try (ClientFixture adminClient = cluster.client(PROCESS_USER, PROCESS_USER_PASSWORD)) { |
| adminClient.alterSystem( |
| ExecConstants.IMPERSONATION_POLICIES_KEY, |
| "[ { proxy_principals : { users: [\"" + PROXY_NAME + "\" ] }," |
| + "target_principals : { users : [\"" + TARGET_NAME + "\"] } } ]" |
| ); |
| |
| // Connect as PROXY_NAME and query for IMPERSONATION_TARGET |
| // data belongs to OWNER, however a view is shared with IMPERSONATION_TARGET |
| ClientFixture.ClientBuilder clientBuilder = cluster.clientBuilder() |
| .property(DrillProperties.USER, PROXY_NAME) |
| .property(DrillProperties.PASSWORD, PROXY_PASSWORD) |
| .property(DrillProperties.IMPERSONATION_TARGET, TARGET_NAME); |
| try (ClientFixture client = clientBuilder.build()) { |
| client.testBuilder() |
| .sqlQuery("SELECT * FROM %s.u0_lineitem ORDER BY l_orderkey LIMIT 1", getWSSchema(OWNER)) |
| .ordered() |
| .baselineColumns("l_orderkey", "l_partkey") |
| .baselineValues(1, 1552) |
| .go(); |
| } |
| adminClient.resetSystem(ExecConstants.IMPERSONATION_POLICIES_KEY); |
| } |
| } |
| |
| @Test(expected = IllegalStateException.class) |
| // PERMISSION ERROR: Proxy user 'user2_1' is not authorized to impersonate target user 'user0_2'. |
| public void unauthorizedTarget() throws Exception { |
| final String unauthorizedTarget = org2Users[0]; |
| ClusterFixtureBuilder builder = new ClusterFixtureBuilder(dirTestWatcher) |
| .configProperty(ExecConstants.USER_AUTHENTICATION_ENABLED, true) |
| .configProperty(ExecConstants.USER_AUTHENTICATOR_IMPL, TYPE) |
| .configProperty(ExecConstants.IMPERSONATION_ENABLED, true) |
| .configClientProperty(DrillProperties.USER, PROXY_NAME) |
| .configClientProperty(DrillProperties.PASSWORD, PROXY_PASSWORD) |
| .configClientProperty(DrillProperties.IMPERSONATION_TARGET, unauthorizedTarget); |
| startCluster(builder); |
| } |
| |
| @Test |
| public void invalidPolicy() throws Exception { |
| ClusterFixtureBuilder builder = new ClusterFixtureBuilder(dirTestWatcher) |
| .configProperty(ExecConstants.USER_AUTHENTICATION_ENABLED, true) |
| .configProperty(ExecConstants.USER_AUTHENTICATOR_IMPL, TYPE) |
| .configProperty(ExecConstants.IMPERSONATION_ENABLED, true) |
| .configClientProperty(DrillProperties.USER, PROCESS_USER) |
| .configClientProperty(DrillProperties.PASSWORD, PROCESS_USER_PASSWORD); |
| startCluster(builder); |
| |
| client.queryBuilder() |
| .sql("ALTER SYSTEM SET `%s`='%s'", ExecConstants.IMPERSONATION_POLICIES_KEY, |
| "[ invalid json ]") |
| .userExceptionMatcher() |
| .expectedType(UserBitShared.DrillPBError.ErrorType.VALIDATION) |
| .include("Invalid impersonation policies.") |
| .match(); |
| } |
| |
| @Test |
| public void invalidProxy() throws Exception { |
| ClusterFixtureBuilder builder = new ClusterFixtureBuilder(dirTestWatcher) |
| .configProperty(ExecConstants.USER_AUTHENTICATION_ENABLED, true) |
| .configProperty(ExecConstants.USER_AUTHENTICATOR_IMPL, TYPE) |
| .configProperty(ExecConstants.IMPERSONATION_ENABLED, true) |
| .configClientProperty(DrillProperties.USER, PROCESS_USER) |
| .configClientProperty(DrillProperties.PASSWORD, PROCESS_USER_PASSWORD); |
| startCluster(builder); |
| |
| client.queryBuilder() |
| .sql("ALTER SYSTEM SET `%s`='%s'", ExecConstants.IMPERSONATION_POLICIES_KEY, |
| "[ { proxy_principals : { users: [\"*\" ] }," + "target_principals : { users : [\"" + TARGET_NAME + "\"] } } ]") |
| .userExceptionMatcher() |
| .expectedType(UserBitShared.DrillPBError.ErrorType.VALIDATION) |
| .include("Proxy principals cannot have a wildcard entry.") |
| .match(); |
| } |
| |
| private static Map<String, WorkspaceConfig> createTestWorkspaces() throws Exception { |
| Map<String, WorkspaceConfig> workspaces = Maps.newHashMap(); |
| createAndAddWorkspace(OWNER, getUserHome(OWNER), (short) 0755, OWNER, DATA_GROUP, workspaces); |
| createAndAddWorkspace(PROXY_NAME, getUserHome(PROXY_NAME), (short) 0755, PROXY_NAME, DATA_GROUP, |
| workspaces); |
| return workspaces; |
| } |
| |
| private static void createTestData() throws Exception { |
| // Create table accessible only by OWNER |
| final String tableName = "lineitem"; |
| try (ClientFixture client = cluster.client(OWNER, OWNER_PASSWORD)) { |
| client.run("USE " + getWSSchema(OWNER)); |
| client.run("CREATE TABLE %s as SELECT * FROM cp.`tpch/%s.parquet`", tableName, tableName); |
| |
| // Change the ownership and permissions manually. |
| // Currently there is no option to specify the default permissions and ownership for new tables. |
| final Path tablePath = new Path(getUserHome(OWNER), tableName); |
| fs.setOwner(tablePath, OWNER, DATA_GROUP); |
| fs.setPermission(tablePath, new FsPermission((short) 0700)); |
| |
| // Create a view on top of lineitem table; allow IMPERSONATION_TARGET to read the view |
| // /user/user0_1 u0_lineitem 750 user0_1:group0_1 |
| final String viewName = "u0_lineitem"; |
| client.alterSession(ExecConstants.NEW_VIEW_DEFAULT_PERMS_KEY, "0750"); |
| client.run("CREATE VIEW %s.%s AS SELECT l_orderkey, l_partkey FROM %s.%s", |
| getWSSchema(OWNER), viewName, getWSSchema(OWNER), "lineitem"); |
| // Verify the view file created has the expected permissions and ownership |
| final Path viewFilePath = new Path(getUserHome(OWNER), viewName + DotDrillType.VIEW.getEnding()); |
| final FileStatus status = fs.getFileStatus(viewFilePath); |
| assertEquals(org1Groups[0], status.getGroup()); |
| assertEquals(OWNER, status.getOwner()); |
| assertEquals(0750, status.getPermission().toShort()); |
| } |
| } |
| } |