001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019package org.apache.hadoop.hbase.security.access;
020
021import java.io.Closeable;
022import java.io.IOException;
023import java.util.HashMap;
024import java.util.HashSet;
025import java.util.List;
026import java.util.Map;
027import java.util.Set;
028import java.util.concurrent.ConcurrentHashMap;
029import java.util.concurrent.atomic.AtomicLong;
030
031import org.apache.hadoop.conf.Configuration;
032import org.apache.hadoop.hbase.AuthUtil;
033import org.apache.hadoop.hbase.Cell;
034import org.apache.hadoop.hbase.TableName;
035import org.apache.hadoop.hbase.exceptions.DeserializationException;
036import org.apache.hadoop.hbase.log.HBaseMarkers;
037import org.apache.hadoop.hbase.security.Superusers;
038import org.apache.hadoop.hbase.security.User;
039import org.apache.hadoop.hbase.util.Bytes;
040import org.apache.hadoop.hbase.zookeeper.ZKWatcher;
041import org.apache.yetus.audience.InterfaceAudience;
042import org.apache.zookeeper.KeeperException;
043import org.slf4j.Logger;
044import org.slf4j.LoggerFactory;
045
046import org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesting;
047import org.apache.hbase.thirdparty.com.google.common.collect.ListMultimap;
048
049/**
050 * Performs authorization checks for a given user's assigned permissions.
051 * <p>
052 *   There're following scopes: <b>Global</b>, <b>Namespace</b>, <b>Table</b>, <b>Family</b>,
053 *   <b>Qualifier</b>, <b>Cell</b>.
054 *   Generally speaking, higher scopes can overrides lower scopes,
055 *   except for Cell permission can be granted even a user has not permission on specified table,
056 *   which means the user can get/scan only those granted cells parts.
057 * </p>
058 * e.g, if user A has global permission R(ead), he can
059 * read table T without checking table scope permission, so authorization checks alway starts from
060 * Global scope.
061 * <p>
062 *   For each scope, not only user but also groups he belongs to will be checked.
063 * </p>
064 */
065@InterfaceAudience.Private
066public final class AuthManager implements Closeable {
067
068  /**
069   * Cache of permissions, it is thread safe.
070   * @param <T> T extends Permission
071   */
072  private static class PermissionCache<T extends Permission> {
073    private final Object mutex = new Object();
074    private Map<String, Set<T>> cache = new HashMap<>();
075
076    void put(String name, T perm) {
077      synchronized (mutex) {
078        Set<T> perms = cache.getOrDefault(name, new HashSet<>());
079        perms.add(perm);
080        cache.put(name, perms);
081      }
082    }
083
084    Set<T> get(String name) {
085      synchronized (mutex) {
086        return cache.get(name);
087      }
088    }
089
090    void clear() {
091      synchronized (mutex) {
092        for (Map.Entry<String, Set<T>> entry : cache.entrySet()) {
093          entry.getValue().clear();
094        }
095        cache.clear();
096      }
097    }
098  }
099  PermissionCache<NamespacePermission> NS_NO_PERMISSION = new PermissionCache<>();
100  PermissionCache<TablePermission> TBL_NO_PERMISSION = new PermissionCache<>();
101
102  /**
103   * Cache for global permission excluding superuser and supergroup.
104   * Since every user/group can only have one global permission, no need to use PermissionCache.
105   */
106  private Map<String, GlobalPermission> globalCache = new ConcurrentHashMap<>();
107  /** Cache for namespace permission. */
108  private ConcurrentHashMap<String, PermissionCache<NamespacePermission>> namespaceCache =
109    new ConcurrentHashMap<>();
110  /** Cache for table permission. */
111  private ConcurrentHashMap<TableName, PermissionCache<TablePermission>> tableCache =
112    new ConcurrentHashMap<>();
113
114  private static final Logger LOG = LoggerFactory.getLogger(AuthManager.class);
115
116  private Configuration conf;
117  private ZKPermissionWatcher zkperms;
118  private final AtomicLong mtime = new AtomicLong(0L);
119
120  private AuthManager(ZKWatcher watcher, Configuration conf)
121      throws IOException {
122    this.conf = conf;
123
124    this.zkperms = new ZKPermissionWatcher(watcher, this, conf);
125    try {
126      this.zkperms.start();
127    } catch (KeeperException ke) {
128      LOG.error("ZooKeeper initialization failed", ke);
129    }
130  }
131
132  @Override
133  public void close() {
134    this.zkperms.close();
135  }
136
137  public ZKPermissionWatcher getZKPermissionWatcher() {
138    return this.zkperms;
139  }
140
141  /**
142   * Update acl info for table.
143   * @param table name of table
144   * @param data updated acl data
145   * @throws IOException exception when deserialize data
146   */
147  public void refreshTableCacheFromWritable(TableName table, byte[] data) throws IOException {
148    if (data != null && data.length > 0) {
149      try {
150        ListMultimap<String, Permission> perms =
151          AccessControlLists.readPermissions(data, conf);
152        if (perms != null) {
153          if (Bytes.equals(table.getName(), AccessControlLists.ACL_GLOBAL_NAME)) {
154            updateGlobalCache(perms);
155          } else {
156            updateTableCache(table, perms);
157          }
158        }
159      } catch (DeserializationException e) {
160        throw new IOException(e);
161      }
162    } else {
163      LOG.info("Skipping permission cache refresh because writable data is empty");
164    }
165  }
166
167  /**
168   * Update acl info for namespace.
169   * @param namespace namespace
170   * @param data updated acl data
171   * @throws IOException exception when deserialize data
172   */
173  public void refreshNamespaceCacheFromWritable(String namespace, byte[] data) throws IOException {
174    if (data != null && data.length > 0) {
175      try {
176        ListMultimap<String, Permission> perms =
177          AccessControlLists.readPermissions(data, conf);
178        if (perms != null) {
179          updateNamespaceCache(namespace, perms);
180        }
181      } catch (DeserializationException e) {
182        throw new IOException(e);
183      }
184    } else {
185      LOG.debug("Skipping permission cache refresh because writable data is empty");
186    }
187  }
188
189  /**
190   * Updates the internal global permissions cache.
191   * @param globalPerms new global permissions
192   */
193  private void updateGlobalCache(ListMultimap<String, Permission> globalPerms) {
194    globalCache.clear();
195    for (String name : globalPerms.keySet()) {
196      for (Permission permission : globalPerms.get(name)) {
197        // Before 2.2, the global permission which storage in zk is not right. It was saved as a
198        // table permission. So here need to handle this for compatibility. See HBASE-22503.
199        if (permission instanceof TablePermission) {
200          globalCache.put(name, new GlobalPermission(permission.getActions()));
201        } else {
202          globalCache.put(name, (GlobalPermission) permission);
203        }
204      }
205    }
206    mtime.incrementAndGet();
207  }
208
209  /**
210   * Updates the internal table permissions cache for specified table.
211   * @param table updated table name
212   * @param tablePerms new table permissions
213   */
214  private void updateTableCache(TableName table, ListMultimap<String, Permission> tablePerms) {
215    PermissionCache<TablePermission> cacheToUpdate =
216      tableCache.getOrDefault(table, new PermissionCache<>());
217    clearCache(cacheToUpdate);
218    updateCache(tablePerms, cacheToUpdate);
219    tableCache.put(table, cacheToUpdate);
220    mtime.incrementAndGet();
221  }
222
223  /**
224   * Updates the internal namespace permissions cache for specified namespace.
225   * @param namespace updated namespace
226   * @param nsPerms new namespace permissions
227   */
228  private void updateNamespaceCache(String namespace,
229      ListMultimap<String, Permission> nsPerms) {
230    PermissionCache<NamespacePermission> cacheToUpdate =
231      namespaceCache.getOrDefault(namespace, new PermissionCache<>());
232    clearCache(cacheToUpdate);
233    updateCache(nsPerms, cacheToUpdate);
234    namespaceCache.put(namespace, cacheToUpdate);
235    mtime.incrementAndGet();
236  }
237
238  private void clearCache(PermissionCache cacheToUpdate) {
239    cacheToUpdate.clear();
240  }
241
242  @SuppressWarnings("unchecked")
243  private void updateCache(ListMultimap<String, ? extends Permission> newPermissions,
244      PermissionCache cacheToUpdate) {
245    for (String name : newPermissions.keySet()) {
246      for (Permission permission : newPermissions.get(name)) {
247        cacheToUpdate.put(name, permission);
248      }
249    }
250  }
251
252  /**
253   * Check if user has given action privilige in global scope.
254   * @param user user name
255   * @param action one of action in [Read, Write, Create, Exec, Admin]
256   * @return true if user has, false otherwise
257   */
258  public boolean authorizeUserGlobal(User user, Permission.Action action) {
259    if (user == null) {
260      return false;
261    }
262    if (Superusers.isSuperUser(user)) {
263      return true;
264    }
265    if (authorizeGlobal(globalCache.get(user.getShortName()), action)) {
266      return true;
267    }
268    for (String group : user.getGroupNames()) {
269      if (authorizeGlobal(globalCache.get(AuthUtil.toGroupEntry(group)), action)) {
270        return true;
271      }
272    }
273    return false;
274  }
275
276  private boolean authorizeGlobal(GlobalPermission permissions, Permission.Action action) {
277    return permissions != null && permissions.implies(action);
278  }
279
280  /**
281   * Check if user has given action privilige in namespace scope.
282   * @param user user name
283   * @param namespace namespace
284   * @param action one of action in [Read, Write, Create, Exec, Admin]
285   * @return true if user has, false otherwise
286   */
287  public boolean authorizeUserNamespace(User user, String namespace, Permission.Action action) {
288    if (user == null) {
289      return false;
290    }
291    if (authorizeUserGlobal(user, action)) {
292      return true;
293    }
294    PermissionCache<NamespacePermission> nsPermissions = namespaceCache.getOrDefault(namespace,
295      NS_NO_PERMISSION);
296    if (authorizeNamespace(nsPermissions.get(user.getShortName()), namespace, action)) {
297      return true;
298    }
299    for (String group : user.getGroupNames()) {
300      if (authorizeNamespace(nsPermissions.get(AuthUtil.toGroupEntry(group)), namespace, action)) {
301        return true;
302      }
303    }
304    return false;
305  }
306
307  private boolean authorizeNamespace(Set<NamespacePermission> permissions,
308      String namespace, Permission.Action action) {
309    if (permissions == null) {
310      return false;
311    }
312    for (NamespacePermission permission : permissions) {
313      if (permission.implies(namespace, action)) {
314        return true;
315      }
316    }
317    return false;
318  }
319
320  /**
321   * Checks if the user has access to the full table or at least a family/qualifier
322   * for the specified action.
323   * @param user user name
324   * @param table table name
325   * @param action action in one of [Read, Write, Create, Exec, Admin]
326   * @return true if the user has access to the table, false otherwise
327   */
328  public boolean accessUserTable(User user, TableName table, Permission.Action action) {
329    if (user == null) {
330      return false;
331    }
332    if (table == null) {
333      table = AccessControlLists.ACL_TABLE_NAME;
334    }
335    if (authorizeUserNamespace(user, table.getNamespaceAsString(), action)) {
336      return true;
337    }
338    PermissionCache<TablePermission> tblPermissions = tableCache.getOrDefault(table,
339      TBL_NO_PERMISSION);
340    if (hasAccessTable(tblPermissions.get(user.getShortName()), action)) {
341      return true;
342    }
343    for (String group : user.getGroupNames()) {
344      if (hasAccessTable(tblPermissions.get(AuthUtil.toGroupEntry(group)), action)) {
345        return true;
346      }
347    }
348    return false;
349  }
350
351  private boolean hasAccessTable(Set<TablePermission> permissions, Permission.Action action) {
352    if (permissions == null) {
353      return false;
354    }
355    for (TablePermission permission : permissions) {
356      if (permission.implies(action)) {
357        return true;
358      }
359    }
360    return false;
361  }
362
363  /**
364   * Check if user has given action privilige in table scope.
365   * @param user user name
366   * @param table table name
367   * @param action one of action in [Read, Write, Create, Exec, Admin]
368   * @return true if user has, false otherwise
369   */
370  public boolean authorizeUserTable(User user, TableName table, Permission.Action action) {
371    return authorizeUserTable(user, table, null, null, action);
372  }
373
374  /**
375   * Check if user has given action privilige in table:family scope.
376   * @param user user name
377   * @param table table name
378   * @param family family name
379   * @param action one of action in [Read, Write, Create, Exec, Admin]
380   * @return true if user has, false otherwise
381   */
382  public boolean authorizeUserTable(User user, TableName table, byte[] family,
383      Permission.Action action) {
384    return authorizeUserTable(user, table, family, null, action);
385  }
386
387  /**
388   * Check if user has given action privilige in table:family:qualifier scope.
389   * @param user user name
390   * @param table table name
391   * @param family family name
392   * @param qualifier qualifier name
393   * @param action one of action in [Read, Write, Create, Exec, Admin]
394   * @return true if user has, false otherwise
395   */
396  public boolean authorizeUserTable(User user, TableName table, byte[] family,
397      byte[] qualifier, Permission.Action action) {
398    if (user == null) {
399      return false;
400    }
401    if (table == null) {
402      table = AccessControlLists.ACL_TABLE_NAME;
403    }
404    if (authorizeUserNamespace(user, table.getNamespaceAsString(), action)) {
405      return true;
406    }
407    PermissionCache<TablePermission> tblPermissions = tableCache.getOrDefault(table,
408      TBL_NO_PERMISSION);
409    if (authorizeTable(tblPermissions.get(user.getShortName()), table, family, qualifier, action)) {
410      return true;
411    }
412    for (String group : user.getGroupNames()) {
413      if (authorizeTable(tblPermissions.get(AuthUtil.toGroupEntry(group)),
414          table, family, qualifier, action)) {
415        return true;
416      }
417    }
418    return false;
419  }
420
421  private boolean authorizeTable(Set<TablePermission> permissions,
422      TableName table, byte[] family, byte[] qualifier, Permission.Action action) {
423    if (permissions == null) {
424      return false;
425    }
426    for (TablePermission permission : permissions) {
427      if (permission.implies(table, family, qualifier, action)) {
428        return true;
429      }
430    }
431    return false;
432  }
433
434  /**
435   * Check if user has given action privilige in table:family scope.
436   * This method is for backward compatibility.
437   * @param user user name
438   * @param table table name
439   * @param family family names
440   * @param action one of action in [Read, Write, Create, Exec, Admin]
441   * @return true if user has, false otherwise
442   */
443  public boolean authorizeUserFamily(User user, TableName table,
444      byte[] family, Permission.Action action) {
445    PermissionCache<TablePermission> tblPermissions = tableCache.getOrDefault(table,
446      TBL_NO_PERMISSION);
447    if (authorizeFamily(tblPermissions.get(user.getShortName()), table, family, action)) {
448      return true;
449    }
450    for (String group : user.getGroupNames()) {
451      if (authorizeFamily(tblPermissions.get(AuthUtil.toGroupEntry(group)),
452          table, family, action)) {
453        return true;
454      }
455    }
456    return false;
457  }
458
459  private boolean authorizeFamily(Set<TablePermission> permissions,
460      TableName table, byte[] family, Permission.Action action) {
461    if (permissions == null) {
462      return false;
463    }
464    for (TablePermission permission : permissions) {
465      if (permission.implies(table, family, action)) {
466        return true;
467      }
468    }
469    return false;
470  }
471
472  /**
473   * Check if user has given action privilige in cell scope.
474   * @param user user name
475   * @param table table name
476   * @param cell cell to be checked
477   * @param action one of action in [Read, Write, Create, Exec, Admin]
478   * @return true if user has, false otherwise
479   */
480  public boolean authorizeCell(User user, TableName table, Cell cell, Permission.Action action) {
481    try {
482      List<Permission> perms = AccessControlLists.getCellPermissionsForUser(user, cell);
483      if (LOG.isTraceEnabled()) {
484        LOG.trace("Perms for user {} in table {} in cell {}: {}",
485          user.getShortName(), table, cell, (perms != null ? perms : ""));
486      }
487      if (perms != null) {
488        for (Permission p: perms) {
489          if (p.implies(action)) {
490            return true;
491          }
492        }
493      }
494    } catch (IOException e) {
495      // We failed to parse the KV tag
496      LOG.error("Failed parse of ACL tag in cell " + cell);
497      // Fall through to check with the table and CF perms we were able
498      // to collect regardless
499    }
500    return false;
501  }
502
503  /**
504   * Remove given namespace from AuthManager's namespace cache.
505   * @param ns namespace
506   */
507  public void removeNamespace(byte[] ns) {
508    namespaceCache.remove(Bytes.toString(ns));
509  }
510
511  /**
512   * Remove given table from AuthManager's table cache.
513   * @param table table name
514   */
515  public void removeTable(TableName table) {
516    tableCache.remove(table);
517  }
518
519  /**
520   * Last modification logical time
521   * @return time
522   */
523  public long getMTime() {
524    return mtime.get();
525  }
526
527  private static Map<ZKWatcher, AuthManager> managerMap = new HashMap<>();
528
529  private static Map<AuthManager, Integer> refCount = new HashMap<>();
530
531  /**
532   * Returns a AuthManager from the cache. If not cached, constructs a new one.
533   * Returned instance should be released back by calling {@link #release(AuthManager)}.
534   * @param watcher zk watcher
535   * @param conf configuration
536   * @return an AuthManager
537   * @throws IOException zookeeper initialization failed
538   */
539  public synchronized static AuthManager getOrCreate(
540      ZKWatcher watcher, Configuration conf) throws IOException {
541    AuthManager instance = managerMap.get(watcher);
542    if (instance == null) {
543      instance = new AuthManager(watcher, conf);
544      managerMap.put(watcher, instance);
545    }
546    int ref = refCount.get(instance) == null ? 0 : refCount.get(instance);
547    refCount.put(instance, ref + 1);
548    return instance;
549  }
550
551  @VisibleForTesting
552  public static int getTotalRefCount() {
553    int total = 0;
554    for (int count : refCount.values()) {
555      total += count;
556    }
557    return total;
558  }
559
560  /**
561   * Releases the resources for the given AuthManager if the reference count is down to 0.
562   * @param instance AuthManager to be released
563   */
564  public synchronized static void release(AuthManager instance) {
565    if (refCount.get(instance) == null || refCount.get(instance) < 1) {
566      String msg = "Something wrong with the AuthManager reference counting: " + instance
567          + " whose count is " + refCount.get(instance);
568      LOG.error(HBaseMarkers.FATAL, msg);
569      instance.close();
570      managerMap.remove(instance.getZKPermissionWatcher().getWatcher());
571      instance.getZKPermissionWatcher().getWatcher().abort(msg, null);
572    } else {
573      int ref = refCount.get(instance);
574      --ref;
575      refCount.put(instance, ref);
576      if (ref == 0) {
577        instance.close();
578        managerMap.remove(instance.getZKPermissionWatcher().getWatcher());
579        refCount.remove(instance);
580      }
581    }
582  }
583}