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