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.IOException;
22 import java.net.InetAddress;
23 import java.security.PrivilegedExceptionAction;
24 import java.util.Collection;
25 import java.util.HashMap;
26 import java.util.Iterator;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Map.Entry;
30 import java.util.Set;
31 import java.util.TreeMap;
32 import java.util.TreeSet;
33
34 import org.apache.commons.logging.Log;
35 import org.apache.commons.logging.LogFactory;
36 import org.apache.hadoop.conf.Configuration;
37 import org.apache.hadoop.hbase.Cell;
38 import org.apache.hadoop.hbase.CellScanner;
39 import org.apache.hadoop.hbase.CellUtil;
40 import org.apache.hadoop.hbase.CompoundConfiguration;
41 import org.apache.hadoop.hbase.CoprocessorEnvironment;
42 import org.apache.hadoop.hbase.DoNotRetryIOException;
43 import org.apache.hadoop.hbase.HBaseInterfaceAudience;
44 import org.apache.hadoop.hbase.HColumnDescriptor;
45 import org.apache.hadoop.hbase.HConstants;
46 import org.apache.hadoop.hbase.HRegionInfo;
47 import org.apache.hadoop.hbase.HTableDescriptor;
48 import org.apache.hadoop.hbase.KeyValue;
49 import org.apache.hadoop.hbase.KeyValue.Type;
50 import org.apache.hadoop.hbase.KeyValueUtil;
51 import org.apache.hadoop.hbase.NamespaceDescriptor;
52 import org.apache.hadoop.hbase.ServerName;
53 import org.apache.hadoop.hbase.TableName;
54 import org.apache.hadoop.hbase.Tag;
55 import org.apache.hadoop.hbase.catalog.MetaReader;
56 import org.apache.hadoop.hbase.classification.InterfaceAudience;
57 import org.apache.hadoop.hbase.client.Append;
58 import org.apache.hadoop.hbase.client.Delete;
59 import org.apache.hadoop.hbase.client.Durability;
60 import org.apache.hadoop.hbase.client.Get;
61 import org.apache.hadoop.hbase.client.Increment;
62 import org.apache.hadoop.hbase.client.Mutation;
63 import org.apache.hadoop.hbase.client.Put;
64 import org.apache.hadoop.hbase.client.Query;
65 import org.apache.hadoop.hbase.client.Result;
66 import org.apache.hadoop.hbase.client.Scan;
67 import org.apache.hadoop.hbase.coprocessor.BaseMasterAndRegionObserver;
68 import org.apache.hadoop.hbase.coprocessor.BulkLoadObserver;
69 import org.apache.hadoop.hbase.coprocessor.CoprocessorException;
70 import org.apache.hadoop.hbase.coprocessor.CoprocessorService;
71 import org.apache.hadoop.hbase.coprocessor.EndpointObserver;
72 import org.apache.hadoop.hbase.coprocessor.MasterCoprocessorEnvironment;
73 import org.apache.hadoop.hbase.coprocessor.ObserverContext;
74 import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment;
75 import org.apache.hadoop.hbase.coprocessor.RegionServerCoprocessorEnvironment;
76 import org.apache.hadoop.hbase.coprocessor.RegionServerObserver;
77 import org.apache.hadoop.hbase.filter.ByteArrayComparable;
78 import org.apache.hadoop.hbase.filter.CompareFilter;
79 import org.apache.hadoop.hbase.filter.Filter;
80 import org.apache.hadoop.hbase.filter.FilterList;
81 import org.apache.hadoop.hbase.io.hfile.HFile;
82 import org.apache.hadoop.hbase.ipc.RpcServer;
83 import org.apache.hadoop.hbase.master.MasterServices;
84 import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
85 import org.apache.hadoop.hbase.protobuf.ResponseConverter;
86 import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos;
87 import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos.AccessControlService;
88 import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.WALEntry;
89 import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.SnapshotDescription;
90 import org.apache.hadoop.hbase.protobuf.generated.SecureBulkLoadProtos.CleanupBulkLoadRequest;
91 import org.apache.hadoop.hbase.protobuf.generated.SecureBulkLoadProtos.PrepareBulkLoadRequest;
92 import org.apache.hadoop.hbase.regionserver.HRegion;
93 import org.apache.hadoop.hbase.regionserver.InternalScanner;
94 import org.apache.hadoop.hbase.regionserver.MiniBatchOperationInProgress;
95 import org.apache.hadoop.hbase.regionserver.RegionScanner;
96 import org.apache.hadoop.hbase.regionserver.ScanType;
97 import org.apache.hadoop.hbase.regionserver.Store;
98 import org.apache.hadoop.hbase.regionserver.wal.WALEdit;
99 import org.apache.hadoop.hbase.replication.ReplicationEndpoint;
100 import org.apache.hadoop.hbase.security.AccessDeniedException;
101 import org.apache.hadoop.hbase.security.Superusers;
102 import org.apache.hadoop.hbase.security.User;
103 import org.apache.hadoop.hbase.security.UserProvider;
104 import org.apache.hadoop.hbase.security.access.Permission.Action;
105 import org.apache.hadoop.hbase.util.ByteRange;
106 import org.apache.hadoop.hbase.util.Bytes;
107 import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
108 import org.apache.hadoop.hbase.util.Pair;
109 import org.apache.hadoop.hbase.util.SimpleByteRange;
110 import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
111
112 import com.google.common.collect.ArrayListMultimap;
113 import com.google.common.collect.ImmutableSet;
114 import com.google.common.collect.ListMultimap;
115 import com.google.common.collect.Lists;
116 import com.google.common.collect.MapMaker;
117 import com.google.common.collect.Maps;
118 import com.google.common.collect.Sets;
119 import com.google.protobuf.Message;
120 import com.google.protobuf.RpcCallback;
121 import com.google.protobuf.RpcController;
122 import com.google.protobuf.Service;
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155 @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.CONFIG)
156 public class AccessController extends BaseMasterAndRegionObserver
157 implements RegionServerObserver,
158 AccessControlService.Interface, CoprocessorService, EndpointObserver, BulkLoadObserver {
159
160 public static final Log LOG = LogFactory.getLog(AccessController.class);
161
162 private static final Log AUDITLOG =
163 LogFactory.getLog("SecurityLogger."+AccessController.class.getName());
164 private static final String CHECK_COVERING_PERM = "check_covering_perm";
165 private static final String TAG_CHECK_PASSED = "tag_check_passed";
166 private static final byte[] TRUE = Bytes.toBytes(true);
167
168 TableAuthManager authManager = null;
169
170
171 boolean aclRegion = false;
172
173
174
175 private RegionCoprocessorEnvironment regionEnv;
176
177
178 private Map<InternalScanner,String> scannerOwners =
179 new MapMaker().weakKeys().makeMap();
180
181 private Map<TableName, List<UserPermission>> tableAcls;
182
183
184 private UserProvider userProvider;
185
186
187
188 boolean authorizationEnabled;
189
190
191 boolean cellFeaturesEnabled;
192
193
194 boolean shouldCheckExecPermission;
195
196
197
198 boolean compatibleEarlyTermination;
199
200
201 private volatile boolean initialized = false;
202
203
204 private volatile boolean aclTabAvailable = false;
205
206 public static boolean isAuthorizationSupported(Configuration conf) {
207 return conf.getBoolean(User.HBASE_SECURITY_AUTHORIZATION_CONF_KEY, true);
208 }
209
210 public static boolean isCellAuthorizationSupported(Configuration conf) {
211 return isAuthorizationSupported(conf) &&
212 (HFile.getFormatVersion(conf) >= HFile.MIN_FORMAT_VERSION_WITH_TAGS);
213 }
214
215 public HRegion getRegion() {
216 return regionEnv != null ? regionEnv.getRegion() : null;
217 }
218
219 public TableAuthManager getAuthManager() {
220 return authManager;
221 }
222
223 void initialize(RegionCoprocessorEnvironment e) throws IOException {
224 final HRegion region = e.getRegion();
225 Configuration conf = e.getConfiguration();
226 Map<byte[], ListMultimap<String,TablePermission>> tables =
227 AccessControlLists.loadAll(region);
228
229
230 for (Map.Entry<byte[], ListMultimap<String,TablePermission>> t:
231 tables.entrySet()) {
232 byte[] entry = t.getKey();
233 ListMultimap<String,TablePermission> perms = t.getValue();
234 byte[] serialized = AccessControlLists.writePermissionsAsBytes(perms, conf);
235 this.authManager.getZKPermissionWatcher().writeToZookeeper(entry, serialized);
236 }
237 initialized = true;
238 }
239
240
241
242
243
244
245 void updateACL(RegionCoprocessorEnvironment e,
246 final Map<byte[], List<Cell>> familyMap) {
247 Set<byte[]> entries =
248 new TreeSet<byte[]>(Bytes.BYTES_RAWCOMPARATOR);
249 for (Map.Entry<byte[], List<Cell>> f : familyMap.entrySet()) {
250 List<Cell> cells = f.getValue();
251 for (Cell cell: cells) {
252 KeyValue kv = KeyValueUtil.ensureKeyValue(cell);
253 if (Bytes.equals(kv.getBuffer(), kv.getFamilyOffset(),
254 kv.getFamilyLength(), AccessControlLists.ACL_LIST_FAMILY, 0,
255 AccessControlLists.ACL_LIST_FAMILY.length)) {
256 entries.add(kv.getRow());
257 }
258 }
259 }
260 ZKPermissionWatcher zkw = this.authManager.getZKPermissionWatcher();
261 Configuration conf = regionEnv.getConfiguration();
262 for (byte[] entry: entries) {
263 try {
264 ListMultimap<String,TablePermission> perms =
265 AccessControlLists.getPermissions(conf, entry);
266 byte[] serialized = AccessControlLists.writePermissionsAsBytes(perms, conf);
267 zkw.writeToZookeeper(entry, serialized);
268 } catch (IOException ex) {
269 LOG.error("Failed updating permissions mirror for '" + Bytes.toString(entry) + "'",
270 ex);
271 }
272 }
273 }
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289 AuthResult permissionGranted(String request, User user, Action permRequest,
290 RegionCoprocessorEnvironment e,
291 Map<byte [], ? extends Collection<?>> families) {
292 HRegionInfo hri = e.getRegion().getRegionInfo();
293 TableName tableName = hri.getTable();
294
295
296
297 if (hri.isMetaRegion()) {
298 if (permRequest == Action.READ) {
299 return AuthResult.allow(request, "All users allowed", user,
300 permRequest, tableName, families);
301 }
302 }
303
304 if (user == null) {
305 return AuthResult.deny(request, "No user associated with request!", null,
306 permRequest, tableName, families);
307 }
308
309
310 if (authManager.authorize(user, tableName, (byte[])null, permRequest)) {
311 return AuthResult.allow(request, "Table permission granted", user,
312 permRequest, tableName, families);
313 }
314
315
316 if (families != null && families.size() > 0) {
317
318 for (Map.Entry<byte [], ? extends Collection<?>> family : families.entrySet()) {
319
320 if (authManager.authorize(user, tableName, family.getKey(),
321 permRequest)) {
322 continue;
323 }
324
325
326 if ((family.getValue() != null) && (family.getValue().size() > 0)) {
327 if (family.getValue() instanceof Set) {
328
329 Set<byte[]> familySet = (Set<byte[]>)family.getValue();
330 for (byte[] qualifier : familySet) {
331 if (!authManager.authorize(user, tableName, family.getKey(),
332 qualifier, permRequest)) {
333 return AuthResult.deny(request, "Failed qualifier check", user,
334 permRequest, tableName, makeFamilyMap(family.getKey(), qualifier));
335 }
336 }
337 } else if (family.getValue() instanceof List) {
338 List<KeyValue> kvList = (List<KeyValue>)family.getValue();
339 for (KeyValue kv : kvList) {
340 if (!authManager.authorize(user, tableName, family.getKey(),
341 kv.getQualifier(), permRequest)) {
342 return AuthResult.deny(request, "Failed qualifier check", user,
343 permRequest, tableName, makeFamilyMap(family.getKey(), kv.getQualifier()));
344 }
345 }
346 }
347 } else {
348
349 return AuthResult.deny(request, "Failed family check", user, permRequest,
350 tableName, makeFamilyMap(family.getKey(), null));
351 }
352 }
353
354
355 return AuthResult.allow(request, "All family checks passed", user, permRequest,
356 tableName, families);
357 }
358
359
360 return AuthResult.deny(request, "No families to check and table permission failed",
361 user, permRequest, tableName, families);
362 }
363
364
365
366
367
368
369
370
371
372
373
374
375 AuthResult permissionGranted(OpType opType, User user, RegionCoprocessorEnvironment e,
376 Map<byte [], ? extends Collection<?>> families, Action... actions) {
377 AuthResult result = null;
378 for (Action action: actions) {
379 result = permissionGranted(opType.toString(), user, action, e, families);
380 if (!result.isAllowed()) {
381 return result;
382 }
383 }
384 return result;
385 }
386
387 private void logResult(AuthResult result) {
388 if (AUDITLOG.isTraceEnabled()) {
389 InetAddress remoteAddr = RpcServer.getRemoteAddress();
390 AUDITLOG.trace("Access " + (result.isAllowed() ? "allowed" : "denied") +
391 " for user " + (result.getUser() != null ? result.getUser().getShortName() : "UNKNOWN") +
392 "; reason: " + result.getReason() +
393 "; remote address: " + (remoteAddr != null ? remoteAddr : "") +
394 "; request: " + result.getRequest() +
395 "; context: " + result.toContextString());
396 }
397 }
398
399
400
401
402
403
404 private User getActiveUser() throws IOException {
405 User user = RpcServer.getRequestUser();
406 if (user == null) {
407
408 user = userProvider.getCurrent();
409 }
410 return user;
411 }
412
413
414
415
416
417
418
419
420
421
422 private void requirePermission(String request, TableName tableName, byte[] family,
423 byte[] qualifier, Action... permissions) throws IOException {
424 User user = getActiveUser();
425 AuthResult result = null;
426
427 for (Action permission : permissions) {
428 if (authManager.authorize(user, tableName, family, qualifier, permission)) {
429 result = AuthResult.allow(request, "Table permission granted", user,
430 permission, tableName, family, qualifier);
431 break;
432 } else {
433
434 result = AuthResult.deny(request, "Insufficient permissions", user,
435 permission, tableName, family, qualifier);
436 }
437 }
438 logResult(result);
439 if (authorizationEnabled && !result.isAllowed()) {
440 throw new AccessDeniedException("Insufficient permissions " + result.toContextString());
441 }
442 }
443
444
445
446
447
448
449
450
451
452
453 private void requireTablePermission(String request, TableName tableName, byte[] family,
454 byte[] qualifier, Action... permissions) throws IOException {
455 User user = getActiveUser();
456 AuthResult result = null;
457
458 for (Action permission : permissions) {
459 if (authManager.authorize(user, tableName, null, null, permission)) {
460 result = AuthResult.allow(request, "Table permission granted", user,
461 permission, tableName, null, null);
462 break;
463 } else {
464
465 result = AuthResult.deny(request, "Insufficient permissions", user,
466 permission, tableName, family, qualifier);
467 }
468 }
469 logResult(result);
470 if (authorizationEnabled && !result.isAllowed()) {
471 throw new AccessDeniedException("Insufficient permissions " + result.toContextString());
472 }
473 }
474
475
476
477
478
479
480
481
482
483 private void requireAccess(String request, TableName tableName,
484 Action... permissions) throws IOException {
485 User user = getActiveUser();
486 AuthResult result = null;
487
488 for (Action permission : permissions) {
489 if (authManager.hasAccess(user, tableName, permission)) {
490 result = AuthResult.allow(request, "Table permission granted", user,
491 permission, tableName, null, null);
492 break;
493 } else {
494
495 result = AuthResult.deny(request, "Insufficient permissions", user,
496 permission, tableName, null, null);
497 }
498 }
499 logResult(result);
500 if (authorizationEnabled && !result.isAllowed()) {
501 throw new AccessDeniedException("Insufficient permissions " + result.toContextString());
502 }
503 }
504
505
506
507
508
509
510
511 private void requirePermission(String request, Action perm) throws IOException {
512 requireGlobalPermission(request, perm, null, null);
513 }
514
515
516
517
518
519
520
521
522
523 private void requireGlobalPermission(String request, Action perm, TableName tableName,
524 Map<byte[], ? extends Collection<byte[]>> familyMap) throws IOException {
525 User user = getActiveUser();
526 if (authManager.authorize(user, perm)) {
527 logResult(AuthResult.allow(request, "Global check allowed", user, perm, tableName, familyMap));
528 } else {
529 logResult(AuthResult.deny(request, "Global check failed", user, perm, tableName, familyMap));
530 if (authorizationEnabled) {
531 throw new AccessDeniedException("Insufficient permissions for user '" +
532 (user != null ? user.getShortName() : "null") +"' (global, action=" +
533 perm.toString() + ")");
534 }
535 }
536 }
537
538
539
540
541
542
543
544
545 private void requireGlobalPermission(String request, Action perm,
546 String namespace) throws IOException {
547 User user = getActiveUser();
548 if (authManager.authorize(user, perm)) {
549 logResult(AuthResult.allow(request, "Global check allowed", user, perm, namespace));
550 } else {
551 logResult(AuthResult.deny(request, "Global check failed", user, perm, namespace));
552 if (authorizationEnabled) {
553 throw new AccessDeniedException("Insufficient permissions for user '" +
554 (user != null ? user.getShortName() : "null") +"' (global, action=" +
555 perm.toString() + ")");
556 }
557 }
558 }
559
560
561
562
563
564
565 public void requireNamespacePermission(String request, String namespace,
566 Action... permissions) throws IOException {
567 User user = getActiveUser();
568 AuthResult result = null;
569
570 for (Action permission : permissions) {
571 if (authManager.authorize(user, namespace, permission)) {
572 result = AuthResult.allow(request, "Namespace permission granted",
573 user, permission, namespace);
574 break;
575 } else {
576
577 result = AuthResult.deny(request, "Insufficient permissions", user,
578 permission, namespace);
579 }
580 }
581 logResult(result);
582 if (authorizationEnabled && !result.isAllowed()) {
583 throw new AccessDeniedException("Insufficient permissions "
584 + result.toContextString());
585 }
586 }
587
588
589
590
591
592
593 public void requireNamespacePermission(String request, String namespace, TableName tableName,
594 Map<byte[], ? extends Collection<byte[]>> familyMap, Action... permissions)
595 throws IOException {
596 User user = getActiveUser();
597 AuthResult result = null;
598
599 for (Action permission : permissions) {
600 if (authManager.authorize(user, namespace, permission)) {
601 result = AuthResult.allow(request, "Namespace permission granted",
602 user, permission, namespace);
603 break;
604 } else {
605
606 result = AuthResult.deny(request, "Insufficient permissions", user,
607 permission, namespace);
608 }
609 }
610 logResult(result);
611 if (authorizationEnabled && !result.isAllowed()) {
612 throw new AccessDeniedException("Insufficient permissions "
613 + result.toContextString());
614 }
615 }
616
617
618
619
620
621 private boolean hasFamilyQualifierPermission(User user,
622 Action perm,
623 RegionCoprocessorEnvironment env,
624 Map<byte[], ? extends Collection<byte[]>> familyMap)
625 throws IOException {
626 HRegionInfo hri = env.getRegion().getRegionInfo();
627 TableName tableName = hri.getTable();
628
629 if (user == null) {
630 return false;
631 }
632
633 if (familyMap != null && familyMap.size() > 0) {
634
635 for (Map.Entry<byte[], ? extends Collection<byte[]>> family :
636 familyMap.entrySet()) {
637 if (family.getValue() != null && !family.getValue().isEmpty()) {
638 for (byte[] qualifier : family.getValue()) {
639 if (authManager.matchPermission(user, tableName,
640 family.getKey(), qualifier, perm)) {
641 return true;
642 }
643 }
644 } else {
645 if (authManager.matchPermission(user, tableName, family.getKey(),
646 perm)) {
647 return true;
648 }
649 }
650 }
651 } else if (LOG.isDebugEnabled()) {
652 LOG.debug("Empty family map passed for permission check");
653 }
654
655 return false;
656 }
657
658 private enum OpType {
659 GET_CLOSEST_ROW_BEFORE("getClosestRowBefore"),
660 GET("get"),
661 EXISTS("exists"),
662 SCAN("scan"),
663 PUT("put"),
664 DELETE("delete"),
665 CHECK_AND_PUT("checkAndPut"),
666 CHECK_AND_DELETE("checkAndDelete"),
667 INCREMENT_COLUMN_VALUE("incrementColumnValue"),
668 APPEND("append"),
669 INCREMENT("increment");
670
671 private String type;
672
673 private OpType(String type) {
674 this.type = type;
675 }
676
677 @Override
678 public String toString() {
679 return type;
680 }
681 }
682
683
684
685
686
687
688 private boolean checkCoveringPermission(OpType request, RegionCoprocessorEnvironment e,
689 byte[] row, Map<byte[], ? extends Collection<?>> familyMap, long opTs, Action... actions)
690 throws IOException {
691 if (!cellFeaturesEnabled) {
692 return false;
693 }
694 long cellGrants = 0;
695 User user = getActiveUser();
696 long latestCellTs = 0;
697 Get get = new Get(row);
698
699
700
701
702
703
704
705 boolean considerCellTs = (request == OpType.PUT || request == OpType.DELETE);
706 if (considerCellTs) {
707 get.setMaxVersions();
708 } else {
709 get.setMaxVersions(1);
710 }
711 boolean diffCellTsFromOpTs = false;
712 for (Map.Entry<byte[], ? extends Collection<?>> entry: familyMap.entrySet()) {
713 byte[] col = entry.getKey();
714
715
716 if (entry.getValue() instanceof Set) {
717 Set<byte[]> set = (Set<byte[]>)entry.getValue();
718 if (set == null || set.isEmpty()) {
719 get.addFamily(col);
720 } else {
721 for (byte[] qual: set) {
722 get.addColumn(col, qual);
723 }
724 }
725 } else if (entry.getValue() instanceof List) {
726 List<Cell> list = (List<Cell>)entry.getValue();
727 if (list == null || list.isEmpty()) {
728 get.addFamily(col);
729 } else {
730
731 for (Cell cell : list) {
732 if (cell.getQualifierLength() == 0
733 && (cell.getTypeByte() == Type.DeleteFamily.getCode()
734 || cell.getTypeByte() == Type.DeleteFamilyVersion.getCode())) {
735 get.addFamily(col);
736 } else {
737 get.addColumn(col, CellUtil.cloneQualifier(cell));
738 }
739 if (considerCellTs) {
740 long cellTs = cell.getTimestamp();
741 latestCellTs = Math.max(latestCellTs, cellTs);
742 diffCellTsFromOpTs = diffCellTsFromOpTs || (opTs != cellTs);
743 }
744 }
745 }
746 } else if (entry.getValue() == null) {
747 get.addFamily(col);
748 } else {
749 throw new RuntimeException("Unhandled collection type " +
750 entry.getValue().getClass().getName());
751 }
752 }
753
754
755
756
757
758
759 long latestTs = Math.max(opTs, latestCellTs);
760 if (latestTs == 0 || latestTs == HConstants.LATEST_TIMESTAMP) {
761 latestTs = EnvironmentEdgeManager.currentTimeMillis();
762 }
763 get.setTimeRange(0, latestTs + 1);
764
765
766
767 if (!diffCellTsFromOpTs && request == OpType.PUT) {
768 get.setMaxVersions(1);
769 }
770 if (LOG.isTraceEnabled()) {
771 LOG.trace("Scanning for cells with " + get);
772 }
773
774
775
776 Map<ByteRange, List<Cell>> familyMap1 = new HashMap<ByteRange, List<Cell>>();
777 for (Entry<byte[], ? extends Collection<?>> entry : familyMap.entrySet()) {
778 if (entry.getValue() instanceof List) {
779 familyMap1.put(new SimpleByteRange(entry.getKey()), (List<Cell>) entry.getValue());
780 }
781 }
782 RegionScanner scanner = getRegion(e).getScanner(new Scan(get));
783 List<Cell> cells = Lists.newArrayList();
784 Cell prevCell = null;
785 ByteRange curFam = new SimpleByteRange();
786 boolean curColAllVersions = (request == OpType.DELETE);
787 long curColCheckTs = opTs;
788 boolean foundColumn = false;
789 try {
790 boolean more = false;
791 do {
792 cells.clear();
793
794 more = scanner.next(cells, 1);
795 for (Cell cell: cells) {
796 if (LOG.isTraceEnabled()) {
797 LOG.trace("Found cell " + cell);
798 }
799 boolean colChange = prevCell == null || !CellUtil.matchingColumn(prevCell, cell);
800 if (colChange) foundColumn = false;
801 prevCell = cell;
802 if (!curColAllVersions && foundColumn) {
803 continue;
804 }
805 if (colChange && considerCellTs) {
806 curFam.set(cell.getFamilyArray(), cell.getFamilyOffset(), cell.getFamilyLength());
807 List<Cell> cols = familyMap1.get(curFam);
808 for (Cell col : cols) {
809
810
811
812 if ((col.getQualifierLength() == 0 && request == OpType.DELETE)
813 || CellUtil.matchingQualifier(cell, col)) {
814 byte type = col.getTypeByte();
815 if (considerCellTs) {
816 curColCheckTs = col.getTimestamp();
817 }
818
819
820
821
822 curColAllVersions = (KeyValue.Type.DeleteColumn.getCode() == type)
823 || (KeyValue.Type.DeleteFamily.getCode() == type);
824 break;
825 }
826 }
827 }
828 if (cell.getTimestamp() > curColCheckTs) {
829
830 continue;
831 }
832 foundColumn = true;
833 for (Action action: actions) {
834
835 if (!authManager.authorize(user, getTableName(e), cell, action)) {
836
837 return false;
838 }
839 }
840 cellGrants++;
841 }
842 } while (more);
843 } catch (AccessDeniedException ex) {
844 throw ex;
845 } catch (IOException ex) {
846 LOG.error("Exception while getting cells to calculate covering permission", ex);
847 } finally {
848 scanner.close();
849 }
850
851
852
853 return cellGrants > 0;
854 }
855
856 private static void addCellPermissions(final byte[] perms, Map<byte[], List<Cell>> familyMap) {
857
858
859 for (Map.Entry<byte[], List<Cell>> e: familyMap.entrySet()) {
860 List<Cell> newCells = Lists.newArrayList();
861 for (Cell cell: e.getValue()) {
862
863 List<Tag> tags = Lists.newArrayList(new Tag(AccessControlLists.ACL_TAG_TYPE, perms));
864 if (cell.getTagsLengthUnsigned() > 0) {
865 Iterator<Tag> tagIterator = CellUtil.tagsIterator(cell.getTagsArray(),
866 cell.getTagsOffset(), cell.getTagsLengthUnsigned());
867 while (tagIterator.hasNext()) {
868 tags.add(tagIterator.next());
869 }
870 }
871
872
873 KeyValue kv = KeyValueUtil.ensureKeyValue(cell);
874 byte[] bytes = kv.getBuffer();
875 newCells.add(
876 new KeyValue(bytes, kv.getRowOffset(), kv.getRowLength(),
877 bytes, kv.getFamilyOffset(), kv.getFamilyLength(),
878 bytes, kv.getQualifierOffset(), kv.getQualifierLength(),
879 kv.getTimestamp(), KeyValue.Type.codeToType(kv.getTypeByte()),
880 bytes, kv.getValueOffset(), kv.getValueLength(),
881 tags));
882 }
883
884 e.setValue(newCells);
885 }
886 }
887
888
889
890 private void checkForReservedTagPresence(User user, Mutation m) throws IOException {
891
892 if (!authorizationEnabled) {
893 m.setAttribute(TAG_CHECK_PASSED, TRUE);
894 return;
895 }
896
897 if (Superusers.isSuperUser(user)) {
898 m.setAttribute(TAG_CHECK_PASSED, TRUE);
899 return;
900 }
901
902 if (m.getAttribute(TAG_CHECK_PASSED) != null) {
903 return;
904 }
905 for (CellScanner cellScanner = m.cellScanner(); cellScanner.advance();) {
906 Cell cell = cellScanner.current();
907 if (cell.getTagsLengthUnsigned() > 0) {
908 Iterator<Tag> tagsItr = CellUtil.tagsIterator(cell.getTagsArray(), cell.getTagsOffset(),
909 cell.getTagsLengthUnsigned());
910 while (tagsItr.hasNext()) {
911 if (tagsItr.next().getType() == AccessControlLists.ACL_TAG_TYPE) {
912 throw new AccessDeniedException("Mutation contains cell with reserved type tag");
913 }
914 }
915 }
916 }
917 m.setAttribute(TAG_CHECK_PASSED, TRUE);
918 }
919
920
921 @Override
922 public void start(CoprocessorEnvironment env) throws IOException {
923 CompoundConfiguration conf = new CompoundConfiguration();
924 conf.add(env.getConfiguration());
925
926 authorizationEnabled = isAuthorizationSupported(conf);
927 if (!authorizationEnabled) {
928 LOG.warn("The AccessController has been loaded with authorization checks disabled.");
929 }
930
931 shouldCheckExecPermission = conf.getBoolean(AccessControlConstants.EXEC_PERMISSION_CHECKS_KEY,
932 AccessControlConstants.DEFAULT_EXEC_PERMISSION_CHECKS);
933
934 cellFeaturesEnabled = (HFile.getFormatVersion(conf) >= HFile.MIN_FORMAT_VERSION_WITH_TAGS);
935 if (!cellFeaturesEnabled) {
936 LOG.info("A minimum HFile version of " + HFile.MIN_FORMAT_VERSION_WITH_TAGS
937 + " is required to persist cell ACLs. Consider setting " + HFile.FORMAT_VERSION_KEY
938 + " accordingly.");
939 }
940
941 ZooKeeperWatcher zk = null;
942 if (env instanceof MasterCoprocessorEnvironment) {
943
944 MasterCoprocessorEnvironment mEnv = (MasterCoprocessorEnvironment) env;
945 zk = mEnv.getMasterServices().getZooKeeper();
946 } else if (env instanceof RegionServerCoprocessorEnvironment) {
947 RegionServerCoprocessorEnvironment rsEnv = (RegionServerCoprocessorEnvironment) env;
948 zk = rsEnv.getRegionServerServices().getZooKeeper();
949 } else if (env instanceof RegionCoprocessorEnvironment) {
950
951 regionEnv = (RegionCoprocessorEnvironment) env;
952 conf.addStringMap(regionEnv.getRegion().getTableDesc().getConfiguration());
953 zk = regionEnv.getRegionServerServices().getZooKeeper();
954 compatibleEarlyTermination = conf.getBoolean(AccessControlConstants.CF_ATTRIBUTE_EARLY_OUT,
955 AccessControlConstants.DEFAULT_ATTRIBUTE_EARLY_OUT);
956 }
957
958
959 this.userProvider = UserProvider.instantiate(env.getConfiguration());
960
961
962
963 if (zk != null) {
964 try {
965 this.authManager = TableAuthManager.getOrCreate(zk, env.getConfiguration());
966 } catch (IOException ioe) {
967 throw new RuntimeException("Error obtaining TableAuthManager", ioe);
968 }
969 } else {
970 throw new RuntimeException("Error obtaining TableAuthManager, zk found null.");
971 }
972
973 if(!compatibleEarlyTermination && !cellFeaturesEnabled) {
974 LOG.warn("A minimum HFile version of " + HFile.MIN_FORMAT_VERSION_WITH_TAGS
975 + " is required for " + AccessControlConstants.CF_ATTRIBUTE_EARLY_OUT
976 + " to have an effect");
977 }
978
979 tableAcls = new MapMaker().weakValues().makeMap();
980 }
981
982 @Override
983 public void stop(CoprocessorEnvironment env) {
984 if (this.authManager != null) {
985 TableAuthManager.release(authManager);
986 }
987 }
988
989 @Override
990 public void preTruncateTable(ObserverContext<MasterCoprocessorEnvironment> c,
991 final TableName tableName) throws IOException {
992 requirePermission("truncateTable", tableName, null, null, Action.ADMIN, Action.CREATE);
993 final Configuration conf = c.getEnvironment().getConfiguration();
994 User.runAsLoginUser(new PrivilegedExceptionAction<Void>() {
995 @Override
996 public Void run() throws Exception {
997 List<UserPermission> acls = AccessControlLists.getUserTablePermissions(conf, tableName);
998 if (acls != null) {
999 tableAcls.put(tableName, acls);
1000 }
1001 return null;
1002 }
1003 });
1004 }
1005
1006 @Override
1007 public void postTruncateTable(ObserverContext<MasterCoprocessorEnvironment> ctx,
1008 final TableName tableName) throws IOException {
1009 final Configuration conf = ctx.getEnvironment().getConfiguration();
1010 User.runAsLoginUser(new PrivilegedExceptionAction<Void>() {
1011 @Override
1012 public Void run() throws Exception {
1013 List<UserPermission> perms = tableAcls.get(tableName);
1014 if (perms != null) {
1015 for (UserPermission perm : perms) {
1016 AccessControlLists.addUserPermission(conf, perm);
1017 }
1018 }
1019 tableAcls.remove(tableName);
1020 return null;
1021 }
1022 });
1023 }
1024
1025 @Override
1026 public void preTruncateTableHandler(ObserverContext<MasterCoprocessorEnvironment> c,
1027 TableName tableName) throws IOException {}
1028
1029 @Override
1030 public void postTruncateTableHandler(ObserverContext<MasterCoprocessorEnvironment> c,
1031 TableName tableName) throws IOException {}
1032
1033 @Override
1034 public void preCreateTable(ObserverContext<MasterCoprocessorEnvironment> c,
1035 HTableDescriptor desc, HRegionInfo[] regions) throws IOException {
1036 Set<byte[]> families = desc.getFamiliesKeys();
1037 Map<byte[], Set<byte[]>> familyMap = new TreeMap<byte[], Set<byte[]>>(Bytes.BYTES_COMPARATOR);
1038 for (byte[] family: families) {
1039 familyMap.put(family, null);
1040 }
1041 requireNamespacePermission("createTable", desc.getTableName().getNamespaceAsString(), Action.CREATE);
1042 }
1043
1044 @Override
1045 public void postCreateTableHandler(final ObserverContext<MasterCoprocessorEnvironment> c,
1046 HTableDescriptor desc, HRegionInfo[] regions) throws IOException {
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062 if (AccessControlLists.isAclTable(desc)) {
1063 this.aclTabAvailable = true;
1064 } else if (!(TableName.NAMESPACE_TABLE_NAME.equals(desc.getTableName()))) {
1065 if (!aclTabAvailable) {
1066 LOG.warn("Not adding owner permission for table " + desc.getTableName() + ". "
1067 + AccessControlLists.ACL_TABLE_NAME + " is not yet created. "
1068 + getClass().getSimpleName() + " should be configured as the first Coprocessor");
1069 } else {
1070 String owner = desc.getOwnerString();
1071
1072 if (owner == null)
1073 owner = getActiveUser().getShortName();
1074 final UserPermission userperm = new UserPermission(Bytes.toBytes(owner),
1075 desc.getTableName(), null, Action.values());
1076
1077 User.runAsLoginUser(new PrivilegedExceptionAction<Void>() {
1078 @Override
1079 public Void run() throws Exception {
1080 AccessControlLists.addUserPermission(c.getEnvironment().getConfiguration(),
1081 userperm);
1082 return null;
1083 }
1084 });
1085 }
1086 }
1087 }
1088
1089 @Override
1090 public void preDeleteTable(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName)
1091 throws IOException {
1092 requirePermission("deleteTable", tableName, null, null, Action.ADMIN, Action.CREATE);
1093 }
1094
1095 @Override
1096 public void postDeleteTable(ObserverContext<MasterCoprocessorEnvironment> c,
1097 final TableName tableName) throws IOException {
1098 final Configuration conf = c.getEnvironment().getConfiguration();
1099 User.runAsLoginUser(new PrivilegedExceptionAction<Void>() {
1100 @Override
1101 public Void run() throws Exception {
1102 AccessControlLists.removeTablePermissions(conf, tableName);
1103 return null;
1104 }
1105 });
1106 this.authManager.getZKPermissionWatcher().deleteTableACLNode(tableName);
1107 }
1108
1109 @Override
1110 public void preModifyTable(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName,
1111 HTableDescriptor htd) throws IOException {
1112 requirePermission("modifyTable", tableName, null, null, Action.ADMIN, Action.CREATE);
1113 }
1114
1115 @Override
1116 public void postModifyTable(ObserverContext<MasterCoprocessorEnvironment> c,
1117 TableName tableName, final HTableDescriptor htd) throws IOException {
1118 final Configuration conf = c.getEnvironment().getConfiguration();
1119
1120 final String owner = (htd.getOwnerString() != null) ? htd.getOwnerString() :
1121 getActiveUser().getShortName();
1122 User.runAsLoginUser(new PrivilegedExceptionAction<Void>() {
1123 @Override
1124 public Void run() throws Exception {
1125 UserPermission userperm = new UserPermission(Bytes.toBytes(owner),
1126 htd.getTableName(), null, Action.values());
1127 AccessControlLists.addUserPermission(conf, userperm);
1128 return null;
1129 }
1130 });
1131 }
1132
1133 @Override
1134 public void preAddColumn(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName,
1135 HColumnDescriptor column) throws IOException {
1136 requirePermission("addColumn", tableName, null, null, Action.ADMIN, Action.CREATE);
1137 }
1138
1139 @Override
1140 public void preModifyColumn(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName,
1141 HColumnDescriptor descriptor) throws IOException {
1142 requirePermission("modifyColumn", tableName, descriptor.getName(), null, Action.ADMIN,
1143 Action.CREATE);
1144 }
1145
1146 @Override
1147 public void preDeleteColumn(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName,
1148 byte[] col) throws IOException {
1149 requirePermission("deleteColumn", tableName, col, null, Action.ADMIN, Action.CREATE);
1150 }
1151
1152 @Override
1153 public void postDeleteColumn(ObserverContext<MasterCoprocessorEnvironment> c,
1154 final TableName tableName, final byte[] col) throws IOException {
1155 final Configuration conf = c.getEnvironment().getConfiguration();
1156 User.runAsLoginUser(new PrivilegedExceptionAction<Void>() {
1157 @Override
1158 public Void run() throws Exception {
1159 AccessControlLists.removeTablePermissions(conf, tableName, col);
1160 return null;
1161 }
1162 });
1163 }
1164
1165 @Override
1166 public void preEnableTable(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName)
1167 throws IOException {
1168 requirePermission("enableTable", tableName, null, null, Action.ADMIN, Action.CREATE);
1169 }
1170
1171 @Override
1172 public void preDisableTable(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName)
1173 throws IOException {
1174 if (Bytes.equals(tableName.getName(), AccessControlLists.ACL_GLOBAL_NAME)) {
1175
1176
1177
1178
1179 throw new AccessDeniedException("Not allowed to disable "
1180 + AccessControlLists.ACL_TABLE_NAME + " table with AccessController installed");
1181 }
1182 requirePermission("disableTable", tableName, null, null, Action.ADMIN, Action.CREATE);
1183 }
1184
1185 @Override
1186 public void preMove(ObserverContext<MasterCoprocessorEnvironment> c, HRegionInfo region,
1187 ServerName srcServer, ServerName destServer) throws IOException {
1188 requirePermission("move", region.getTable(), null, null, Action.ADMIN);
1189 }
1190
1191 @Override
1192 public void preAssign(ObserverContext<MasterCoprocessorEnvironment> c, HRegionInfo regionInfo)
1193 throws IOException {
1194 requirePermission("assign", regionInfo.getTable(), null, null, Action.ADMIN);
1195 }
1196
1197 @Override
1198 public void preUnassign(ObserverContext<MasterCoprocessorEnvironment> c, HRegionInfo regionInfo,
1199 boolean force) throws IOException {
1200 requirePermission("unassign", regionInfo.getTable(), null, null, Action.ADMIN);
1201 }
1202
1203 @Override
1204 public void preRegionOffline(ObserverContext<MasterCoprocessorEnvironment> c,
1205 HRegionInfo regionInfo) throws IOException {
1206 requirePermission("regionOffline", regionInfo.getTable(), null, null, Action.ADMIN);
1207 }
1208
1209 @Override
1210 public void preBalance(ObserverContext<MasterCoprocessorEnvironment> c)
1211 throws IOException {
1212 requirePermission("balance", Action.ADMIN);
1213 }
1214
1215 @Override
1216 public boolean preBalanceSwitch(ObserverContext<MasterCoprocessorEnvironment> c,
1217 boolean newValue) throws IOException {
1218 requirePermission("balanceSwitch", Action.ADMIN);
1219 return newValue;
1220 }
1221
1222 @Override
1223 public void preShutdown(ObserverContext<MasterCoprocessorEnvironment> c)
1224 throws IOException {
1225 requirePermission("shutdown", Action.ADMIN);
1226 }
1227
1228 @Override
1229 public void preStopMaster(ObserverContext<MasterCoprocessorEnvironment> c)
1230 throws IOException {
1231 requirePermission("stopMaster", Action.ADMIN);
1232 }
1233
1234 @Override
1235 public void postStartMaster(ObserverContext<MasterCoprocessorEnvironment> ctx)
1236 throws IOException {
1237 if (!MetaReader.tableExists(ctx.getEnvironment().getMasterServices().getCatalogTracker(),
1238 AccessControlLists.ACL_TABLE_NAME)) {
1239
1240 AccessControlLists.createACLTable(ctx.getEnvironment().getMasterServices());
1241 } else {
1242 aclTabAvailable = true;
1243 }
1244 }
1245
1246 @Override
1247 public void preSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
1248 final SnapshotDescription snapshot, final HTableDescriptor hTableDescriptor)
1249 throws IOException {
1250 requirePermission("snapshot", Action.ADMIN);
1251 }
1252
1253 @Override
1254 public void preCloneSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
1255 final SnapshotDescription snapshot, final HTableDescriptor hTableDescriptor)
1256 throws IOException {
1257 requirePermission("clone", Action.ADMIN);
1258 }
1259
1260 @Override
1261 public void preRestoreSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
1262 final SnapshotDescription snapshot, final HTableDescriptor hTableDescriptor)
1263 throws IOException {
1264 requirePermission("restore", Action.ADMIN);
1265 }
1266
1267 @Override
1268 public void preDeleteSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx,
1269 final SnapshotDescription snapshot) throws IOException {
1270 requirePermission("deleteSnapshot", Action.ADMIN);
1271 }
1272
1273 @Override
1274 public void preCreateNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx,
1275 NamespaceDescriptor ns) throws IOException {
1276 requireGlobalPermission("createNamespace", Action.ADMIN, ns.getName());
1277 }
1278
1279 @Override
1280 public void preDeleteNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx, String namespace)
1281 throws IOException {
1282 requireGlobalPermission("deleteNamespace", Action.ADMIN, namespace);
1283 }
1284
1285 @Override
1286 public void postDeleteNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx,
1287 final String namespace) throws IOException {
1288 final Configuration conf = ctx.getEnvironment().getConfiguration();
1289 User.runAsLoginUser(new PrivilegedExceptionAction<Void>() {
1290 @Override
1291 public Void run() throws Exception {
1292 AccessControlLists.removeNamespacePermissions(conf, namespace);
1293 return null;
1294 }
1295 });
1296 this.authManager.getZKPermissionWatcher().deleteNamespaceACLNode(namespace);
1297 LOG.info(namespace + " entry deleted in " + AccessControlLists.ACL_TABLE_NAME + " table.");
1298 }
1299
1300 @Override
1301 public void preModifyNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx,
1302 NamespaceDescriptor ns) throws IOException {
1303
1304
1305 requireGlobalPermission("modifyNamespace", Action.ADMIN, ns.getName());
1306 }
1307
1308 @Override
1309 public void preGetNamespaceDescriptor(ObserverContext<MasterCoprocessorEnvironment> ctx, String namespace)
1310 throws IOException {
1311 requireNamespacePermission("getNamespaceDescriptor", namespace, Action.ADMIN);
1312 }
1313
1314 @Override
1315 public void postListNamespaceDescriptors(ObserverContext<MasterCoprocessorEnvironment> ctx,
1316 List<NamespaceDescriptor> descriptors) throws IOException {
1317
1318
1319 Iterator<NamespaceDescriptor> itr = descriptors.iterator();
1320 while (itr.hasNext()) {
1321 NamespaceDescriptor desc = itr.next();
1322 try {
1323 requireNamespacePermission("listNamespaces", desc.getName(), Action.ADMIN);
1324 } catch (AccessDeniedException e) {
1325 itr.remove();
1326 }
1327 }
1328 }
1329
1330
1331
1332 @Override
1333 public void preOpen(ObserverContext<RegionCoprocessorEnvironment> e)
1334 throws IOException {
1335 RegionCoprocessorEnvironment env = e.getEnvironment();
1336 final HRegion region = env.getRegion();
1337 if (region == null) {
1338 LOG.error("NULL region from RegionCoprocessorEnvironment in preOpen()");
1339 } else {
1340 HRegionInfo regionInfo = region.getRegionInfo();
1341 if (regionInfo.getTable().isSystemTable()) {
1342 checkSystemOrSuperUser();
1343 } else {
1344 requirePermission("preOpen", Action.ADMIN);
1345 }
1346 }
1347 }
1348
1349 @Override
1350 public void postOpen(ObserverContext<RegionCoprocessorEnvironment> c) {
1351 RegionCoprocessorEnvironment env = c.getEnvironment();
1352 final HRegion region = env.getRegion();
1353 if (region == null) {
1354 LOG.error("NULL region from RegionCoprocessorEnvironment in postOpen()");
1355 return;
1356 }
1357 if (AccessControlLists.isAclRegion(region)) {
1358 aclRegion = true;
1359
1360 if (!region.isRecovering()) {
1361 try {
1362 initialize(env);
1363 } catch (IOException ex) {
1364
1365
1366 throw new RuntimeException("Failed to initialize permissions cache", ex);
1367 }
1368 }
1369 } else {
1370 initialized = true;
1371 }
1372 }
1373
1374 @Override
1375 public void postLogReplay(ObserverContext<RegionCoprocessorEnvironment> c) {
1376 if (aclRegion) {
1377 try {
1378 initialize(c.getEnvironment());
1379 } catch (IOException ex) {
1380
1381
1382 throw new RuntimeException("Failed to initialize permissions cache", ex);
1383 }
1384 }
1385 }
1386
1387 @Override
1388 public void preFlush(ObserverContext<RegionCoprocessorEnvironment> e) throws IOException {
1389 requirePermission("flush", getTableName(e.getEnvironment()), null, null, Action.ADMIN,
1390 Action.CREATE);
1391 }
1392
1393 @Override
1394 public void preSplit(ObserverContext<RegionCoprocessorEnvironment> e) throws IOException {
1395 requirePermission("split", getTableName(e.getEnvironment()), null, null, Action.ADMIN);
1396 }
1397
1398 @Override
1399 public void preSplit(ObserverContext<RegionCoprocessorEnvironment> e,
1400 byte[] splitRow) throws IOException {
1401 requirePermission("split", getTableName(e.getEnvironment()), null, null, Action.ADMIN);
1402 }
1403
1404 @Override
1405 public InternalScanner preCompact(ObserverContext<RegionCoprocessorEnvironment> e,
1406 final Store store, final InternalScanner scanner, final ScanType scanType)
1407 throws IOException {
1408 requirePermission("compact", getTableName(e.getEnvironment()), null, null, Action.ADMIN,
1409 Action.CREATE);
1410 return scanner;
1411 }
1412
1413 @Override
1414 public void preGetClosestRowBefore(final ObserverContext<RegionCoprocessorEnvironment> c,
1415 final byte [] row, final byte [] family, final Result result)
1416 throws IOException {
1417 assert family != null;
1418 RegionCoprocessorEnvironment env = c.getEnvironment();
1419 Map<byte[],? extends Collection<byte[]>> families = makeFamilyMap(family, null);
1420 User user = getActiveUser();
1421 AuthResult authResult = permissionGranted(OpType.GET_CLOSEST_ROW_BEFORE, user, env, families,
1422 Action.READ);
1423 if (!authResult.isAllowed() && cellFeaturesEnabled && !compatibleEarlyTermination) {
1424 authResult.setAllowed(checkCoveringPermission(OpType.GET_CLOSEST_ROW_BEFORE, env, row,
1425 families, HConstants.LATEST_TIMESTAMP, Action.READ));
1426 authResult.setReason("Covering cell set");
1427 }
1428 logResult(authResult);
1429 if (authorizationEnabled && !authResult.isAllowed()) {
1430 throw new AccessDeniedException("Insufficient permissions " +
1431 authResult.toContextString());
1432 }
1433 }
1434
1435 private void internalPreRead(final ObserverContext<RegionCoprocessorEnvironment> c,
1436 final Query query, OpType opType) throws IOException {
1437 Filter filter = query.getFilter();
1438
1439 if (filter != null && filter instanceof AccessControlFilter) {
1440 return;
1441 }
1442 User user = getActiveUser();
1443 RegionCoprocessorEnvironment env = c.getEnvironment();
1444 Map<byte[],? extends Collection<byte[]>> families = null;
1445 switch (opType) {
1446 case GET:
1447 case EXISTS:
1448 families = ((Get)query).getFamilyMap();
1449 break;
1450 case SCAN:
1451 families = ((Scan)query).getFamilyMap();
1452 break;
1453 default:
1454 throw new RuntimeException("Unhandled operation " + opType);
1455 }
1456 AuthResult authResult = permissionGranted(opType, user, env, families, Action.READ);
1457 HRegion region = getRegion(env);
1458 TableName table = getTableName(region);
1459 Map<ByteRange, Integer> cfVsMaxVersions = Maps.newHashMap();
1460 for (HColumnDescriptor hcd : region.getTableDesc().getFamilies()) {
1461 cfVsMaxVersions.put(new SimpleByteRange(hcd.getName()), hcd.getMaxVersions());
1462 }
1463 if (!authResult.isAllowed()) {
1464 if (!cellFeaturesEnabled || compatibleEarlyTermination) {
1465
1466
1467
1468
1469
1470
1471
1472
1473 if (hasFamilyQualifierPermission(user, Action.READ, env, families)) {
1474 authResult.setAllowed(true);
1475 authResult.setReason("Access allowed with filter");
1476
1477 if (authorizationEnabled) {
1478 Filter ourFilter = new AccessControlFilter(authManager, user, table,
1479 AccessControlFilter.Strategy.CHECK_TABLE_AND_CF_ONLY,
1480 cfVsMaxVersions);
1481
1482 if (filter != null) {
1483 ourFilter = new FilterList(FilterList.Operator.MUST_PASS_ALL,
1484 Lists.newArrayList(ourFilter, filter));
1485 }
1486 switch (opType) {
1487 case GET:
1488 case EXISTS:
1489 ((Get)query).setFilter(ourFilter);
1490 break;
1491 case SCAN:
1492 ((Scan)query).setFilter(ourFilter);
1493 break;
1494 default:
1495 throw new RuntimeException("Unhandled operation " + opType);
1496 }
1497 }
1498 }
1499 } else {
1500
1501
1502
1503
1504 authResult.setAllowed(true);
1505 authResult.setReason("Access allowed with filter");
1506
1507 if (authorizationEnabled) {
1508 Filter ourFilter = new AccessControlFilter(authManager, user, table,
1509 AccessControlFilter.Strategy.CHECK_CELL_DEFAULT, cfVsMaxVersions);
1510
1511 if (filter != null) {
1512 ourFilter = new FilterList(FilterList.Operator.MUST_PASS_ALL,
1513 Lists.newArrayList(ourFilter, filter));
1514 }
1515 switch (opType) {
1516 case GET:
1517 case EXISTS:
1518 ((Get)query).setFilter(ourFilter);
1519 break;
1520 case SCAN:
1521 ((Scan)query).setFilter(ourFilter);
1522 break;
1523 default:
1524 throw new RuntimeException("Unhandled operation " + opType);
1525 }
1526 }
1527 }
1528 }
1529
1530 logResult(authResult);
1531 if (authorizationEnabled && !authResult.isAllowed()) {
1532 throw new AccessDeniedException("Insufficient permissions for user '"
1533 + (user != null ? user.getShortName() : "null")
1534 + "' (table=" + table + ", action=READ)");
1535 }
1536 }
1537
1538 @Override
1539 public void preGetOp(final ObserverContext<RegionCoprocessorEnvironment> c,
1540 final Get get, final List<Cell> result) throws IOException {
1541 internalPreRead(c, get, OpType.GET);
1542 }
1543
1544 @Override
1545 public boolean preExists(final ObserverContext<RegionCoprocessorEnvironment> c,
1546 final Get get, final boolean exists) throws IOException {
1547 internalPreRead(c, get, OpType.EXISTS);
1548 return exists;
1549 }
1550
1551 @Override
1552 public void prePut(final ObserverContext<RegionCoprocessorEnvironment> c,
1553 final Put put, final WALEdit edit, final Durability durability)
1554 throws IOException {
1555 User user = getActiveUser();
1556 checkForReservedTagPresence(user, put);
1557
1558
1559
1560
1561
1562
1563
1564 RegionCoprocessorEnvironment env = c.getEnvironment();
1565 Map<byte[],? extends Collection<Cell>> families = put.getFamilyCellMap();
1566 AuthResult authResult = permissionGranted(OpType.PUT, user, env, families, Action.WRITE);
1567 logResult(authResult);
1568 if (!authResult.isAllowed()) {
1569 if (cellFeaturesEnabled && !compatibleEarlyTermination) {
1570 put.setAttribute(CHECK_COVERING_PERM, TRUE);
1571 } else if (authorizationEnabled) {
1572 throw new AccessDeniedException("Insufficient permissions " + authResult.toContextString());
1573 }
1574 }
1575
1576
1577 byte[] bytes = put.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL);
1578 if (bytes != null) {
1579 if (cellFeaturesEnabled) {
1580 addCellPermissions(bytes, put.getFamilyCellMap());
1581 } else {
1582 throw new DoNotRetryIOException("Cell ACLs cannot be persisted");
1583 }
1584 }
1585 }
1586
1587 @Override
1588 public void postPut(final ObserverContext<RegionCoprocessorEnvironment> c,
1589 final Put put, final WALEdit edit, final Durability durability) {
1590 if (aclRegion) {
1591 updateACL(c.getEnvironment(), put.getFamilyCellMap());
1592 }
1593 }
1594
1595 @Override
1596 public void preDelete(final ObserverContext<RegionCoprocessorEnvironment> c,
1597 final Delete delete, final WALEdit edit, final Durability durability)
1598 throws IOException {
1599
1600 if (delete.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL) != null) {
1601 throw new DoNotRetryIOException("ACL on delete has no effect: " + delete.toString());
1602 }
1603
1604
1605
1606
1607
1608 RegionCoprocessorEnvironment env = c.getEnvironment();
1609 Map<byte[],? extends Collection<Cell>> families = delete.getFamilyCellMap();
1610 User user = getActiveUser();
1611 AuthResult authResult = permissionGranted(OpType.DELETE, user, env, families, Action.WRITE);
1612 logResult(authResult);
1613 if (!authResult.isAllowed()) {
1614 if (cellFeaturesEnabled && !compatibleEarlyTermination) {
1615 delete.setAttribute(CHECK_COVERING_PERM, TRUE);
1616 } else if (authorizationEnabled) {
1617 throw new AccessDeniedException("Insufficient permissions " +
1618 authResult.toContextString());
1619 }
1620 }
1621 }
1622
1623 @Override
1624 public void preBatchMutate(ObserverContext<RegionCoprocessorEnvironment> c,
1625 MiniBatchOperationInProgress<Mutation> miniBatchOp) throws IOException {
1626 if (cellFeaturesEnabled && !compatibleEarlyTermination) {
1627 TableName table = c.getEnvironment().getRegion().getRegionInfo().getTable();
1628 for (int i = 0; i < miniBatchOp.size(); i++) {
1629 Mutation m = miniBatchOp.getOperation(i);
1630 if (m.getAttribute(CHECK_COVERING_PERM) != null) {
1631
1632
1633 OpType opType;
1634 if (m instanceof Put) {
1635 checkForReservedTagPresence(getActiveUser(), m);
1636 opType = OpType.PUT;
1637 } else {
1638 opType = OpType.DELETE;
1639 }
1640 AuthResult authResult = null;
1641 if (checkCoveringPermission(opType, c.getEnvironment(), m.getRow(),
1642 m.getFamilyCellMap(), m.getTimeStamp(), Action.WRITE)) {
1643 authResult = AuthResult.allow(opType.toString(), "Covering cell set",
1644 getActiveUser(), Action.WRITE, table, m.getFamilyCellMap());
1645 } else {
1646 authResult = AuthResult.deny(opType.toString(), "Covering cell set",
1647 getActiveUser(), Action.WRITE, table, m.getFamilyCellMap());
1648 }
1649 logResult(authResult);
1650 if (authorizationEnabled && !authResult.isAllowed()) {
1651 throw new AccessDeniedException("Insufficient permissions "
1652 + authResult.toContextString());
1653 }
1654 }
1655 }
1656 }
1657 }
1658
1659 @Override
1660 public void postDelete(final ObserverContext<RegionCoprocessorEnvironment> c,
1661 final Delete delete, final WALEdit edit, final Durability durability)
1662 throws IOException {
1663 if (aclRegion) {
1664 updateACL(c.getEnvironment(), delete.getFamilyCellMap());
1665 }
1666 }
1667
1668 @Override
1669 public boolean preCheckAndPut(final ObserverContext<RegionCoprocessorEnvironment> c,
1670 final byte [] row, final byte [] family, final byte [] qualifier,
1671 final CompareFilter.CompareOp compareOp,
1672 final ByteArrayComparable comparator, final Put put,
1673 final boolean result) throws IOException {
1674 User user = getActiveUser();
1675 checkForReservedTagPresence(user, put);
1676
1677
1678 RegionCoprocessorEnvironment env = c.getEnvironment();
1679 Map<byte[],? extends Collection<byte[]>> families = makeFamilyMap(family, qualifier);
1680 AuthResult authResult = permissionGranted(OpType.CHECK_AND_PUT, user, env, families,
1681 Action.READ, Action.WRITE);
1682 logResult(authResult);
1683 if (!authResult.isAllowed()) {
1684 if (cellFeaturesEnabled && !compatibleEarlyTermination) {
1685 put.setAttribute(CHECK_COVERING_PERM, TRUE);
1686 } else if (authorizationEnabled) {
1687 throw new AccessDeniedException("Insufficient permissions " +
1688 authResult.toContextString());
1689 }
1690 }
1691
1692 byte[] bytes = put.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL);
1693 if (bytes != null) {
1694 if (cellFeaturesEnabled) {
1695 addCellPermissions(bytes, put.getFamilyCellMap());
1696 } else {
1697 throw new DoNotRetryIOException("Cell ACLs cannot be persisted");
1698 }
1699 }
1700 return result;
1701 }
1702
1703 @Override
1704 public boolean preCheckAndPutAfterRowLock(final ObserverContext<RegionCoprocessorEnvironment> c,
1705 final byte[] row, final byte[] family, final byte[] qualifier,
1706 final CompareFilter.CompareOp compareOp, final ByteArrayComparable comparator, final Put put,
1707 final boolean result) throws IOException {
1708 if (put.getAttribute(CHECK_COVERING_PERM) != null) {
1709
1710
1711 TableName table = c.getEnvironment().getRegion().getRegionInfo().getTable();
1712 Map<byte[], ? extends Collection<byte[]>> families = makeFamilyMap(family, qualifier);
1713 AuthResult authResult = null;
1714 if (checkCoveringPermission(OpType.CHECK_AND_PUT, c.getEnvironment(), row, families,
1715 HConstants.LATEST_TIMESTAMP, Action.READ)) {
1716 authResult = AuthResult.allow(OpType.CHECK_AND_PUT.toString(), "Covering cell set",
1717 getActiveUser(), Action.READ, table, families);
1718 } else {
1719 authResult = AuthResult.deny(OpType.CHECK_AND_PUT.toString(), "Covering cell set",
1720 getActiveUser(), Action.READ, table, families);
1721 }
1722 logResult(authResult);
1723 if (authorizationEnabled && !authResult.isAllowed()) {
1724 throw new AccessDeniedException("Insufficient permissions " + authResult.toContextString());
1725 }
1726 }
1727 return result;
1728 }
1729
1730 @Override
1731 public boolean preCheckAndDelete(final ObserverContext<RegionCoprocessorEnvironment> c,
1732 final byte [] row, final byte [] family, final byte [] qualifier,
1733 final CompareFilter.CompareOp compareOp,
1734 final ByteArrayComparable comparator, final Delete delete,
1735 final boolean result) throws IOException {
1736
1737 if (delete.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL) != null) {
1738 throw new DoNotRetryIOException("ACL on checkAndDelete has no effect: " +
1739 delete.toString());
1740 }
1741
1742
1743 RegionCoprocessorEnvironment env = c.getEnvironment();
1744 Map<byte[],? extends Collection<byte[]>> families = makeFamilyMap(family, qualifier);
1745 User user = getActiveUser();
1746 AuthResult authResult = permissionGranted(OpType.CHECK_AND_DELETE, user, env, families,
1747 Action.READ, Action.WRITE);
1748 logResult(authResult);
1749 if (!authResult.isAllowed()) {
1750 if (cellFeaturesEnabled && !compatibleEarlyTermination) {
1751 delete.setAttribute(CHECK_COVERING_PERM, TRUE);
1752 } else if (authorizationEnabled) {
1753 throw new AccessDeniedException("Insufficient permissions " +
1754 authResult.toContextString());
1755 }
1756 }
1757 return result;
1758 }
1759
1760 @Override
1761 public boolean preCheckAndDeleteAfterRowLock(
1762 final ObserverContext<RegionCoprocessorEnvironment> c, final byte[] row, final byte[] family,
1763 final byte[] qualifier, final CompareFilter.CompareOp compareOp,
1764 final ByteArrayComparable comparator, final Delete delete, final boolean result)
1765 throws IOException {
1766 if (delete.getAttribute(CHECK_COVERING_PERM) != null) {
1767
1768
1769 TableName table = c.getEnvironment().getRegion().getRegionInfo().getTable();
1770 Map<byte[], ? extends Collection<byte[]>> families = makeFamilyMap(family, qualifier);
1771 AuthResult authResult = null;
1772 if (checkCoveringPermission(OpType.CHECK_AND_DELETE, c.getEnvironment(), row, families,
1773 HConstants.LATEST_TIMESTAMP, Action.READ)) {
1774 authResult = AuthResult.allow(OpType.CHECK_AND_DELETE.toString(), "Covering cell set",
1775 getActiveUser(), Action.READ, table, families);
1776 } else {
1777 authResult = AuthResult.deny(OpType.CHECK_AND_DELETE.toString(), "Covering cell set",
1778 getActiveUser(), Action.READ, table, families);
1779 }
1780 logResult(authResult);
1781 if (authorizationEnabled && !authResult.isAllowed()) {
1782 throw new AccessDeniedException("Insufficient permissions " + authResult.toContextString());
1783 }
1784 }
1785 return result;
1786 }
1787
1788 @Override
1789 public long preIncrementColumnValue(final ObserverContext<RegionCoprocessorEnvironment> c,
1790 final byte [] row, final byte [] family, final byte [] qualifier,
1791 final long amount, final boolean writeToWAL)
1792 throws IOException {
1793
1794
1795 RegionCoprocessorEnvironment env = c.getEnvironment();
1796 Map<byte[],? extends Collection<byte[]>> families = makeFamilyMap(family, qualifier);
1797 User user = getActiveUser();
1798 AuthResult authResult = permissionGranted(OpType.INCREMENT_COLUMN_VALUE, user, env, families,
1799 Action.WRITE);
1800 if (!authResult.isAllowed() && cellFeaturesEnabled && !compatibleEarlyTermination) {
1801 authResult.setAllowed(checkCoveringPermission(OpType.INCREMENT_COLUMN_VALUE, env, row,
1802 families, HConstants.LATEST_TIMESTAMP, Action.WRITE));
1803 authResult.setReason("Covering cell set");
1804 }
1805 logResult(authResult);
1806 if (authorizationEnabled && !authResult.isAllowed()) {
1807 throw new AccessDeniedException("Insufficient permissions " + authResult.toContextString());
1808 }
1809 return -1;
1810 }
1811
1812 @Override
1813 public Result preAppend(ObserverContext<RegionCoprocessorEnvironment> c, Append append)
1814 throws IOException {
1815 User user = getActiveUser();
1816 checkForReservedTagPresence(user, append);
1817
1818
1819 RegionCoprocessorEnvironment env = c.getEnvironment();
1820 Map<byte[],? extends Collection<Cell>> families = append.getFamilyCellMap();
1821 AuthResult authResult = permissionGranted(OpType.APPEND, user, env, families, Action.WRITE);
1822 logResult(authResult);
1823 if (!authResult.isAllowed()) {
1824 if (cellFeaturesEnabled && !compatibleEarlyTermination) {
1825 append.setAttribute(CHECK_COVERING_PERM, TRUE);
1826 } else if (authorizationEnabled) {
1827 throw new AccessDeniedException("Insufficient permissions " +
1828 authResult.toContextString());
1829 }
1830 }
1831
1832 byte[] bytes = append.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL);
1833 if (bytes != null) {
1834 if (cellFeaturesEnabled) {
1835 addCellPermissions(bytes, append.getFamilyCellMap());
1836 } else {
1837 throw new DoNotRetryIOException("Cell ACLs cannot be persisted");
1838 }
1839 }
1840
1841 return null;
1842 }
1843
1844 @Override
1845 public Result preAppendAfterRowLock(final ObserverContext<RegionCoprocessorEnvironment> c,
1846 final Append append) throws IOException {
1847 if (append.getAttribute(CHECK_COVERING_PERM) != null) {
1848
1849
1850 TableName table = c.getEnvironment().getRegion().getRegionInfo().getTable();
1851 AuthResult authResult = null;
1852 if (checkCoveringPermission(OpType.APPEND, c.getEnvironment(), append.getRow(),
1853 append.getFamilyCellMap(), HConstants.LATEST_TIMESTAMP, Action.WRITE)) {
1854 authResult = AuthResult.allow(OpType.APPEND.toString(), "Covering cell set",
1855 getActiveUser(), Action.WRITE, table, append.getFamilyCellMap());
1856 } else {
1857 authResult = AuthResult.deny(OpType.APPEND.toString(), "Covering cell set",
1858 getActiveUser(), Action.WRITE, table, append.getFamilyCellMap());
1859 }
1860 logResult(authResult);
1861 if (authorizationEnabled && !authResult.isAllowed()) {
1862 throw new AccessDeniedException("Insufficient permissions " +
1863 authResult.toContextString());
1864 }
1865 }
1866 return null;
1867 }
1868
1869 @Override
1870 public Result preIncrement(final ObserverContext<RegionCoprocessorEnvironment> c,
1871 final Increment increment)
1872 throws IOException {
1873 User user = getActiveUser();
1874 checkForReservedTagPresence(user, increment);
1875
1876
1877
1878 RegionCoprocessorEnvironment env = c.getEnvironment();
1879 Map<byte[],? extends Collection<Cell>> families = increment.getFamilyCellMap();
1880 AuthResult authResult = permissionGranted(OpType.INCREMENT, user, env, families,
1881 Action.WRITE);
1882 logResult(authResult);
1883 if (!authResult.isAllowed()) {
1884 if (cellFeaturesEnabled && !compatibleEarlyTermination) {
1885 increment.setAttribute(CHECK_COVERING_PERM, TRUE);
1886 } else if (authorizationEnabled) {
1887 throw new AccessDeniedException("Insufficient permissions " +
1888 authResult.toContextString());
1889 }
1890 }
1891
1892 byte[] bytes = increment.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL);
1893 if (bytes != null) {
1894 if (cellFeaturesEnabled) {
1895 addCellPermissions(bytes, increment.getFamilyCellMap());
1896 } else {
1897 throw new DoNotRetryIOException("Cell ACLs cannot be persisted");
1898 }
1899 }
1900
1901 return null;
1902 }
1903
1904 @Override
1905 public Result preIncrementAfterRowLock(final ObserverContext<RegionCoprocessorEnvironment> c,
1906 final Increment increment) throws IOException {
1907 if (increment.getAttribute(CHECK_COVERING_PERM) != null) {
1908
1909
1910 TableName table = c.getEnvironment().getRegion().getRegionInfo().getTable();
1911 AuthResult authResult = null;
1912 if (checkCoveringPermission(OpType.INCREMENT, c.getEnvironment(), increment.getRow(),
1913 increment.getFamilyCellMap(), increment.getTimeRange().getMax(), Action.WRITE)) {
1914 authResult = AuthResult.allow(OpType.INCREMENT.toString(), "Covering cell set",
1915 getActiveUser(), Action.WRITE, table, increment.getFamilyCellMap());
1916 } else {
1917 authResult = AuthResult.deny(OpType.INCREMENT.toString(), "Covering cell set",
1918 getActiveUser(), Action.WRITE, table, increment.getFamilyCellMap());
1919 }
1920 logResult(authResult);
1921 if (authorizationEnabled && !authResult.isAllowed()) {
1922 throw new AccessDeniedException("Insufficient permissions " +
1923 authResult.toContextString());
1924 }
1925 }
1926 return null;
1927 }
1928
1929 @Override
1930 public Cell postMutationBeforeWAL(ObserverContext<RegionCoprocessorEnvironment> ctx,
1931 MutationType opType, Mutation mutation, Cell oldCell, Cell newCell) throws IOException {
1932
1933
1934 if (!cellFeaturesEnabled) {
1935 return newCell;
1936 }
1937
1938
1939 List<Tag> tags = Lists.newArrayList();
1940 ListMultimap<String,Permission> perms = ArrayListMultimap.create();
1941 if (oldCell != null) {
1942
1943 if (oldCell.getTagsLengthUnsigned() > 0) {
1944 Iterator<Tag> tagIterator = CellUtil.tagsIterator(oldCell.getTagsArray(),
1945 oldCell.getTagsOffset(), oldCell.getTagsLengthUnsigned());
1946 while (tagIterator.hasNext()) {
1947 Tag tag = tagIterator.next();
1948 if (tag.getType() != AccessControlLists.ACL_TAG_TYPE) {
1949
1950 if (LOG.isTraceEnabled()) {
1951 LOG.trace("Carrying forward tag from " + oldCell + ": type " + tag.getType() +
1952 " length " + tag.getTagLength());
1953 }
1954 tags.add(tag);
1955 } else {
1956
1957
1958
1959 AccessControlProtos.UsersAndPermissions.Builder builder =
1960 AccessControlProtos.UsersAndPermissions.newBuilder();
1961 ProtobufUtil.mergeFrom(builder, tag.getBuffer(), tag.getTagOffset(), tag.getTagLength());
1962 ListMultimap<String,Permission> kvPerms =
1963 ProtobufUtil.toUsersAndPermissions(builder.build());
1964 perms.putAll(kvPerms);
1965 }
1966 }
1967 }
1968 }
1969
1970
1971 byte[] aclBytes = mutation.getACL();
1972 if (aclBytes != null) {
1973
1974 tags.add(new Tag(AccessControlLists.ACL_TAG_TYPE, aclBytes));
1975 } else {
1976
1977 if (perms != null) {
1978
1979
1980
1981 if (LOG.isTraceEnabled()) {
1982 LOG.trace("Carrying forward ACLs from " + oldCell + ": " + perms);
1983 }
1984 tags.add(new Tag(AccessControlLists.ACL_TAG_TYPE,
1985 ProtobufUtil.toUsersAndPermissions(perms).toByteArray()));
1986 }
1987 }
1988
1989
1990 if (tags.isEmpty()) {
1991 return newCell;
1992 }
1993
1994
1995
1996 KeyValue newKv = KeyValueUtil.ensureKeyValue(newCell);
1997 byte[] bytes = newKv.getBuffer();
1998 KeyValue rewriteKv = new KeyValue(bytes, newKv.getRowOffset(), newKv.getRowLength(),
1999 bytes, newKv.getFamilyOffset(), newKv.getFamilyLength(),
2000 bytes, newKv.getQualifierOffset(), newKv.getQualifierLength(),
2001 newKv.getTimestamp(), KeyValue.Type.codeToType(newKv.getTypeByte()),
2002 bytes, newKv.getValueOffset(), newKv.getValueLength(),
2003 tags);
2004
2005 rewriteKv.setMvccVersion(newKv.getMvccVersion());
2006 return rewriteKv;
2007 }
2008
2009 @Override
2010 public RegionScanner preScannerOpen(final ObserverContext<RegionCoprocessorEnvironment> c,
2011 final Scan scan, final RegionScanner s) throws IOException {
2012 internalPreRead(c, scan, OpType.SCAN);
2013 return s;
2014 }
2015
2016 @Override
2017 public RegionScanner postScannerOpen(final ObserverContext<RegionCoprocessorEnvironment> c,
2018 final Scan scan, final RegionScanner s) throws IOException {
2019 User user = getActiveUser();
2020 if (user != null && user.getShortName() != null) {
2021
2022 scannerOwners.put(s, user.getShortName());
2023 }
2024 return s;
2025 }
2026
2027 @Override
2028 public boolean preScannerNext(final ObserverContext<RegionCoprocessorEnvironment> c,
2029 final InternalScanner s, final List<Result> result,
2030 final int limit, final boolean hasNext) throws IOException {
2031 requireScannerOwner(s);
2032 return hasNext;
2033 }
2034
2035 @Override
2036 public void preScannerClose(final ObserverContext<RegionCoprocessorEnvironment> c,
2037 final InternalScanner s) throws IOException {
2038 requireScannerOwner(s);
2039 }
2040
2041 @Override
2042 public void postScannerClose(final ObserverContext<RegionCoprocessorEnvironment> c,
2043 final InternalScanner s) throws IOException {
2044
2045 scannerOwners.remove(s);
2046 }
2047
2048
2049
2050
2051
2052
2053 private void requireScannerOwner(InternalScanner s) throws AccessDeniedException {
2054 if (!RpcServer.isInRpcCallContext())
2055 return;
2056 String requestUserName = RpcServer.getRequestUserName();
2057 String owner = scannerOwners.get(s);
2058 if (authorizationEnabled && owner != null && !owner.equals(requestUserName)) {
2059 throw new AccessDeniedException("User '"+ requestUserName +"' is not the scanner owner!");
2060 }
2061 }
2062
2063
2064
2065
2066
2067
2068
2069 @Override
2070 public void preBulkLoadHFile(ObserverContext<RegionCoprocessorEnvironment> ctx,
2071 List<Pair<byte[], String>> familyPaths) throws IOException {
2072 for(Pair<byte[],String> el : familyPaths) {
2073 requirePermission("preBulkLoadHFile",
2074 ctx.getEnvironment().getRegion().getTableDesc().getTableName(),
2075 el.getFirst(),
2076 null,
2077 Action.CREATE);
2078 }
2079 }
2080
2081
2082
2083
2084
2085
2086
2087
2088 @Override
2089 public void prePrepareBulkLoad(ObserverContext<RegionCoprocessorEnvironment> ctx,
2090 PrepareBulkLoadRequest request) throws IOException {
2091 requireAccess("prePareBulkLoad",
2092 ctx.getEnvironment().getRegion().getTableDesc().getTableName(), Action.CREATE);
2093 }
2094
2095
2096
2097
2098
2099
2100
2101
2102 @Override
2103 public void preCleanupBulkLoad(ObserverContext<RegionCoprocessorEnvironment> ctx,
2104 CleanupBulkLoadRequest request) throws IOException {
2105 requireAccess("preCleanupBulkLoad",
2106 ctx.getEnvironment().getRegion().getTableDesc().getTableName(), Action.CREATE);
2107 }
2108
2109
2110
2111 @Override
2112 public Message preEndpointInvocation(ObserverContext<RegionCoprocessorEnvironment> ctx,
2113 Service service, String methodName, Message request) throws IOException {
2114
2115
2116 if (shouldCheckExecPermission && !(service instanceof AccessControlService)) {
2117 requirePermission("invoke(" + service.getDescriptorForType().getName() + "." +
2118 methodName + ")",
2119 getTableName(ctx.getEnvironment()), null, null,
2120 Action.EXEC);
2121 }
2122 return request;
2123 }
2124
2125 @Override
2126 public void postEndpointInvocation(ObserverContext<RegionCoprocessorEnvironment> ctx,
2127 Service service, String methodName, Message request, Message.Builder responseBuilder)
2128 throws IOException { }
2129
2130
2131
2132 @Override
2133 public void grant(RpcController controller,
2134 AccessControlProtos.GrantRequest request,
2135 RpcCallback<AccessControlProtos.GrantResponse> done) {
2136 final UserPermission perm = ProtobufUtil.toUserPermission(request.getUserPermission());
2137 AccessControlProtos.GrantResponse response = null;
2138 try {
2139
2140 if (aclRegion) {
2141 if (!initialized) {
2142 throw new CoprocessorException("AccessController not yet initialized");
2143 }
2144 if (LOG.isDebugEnabled()) {
2145 LOG.debug("Received request to grant access permission " + perm.toString());
2146 }
2147
2148 switch(request.getUserPermission().getPermission().getType()) {
2149 case Global :
2150 case Table :
2151 requirePermission("grant", perm.getTableName(), perm.getFamily(),
2152 perm.getQualifier(), Action.ADMIN);
2153 break;
2154 case Namespace :
2155 requireNamespacePermission("grant", perm.getNamespace(), Action.ADMIN);
2156 break;
2157 }
2158
2159 User.runAsLoginUser(new PrivilegedExceptionAction<Void>() {
2160 @Override
2161 public Void run() throws Exception {
2162 AccessControlLists.addUserPermission(regionEnv.getConfiguration(), perm);
2163 return null;
2164 }
2165 });
2166
2167 if (AUDITLOG.isTraceEnabled()) {
2168
2169 AUDITLOG.trace("Granted permission " + perm.toString());
2170 }
2171 } else {
2172 throw new CoprocessorException(AccessController.class, "This method "
2173 + "can only execute at " + AccessControlLists.ACL_TABLE_NAME + " table.");
2174 }
2175 response = AccessControlProtos.GrantResponse.getDefaultInstance();
2176 } catch (IOException ioe) {
2177
2178 ResponseConverter.setControllerException(controller, ioe);
2179 }
2180 done.run(response);
2181 }
2182
2183 @Override
2184 public void revoke(RpcController controller,
2185 AccessControlProtos.RevokeRequest request,
2186 RpcCallback<AccessControlProtos.RevokeResponse> done) {
2187 final UserPermission perm = ProtobufUtil.toUserPermission(request.getUserPermission());
2188 AccessControlProtos.RevokeResponse response = null;
2189 try {
2190
2191 if (aclRegion) {
2192 if (!initialized) {
2193 throw new CoprocessorException("AccessController not yet initialized");
2194 }
2195 if (LOG.isDebugEnabled()) {
2196 LOG.debug("Received request to revoke access permission " + perm.toString());
2197 }
2198
2199 switch(request.getUserPermission().getPermission().getType()) {
2200 case Global :
2201 case Table :
2202 requirePermission("revoke", perm.getTableName(), perm.getFamily(),
2203 perm.getQualifier(), Action.ADMIN);
2204 break;
2205 case Namespace :
2206 requireNamespacePermission("revoke", perm.getNamespace(), Action.ADMIN);
2207 break;
2208 }
2209
2210 User.runAsLoginUser(new PrivilegedExceptionAction<Void>() {
2211 @Override
2212 public Void run() throws Exception {
2213 AccessControlLists.removeUserPermission(regionEnv.getConfiguration(), perm);
2214 return null;
2215 }
2216 });
2217
2218 if (AUDITLOG.isTraceEnabled()) {
2219
2220 AUDITLOG.trace("Revoked permission " + perm.toString());
2221 }
2222 } else {
2223 throw new CoprocessorException(AccessController.class, "This method "
2224 + "can only execute at " + AccessControlLists.ACL_TABLE_NAME + " table.");
2225 }
2226 response = AccessControlProtos.RevokeResponse.getDefaultInstance();
2227 } catch (IOException ioe) {
2228
2229 ResponseConverter.setControllerException(controller, ioe);
2230 }
2231 done.run(response);
2232 }
2233
2234 @Override
2235 public void getUserPermissions(RpcController controller,
2236 AccessControlProtos.GetUserPermissionsRequest request,
2237 RpcCallback<AccessControlProtos.GetUserPermissionsResponse> done) {
2238 AccessControlProtos.GetUserPermissionsResponse response = null;
2239 try {
2240
2241 if (aclRegion) {
2242 if (!initialized) {
2243 throw new CoprocessorException("AccessController not yet initialized");
2244 }
2245 List<UserPermission> perms = null;
2246 if (request.getType() == AccessControlProtos.Permission.Type.Table) {
2247 final TableName table = request.hasTableName() ?
2248 ProtobufUtil.toTableName(request.getTableName()) : null;
2249 requirePermission("userPermissions", table, null, null, Action.ADMIN);
2250 perms = User.runAsLoginUser(new PrivilegedExceptionAction<List<UserPermission>>() {
2251 @Override
2252 public List<UserPermission> run() throws Exception {
2253 return AccessControlLists.getUserTablePermissions(regionEnv.getConfiguration(), table);
2254 }
2255 });
2256 } else if (request.getType() == AccessControlProtos.Permission.Type.Namespace) {
2257 final String namespace = request.getNamespaceName().toStringUtf8();
2258 requireNamespacePermission("userPermissions", namespace, Action.ADMIN);
2259 perms = User.runAsLoginUser(new PrivilegedExceptionAction<List<UserPermission>>() {
2260 @Override
2261 public List<UserPermission> run() throws Exception {
2262 return AccessControlLists.getUserNamespacePermissions(regionEnv.getConfiguration(),
2263 namespace);
2264 }
2265 });
2266 } else {
2267 requirePermission("userPermissions", Action.ADMIN);
2268 perms = User.runAsLoginUser(new PrivilegedExceptionAction<List<UserPermission>>() {
2269 @Override
2270 public List<UserPermission> run() throws Exception {
2271 return AccessControlLists.getUserPermissions(regionEnv.getConfiguration(), null);
2272 }
2273 });
2274
2275
2276
2277 for (String user: Superusers.getSuperUsers()) {
2278 perms.add(new UserPermission(user.getBytes(), AccessControlLists.ACL_TABLE_NAME, null,
2279 Action.values()));
2280 }
2281 }
2282 response = ResponseConverter.buildGetUserPermissionsResponse(perms);
2283 } else {
2284 throw new CoprocessorException(AccessController.class, "This method "
2285 + "can only execute at " + AccessControlLists.ACL_TABLE_NAME + " table.");
2286 }
2287 } catch (IOException ioe) {
2288
2289 ResponseConverter.setControllerException(controller, ioe);
2290 }
2291 done.run(response);
2292 }
2293
2294 @Override
2295 public void checkPermissions(RpcController controller,
2296 AccessControlProtos.CheckPermissionsRequest request,
2297 RpcCallback<AccessControlProtos.CheckPermissionsResponse> done) {
2298 Permission[] permissions = new Permission[request.getPermissionCount()];
2299 for (int i=0; i < request.getPermissionCount(); i++) {
2300 permissions[i] = ProtobufUtil.toPermission(request.getPermission(i));
2301 }
2302 AccessControlProtos.CheckPermissionsResponse response = null;
2303 try {
2304 User user = getActiveUser();
2305 TableName tableName = regionEnv.getRegion().getTableDesc().getTableName();
2306 for (Permission permission : permissions) {
2307 if (permission instanceof TablePermission) {
2308
2309
2310 TablePermission tperm = (TablePermission) permission;
2311 for (Action action : permission.getActions()) {
2312 if (!tperm.getTableName().equals(tableName)) {
2313 throw new CoprocessorException(AccessController.class, String.format("This method "
2314 + "can only execute at the table specified in TablePermission. " +
2315 "Table of the region:%s , requested table:%s", tableName,
2316 tperm.getTableName()));
2317 }
2318
2319 Map<byte[], Set<byte[]>> familyMap =
2320 new TreeMap<byte[], Set<byte[]>>(Bytes.BYTES_COMPARATOR);
2321 if (tperm.getFamily() != null) {
2322 if (tperm.getQualifier() != null) {
2323 Set<byte[]> qualifiers = Sets.newTreeSet(Bytes.BYTES_COMPARATOR);
2324 qualifiers.add(tperm.getQualifier());
2325 familyMap.put(tperm.getFamily(), qualifiers);
2326 } else {
2327 familyMap.put(tperm.getFamily(), null);
2328 }
2329 }
2330
2331 AuthResult result = permissionGranted("checkPermissions", user, action, regionEnv,
2332 familyMap);
2333 logResult(result);
2334 if (!result.isAllowed()) {
2335
2336
2337 throw new AccessDeniedException("Insufficient permissions (table=" + tableName +
2338 (familyMap.size() > 0 ? ", family: " + result.toFamilyString() : "") +
2339 ", action=" + action.toString() + ")");
2340 }
2341 }
2342
2343 } else {
2344
2345
2346 for (Action action : permission.getActions()) {
2347 AuthResult result;
2348 if (authManager.authorize(user, action)) {
2349 result = AuthResult.allow("checkPermissions", "Global action allowed", user,
2350 action, null, null);
2351 } else {
2352 result = AuthResult.deny("checkPermissions", "Global action denied", user, action,
2353 null, null);
2354 }
2355 logResult(result);
2356 if (!result.isAllowed()) {
2357
2358
2359 throw new AccessDeniedException("Insufficient permissions (action=" +
2360 action.toString() + ")");
2361 }
2362 }
2363 }
2364 }
2365 response = AccessControlProtos.CheckPermissionsResponse.getDefaultInstance();
2366 } catch (IOException ioe) {
2367 ResponseConverter.setControllerException(controller, ioe);
2368 }
2369 done.run(response);
2370 }
2371
2372 @Override
2373 public Service getService() {
2374 return AccessControlProtos.AccessControlService.newReflectiveService(this);
2375 }
2376
2377 private HRegion getRegion(RegionCoprocessorEnvironment e) {
2378 return e.getRegion();
2379 }
2380
2381 private TableName getTableName(RegionCoprocessorEnvironment e) {
2382 HRegion region = e.getRegion();
2383 if (region != null) {
2384 return getTableName(region);
2385 }
2386 return null;
2387 }
2388
2389 private TableName getTableName(HRegion region) {
2390 HRegionInfo regionInfo = region.getRegionInfo();
2391 if (regionInfo != null) {
2392 return regionInfo.getTable();
2393 }
2394 return null;
2395 }
2396
2397 @Override
2398 public void preClose(ObserverContext<RegionCoprocessorEnvironment> e, boolean abortRequested)
2399 throws IOException {
2400 requirePermission("preClose", Action.ADMIN);
2401 }
2402
2403 private void checkSystemOrSuperUser() throws IOException {
2404
2405 if (!authorizationEnabled) {
2406 return;
2407 }
2408 User activeUser = getActiveUser();
2409 if (!Superusers.isSuperUser(activeUser)) {
2410 throw new AccessDeniedException("User '" + (activeUser != null ?
2411 activeUser.getShortName() : "null") + "' is not system or super user.");
2412 }
2413 }
2414
2415 @Override
2416 public void preStopRegionServer(
2417 ObserverContext<RegionServerCoprocessorEnvironment> env)
2418 throws IOException {
2419 requirePermission("preStopRegionServer", Action.ADMIN);
2420 }
2421
2422 private Map<byte[], ? extends Collection<byte[]>> makeFamilyMap(byte[] family,
2423 byte[] qualifier) {
2424 if (family == null) {
2425 return null;
2426 }
2427
2428 Map<byte[], Collection<byte[]>> familyMap = new TreeMap<byte[], Collection<byte[]>>(Bytes.BYTES_COMPARATOR);
2429 familyMap.put(family, qualifier != null ? ImmutableSet.of(qualifier) : null);
2430 return familyMap;
2431 }
2432
2433 @Override
2434 public void preGetTableDescriptors(ObserverContext<MasterCoprocessorEnvironment> ctx,
2435 List<TableName> tableNamesList,
2436 List<HTableDescriptor> descriptors) throws IOException {
2437
2438
2439 if (tableNamesList == null || tableNamesList.isEmpty()) {
2440 requireGlobalPermission("getTableDescriptors", Action.ADMIN, null, null);
2441 }
2442
2443
2444 else {
2445 MasterServices masterServices = ctx.getEnvironment().getMasterServices();
2446 for (TableName tableName: tableNamesList) {
2447
2448 if (masterServices.getTableDescriptors().get(tableName) == null) {
2449 continue;
2450 }
2451 requirePermission("getTableDescriptors", tableName, null, null,
2452 Action.ADMIN, Action.CREATE);
2453 }
2454 }
2455 }
2456
2457 @Override
2458 public void preMerge(ObserverContext<RegionServerCoprocessorEnvironment> ctx, HRegion regionA,
2459 HRegion regionB) throws IOException {
2460 requirePermission("mergeRegions", regionA.getTableDesc().getTableName(), null, null,
2461 Action.ADMIN);
2462 }
2463
2464 @Override
2465 public void postMerge(ObserverContext<RegionServerCoprocessorEnvironment> c, HRegion regionA,
2466 HRegion regionB, HRegion mergedRegion) throws IOException { }
2467
2468 @Override
2469 public void preMergeCommit(ObserverContext<RegionServerCoprocessorEnvironment> ctx,
2470 HRegion regionA, HRegion regionB, List<Mutation> metaEntries) throws IOException { }
2471
2472 @Override
2473 public void postMergeCommit(ObserverContext<RegionServerCoprocessorEnvironment> ctx,
2474 HRegion regionA, HRegion regionB, HRegion mergedRegion) throws IOException { }
2475
2476 @Override
2477 public void preRollBackMerge(ObserverContext<RegionServerCoprocessorEnvironment> ctx,
2478 HRegion regionA, HRegion regionB) throws IOException { }
2479
2480 @Override
2481 public void postRollBackMerge(ObserverContext<RegionServerCoprocessorEnvironment> ctx,
2482 HRegion regionA, HRegion regionB) throws IOException { }
2483
2484 @Override
2485 public void preRollWALWriterRequest(ObserverContext<RegionServerCoprocessorEnvironment> ctx)
2486 throws IOException {
2487 requirePermission("preRollLogWriterRequest", Permission.Action.ADMIN);
2488 }
2489
2490 @Override
2491 public void postRollWALWriterRequest(ObserverContext<RegionServerCoprocessorEnvironment> ctx)
2492 throws IOException { }
2493
2494 @Override
2495 public ReplicationEndpoint postCreateReplicationEndPoint(
2496 ObserverContext<RegionServerCoprocessorEnvironment> ctx, ReplicationEndpoint endpoint) {
2497 return endpoint;
2498 }
2499
2500 @Override
2501 public void preReplicateLogEntries(ObserverContext<RegionServerCoprocessorEnvironment> ctx,
2502 List<WALEntry> entries, CellScanner cells) throws IOException {
2503 requirePermission("replicateLogEntries", Action.WRITE);
2504 }
2505
2506 @Override
2507 public void postReplicateLogEntries(ObserverContext<RegionServerCoprocessorEnvironment> ctx,
2508 List<WALEntry> entries, CellScanner cells) throws IOException {
2509 }
2510 }