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