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