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,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.hadoop.hbase.security.access;
020
021import java.io.IOException;
022import java.net.InetAddress;
023import java.util.Collection;
024import java.util.Map;
025
026import org.apache.hadoop.conf.Configuration;
027import org.apache.hadoop.hbase.DoNotRetryIOException;
028import org.apache.hadoop.hbase.HBaseInterfaceAudience;
029import org.apache.hadoop.hbase.TableName;
030import org.apache.hadoop.hbase.client.RegionInfo;
031import org.apache.hadoop.hbase.ipc.RpcServer;
032import org.apache.hadoop.hbase.security.AccessDeniedException;
033import org.apache.hadoop.hbase.security.User;
034import org.apache.hadoop.hbase.security.access.Permission.Action;
035import org.apache.hadoop.hbase.zookeeper.ZKWatcher;
036import org.apache.yetus.audience.InterfaceAudience;
037import org.apache.yetus.audience.InterfaceStability;
038import org.slf4j.Logger;
039import org.slf4j.LoggerFactory;
040
041@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.COPROC)
042@InterfaceStability.Evolving
043public class AccessChecker {
044  private static final Logger AUDITLOG =
045      LoggerFactory.getLogger("SecurityLogger." + AccessChecker.class.getName());
046  // TODO: we should move to a design where we don't even instantiate an AccessChecker if
047  // authorization is not enabled (like in RSRpcServices), instead of always instantiating one and
048  // calling requireXXX() only to do nothing (since authorizationEnabled will be false).
049  private TableAuthManager authManager;
050  /**
051   * if we are active, usually false, only true if "hbase.security.authorization"
052   * has been set to true in site configuration.see HBASE-19483.
053   */
054  private boolean authorizationEnabled;
055
056  public static boolean isAuthorizationSupported(Configuration conf) {
057    return conf.getBoolean(User.HBASE_SECURITY_AUTHORIZATION_CONF_KEY, false);
058  }
059
060  /**
061   * Constructor with existing configuration
062   *
063   * @param conf Existing configuration to use
064   * @param zkw reference to the {@link ZKWatcher}
065   */
066  public AccessChecker(final Configuration conf, final ZKWatcher zkw)
067      throws RuntimeException {
068    if (zkw != null) {
069      try {
070        this.authManager = TableAuthManager.getOrCreate(zkw, conf);
071      } catch (IOException ioe) {
072        throw new RuntimeException("Error obtaining AccessChecker", ioe);
073      }
074    } else {
075      throw new NullPointerException("Error obtaining AccessChecker, zk found null.");
076    }
077    authorizationEnabled = isAuthorizationSupported(conf);
078  }
079
080  /**
081   * Releases {@link TableAuthManager}'s reference.
082   */
083  public void stop() {
084    TableAuthManager.release(authManager);
085  }
086
087  public TableAuthManager getAuthManager() {
088    return authManager;
089  }
090
091  /**
092   * Authorizes that the current user has any of the given permissions to access the table.
093   *
094   * @param tableName   Table requested
095   * @param permissions Actions being requested
096   * @throws IOException           if obtaining the current user fails
097   * @throws AccessDeniedException if user has no authorization
098   */
099  public void requireAccess(User user, String request, TableName tableName,
100      Action... permissions) throws IOException {
101    if (!authorizationEnabled) {
102      return;
103    }
104    AuthResult result = null;
105
106    for (Action permission : permissions) {
107      if (authManager.hasAccess(user, tableName, permission)) {
108        result = AuthResult.allow(request, "Table permission granted",
109            user, permission, tableName, null, null);
110        break;
111      } else {
112        // rest of the world
113        result = AuthResult.deny(request, "Insufficient permissions",
114            user, permission, tableName, null, null);
115      }
116    }
117    logResult(result);
118    if (!result.isAllowed()) {
119      throw new AccessDeniedException("Insufficient permissions " + result.toContextString());
120    }
121  }
122
123  /**
124   * Authorizes that the current user has global privileges for the given action.
125   *
126   * @param perm The action being requested
127   * @throws IOException           if obtaining the current user fails
128   * @throws AccessDeniedException if authorization is denied
129   */
130  public void requirePermission(User user, String request, Action perm)
131      throws IOException {
132    requireGlobalPermission(user, request, perm, null, null);
133  }
134
135  /**
136   * Checks that the user has the given global permission. The generated
137   * audit log message will contain context information for the operation
138   * being authorized, based on the given parameters.
139   *
140   * @param perm      Action being requested
141   * @param tableName Affected table name.
142   * @param familyMap Affected column families.
143   */
144  public void requireGlobalPermission(User user, String request,
145      Action perm, TableName tableName,
146      Map<byte[], ? extends Collection<byte[]>> familyMap)throws IOException {
147    if (!authorizationEnabled) {
148      return;
149    }
150    AuthResult result;
151    if (authManager.authorize(user, perm)) {
152      result = AuthResult.allow(request, "Global check allowed", user, perm, tableName, familyMap);
153      result.getParams().setTableName(tableName).setFamilies(familyMap);
154      logResult(result);
155    } else {
156      result = AuthResult.deny(request, "Global check failed", user, perm, tableName, familyMap);
157      result.getParams().setTableName(tableName).setFamilies(familyMap);
158      logResult(result);
159      throw new AccessDeniedException(
160          "Insufficient permissions for user '" + (user != null ? user.getShortName() : "null")
161              + "' (global, action=" + perm.toString() + ")");
162    }
163  }
164
165  /**
166   * Checks that the user has the given global permission. The generated
167   * audit log message will contain context information for the operation
168   * being authorized, based on the given parameters.
169   *
170   * @param perm      Action being requested
171   * @param namespace The given namespace
172   */
173  public void requireGlobalPermission(User user, String request, Action perm,
174      String namespace) throws IOException {
175    if (!authorizationEnabled) {
176      return;
177    }
178    AuthResult authResult;
179    if (authManager.authorize(user, perm)) {
180      authResult = AuthResult.allow(request, "Global check allowed", user, perm, null);
181      authResult.getParams().setNamespace(namespace);
182      logResult(authResult);
183    } else {
184      authResult = AuthResult.deny(request, "Global check failed", user, perm, null);
185      authResult.getParams().setNamespace(namespace);
186      logResult(authResult);
187      throw new AccessDeniedException(
188          "Insufficient permissions for user '" + (user != null ? user.getShortName() : "null")
189              + "' (global, action=" + perm.toString() + ")");
190    }
191  }
192
193  /**
194   * Checks that the user has the given global or namespace permission.
195   *
196   * @param namespace  The given namespace
197   * @param permissions Actions being requested
198   */
199  public void requireNamespacePermission(User user, String request, String namespace,
200      Action... permissions) throws IOException {
201    if (!authorizationEnabled) {
202      return;
203    }
204    AuthResult result = null;
205
206    for (Action permission : permissions) {
207      if (authManager.authorize(user, namespace, permission)) {
208        result =
209            AuthResult.allow(request, "Namespace permission granted", user, permission, namespace);
210        break;
211      } else {
212        // rest of the world
213        result = AuthResult.deny(request, "Insufficient permissions", user, permission, namespace);
214      }
215    }
216    logResult(result);
217    if (!result.isAllowed()) {
218      throw new AccessDeniedException("Insufficient permissions " + result.toContextString());
219    }
220  }
221
222  /**
223   * Checks that the user has the given global or namespace permission.
224   *
225   * @param namespace  The given namespace
226   * @param permissions Actions being requested
227   */
228  public void requireNamespacePermission(User user, String request, String namespace,
229      TableName tableName, Map<byte[], ? extends Collection<byte[]>> familyMap,
230      Action... permissions) throws IOException {
231    if (!authorizationEnabled) {
232      return;
233    }
234    AuthResult result = null;
235
236    for (Action permission : permissions) {
237      if (authManager.authorize(user, namespace, permission)) {
238        result =
239            AuthResult.allow(request, "Namespace permission granted", user, permission, namespace);
240        result.getParams().setTableName(tableName).setFamilies(familyMap);
241        break;
242      } else {
243        // rest of the world
244        result = AuthResult.deny(request, "Insufficient permissions", user, permission, namespace);
245        result.getParams().setTableName(tableName).setFamilies(familyMap);
246      }
247    }
248    logResult(result);
249    if (!result.isAllowed()) {
250      throw new AccessDeniedException("Insufficient permissions " + result.toContextString());
251    }
252  }
253
254  /**
255   * Authorizes that the current user has any of the given permissions for the
256   * given table, column family and column qualifier.
257   *
258   * @param tableName Table requested
259   * @param family    Column family requested
260   * @param qualifier Column qualifier requested
261   * @throws IOException           if obtaining the current user fails
262   * @throws AccessDeniedException if user has no authorization
263   */
264  public void requirePermission(User user, String request, TableName tableName, byte[] family,
265      byte[] qualifier, Action... permissions) throws IOException {
266    if (!authorizationEnabled) {
267      return;
268    }
269    AuthResult result = null;
270
271    for (Action permission : permissions) {
272      if (authManager.authorize(user, tableName, family, qualifier, permission)) {
273        result = AuthResult.allow(request, "Table permission granted",
274            user, permission, tableName, family, qualifier);
275        break;
276      } else {
277        // rest of the world
278        result = AuthResult.deny(request, "Insufficient permissions",
279                user, permission, tableName, family, qualifier);
280      }
281    }
282    logResult(result);
283    if (!result.isAllowed()) {
284      throw new AccessDeniedException("Insufficient permissions " + result.toContextString());
285    }
286  }
287
288  /**
289   * Authorizes that the current user has any of the given permissions for the
290   * given table, column family and column qualifier.
291   *
292   * @param tableName Table requested
293   * @param family    Column family param
294   * @param qualifier Column qualifier param
295   * @throws IOException           if obtaining the current user fails
296   * @throws AccessDeniedException if user has no authorization
297   */
298  public void requireTablePermission(User user, String request,
299      TableName tableName,byte[] family, byte[] qualifier,
300      Action... permissions) throws IOException {
301    if (!authorizationEnabled) {
302      return;
303    }
304    AuthResult result = null;
305
306    for (Action permission : permissions) {
307      if (authManager.authorize(user, tableName, null, null, permission)) {
308        result = AuthResult.allow(request, "Table permission granted",
309            user, permission, tableName, null, null);
310        result.getParams().setFamily(family).setQualifier(qualifier);
311        break;
312      } else {
313        // rest of the world
314        result = AuthResult.deny(request, "Insufficient permissions",
315                user, permission, tableName, family, qualifier);
316        result.getParams().setFamily(family).setQualifier(qualifier);
317      }
318    }
319    logResult(result);
320    if (!result.isAllowed()) {
321      throw new AccessDeniedException("Insufficient permissions " + result.toContextString());
322    }
323  }
324
325  public void checkLockPermissions(User user, String namespace,
326      TableName tableName, RegionInfo[] regionInfos, String reason)
327      throws IOException {
328    if (namespace != null && !namespace.isEmpty()) {
329      requireNamespacePermission(user, reason, namespace, Action.ADMIN, Action.CREATE);
330    } else if (tableName != null || (regionInfos != null && regionInfos.length > 0)) {
331      // So, either a table or regions op. If latter, check perms ons table.
332      TableName tn = tableName != null? tableName: regionInfos[0].getTable();
333      requireTablePermission(user, reason, tn, null, null,
334          Action.ADMIN, Action.CREATE);
335    } else {
336      throw new DoNotRetryIOException("Invalid lock level when requesting permissions.");
337    }
338  }
339
340  public static void logResult(AuthResult result) {
341    if (AUDITLOG.isTraceEnabled()) {
342      AUDITLOG.trace(
343        "Access {} for user {}; reason: {}; remote address: {}; request: {}; context: {}",
344        (result.isAllowed() ? "allowed" : "denied"),
345        (result.getUser() != null ? result.getUser().getShortName() : "UNKNOWN"),
346        result.getReason(),
347        RpcServer.getRemoteAddress().map(InetAddress::toString).orElse(""),
348        result.getRequest(), result.toContextString());
349    }
350  }
351}