1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.hadoop.hbase.security.access;
20
21 import static org.junit.Assert.assertEquals;
22 import static org.junit.Assert.fail;
23
24 import java.io.IOException;
25 import java.lang.reflect.UndeclaredThrowableException;
26 import java.security.PrivilegedActionException;
27 import java.security.PrivilegedExceptionAction;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.concurrent.Callable;
31
32 import org.apache.commons.logging.Log;
33 import org.apache.commons.logging.LogFactory;
34 import org.apache.hadoop.conf.Configuration;
35 import org.apache.hadoop.hbase.Coprocessor;
36 import org.apache.hadoop.hbase.HBaseTestingUtility;
37 import org.apache.hadoop.hbase.HConstants;
38 import org.apache.hadoop.hbase.MiniHBaseCluster;
39 import org.apache.hadoop.hbase.TableName;
40 import org.apache.hadoop.hbase.Waiter.Predicate;
41 import org.apache.hadoop.hbase.client.HTable;
42 import org.apache.hadoop.hbase.client.RetriesExhaustedWithDetailsException;
43 import org.apache.hadoop.hbase.coprocessor.CoprocessorHost;
44 import org.apache.hadoop.hbase.io.hfile.HFile;
45 import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
46 import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos.AccessControlService;
47 import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos.CheckPermissionsRequest;
48 import org.apache.hadoop.hbase.regionserver.HRegion;
49 import org.apache.hadoop.hbase.security.AccessDeniedException;
50 import org.apache.hadoop.hbase.security.User;
51 import org.apache.hadoop.hbase.util.JVMClusterUtil.RegionServerThread;
52
53 import com.google.common.collect.Lists;
54 import com.google.common.collect.Maps;
55 import com.google.protobuf.BlockingRpcChannel;
56 import com.google.protobuf.ServiceException;
57
58
59
60
61 public class SecureTestUtil {
62
63 private static final Log LOG = LogFactory.getLog(SecureTestUtil.class);
64 private static final int WAIT_TIME = 10000;
65
66 public static void enableSecurity(Configuration conf) throws IOException {
67 conf.set("hadoop.security.authorization", "false");
68 conf.set("hadoop.security.authentication", "simple");
69 conf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY, AccessController.class.getName());
70 conf.set(CoprocessorHost.REGION_COPROCESSOR_CONF_KEY, AccessController.class.getName() +
71 "," + SecureBulkLoadEndpoint.class.getName());
72 conf.set(CoprocessorHost.REGIONSERVER_COPROCESSOR_CONF_KEY, AccessController.class.getName());
73
74
75
76
77 String currentUser = User.getCurrent().getName();
78 StringBuffer sb = new StringBuffer();
79 sb.append("admin,");
80 sb.append(currentUser);
81
82 for (int i = 0; i < 5; i++) {
83 sb.append(',');
84 sb.append(currentUser); sb.append(".hfs."); sb.append(i);
85 }
86 conf.set("hbase.superuser", sb.toString());
87
88 conf.setInt(HFile.FORMAT_VERSION_KEY, 3);
89 }
90
91 public static void verifyConfiguration(Configuration conf) {
92 if (!(conf.get(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY).contains(
93 AccessController.class.getName())
94 && conf.get(CoprocessorHost.REGION_COPROCESSOR_CONF_KEY).contains(
95 AccessController.class.getName()) && conf.get(
96 CoprocessorHost.REGIONSERVER_COPROCESSOR_CONF_KEY).contains(
97 AccessController.class.getName()))) {
98 throw new RuntimeException("AccessController is missing from a system coprocessor list");
99 }
100 if (conf.getInt(HFile.FORMAT_VERSION_KEY, 2) < HFile.MIN_FORMAT_VERSION_WITH_TAGS) {
101 throw new RuntimeException("Post 0.96 security features require HFile version >= 3");
102 }
103 }
104
105 public static void checkTablePerms(Configuration conf, byte[] table, byte[] family, byte[] column,
106 Permission.Action... actions) throws IOException {
107 Permission[] perms = new Permission[actions.length];
108 for (int i = 0; i < actions.length; i++) {
109 perms[i] = new TablePermission(TableName.valueOf(table), family, column, actions[i]);
110 }
111
112 checkTablePerms(conf, table, perms);
113 }
114
115 public static void checkTablePerms(Configuration conf, byte[] table, Permission... perms) throws IOException {
116 CheckPermissionsRequest.Builder request = CheckPermissionsRequest.newBuilder();
117 for (Permission p : perms) {
118 request.addPermission(ProtobufUtil.toPermission(p));
119 }
120 HTable acl = new HTable(conf, table);
121 try {
122 AccessControlService.BlockingInterface protocol =
123 AccessControlService.newBlockingStub(acl.coprocessorService(new byte[0]));
124 try {
125 protocol.checkPermissions(null, request.build());
126 } catch (ServiceException se) {
127 ProtobufUtil.toIOException(se);
128 }
129 } finally {
130 acl.close();
131 }
132 }
133
134
135
136
137
138
139
140
141
142
143
144 static interface AccessTestAction extends PrivilegedExceptionAction<Object> { }
145
146 public static void verifyAllowed(User user, AccessTestAction... actions) throws Exception {
147 for (AccessTestAction action : actions) {
148 try {
149 Object obj = user.runAs(action);
150 if (obj != null && obj instanceof List<?>) {
151 List<?> results = (List<?>) obj;
152 if (results != null && results.isEmpty()) {
153 fail("Empty non null results from action for user '" + user.getShortName() + "'");
154 }
155 }
156 } catch (AccessDeniedException ade) {
157 fail("Expected action to pass for user '" + user.getShortName() + "' but was denied");
158 }
159 }
160 }
161
162 public static void verifyAllowed(AccessTestAction action, User... users) throws Exception {
163 for (User user : users) {
164 verifyAllowed(user, action);
165 }
166 }
167
168 public static void verifyAllowed(User user, AccessTestAction action, int count) throws Exception {
169 try {
170 Object obj = user.runAs(action);
171 if (obj != null && obj instanceof List<?>) {
172 List<?> results = (List<?>) obj;
173 if (results != null && results.isEmpty()) {
174 fail("Empty non null results from action for user '" + user.getShortName() + "'");
175 }
176 assertEquals(count, results.size());
177 }
178 } catch (AccessDeniedException ade) {
179 fail("Expected action to pass for user '" + user.getShortName() + "' but was denied");
180 }
181 }
182
183 public static void verifyDeniedWithException(User user, AccessTestAction... actions)
184 throws Exception {
185 verifyDenied(user, true, actions);
186 }
187
188 public static void verifyDeniedWithException(AccessTestAction action, User... users)
189 throws Exception {
190 for (User user : users) {
191 verifyDenied(user, true, action);
192 }
193 }
194
195 public static void verifyDenied(User user, AccessTestAction... actions) throws Exception {
196 verifyDenied(user, false, actions);
197 }
198
199 public static void verifyDenied(User user, boolean requireException,
200 AccessTestAction... actions) throws Exception {
201 for (AccessTestAction action : actions) {
202 try {
203 Object obj = user.runAs(action);
204 if (requireException) {
205 fail("Expected exception was not thrown for user '" + user.getShortName() + "'");
206 }
207 if (obj != null && obj instanceof List<?>) {
208 List<?> results = (List<?>) obj;
209 if (results != null && !results.isEmpty()) {
210 fail("Unexpected results for user '" + user.getShortName() + "'");
211 }
212 }
213 } catch (IOException e) {
214 boolean isAccessDeniedException = false;
215 if(e instanceof RetriesExhaustedWithDetailsException) {
216
217
218
219 for(Throwable ex : ((RetriesExhaustedWithDetailsException) e).getCauses()) {
220 if (ex instanceof AccessDeniedException) {
221 isAccessDeniedException = true;
222 break;
223 }
224 }
225 }
226 else {
227
228
229 Throwable ex = e;
230 do {
231 if (ex instanceof AccessDeniedException) {
232 isAccessDeniedException = true;
233 break;
234 }
235 } while((ex = ex.getCause()) != null);
236 }
237 if (!isAccessDeniedException) {
238 fail("Expected exception was not thrown for user '" + user.getShortName() + "'");
239 }
240 } catch (UndeclaredThrowableException ute) {
241
242 Throwable ex = ute.getUndeclaredThrowable();
243 if (ex instanceof PrivilegedActionException) {
244 ex = ((PrivilegedActionException) ex).getException();
245 }
246 if (ex instanceof ServiceException) {
247 ServiceException se = (ServiceException)ex;
248 if (se.getCause() != null && se.getCause() instanceof AccessDeniedException) {
249
250 return;
251 }
252 }
253 fail("Expected exception was not thrown for user '" + user.getShortName() + "'");
254 }
255 }
256 }
257
258 public static void verifyDenied(AccessTestAction action, User... users) throws Exception {
259 for (User user : users) {
260 verifyDenied(user, action);
261 }
262 }
263
264 private static List<AccessController> getAccessControllers(MiniHBaseCluster cluster) {
265 List<AccessController> result = Lists.newArrayList();
266 for (RegionServerThread t: cluster.getLiveRegionServerThreads()) {
267 for (HRegion region: t.getRegionServer().getOnlineRegionsLocalContext()) {
268 Coprocessor cp = region.getCoprocessorHost()
269 .findCoprocessor(AccessController.class.getName());
270 if (cp != null) {
271 result.add((AccessController)cp);
272 }
273 }
274 }
275 return result;
276 }
277
278 private static Map<AccessController,Long> getAuthManagerMTimes(MiniHBaseCluster cluster) {
279 Map<AccessController,Long> result = Maps.newHashMap();
280 for (AccessController ac: getAccessControllers(cluster)) {
281 result.put(ac, ac.getAuthManager().getMTime());
282 }
283 return result;
284 }
285
286 @SuppressWarnings("rawtypes")
287 private static void updateACLs(final HBaseTestingUtility util, Callable c) throws Exception {
288
289 final Map<AccessController,Long> oldMTimes = getAuthManagerMTimes(util.getHBaseCluster());
290
291
292 c.call();
293
294
295 util.waitFor(WAIT_TIME, 100, new Predicate<IOException>() {
296 @Override
297 public boolean evaluate() throws IOException {
298 Map<AccessController,Long> mtimes = getAuthManagerMTimes(util.getHBaseCluster());
299 for (Map.Entry<AccessController,Long> e: mtimes.entrySet()) {
300 if (!oldMTimes.containsKey(e.getKey())) {
301 LOG.error("Snapshot of AccessController state does not include instance on region " +
302 e.getKey().getRegion().getRegionNameAsString());
303
304 return false;
305 }
306 long old = oldMTimes.get(e.getKey());
307 long now = e.getValue();
308 if (now <= old) {
309 LOG.info("AccessController on region " +
310 e.getKey().getRegion().getRegionNameAsString() + " has not updated: mtime=" +
311 now);
312 return false;
313 }
314 }
315 return true;
316 }
317 });
318 }
319
320
321
322
323
324
325 public static void grantGlobal(final HBaseTestingUtility util, final String user,
326 final Permission.Action... actions) throws Exception {
327 SecureTestUtil.updateACLs(util, new Callable<Void>() {
328 @Override
329 public Void call() throws Exception {
330 HTable acl = new HTable(util.getConfiguration(), AccessControlLists.ACL_TABLE_NAME);
331 try {
332 BlockingRpcChannel service = acl.coprocessorService(HConstants.EMPTY_START_ROW);
333 AccessControlService.BlockingInterface protocol =
334 AccessControlService.newBlockingStub(service);
335 ProtobufUtil.grant(protocol, user, actions);
336 } finally {
337 acl.close();
338 }
339 return null;
340 }
341 });
342 }
343
344
345
346
347
348
349 public static void revokeGlobal(final HBaseTestingUtility util, final String user,
350 final Permission.Action... actions) throws Exception {
351 SecureTestUtil.updateACLs(util, new Callable<Void>() {
352 @Override
353 public Void call() throws Exception {
354 HTable acl = new HTable(util.getConfiguration(), AccessControlLists.ACL_TABLE_NAME);
355 try {
356 BlockingRpcChannel service = acl.coprocessorService(HConstants.EMPTY_START_ROW);
357 AccessControlService.BlockingInterface protocol =
358 AccessControlService.newBlockingStub(service);
359 ProtobufUtil.revoke(protocol, user, actions);
360 } finally {
361 acl.close();
362 }
363 return null;
364 }
365 });
366 }
367
368
369
370
371
372
373 public static void grantOnNamespace(final HBaseTestingUtility util, final String user,
374 final String namespace, final Permission.Action... actions) throws Exception {
375 SecureTestUtil.updateACLs(util, new Callable<Void>() {
376 @Override
377 public Void call() throws Exception {
378 HTable acl = new HTable(util.getConfiguration(), AccessControlLists.ACL_TABLE_NAME);
379 try {
380 BlockingRpcChannel service = acl.coprocessorService(HConstants.EMPTY_START_ROW);
381 AccessControlService.BlockingInterface protocol =
382 AccessControlService.newBlockingStub(service);
383 ProtobufUtil.grant(protocol, user, namespace, actions);
384 } finally {
385 acl.close();
386 }
387 return null;
388 }
389 });
390 }
391
392
393
394
395
396
397 public static void revokeFromNamespace(final HBaseTestingUtility util, final String user,
398 final String namespace, final Permission.Action... actions) throws Exception {
399 SecureTestUtil.updateACLs(util, new Callable<Void>() {
400 @Override
401 public Void call() throws Exception {
402 HTable acl = new HTable(util.getConfiguration(), AccessControlLists.ACL_TABLE_NAME);
403 try {
404 BlockingRpcChannel service = acl.coprocessorService(HConstants.EMPTY_START_ROW);
405 AccessControlService.BlockingInterface protocol =
406 AccessControlService.newBlockingStub(service);
407 ProtobufUtil.revoke(protocol, user, namespace, actions);
408 } finally {
409 acl.close();
410 }
411 return null;
412 }
413 });
414 }
415
416
417
418
419
420
421 public static void grantOnTable(final HBaseTestingUtility util, final String user,
422 final TableName table, final byte[] family, final byte[] qualifier,
423 final Permission.Action... actions) throws Exception {
424 SecureTestUtil.updateACLs(util, new Callable<Void>() {
425 @Override
426 public Void call() throws Exception {
427 HTable acl = new HTable(util.getConfiguration(), AccessControlLists.ACL_TABLE_NAME);
428 try {
429 BlockingRpcChannel service = acl.coprocessorService(HConstants.EMPTY_START_ROW);
430 AccessControlService.BlockingInterface protocol =
431 AccessControlService.newBlockingStub(service);
432 ProtobufUtil.grant(protocol, user, table, family, qualifier, actions);
433 } finally {
434 acl.close();
435 }
436 return null;
437 }
438 });
439 }
440
441
442
443
444
445
446 public static void revokeFromTable(final HBaseTestingUtility util, final String user,
447 final TableName table, final byte[] family, final byte[] qualifier,
448 final Permission.Action... actions) throws Exception {
449 SecureTestUtil.updateACLs(util, new Callable<Void>() {
450 @Override
451 public Void call() throws Exception {
452 HTable acl = new HTable(util.getConfiguration(), AccessControlLists.ACL_TABLE_NAME);
453 try {
454 BlockingRpcChannel service = acl.coprocessorService(HConstants.EMPTY_START_ROW);
455 AccessControlService.BlockingInterface protocol =
456 AccessControlService.newBlockingStub(service);
457 ProtobufUtil.revoke(protocol, user, table, family, qualifier, actions);
458 } finally {
459 acl.close();
460 }
461 return null;
462 }
463 });
464 }
465 }