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