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 java.io.Closeable;
22 import java.io.IOException;
23 import java.util.HashMap;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.concurrent.ConcurrentSkipListMap;
27 import java.util.concurrent.atomic.AtomicLong;
28
29 import org.apache.commons.logging.Log;
30 import org.apache.commons.logging.LogFactory;
31 import org.apache.hadoop.hbase.AuthUtil;
32 import org.apache.hadoop.hbase.classification.InterfaceAudience;
33 import org.apache.hadoop.conf.Configuration;
34 import org.apache.hadoop.hbase.Cell;
35 import org.apache.hadoop.hbase.TableName;
36 import org.apache.hadoop.hbase.exceptions.DeserializationException;
37 import org.apache.hadoop.hbase.security.Superusers;
38 import org.apache.hadoop.hbase.security.User;
39 import org.apache.hadoop.hbase.security.UserProvider;
40 import org.apache.hadoop.hbase.util.Bytes;
41 import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
42 import org.apache.zookeeper.KeeperException;
43
44 import com.google.common.annotations.VisibleForTesting;
45 import com.google.common.collect.ArrayListMultimap;
46 import com.google.common.collect.ListMultimap;
47 import com.google.common.collect.Lists;
48
49
50
51
52 @InterfaceAudience.Private
53 public class TableAuthManager implements Closeable {
54 private static class PermissionCache<T extends Permission> {
55
56 private ListMultimap<String,T> userCache = ArrayListMultimap.create();
57
58 private ListMultimap<String,T> groupCache = ArrayListMultimap.create();
59
60 public List<T> getUser(String user) {
61 return userCache.get(user);
62 }
63
64 public void putUser(String user, T perm) {
65 userCache.put(user, perm);
66 }
67
68 public List<T> replaceUser(String user, Iterable<? extends T> perms) {
69 return userCache.replaceValues(user, perms);
70 }
71
72 public List<T> getGroup(String group) {
73 return groupCache.get(group);
74 }
75
76 public void putGroup(String group, T perm) {
77 groupCache.put(group, perm);
78 }
79
80 public List<T> replaceGroup(String group, Iterable<? extends T> perms) {
81 return groupCache.replaceValues(group, perms);
82 }
83
84
85
86
87
88 public ListMultimap<String,T> getAllPermissions() {
89 ListMultimap<String,T> tmp = ArrayListMultimap.create();
90 tmp.putAll(userCache);
91 for (String group : groupCache.keySet()) {
92 tmp.putAll(AuthUtil.toGroupEntry(group), groupCache.get(group));
93 }
94 return tmp;
95 }
96 }
97
98 private static final Log LOG = LogFactory.getLog(TableAuthManager.class);
99
100
101 private volatile PermissionCache<Permission> globalCache;
102
103 private ConcurrentSkipListMap<TableName, PermissionCache<TablePermission>> tableCache =
104 new ConcurrentSkipListMap<TableName, PermissionCache<TablePermission>>();
105
106 private ConcurrentSkipListMap<String, PermissionCache<TablePermission>> nsCache =
107 new ConcurrentSkipListMap<String, PermissionCache<TablePermission>>();
108
109 private Configuration conf;
110 private ZKPermissionWatcher zkperms;
111 private final AtomicLong mtime = new AtomicLong(0L);
112
113 private TableAuthManager(ZooKeeperWatcher watcher, Configuration conf)
114 throws IOException {
115 this.conf = conf;
116
117
118 globalCache = initGlobal(conf);
119
120 this.zkperms = new ZKPermissionWatcher(watcher, this, conf);
121 try {
122 this.zkperms.start();
123 } catch (KeeperException ke) {
124 LOG.error("ZooKeeper initialization failed", ke);
125 }
126 }
127
128 @Override
129 public void close() {
130 this.zkperms.close();
131 }
132
133
134
135
136
137 private PermissionCache<Permission> initGlobal(Configuration conf) throws IOException {
138 UserProvider userProvider = UserProvider.instantiate(conf);
139 User user = userProvider.getCurrent();
140 if (user == null) {
141 throw new IOException("Unable to obtain the current user, " +
142 "authorization checks for internal operations will not work correctly!");
143 }
144 PermissionCache<Permission> newCache = new PermissionCache<Permission>();
145 String currentUser = user.getShortName();
146
147
148 List<String> superusers = Lists.asList(currentUser, conf.getStrings(
149 Superusers.SUPERUSER_CONF_KEY, new String[0]));
150 if (superusers != null) {
151 for (String name : superusers) {
152 if (AuthUtil.isGroupPrincipal(name)) {
153 newCache.putGroup(AuthUtil.getGroupName(name),
154 new Permission(Permission.Action.values()));
155 } else {
156 newCache.putUser(name, new Permission(Permission.Action.values()));
157 }
158 }
159 }
160 return newCache;
161 }
162
163 public ZKPermissionWatcher getZKPermissionWatcher() {
164 return this.zkperms;
165 }
166
167 public void refreshTableCacheFromWritable(TableName table,
168 byte[] data) throws IOException {
169 if (data != null && data.length > 0) {
170 ListMultimap<String,TablePermission> perms;
171 try {
172 perms = AccessControlLists.readPermissions(data, conf);
173 } catch (DeserializationException e) {
174 throw new IOException(e);
175 }
176
177 if (perms != null) {
178 if (Bytes.equals(table.getName(), AccessControlLists.ACL_GLOBAL_NAME)) {
179 updateGlobalCache(perms);
180 } else {
181 updateTableCache(table, perms);
182 }
183 }
184 } else {
185 LOG.debug("Skipping permission cache refresh because writable data is empty");
186 }
187 }
188
189 public void refreshNamespaceCacheFromWritable(String namespace, byte[] data) throws IOException {
190 if (data != null && data.length > 0) {
191 ListMultimap<String,TablePermission> perms;
192 try {
193 perms = AccessControlLists.readPermissions(data, conf);
194 } catch (DeserializationException e) {
195 throw new IOException(e);
196 }
197 if (perms != null) {
198 updateNsCache(namespace, perms);
199 }
200 } else {
201 LOG.debug("Skipping permission cache refresh because writable data is empty");
202 }
203 }
204
205
206
207
208
209
210 private void updateGlobalCache(ListMultimap<String,TablePermission> userPerms) {
211 PermissionCache<Permission> newCache = null;
212 try {
213 newCache = initGlobal(conf);
214 for (Map.Entry<String,TablePermission> entry : userPerms.entries()) {
215 if (AuthUtil.isGroupPrincipal(entry.getKey())) {
216 newCache.putGroup(AuthUtil.getGroupName(entry.getKey()),
217 new Permission(entry.getValue().getActions()));
218 } else {
219 newCache.putUser(entry.getKey(), new Permission(entry.getValue().getActions()));
220 }
221 }
222 globalCache = newCache;
223 mtime.incrementAndGet();
224 } catch (IOException e) {
225
226 LOG.error("Error occured while updating the global cache", e);
227 }
228 }
229
230
231
232
233
234
235
236
237
238 private void updateTableCache(TableName table,
239 ListMultimap<String,TablePermission> tablePerms) {
240 PermissionCache<TablePermission> newTablePerms = new PermissionCache<TablePermission>();
241
242 for (Map.Entry<String,TablePermission> entry : tablePerms.entries()) {
243 if (AuthUtil.isGroupPrincipal(entry.getKey())) {
244 newTablePerms.putGroup(AuthUtil.getGroupName(entry.getKey()), entry.getValue());
245 } else {
246 newTablePerms.putUser(entry.getKey(), entry.getValue());
247 }
248 }
249
250 tableCache.put(table, newTablePerms);
251 mtime.incrementAndGet();
252 }
253
254
255
256
257
258
259
260
261
262 private void updateNsCache(String namespace,
263 ListMultimap<String, TablePermission> tablePerms) {
264 PermissionCache<TablePermission> newTablePerms = new PermissionCache<TablePermission>();
265
266 for (Map.Entry<String, TablePermission> entry : tablePerms.entries()) {
267 if (AuthUtil.isGroupPrincipal(entry.getKey())) {
268 newTablePerms.putGroup(AuthUtil.getGroupName(entry.getKey()), entry.getValue());
269 } else {
270 newTablePerms.putUser(entry.getKey(), entry.getValue());
271 }
272 }
273
274 nsCache.put(namespace, newTablePerms);
275 mtime.incrementAndGet();
276 }
277
278 private PermissionCache<TablePermission> getTablePermissions(TableName table) {
279 if (!tableCache.containsKey(table)) {
280 tableCache.putIfAbsent(table, new PermissionCache<TablePermission>());
281 }
282 return tableCache.get(table);
283 }
284
285 private PermissionCache<TablePermission> getNamespacePermissions(String namespace) {
286 if (!nsCache.containsKey(namespace)) {
287 nsCache.putIfAbsent(namespace, new PermissionCache<TablePermission>());
288 }
289 return nsCache.get(namespace);
290 }
291
292
293
294
295
296
297
298 private boolean authorize(List<Permission> perms, Permission.Action action) {
299 if (perms != null) {
300 for (Permission p : perms) {
301 if (p.implies(action)) {
302 return true;
303 }
304 }
305 } else if (LOG.isDebugEnabled()) {
306 LOG.debug("No permissions found for " + action);
307 }
308
309 return false;
310 }
311
312
313
314
315
316
317
318
319 public boolean authorize(User user, Permission.Action action) {
320 if (user == null) {
321 return false;
322 }
323
324 if (authorize(globalCache.getUser(user.getShortName()), action)) {
325 return true;
326 }
327
328 String[] groups = user.getGroupNames();
329 if (groups != null) {
330 for (String group : groups) {
331 if (authorize(globalCache.getGroup(group), action)) {
332 return true;
333 }
334 }
335 }
336 return false;
337 }
338
339 private boolean authorize(List<TablePermission> perms,
340 TableName table, byte[] family,
341 Permission.Action action) {
342 return authorize(perms, table, family, null, action);
343 }
344
345 private boolean authorize(List<TablePermission> perms,
346 TableName table, byte[] family,
347 byte[] qualifier, Permission.Action action) {
348 if (perms != null) {
349 for (TablePermission p : perms) {
350 if (p.implies(table, family, qualifier, action)) {
351 return true;
352 }
353 }
354 } else if (LOG.isDebugEnabled()) {
355 LOG.debug("No permissions found for table="+table);
356 }
357 return false;
358 }
359
360 private boolean hasAccess(List<TablePermission> perms,
361 TableName table, Permission.Action action) {
362 if (perms != null) {
363 for (TablePermission p : perms) {
364 if (p.implies(action)) {
365 return true;
366 }
367 }
368 } else if (LOG.isDebugEnabled()) {
369 LOG.debug("No permissions found for table="+table);
370 }
371 return false;
372 }
373
374
375
376
377 public boolean authorize(User user, TableName table, Cell cell, Permission.Action action) {
378 try {
379 List<Permission> perms = AccessControlLists.getCellPermissionsForUser(user, cell);
380 if (LOG.isTraceEnabled()) {
381 LOG.trace("Perms for user " + user.getShortName() + " in cell " + cell + ": " +
382 (perms != null ? perms : ""));
383 }
384 if (perms != null) {
385 for (Permission p: perms) {
386 if (p.implies(action)) {
387 return true;
388 }
389 }
390 }
391 } catch (IOException e) {
392
393 LOG.error("Failed parse of ACL tag in cell " + cell);
394
395
396 }
397 return false;
398 }
399
400 public boolean authorize(User user, String namespace, Permission.Action action) {
401
402 if (authorize(user, action)) {
403 return true;
404 }
405
406 PermissionCache<TablePermission> tablePerms = nsCache.get(namespace);
407 if (tablePerms != null) {
408 List<TablePermission> userPerms = tablePerms.getUser(user.getShortName());
409 if (authorize(userPerms, namespace, action)) {
410 return true;
411 }
412 String[] groupNames = user.getGroupNames();
413 if (groupNames != null) {
414 for (String group : groupNames) {
415 List<TablePermission> groupPerms = tablePerms.getGroup(group);
416 if (authorize(groupPerms, namespace, action)) {
417 return true;
418 }
419 }
420 }
421 }
422 return false;
423 }
424
425 private boolean authorize(List<TablePermission> perms, String namespace,
426 Permission.Action action) {
427 if (perms != null) {
428 for (TablePermission p : perms) {
429 if (p.implies(namespace, action)) {
430 return true;
431 }
432 }
433 } else if (LOG.isDebugEnabled()) {
434 LOG.debug("No permissions for authorize() check, table=" + namespace);
435 }
436
437 return false;
438 }
439
440
441
442
443
444
445
446
447
448
449
450 public boolean authorizeUser(User user, TableName table, byte[] family,
451 Permission.Action action) {
452 return authorizeUser(user, table, family, null, action);
453 }
454
455 public boolean authorizeUser(User user, TableName table, byte[] family,
456 byte[] qualifier, Permission.Action action) {
457 if (table == null) table = AccessControlLists.ACL_TABLE_NAME;
458
459 if (authorize(user, table.getNamespaceAsString(), action)) {
460 return true;
461 }
462
463 return authorize(getTablePermissions(table).getUser(user.getShortName()), table, family,
464 qualifier, action);
465 }
466
467
468
469
470
471
472
473
474
475
476 public boolean userHasAccess(User user, TableName table, Permission.Action action) {
477 if (table == null) table = AccessControlLists.ACL_TABLE_NAME;
478
479 if (authorize(user, table.getNamespaceAsString(), action)) {
480 return true;
481 }
482
483 return hasAccess(getTablePermissions(table).getUser(user.getShortName()), table, action);
484 }
485
486
487
488
489
490 public boolean authorizeGroup(String groupName, Permission.Action action) {
491 List<Permission> perms = globalCache.getGroup(groupName);
492 if (LOG.isDebugEnabled()) {
493 LOG.debug("authorizing " + (perms != null && !perms.isEmpty() ? perms.get(0) : "") +
494 " for " + action);
495 }
496 return authorize(perms, action);
497 }
498
499
500
501
502
503
504
505
506
507
508
509 public boolean authorizeGroup(String groupName, TableName table, byte[] family,
510 byte[] qualifier, Permission.Action action) {
511
512 if (authorizeGroup(groupName, action)) {
513 return true;
514 }
515 if (table == null) table = AccessControlLists.ACL_TABLE_NAME;
516
517 String namespace = table.getNamespaceAsString();
518 if (authorize(getNamespacePermissions(namespace).getGroup(groupName), namespace, action)) {
519 return true;
520 }
521
522 List<TablePermission> tblPerms = getTablePermissions(table).getGroup(groupName);
523 if (LOG.isDebugEnabled()) {
524 LOG.debug("authorizing " + (tblPerms != null && !tblPerms.isEmpty() ? tblPerms.get(0) : "") +
525 " for " +groupName + " on " + table + "." + Bytes.toString(family) + "." +
526 Bytes.toString(qualifier) + " with " + action);
527 }
528 return authorize(tblPerms, table, family, qualifier, action);
529 }
530
531
532
533
534
535
536
537
538
539 public boolean groupHasAccess(String groupName, TableName table, Permission.Action action) {
540
541 if (authorizeGroup(groupName, action)) {
542 return true;
543 }
544 if (table == null) table = AccessControlLists.ACL_TABLE_NAME;
545
546 if (hasAccess(getNamespacePermissions(table.getNamespaceAsString()).getGroup(groupName),
547 table, action)) {
548 return true;
549 }
550
551 return hasAccess(getTablePermissions(table).getGroup(groupName), table, action);
552 }
553
554 public boolean authorize(User user, TableName table, byte[] family,
555 byte[] qualifier, Permission.Action action) {
556 if (authorizeUser(user, table, family, qualifier, action)) {
557 return true;
558 }
559
560 String[] groups = user.getGroupNames();
561 if (groups != null) {
562 for (String group : groups) {
563 if (authorizeGroup(group, table, family, qualifier, action)) {
564 return true;
565 }
566 }
567 }
568 return false;
569 }
570
571 public boolean hasAccess(User user, TableName table, Permission.Action action) {
572 if (userHasAccess(user, table, action)) {
573 return true;
574 }
575
576 String[] groups = user.getGroupNames();
577 if (groups != null) {
578 for (String group : groups) {
579 if (groupHasAccess(group, table, action)) {
580 return true;
581 }
582 }
583 }
584 return false;
585 }
586
587 public boolean authorize(User user, TableName table, byte[] family,
588 Permission.Action action) {
589 return authorize(user, table, family, null, action);
590 }
591
592
593
594
595
596
597
598 public boolean matchPermission(User user,
599 TableName table, byte[] family, Permission.Action action) {
600 PermissionCache<TablePermission> tablePerms = tableCache.get(table);
601 if (tablePerms != null) {
602 List<TablePermission> userPerms = tablePerms.getUser(user.getShortName());
603 if (userPerms != null) {
604 for (TablePermission p : userPerms) {
605 if (p.matchesFamily(table, family, action)) {
606 return true;
607 }
608 }
609 }
610
611 String[] groups = user.getGroupNames();
612 if (groups != null) {
613 for (String group : groups) {
614 List<TablePermission> groupPerms = tablePerms.getGroup(group);
615 if (groupPerms != null) {
616 for (TablePermission p : groupPerms) {
617 if (p.matchesFamily(table, family, action)) {
618 return true;
619 }
620 }
621 }
622 }
623 }
624 }
625
626 return false;
627 }
628
629 public boolean matchPermission(User user,
630 TableName table, byte[] family, byte[] qualifier,
631 Permission.Action action) {
632 PermissionCache<TablePermission> tablePerms = tableCache.get(table);
633 if (tablePerms != null) {
634 List<TablePermission> userPerms = tablePerms.getUser(user.getShortName());
635 if (userPerms != null) {
636 for (TablePermission p : userPerms) {
637 if (p.matchesFamilyQualifier(table, family, qualifier, action)) {
638 return true;
639 }
640 }
641 }
642
643 String[] groups = user.getGroupNames();
644 if (groups != null) {
645 for (String group : groups) {
646 List<TablePermission> groupPerms = tablePerms.getGroup(group);
647 if (groupPerms != null) {
648 for (TablePermission p : groupPerms) {
649 if (p.matchesFamilyQualifier(table, family, qualifier, action)) {
650 return true;
651 }
652 }
653 }
654 }
655 }
656 }
657 return false;
658 }
659
660 public void removeNamespace(byte[] ns) {
661 nsCache.remove(Bytes.toString(ns));
662 }
663
664 public void removeTable(TableName table) {
665 tableCache.remove(table);
666 }
667
668
669
670
671
672
673
674
675 public void setTableUserPermissions(String username, TableName table,
676 List<TablePermission> perms) {
677 PermissionCache<TablePermission> tablePerms = getTablePermissions(table);
678 tablePerms.replaceUser(username, perms);
679 writeTableToZooKeeper(table, tablePerms);
680 }
681
682
683
684
685
686
687
688
689 public void setTableGroupPermissions(String group, TableName table,
690 List<TablePermission> perms) {
691 PermissionCache<TablePermission> tablePerms = getTablePermissions(table);
692 tablePerms.replaceGroup(group, perms);
693 writeTableToZooKeeper(table, tablePerms);
694 }
695
696
697
698
699
700
701
702
703 public void setNamespaceUserPermissions(String username, String namespace,
704 List<TablePermission> perms) {
705 PermissionCache<TablePermission> tablePerms = getNamespacePermissions(namespace);
706 tablePerms.replaceUser(username, perms);
707 writeNamespaceToZooKeeper(namespace, tablePerms);
708 }
709
710
711
712
713
714
715
716
717 public void setNamespaceGroupPermissions(String group, String namespace,
718 List<TablePermission> perms) {
719 PermissionCache<TablePermission> tablePerms = getNamespacePermissions(namespace);
720 tablePerms.replaceGroup(group, perms);
721 writeNamespaceToZooKeeper(namespace, tablePerms);
722 }
723
724 public void writeTableToZooKeeper(TableName table,
725 PermissionCache<TablePermission> tablePerms) {
726 byte[] serialized = new byte[0];
727 if (tablePerms != null) {
728 serialized = AccessControlLists.writePermissionsAsBytes(tablePerms.getAllPermissions(), conf);
729 }
730 zkperms.writeToZookeeper(table.getName(), serialized);
731 }
732
733 public void writeNamespaceToZooKeeper(String namespace,
734 PermissionCache<TablePermission> tablePerms) {
735 byte[] serialized = new byte[0];
736 if (tablePerms != null) {
737 serialized = AccessControlLists.writePermissionsAsBytes(tablePerms.getAllPermissions(), conf);
738 }
739 zkperms.writeToZookeeper(Bytes.toBytes(AccessControlLists.toNamespaceEntry(namespace)),
740 serialized);
741 }
742
743 public long getMTime() {
744 return mtime.get();
745 }
746
747 private static Map<ZooKeeperWatcher,TableAuthManager> managerMap =
748 new HashMap<ZooKeeperWatcher,TableAuthManager>();
749
750 private static Map<TableAuthManager, Integer> refCount = new HashMap<>();
751
752
753
754 public synchronized static TableAuthManager getOrCreate(
755 ZooKeeperWatcher watcher, Configuration conf) throws IOException {
756 TableAuthManager instance = managerMap.get(watcher);
757 if (instance == null) {
758 instance = new TableAuthManager(watcher, conf);
759 managerMap.put(watcher, instance);
760 }
761 int ref = refCount.get(instance) == null ? 0 : refCount.get(instance).intValue();
762 refCount.put(instance, ref + 1);
763 return instance;
764 }
765
766 @VisibleForTesting
767 static int getTotalRefCount() {
768 int total = 0;
769 for (int count : refCount.values()) {
770 total += count;
771 }
772 return total;
773 }
774
775
776
777
778
779 public synchronized static void release(TableAuthManager instance) {
780 if (refCount.get(instance) == null || refCount.get(instance) < 1) {
781 String msg = "Something wrong with the TableAuthManager reference counting: " + instance
782 + " whose count is " + refCount.get(instance);
783 LOG.fatal(msg);
784 instance.close();
785 managerMap.remove(instance.getZKPermissionWatcher().getWatcher());
786 instance.getZKPermissionWatcher().getWatcher().abort(msg, null);
787 } else {
788 int ref = refCount.get(instance);
789 refCount.put(instance, ref-1);
790 if (ref-1 == 0) {
791 instance.close();
792 managerMap.remove(instance.getZKPermissionWatcher().getWatcher());
793 refCount.remove(instance);
794 }
795 }
796 }
797 }