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