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.ByteArrayInputStream;
22 import java.io.DataInput;
23 import java.io.DataInputStream;
24 import java.io.IOException;
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Set;
30 import java.util.TreeMap;
31 import java.util.TreeSet;
32
33 import org.apache.commons.logging.Log;
34 import org.apache.commons.logging.LogFactory;
35 import org.apache.hadoop.conf.Configuration;
36 import org.apache.hadoop.hbase.TableName;
37 import org.apache.hadoop.hbase.exceptions.DeserializationException;
38 import org.apache.hadoop.hbase.HColumnDescriptor;
39 import org.apache.hadoop.hbase.HConstants;
40 import org.apache.hadoop.hbase.HTableDescriptor;
41 import org.apache.hadoop.hbase.KeyValue;
42 import org.apache.hadoop.hbase.NamespaceDescriptor;
43 import org.apache.hadoop.hbase.catalog.MetaReader;
44 import org.apache.hadoop.hbase.client.Delete;
45 import org.apache.hadoop.hbase.client.Get;
46 import org.apache.hadoop.hbase.client.HTable;
47 import org.apache.hadoop.hbase.client.Put;
48 import org.apache.hadoop.hbase.client.Result;
49 import org.apache.hadoop.hbase.client.ResultScanner;
50 import org.apache.hadoop.hbase.client.Scan;
51 import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp;
52 import org.apache.hadoop.hbase.filter.QualifierFilter;
53 import org.apache.hadoop.hbase.filter.RegexStringComparator;
54 import org.apache.hadoop.hbase.io.compress.Compression;
55 import org.apache.hadoop.hbase.master.MasterServices;
56 import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
57 import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos;
58 import org.apache.hadoop.hbase.regionserver.BloomType;
59 import org.apache.hadoop.hbase.regionserver.HRegion;
60 import org.apache.hadoop.hbase.regionserver.InternalScanner;
61 import org.apache.hadoop.hbase.util.Bytes;
62 import org.apache.hadoop.hbase.util.Pair;
63 import org.apache.hadoop.io.Text;
64
65 import com.google.common.collect.ArrayListMultimap;
66 import com.google.common.collect.ListMultimap;
67 import com.google.protobuf.InvalidProtocolBufferException;
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92 public class AccessControlLists {
93
94 public static final TableName ACL_TABLE_NAME =
95 TableName.valueOf(NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR, "acl");
96 public static final byte[] ACL_GLOBAL_NAME = ACL_TABLE_NAME.getName();
97
98 public static final String ACL_LIST_FAMILY_STR = "l";
99 public static final byte[] ACL_LIST_FAMILY = Bytes.toBytes(ACL_LIST_FAMILY_STR);
100
101 public static final char NAMESPACE_PREFIX = '@';
102
103
104 public static final HTableDescriptor ACL_TABLEDESC = new HTableDescriptor(ACL_TABLE_NAME);
105 static {
106 ACL_TABLEDESC.addFamily(
107 new HColumnDescriptor(ACL_LIST_FAMILY,
108 10,
109 Compression.Algorithm.NONE.getName(), true, true, 8 * 1024,
110 HConstants.FOREVER, BloomType.NONE.toString(),
111 HConstants.REPLICATION_SCOPE_LOCAL));
112 }
113
114
115
116
117 public static final char ACL_KEY_DELIMITER = ',';
118
119 public static final String GROUP_PREFIX = "@";
120
121 public static final String SUPERUSER_CONF_KEY = "hbase.superuser";
122
123 private static Log LOG = LogFactory.getLog(AccessControlLists.class);
124
125
126
127
128
129 static void init(MasterServices master) throws IOException {
130 if (!MetaReader.tableExists(master.getCatalogTracker(), ACL_TABLE_NAME)) {
131 master.createTable(ACL_TABLEDESC, null);
132 }
133 }
134
135
136
137
138
139
140
141 static void addUserPermission(Configuration conf, UserPermission userPerm)
142 throws IOException {
143 Permission.Action[] actions = userPerm.getActions();
144 byte[] rowKey = userPermissionRowKey(userPerm);
145 Put p = new Put(rowKey);
146 byte[] key = userPermissionKey(userPerm);
147
148 if ((actions == null) || (actions.length == 0)) {
149 LOG.warn("No actions associated with user '"+Bytes.toString(userPerm.getUser())+"'");
150 return;
151 }
152
153 byte[] value = new byte[actions.length];
154 for (int i = 0; i < actions.length; i++) {
155 value[i] = actions[i].code();
156 }
157 p.add(ACL_LIST_FAMILY, key, value);
158 if (LOG.isDebugEnabled()) {
159 LOG.debug("Writing permission with rowKey "+
160 Bytes.toString(rowKey)+" "+
161 Bytes.toString(key)+": "+Bytes.toStringBinary(value)
162 );
163 }
164 HTable acls = null;
165 try {
166 acls = new HTable(conf, ACL_TABLE_NAME);
167 acls.put(p);
168 } finally {
169 if (acls != null) acls.close();
170 }
171 }
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186 static void removeUserPermission(Configuration conf, UserPermission userPerm)
187 throws IOException {
188 Delete d = new Delete(userPermissionRowKey(userPerm));
189 byte[] key = userPermissionKey(userPerm);
190
191 if (LOG.isDebugEnabled()) {
192 LOG.debug("Removing permission "+ userPerm.toString());
193 }
194 d.deleteColumns(ACL_LIST_FAMILY, key);
195 HTable acls = null;
196 try {
197 acls = new HTable(conf, ACL_TABLE_NAME);
198 acls.delete(d);
199 } finally {
200 if (acls != null) acls.close();
201 }
202 }
203
204
205
206
207 static void removeTablePermissions(Configuration conf, TableName tableName)
208 throws IOException{
209 Delete d = new Delete(tableName.getName());
210
211 if (LOG.isDebugEnabled()) {
212 LOG.debug("Removing permissions of removed table "+ tableName);
213 }
214
215 HTable acls = null;
216 try {
217 acls = new HTable(conf, ACL_TABLE_NAME);
218 acls.delete(d);
219 } finally {
220 if (acls != null) acls.close();
221 }
222 }
223
224
225
226
227 static void removeNamespacePermissions(Configuration conf, String namespace)
228 throws IOException{
229 Delete d = new Delete(Bytes.toBytes(toNamespaceEntry(namespace)));
230
231 if (LOG.isDebugEnabled()) {
232 LOG.debug("Removing permissions of removed namespace "+ namespace);
233 }
234
235 HTable acls = null;
236 try {
237 acls = new HTable(conf, ACL_TABLE_NAME);
238 acls.delete(d);
239 } finally {
240 if (acls != null) acls.close();
241 }
242 }
243
244
245
246
247 static void removeTablePermissions(Configuration conf, TableName tableName, byte[] column)
248 throws IOException{
249
250 if (LOG.isDebugEnabled()) {
251 LOG.debug("Removing permissions of removed column " + Bytes.toString(column) +
252 " from table "+ tableName);
253 }
254
255 HTable acls = null;
256 try {
257 acls = new HTable(conf, ACL_TABLE_NAME);
258
259 Scan scan = new Scan();
260 scan.addFamily(ACL_LIST_FAMILY);
261
262 String columnName = Bytes.toString(column);
263 scan.setFilter(new QualifierFilter(CompareOp.EQUAL, new RegexStringComparator(
264 String.format("(%s%s%s)|(%s%s)$",
265 ACL_KEY_DELIMITER, columnName, ACL_KEY_DELIMITER,
266 ACL_KEY_DELIMITER, columnName))));
267
268 Set<byte[]> qualifierSet = new TreeSet<byte[]>(Bytes.BYTES_COMPARATOR);
269 ResultScanner scanner = acls.getScanner(scan);
270 try {
271 for (Result res : scanner) {
272 for (byte[] q : res.getFamilyMap(ACL_LIST_FAMILY).navigableKeySet()) {
273 qualifierSet.add(q);
274 }
275 }
276 } finally {
277 scanner.close();
278 }
279
280 if (qualifierSet.size() > 0) {
281 Delete d = new Delete(tableName.getName());
282 for (byte[] qualifier : qualifierSet) {
283 d.deleteColumns(ACL_LIST_FAMILY, qualifier);
284 }
285 acls.delete(d);
286 }
287 } finally {
288 if (acls != null) acls.close();
289 }
290 }
291
292 static byte[] userPermissionRowKey(UserPermission userPerm) {
293 byte[] row;
294 if(userPerm.hasNamespace()) {
295 row = Bytes.toBytes(toNamespaceEntry(userPerm.getNamespace()));
296 } else if(userPerm.isGlobal()) {
297 row = ACL_GLOBAL_NAME;
298 } else {
299 row = userPerm.getTable().getName();
300 }
301 return row;
302 }
303
304
305
306
307
308
309
310 static byte[] userPermissionKey(UserPermission userPerm) {
311 byte[] qualifier = userPerm.getQualifier();
312 byte[] family = userPerm.getFamily();
313 byte[] key = userPerm.getUser();
314
315 if (family != null && family.length > 0) {
316 key = Bytes.add(key, Bytes.add(new byte[]{ACL_KEY_DELIMITER}, family));
317 if (qualifier != null && qualifier.length > 0) {
318 key = Bytes.add(key, Bytes.add(new byte[]{ACL_KEY_DELIMITER}, qualifier));
319 }
320 }
321
322 return key;
323 }
324
325
326
327
328
329 static boolean isAclRegion(HRegion region) {
330 return ACL_TABLE_NAME.equals(region.getTableDesc().getTableName());
331 }
332
333
334
335
336 static boolean isAclTable(HTableDescriptor desc) {
337 return ACL_TABLE_NAME.equals(desc.getTableName());
338 }
339
340
341
342
343
344
345
346
347
348 static Map<byte[], ListMultimap<String,TablePermission>> loadAll(
349 HRegion aclRegion)
350 throws IOException {
351
352 if (!isAclRegion(aclRegion)) {
353 throw new IOException("Can only load permissions from "+ACL_TABLE_NAME);
354 }
355
356 Map<byte[], ListMultimap<String, TablePermission>> allPerms =
357 new TreeMap<byte[], ListMultimap<String, TablePermission>>();
358
359
360
361 Scan scan = new Scan();
362 scan.addFamily(ACL_LIST_FAMILY);
363
364 InternalScanner iScanner = null;
365 try {
366 iScanner = aclRegion.getScanner(scan);
367
368 while (true) {
369 List<KeyValue> row = new ArrayList<KeyValue>();
370
371 boolean hasNext = iScanner.next(row);
372 ListMultimap<String,TablePermission> perms = ArrayListMultimap.create();
373 byte[] entry = null;
374 for (KeyValue kv : row) {
375 if (entry == null) {
376 entry = kv.getRow();
377 }
378 Pair<String,TablePermission> permissionsOfUserOnTable =
379 parsePermissionRecord(entry, kv);
380 if (permissionsOfUserOnTable != null) {
381 String username = permissionsOfUserOnTable.getFirst();
382 TablePermission permissions = permissionsOfUserOnTable.getSecond();
383 perms.put(username, permissions);
384 }
385 }
386 if (entry != null) {
387 allPerms.put(entry, perms);
388 }
389 if (!hasNext) {
390 break;
391 }
392 }
393 } finally {
394 if (iScanner != null) {
395 iScanner.close();
396 }
397 }
398
399 return allPerms;
400 }
401
402
403
404
405
406 static Map<byte[], ListMultimap<String,TablePermission>> loadAll(
407 Configuration conf) throws IOException {
408 Map<byte[], ListMultimap<String,TablePermission>> allPerms =
409 new TreeMap<byte[], ListMultimap<String,TablePermission>>(Bytes.BYTES_RAWCOMPARATOR);
410
411
412
413 Scan scan = new Scan();
414 scan.addFamily(ACL_LIST_FAMILY);
415
416 HTable acls = null;
417 ResultScanner scanner = null;
418 try {
419 acls = new HTable(conf, ACL_TABLE_NAME);
420 scanner = acls.getScanner(scan);
421 for (Result row : scanner) {
422 ListMultimap<String,TablePermission> resultPerms =
423 parsePermissions(row.getRow(), row);
424 allPerms.put(row.getRow(), resultPerms);
425 }
426 } finally {
427 if (scanner != null) scanner.close();
428 if (acls != null) acls.close();
429 }
430
431 return allPerms;
432 }
433
434 static ListMultimap<String, TablePermission> getTablePermissions(Configuration conf,
435 TableName tableName) throws IOException {
436 return getPermissions(conf, tableName != null ? tableName.getName() : null);
437 }
438
439 static ListMultimap<String, TablePermission> getNamespacePermissions(Configuration conf,
440 String namespace) throws IOException {
441 return getPermissions(conf, Bytes.toBytes(toNamespaceEntry(namespace)));
442 }
443
444
445
446
447
448
449
450
451
452
453 static ListMultimap<String, TablePermission> getPermissions(Configuration conf,
454 byte[] entryName) throws IOException {
455 if (entryName == null) entryName = ACL_TABLE_NAME.getName();
456
457
458 ListMultimap<String, TablePermission> perms = ArrayListMultimap.create();
459 HTable acls = null;
460 try {
461 acls = new HTable(conf, ACL_TABLE_NAME);
462 Get get = new Get(entryName);
463 get.addFamily(ACL_LIST_FAMILY);
464 Result row = acls.get(get);
465 if (!row.isEmpty()) {
466 perms = parsePermissions(entryName, row);
467 } else {
468 LOG.info("No permissions found in " + ACL_TABLE_NAME + " for acl entry "
469 + Bytes.toString(entryName));
470 }
471 } finally {
472 if (acls != null) acls.close();
473 }
474
475 return perms;
476 }
477
478
479
480
481
482 static List<UserPermission> getUserTablePermissions(
483 Configuration conf, TableName tableName) throws IOException {
484 return getUserPermissions(conf, tableName.getName());
485 }
486
487 static List<UserPermission> getUserNamespacePermissions(
488 Configuration conf, String namespace) throws IOException {
489 return getUserPermissions(conf, Bytes.toBytes(toNamespaceEntry(namespace)));
490 }
491
492 static List<UserPermission> getUserPermissions(
493 Configuration conf, byte[] entryName)
494 throws IOException {
495 ListMultimap<String,TablePermission> allPerms = getPermissions(
496 conf, entryName);
497
498 List<UserPermission> perms = new ArrayList<UserPermission>();
499
500 for (Map.Entry<String, TablePermission> entry : allPerms.entries()) {
501 UserPermission up = new UserPermission(Bytes.toBytes(entry.getKey()),
502 entry.getValue().getTable(), entry.getValue().getFamily(),
503 entry.getValue().getQualifier(), entry.getValue().getActions());
504 perms.add(up);
505 }
506 return perms;
507 }
508
509 private static ListMultimap<String, TablePermission> parsePermissions(
510 byte[] entryName, Result result) {
511 ListMultimap<String, TablePermission> perms = ArrayListMultimap.create();
512 if (result != null && result.size() > 0) {
513 for (KeyValue kv : result.raw()) {
514
515 Pair<String,TablePermission> permissionsOfUserOnTable =
516 parsePermissionRecord(entryName, kv);
517
518 if (permissionsOfUserOnTable != null) {
519 String username = permissionsOfUserOnTable.getFirst();
520 TablePermission permissions = permissionsOfUserOnTable.getSecond();
521 perms.put(username, permissions);
522 }
523 }
524 }
525 return perms;
526 }
527
528 private static Pair<String, TablePermission> parsePermissionRecord(
529 byte[] entryName, KeyValue kv) {
530
531 byte[] family = kv.getFamily();
532
533 if (!Bytes.equals(family, ACL_LIST_FAMILY)) {
534 return null;
535 }
536
537 byte[] key = kv.getQualifier();
538 byte[] value = kv.getValue();
539 if (LOG.isDebugEnabled()) {
540 LOG.debug("Read acl: kv ["+
541 Bytes.toStringBinary(key)+": "+
542 Bytes.toStringBinary(value)+"]");
543 }
544
545
546
547 String username = Bytes.toString(key);
548
549
550 if(isNamespaceEntry(entryName)) {
551 return new Pair<String, TablePermission>(username,
552 new TablePermission(Bytes.toString(fromNamespaceEntry(entryName)), value));
553 }
554
555
556
557 int idx = username.indexOf(ACL_KEY_DELIMITER);
558 byte[] permFamily = null;
559 byte[] permQualifier = null;
560 if (idx > 0 && idx < username.length()-1) {
561 String remainder = username.substring(idx+1);
562 username = username.substring(0, idx);
563 idx = remainder.indexOf(ACL_KEY_DELIMITER);
564 if (idx > 0 && idx < remainder.length()-1) {
565 permFamily = Bytes.toBytes(remainder.substring(0, idx));
566 permQualifier = Bytes.toBytes(remainder.substring(idx+1));
567 } else {
568 permFamily = Bytes.toBytes(remainder);
569 }
570 }
571
572 return new Pair<String,TablePermission>(username,
573 new TablePermission(TableName.valueOf(entryName), permFamily, permQualifier, value));
574 }
575
576
577
578
579
580
581
582 public static byte[] writePermissionsAsBytes(ListMultimap<String, TablePermission> perms,
583 Configuration conf) {
584 return ProtobufUtil.prependPBMagic(ProtobufUtil.toUserTablePermissions(perms).toByteArray());
585 }
586
587
588
589
590
591 public static ListMultimap<String, TablePermission> readPermissions(byte[] data,
592 Configuration conf)
593 throws DeserializationException {
594 if (ProtobufUtil.isPBMagicPrefix(data)) {
595 int pblen = ProtobufUtil.lengthOfPBMagic();
596 try {
597 AccessControlProtos.UsersAndPermissions perms =
598 AccessControlProtos.UsersAndPermissions.newBuilder().mergeFrom(
599 data, pblen, data.length - pblen).build();
600 return ProtobufUtil.toUserTablePermissions(perms);
601 } catch (InvalidProtocolBufferException e) {
602 throw new DeserializationException(e);
603 }
604 } else {
605 ListMultimap<String,TablePermission> perms = ArrayListMultimap.create();
606 try {
607 DataInput in = new DataInputStream(new ByteArrayInputStream(data));
608 int length = in.readInt();
609 for (int i=0; i<length; i++) {
610 String user = Text.readString(in);
611 List<TablePermission> userPerms =
612 (List)HbaseObjectWritableFor96Migration.readObject(in, conf);
613 perms.putAll(user, userPerms);
614 }
615 } catch (IOException e) {
616 throw new DeserializationException(e);
617 }
618 return perms;
619 }
620 }
621
622
623
624
625
626
627 public static boolean isGroupPrincipal(String name) {
628 return name != null && name.startsWith(GROUP_PREFIX);
629 }
630
631
632
633
634
635 public static String getGroupName(String aclKey) {
636 if (!isGroupPrincipal(aclKey)) {
637 return aclKey;
638 }
639
640 return aclKey.substring(GROUP_PREFIX.length());
641 }
642
643 public static boolean isNamespaceEntry(String entryName) {
644 return entryName.charAt(0) == NAMESPACE_PREFIX;
645 }
646
647 public static boolean isNamespaceEntry(byte[] entryName) {
648 return entryName[0] == NAMESPACE_PREFIX;
649 }
650
651 public static String toNamespaceEntry(String namespace) {
652 return NAMESPACE_PREFIX + namespace;
653 }
654
655 public static String fromNamespaceEntry(String namespace) {
656 if(namespace.charAt(0) != NAMESPACE_PREFIX)
657 throw new IllegalArgumentException("Argument is not a valid namespace entry");
658 return namespace.substring(1);
659 }
660
661 public static byte[] toNamespaceEntry(byte[] namespace) {
662 byte[] ret = new byte[namespace.length+1];
663 ret[0] = NAMESPACE_PREFIX;
664 System.arraycopy(namespace, 0, ret, 1, namespace.length);
665 return ret;
666 }
667
668 public static byte[] fromNamespaceEntry(byte[] namespace) {
669 if(namespace[0] != NAMESPACE_PREFIX) {
670 throw new IllegalArgumentException("Argument is not a valid namespace entry: " +
671 Bytes.toString(namespace));
672 }
673 return Arrays.copyOfRange(namespace, 1, namespace.length);
674 }
675 }