Skip to content

Commit f4c9685

Browse files
committed
Add test cases to reproduce HiveAccessControlException
1 parent 6b5febc commit f4c9685

17 files changed

+701
-11
lines changed

iceberg/iceberg-catalog/src/test/java/org/apache/iceberg/hive/HiveMetastoreExtension.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public void beforeAll(ExtensionContext extensionContext) throws Exception {
4949
}
5050
}
5151

52-
metastore.start(hiveConfWithOverrides, 5, true);
52+
metastore.start(hiveConfWithOverrides, 20, true);
5353
metastoreClient = new HiveMetaStoreClient(hiveConfWithOverrides);
5454
if (null != databaseName) {
5555
String dbPath = metastore.getDatabasePath(databaseName);
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.iceberg.hive;
21+
22+
import java.util.List;
23+
import org.apache.hadoop.hive.conf.HiveConf;
24+
import org.apache.hadoop.hive.ql.security.HiveAuthenticationProvider;
25+
import org.apache.hadoop.hive.ql.security.authorization.plugin.AbstractHiveAuthorizer;
26+
import org.apache.hadoop.hive.ql.security.authorization.plugin.HiveAccessControlException;
27+
import org.apache.hadoop.hive.ql.security.authorization.plugin.HiveAuthzContext;
28+
import org.apache.hadoop.hive.ql.security.authorization.plugin.HiveOperationType;
29+
import org.apache.hadoop.hive.ql.security.authorization.plugin.HivePrincipal;
30+
import org.apache.hadoop.hive.ql.security.authorization.plugin.HivePrivilege;
31+
import org.apache.hadoop.hive.ql.security.authorization.plugin.HivePrivilegeInfo;
32+
import org.apache.hadoop.hive.ql.security.authorization.plugin.HivePrivilegeObject;
33+
import org.apache.hadoop.hive.ql.security.authorization.plugin.HiveRoleGrant;
34+
import org.slf4j.Logger;
35+
import org.slf4j.LoggerFactory;
36+
37+
public class MockHiveAuthorizer extends AbstractHiveAuthorizer {
38+
public static final String PERMISSION_TEST_USER = "permission_test_user";
39+
private static final Logger LOG = LoggerFactory.getLogger(MockHiveAuthorizer.class);
40+
41+
private final HiveAuthenticationProvider authenticator;
42+
43+
public MockHiveAuthorizer(HiveAuthenticationProvider authenticator) {
44+
this.authenticator = authenticator;
45+
}
46+
47+
@Override
48+
public VERSION getVersion() {
49+
return null;
50+
}
51+
52+
@Override
53+
public void grantPrivileges(List<HivePrincipal> hivePrincipals, List<HivePrivilege> hivePrivileges,
54+
HivePrivilegeObject hivePrivObject, HivePrincipal grantorPrincipal, boolean grantOption) {
55+
// NOP
56+
}
57+
58+
@Override
59+
public void revokePrivileges(List<HivePrincipal> hivePrincipals, List<HivePrivilege> hivePrivileges,
60+
HivePrivilegeObject hivePrivObject, HivePrincipal grantorPrincipal, boolean grantOption) {
61+
// NOP
62+
}
63+
64+
@Override
65+
public void createRole(String roleName, HivePrincipal adminGrantor) {
66+
// NOP
67+
}
68+
69+
@Override
70+
public void dropRole(String roleName) {
71+
// NOP
72+
}
73+
74+
@Override
75+
public List<HiveRoleGrant> getPrincipalGrantInfoForRole(String roleName) {
76+
return List.of();
77+
}
78+
79+
@Override
80+
public List<HiveRoleGrant> getRoleGrantInfoForPrincipal(HivePrincipal principal) {
81+
return List.of();
82+
}
83+
84+
@Override
85+
public void grantRole(List<HivePrincipal> hivePrincipals, List<String> roles, boolean grantOption,
86+
HivePrincipal grantorPrinc) {
87+
// NOP
88+
}
89+
90+
@Override
91+
public void revokeRole(List<HivePrincipal> hivePrincipals, List<String> roles, boolean grantOption,
92+
HivePrincipal grantorPrinc) {
93+
// NOP
94+
}
95+
96+
@Override
97+
public void checkPrivileges(HiveOperationType hiveOpType, List<HivePrivilegeObject> inputsHObjs,
98+
List<HivePrivilegeObject> outputHObjs, HiveAuthzContext context) throws HiveAccessControlException {
99+
LOG.info("Checking privileges. User={}, Operation={}, inputs={}, outputs={}", authenticator.getUserName(),
100+
hiveOpType, inputsHObjs, outputHObjs);
101+
if (PERMISSION_TEST_USER.equals(authenticator.getUserName())) {
102+
throw new HiveAccessControlException(String.format("Unauthorized. Operation=%s, inputs=%s, outputs=%s",
103+
hiveOpType, inputsHObjs, outputHObjs));
104+
}
105+
}
106+
107+
@Override
108+
public List<HivePrivilegeObject> filterListCmdObjects(List<HivePrivilegeObject> listObjs, HiveAuthzContext context) {
109+
return List.of();
110+
}
111+
112+
@Override
113+
public List<String> getAllRoles() {
114+
return List.of();
115+
}
116+
117+
@Override
118+
public List<HivePrivilegeInfo> showPrivileges(HivePrincipal principal, HivePrivilegeObject privObj) {
119+
return List.of();
120+
}
121+
122+
@Override
123+
public void setCurrentRole(String roleName) {
124+
// NOP
125+
}
126+
127+
@Override
128+
public List<String> getCurrentRoleNames() {
129+
return List.of();
130+
}
131+
132+
@Override
133+
public void applyAuthorizationConfigPolicy(HiveConf hiveConf) {
134+
// NOP
135+
}
136+
137+
@Override
138+
public List<HivePrivilegeObject> applyRowFilterAndColumnMasking(HiveAuthzContext context,
139+
List<HivePrivilegeObject> privObjs) {
140+
return List.of();
141+
}
142+
143+
@Override
144+
public boolean needTransform() {
145+
return false;
146+
}
147+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.iceberg.hive;
21+
22+
import org.apache.hadoop.hive.conf.HiveConf;
23+
import org.apache.hadoop.hive.ql.security.HiveAuthenticationProvider;
24+
import org.apache.hadoop.hive.ql.security.authorization.plugin.HiveAuthorizer;
25+
import org.apache.hadoop.hive.ql.security.authorization.plugin.HiveAuthorizerFactory;
26+
import org.apache.hadoop.hive.ql.security.authorization.plugin.HiveAuthzSessionContext;
27+
import org.apache.hadoop.hive.ql.security.authorization.plugin.HiveMetastoreClientFactory;
28+
29+
public class MockHiveAuthorizerFactory implements HiveAuthorizerFactory {
30+
@Override
31+
public HiveAuthorizer createHiveAuthorizer(HiveMetastoreClientFactory metastoreClientFactory, HiveConf conf,
32+
HiveAuthenticationProvider hiveAuthenticator, HiveAuthzSessionContext ctx) {
33+
return new MockHiveAuthorizer(hiveAuthenticator);
34+
}
35+
}
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.iceberg.hive;
21+
22+
import java.security.PrivilegedExceptionAction;
23+
import java.util.Collections;
24+
import java.util.List;
25+
import java.util.Map;
26+
import java.util.UUID;
27+
import java.util.function.Consumer;
28+
import org.apache.hadoop.hive.metastore.conf.MetastoreConf.ConfVars;
29+
import org.apache.hadoop.hive.ql.security.authorization.plugin.metastore.HiveMetaStoreAuthorizer;
30+
import org.apache.hadoop.security.UserGroupInformation;
31+
import org.apache.iceberg.CatalogProperties;
32+
import org.apache.iceberg.CatalogUtil;
33+
import org.apache.iceberg.Schema;
34+
import org.apache.iceberg.catalog.Namespace;
35+
import org.apache.iceberg.catalog.TableIdentifier;
36+
import org.apache.iceberg.exceptions.ForbiddenException;
37+
import org.assertj.core.api.Assertions;
38+
import org.junit.jupiter.api.AfterEach;
39+
import org.junit.jupiter.api.Test;
40+
import org.junit.jupiter.api.extension.RegisterExtension;
41+
42+
class TestHiveCatalogAccessControl {
43+
private static final Schema DUMMY_SCHEMA = new Schema();
44+
45+
@RegisterExtension
46+
private static final HiveMetastoreExtension HIVE_METASTORE_EXTENSION = HiveMetastoreExtension.builder()
47+
.withConfig(Map.of(
48+
ConfVars.HIVE_AUTHORIZATION_MANAGER.getVarname(), MockHiveAuthorizerFactory.class.getName(),
49+
ConfVars.PRE_EVENT_LISTENERS.getVarname(), HiveMetaStoreAuthorizer.class.getName()
50+
)).build();
51+
52+
@AfterEach
53+
void afterEach() throws Exception {
54+
HIVE_METASTORE_EXTENSION.metastore().reset();
55+
}
56+
57+
@Test
58+
void testNamespace() throws Exception {
59+
var namespace = Namespace.of("permission_test_db");
60+
asAuthorized(catalog -> catalog.createNamespace(namespace, Collections.emptyMap()));
61+
asUnauthorized(catalog -> {
62+
// Should HMS omit namespaces?
63+
Assertions.assertThat(catalog.listNamespaces()).isEqualTo(List.of(Namespace.of("default"), namespace));
64+
Assertions.assertThatThrownBy(() -> catalog.namespaceExists(namespace)).isInstanceOf(ForbiddenException.class);
65+
Assertions.assertThatThrownBy(() -> catalog.loadNamespaceMetadata(namespace))
66+
.isInstanceOf(ForbiddenException.class);
67+
var newNamespace = Namespace.of("new_db");
68+
Assertions.assertThatThrownBy(() -> catalog.createNamespace(newNamespace)).isInstanceOf(ForbiddenException.class);
69+
Assertions.assertThatThrownBy(() -> catalog.dropNamespace(namespace)).isInstanceOf(ForbiddenException.class);
70+
var properties = Collections.singletonMap("key", "value");
71+
Assertions.assertThatThrownBy(() -> catalog.setProperties(namespace, properties))
72+
.isInstanceOf(ForbiddenException.class);
73+
var propertyKeys = properties.keySet();
74+
Assertions.assertThatThrownBy(() -> catalog.removeProperties(namespace, propertyKeys))
75+
.isInstanceOf(ForbiddenException.class);
76+
});
77+
}
78+
79+
@Test
80+
void testTable() throws Exception {
81+
Namespace namespace = Namespace.of("permission_test_db");
82+
TableIdentifier table = TableIdentifier.of(namespace, "permission_test_table");
83+
asAuthorized(catalog -> {
84+
catalog.createNamespace(namespace, Collections.emptyMap());
85+
catalog.createTable(table, new Schema());
86+
});
87+
asUnauthorized(catalog -> {
88+
// Should HMS omit namespaces?
89+
Assertions.assertThat(catalog.listTables(namespace)).isEqualTo(Collections.singletonList(table));
90+
Assertions.assertThatThrownBy(() -> catalog.tableExists(table)).isInstanceOf(ForbiddenException.class);
91+
Assertions.assertThatThrownBy(() -> catalog.loadTable(table)).isInstanceOf(ForbiddenException.class);
92+
var newTable = TableIdentifier.of(namespace, "new_table");
93+
Assertions.assertThatThrownBy(() -> catalog.createTable(newTable, DUMMY_SCHEMA))
94+
.isInstanceOf(ForbiddenException.class);
95+
Assertions.assertThatThrownBy(() -> catalog.renameTable(table, newTable)).isInstanceOf(ForbiddenException.class);
96+
Assertions.assertThatThrownBy(() -> catalog.dropTable(table)).isInstanceOf(ForbiddenException.class);
97+
});
98+
}
99+
100+
@Test
101+
void testView() throws Exception {
102+
Namespace namespace = Namespace.of("permission_test_db");
103+
TableIdentifier view = TableIdentifier.of(namespace, "permission_test_view");
104+
asAuthorized(catalog -> {
105+
catalog.createNamespace(namespace, Collections.emptyMap());
106+
catalog.buildView(view).withQuery("hive", "SELECT 1 AS id").withSchema(new Schema())
107+
.withDefaultNamespace(namespace).create();
108+
});
109+
asUnauthorized(catalog -> {
110+
// Should HMS omit namespaces?
111+
Assertions.assertThat(catalog.listViews(namespace)).isEqualTo(Collections.singletonList(view));
112+
Assertions.assertThatThrownBy(() -> catalog.viewExists(view)).isInstanceOf(ForbiddenException.class);
113+
Assertions.assertThatThrownBy(() -> catalog.loadView(view)).isInstanceOf(ForbiddenException.class);
114+
var newView = TableIdentifier.of(namespace, "new_view");
115+
var builder = catalog.buildView(newView).withQuery("hive", "SELECT 1 AS id").withSchema(DUMMY_SCHEMA)
116+
.withDefaultNamespace(namespace);
117+
Assertions.assertThatThrownBy(builder::create).isInstanceOf(ForbiddenException.class);
118+
Assertions.assertThatThrownBy(() -> catalog.renameView(view, newView)).isInstanceOf(ForbiddenException.class);
119+
Assertions.assertThatThrownBy(() -> catalog.dropView(view)).isInstanceOf(ForbiddenException.class);
120+
});
121+
}
122+
123+
@Test
124+
void testTransaction() throws Exception {
125+
var namespace = Namespace.of("permission_test_db");
126+
asAuthorized(catalog -> catalog.createNamespace(namespace, Collections.emptyMap()));
127+
asUnauthorized(catalog -> {
128+
var newTable = TableIdentifier.of(namespace, "new_table");
129+
Assertions.assertThatThrownBy(() -> catalog.newCreateTableTransaction(newTable, DUMMY_SCHEMA))
130+
.isInstanceOf(ForbiddenException.class);
131+
Assertions.assertThatThrownBy(() -> catalog.newReplaceTableTransaction(newTable, DUMMY_SCHEMA, true))
132+
.isInstanceOf(ForbiddenException.class);
133+
});
134+
}
135+
136+
private static void asAuthorized(Consumer<HiveCatalog> consumer) throws Exception {
137+
withUser("authorized_user", consumer);
138+
}
139+
140+
private static void asUnauthorized(Consumer<HiveCatalog> consumer) throws Exception {
141+
withUser(MockHiveAuthorizer.PERMISSION_TEST_USER, consumer);
142+
}
143+
144+
private static void withUser(String username, Consumer<HiveCatalog> consumer) throws Exception {
145+
var ugi = UserGroupInformation.createRemoteUser(username);
146+
ugi.doAs((PrivilegedExceptionAction<Void>) () -> {
147+
try (HiveCatalog catalog = createCatalog()) {
148+
consumer.accept(catalog);
149+
return null;
150+
}
151+
});
152+
}
153+
154+
private static HiveCatalog createCatalog() {
155+
return (HiveCatalog)
156+
CatalogUtil.loadCatalog(
157+
HiveCatalog.class.getName(),
158+
UUID.randomUUID().toString(),
159+
Map.of(
160+
CatalogProperties.CLIENT_POOL_CACHE_KEYS, "ugi"
161+
),
162+
HIVE_METASTORE_EXTENSION.hiveConf());
163+
}
164+
}

iceberg/iceberg-catalog/src/test/java/org/apache/iceberg/hive/TestHiveMetastore.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,20 @@
3333
import org.apache.hadoop.hive.metastore.IHMSHandler;
3434
import org.apache.hadoop.hive.metastore.IMetaStoreClient;
3535
import org.apache.hadoop.hive.metastore.TSetIpAddressProcessor;
36+
import org.apache.hadoop.hive.metastore.TUGIBasedProcessor;
3637
import org.apache.hadoop.hive.metastore.api.GetTableRequest;
3738
import org.apache.hadoop.hive.metastore.api.Table;
3839
import org.apache.hadoop.hive.metastore.conf.MetastoreConf;
40+
import org.apache.hadoop.hive.metastore.conf.MetastoreConf.ConfVars;
41+
import org.apache.hadoop.hive.metastore.security.TUGIContainingTransport;
3942
import org.apache.hadoop.hive.metastore.utils.TestTxnDbUtil;
4043
import org.apache.iceberg.ClientPool;
4144
import org.apache.iceberg.catalog.TableIdentifier;
4245
import org.apache.iceberg.common.DynConstructors;
4346
import org.apache.iceberg.common.DynMethods;
4447
import org.apache.iceberg.hadoop.Util;
4548
import org.apache.thrift.TException;
49+
import org.apache.thrift.TProcessor;
4650
import org.apache.thrift.protocol.TBinaryProtocol;
4751
import org.apache.thrift.server.TServer;
4852
import org.apache.thrift.server.TThreadPoolServer;
@@ -244,9 +248,18 @@ private TServer newThriftServer(TServerSocket socket, int poolSize, HiveConf con
244248
baseHandler = HMS_HANDLER_CTOR.newInstance("new db based metaserver", serverConf);
245249
IHMSHandler handler = GET_BASE_HMS_HANDLER.invoke(serverConf, baseHandler, false);
246250

251+
TProcessor processor;
252+
TTransportFactory transportFactory;
253+
if (MetastoreConf.getBoolVar(conf, ConfVars.EXECUTE_SET_UGI)) {
254+
processor = new TUGIBasedProcessor<>(handler);
255+
transportFactory = new TUGIContainingTransport.Factory();
256+
} else {
257+
processor = new TSetIpAddressProcessor<>(handler);
258+
transportFactory = new TTransportFactory();
259+
}
247260
TThreadPoolServer.Args args = new TThreadPoolServer.Args(socket)
248-
.processor(new TSetIpAddressProcessor<>(handler))
249-
.transportFactory(new TTransportFactory())
261+
.processor(processor)
262+
.transportFactory(transportFactory)
250263
.protocolFactory(new TBinaryProtocol.Factory())
251264
.minWorkerThreads(poolSize)
252265
.maxWorkerThreads(poolSize);

0 commit comments

Comments
 (0)