View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.hadoop.hbase.security.access;
20  
21  import java.io.IOException;
22  import java.net.InetAddress;
23  import java.util.Collection;
24  import java.util.Map;
25  
26  import org.apache.hadoop.conf.Configuration;
27  import org.apache.hadoop.hbase.HBaseInterfaceAudience;
28  import org.apache.hadoop.hbase.TableName;
29  import org.apache.hadoop.hbase.classification.InterfaceAudience;
30  import org.apache.hadoop.hbase.classification.InterfaceStability;
31  import org.apache.hadoop.hbase.ipc.RpcServer;
32  import org.apache.hadoop.hbase.security.AccessDeniedException;
33  import org.apache.hadoop.hbase.security.User;
34  import org.apache.hadoop.hbase.security.access.Permission.Action;
35  import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
36  import org.slf4j.Logger;
37  import org.slf4j.LoggerFactory;
38  
39  @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.COPROC)
40  @InterfaceStability.Evolving
41  public final class AccessChecker {
42    private static final Logger AUDITLOG =
43        LoggerFactory.getLogger("SecurityLogger." + AccessChecker.class.getName());
44    // TODO: we should move to a design where we don't even instantiate an AccessChecker if
45    // authorization is not enabled (like in RSRpcServices), instead of always instantiating one and
46    // calling requireXXX() only to do nothing (since authorizationEnabled will be false).
47    private TableAuthManager authManager;
48    /**
49     * if we are active, usually false, only true if "hbase.security.authorization"
50     * has been set to true in site configuration.see HBASE-19483.
51     */
52    private boolean authorizationEnabled;
53  
54    public static boolean isAuthorizationSupported(Configuration conf) {
55      return conf.getBoolean(User.HBASE_SECURITY_AUTHORIZATION_CONF_KEY, false);
56    }
57  
58    /**
59     * Constructor with existing configuration
60     *
61     * @param conf Existing configuration to use
62     * @param zkw reference to the {@link ZooKeeperWatcher}
63     */
64    public AccessChecker(final Configuration conf, final ZooKeeperWatcher zkw)
65        throws RuntimeException {
66      if (zkw != null) {
67        try {
68          this.authManager = TableAuthManager.getOrCreate(zkw, conf);
69        } catch (IOException ioe) {
70          throw new RuntimeException("Error obtaining AccessChecker", ioe);
71        }
72      } else {
73        throw new NullPointerException("Error obtaining AccessChecker, zk found null.");
74      }
75      authorizationEnabled = isAuthorizationSupported(conf);
76    }
77  
78    /**
79     * Releases {@link TableAuthManager}'s reference.
80     */
81    public void stop() {
82      TableAuthManager.release(authManager);
83    }
84  
85    public TableAuthManager getAuthManager() {
86      return authManager;
87    }
88  
89    public void logResult(AuthResult result) {
90      if (AUDITLOG.isTraceEnabled()) {
91        InetAddress remoteAddr = RpcServer.getRemoteAddress();
92        AUDITLOG.trace("Access " + (result.isAllowed() ? "allowed" : "denied") +
93            " for user " + (result.getUser() != null ? result.getUser().getShortName() : "UNKNOWN") +
94            "; reason: " + result.getReason() +
95            "; remote address: " + (remoteAddr != null ? remoteAddr : "") +
96            "; request: " + result.getRequest() +
97            "; context: " + result.toContextString());
98      }
99    }
100 
101   /**
102    * Authorizes that the current user has any of the given permissions for the
103    * given table, column family and column qualifier.
104    * @param tableName Table requested
105    * @param family Column family requested
106    * @param qualifier Column qualifier requested
107    * @throws IOException if obtaining the current user fails
108    * @throws AccessDeniedException if user has no authorization
109    */
110   public void requirePermission(User user, String request, TableName tableName, byte[] family,
111       byte[] qualifier, Action... permissions) throws IOException {
112     AuthResult result = null;
113 
114     for (Action permission : permissions) {
115       if (authManager.authorize(user, tableName, family, qualifier, permission)) {
116         result = AuthResult.allow(request, "Table permission granted", user,
117             permission, tableName, family, qualifier);
118         break;
119       } else {
120         // rest of the world
121         result = AuthResult.deny(request, "Insufficient permissions", user,
122             permission, tableName, family, qualifier);
123       }
124     }
125     logResult(result);
126     if (authorizationEnabled && !result.isAllowed()) {
127       throw new AccessDeniedException("Insufficient permissions " + result.toContextString());
128     }
129   }
130 
131   /**
132    * Authorizes that the current user has any of the given permissions for the
133    * given table, column family and column qualifier.
134    * @param tableName Table requested
135    * @param family Column family param
136    * @param qualifier Column qualifier param
137    * @throws IOException if obtaining the current user fails
138    * @throws AccessDeniedException if user has no authorization
139    */
140   public void requireTablePermission(User user, String request, TableName tableName, byte[] family,
141       byte[] qualifier, Action... permissions) throws IOException {
142     AuthResult result = null;
143 
144     for (Action permission : permissions) {
145       if (authManager.authorize(user, tableName, null, null, permission)) {
146         result = AuthResult.allow(request, "Table permission granted", user,
147             permission, tableName, null, null);
148         result.getParams().setFamily(family).setQualifier(qualifier);
149         break;
150       } else {
151         // rest of the world
152         result = AuthResult.deny(request, "Insufficient permissions", user,
153             permission, tableName, family, qualifier);
154         result.getParams().setFamily(family).setQualifier(qualifier);
155       }
156     }
157     logResult(result);
158     if (authorizationEnabled && !result.isAllowed()) {
159       throw new AccessDeniedException("Insufficient permissions " + result.toContextString());
160     }
161   }
162 
163   /**
164    * Authorizes that the current user has any of the given permissions to access the table.
165    *
166    * @param tableName Table requested
167    * @param permissions Actions being requested
168    * @throws IOException if obtaining the current user fails
169    * @throws AccessDeniedException if user has no authorization
170    */
171   public void requireAccess(User user, String request, TableName tableName,
172       Action... permissions) throws IOException {
173     if (!authorizationEnabled) {
174       return;
175     }
176     AuthResult result = null;
177 
178     for (Action permission : permissions) {
179       if (authManager.hasAccess(user, tableName, permission)) {
180         result = AuthResult.allow(request, "Table permission granted", user,
181             permission, tableName, null, null);
182         break;
183       } else {
184         // rest of the world
185         result = AuthResult.deny(request, "Insufficient permissions", user,
186             permission, tableName, null, null);
187       }
188     }
189     logResult(result);
190     if (!result.isAllowed()) {
191       throw new AccessDeniedException("Insufficient permissions " + result.toContextString());
192     }
193   }
194 
195   /**
196    * Authorizes that the current user has global privileges for the given action.
197    * @param perm The action being requested
198    * @throws IOException if obtaining the current user fails
199    * @throws AccessDeniedException if authorization is denied
200    */
201   public void requirePermission(User user, String request, Action perm) throws IOException {
202     requireGlobalPermission(user, request, perm, null, null);
203   }
204 
205   /**
206    * Checks that the user has the given global permission. The generated
207    * audit log message will contain context information for the operation
208    * being authorized, based on the given parameters.
209    * @param perm Action being requested
210    * @param tableName Affected table name.
211    * @param familyMap Affected column families.
212    */
213   public void requireGlobalPermission(User user, String request, Action perm, TableName tableName,
214       Map<byte[], ? extends Collection<byte[]>> familyMap) throws IOException {
215     if (!authorizationEnabled) {
216       return;
217     }
218     AuthResult result;
219     if (authManager.authorize(user, perm)) {
220       result = AuthResult.allow(request, "Global check allowed", user, perm, tableName, familyMap);
221       result.getParams().setTableName(tableName).setFamilies(familyMap);
222       logResult(result);
223     } else {
224       result = AuthResult.deny(request, "Global check failed", user, perm, tableName, familyMap);
225       result.getParams().setTableName(tableName).setFamilies(familyMap);
226       logResult(result);
227       throw new AccessDeniedException(
228           "Insufficient permissions for user '" + (user != null ? user.getShortName() : "null")
229               + "' (global, action=" + perm.toString() + ")");
230     }
231   }
232 
233   /**
234    * Checks that the user has the given global permission. The generated
235    * audit log message will contain context information for the operation
236    * being authorized, based on the given parameters.
237    * @param perm Action being requested
238    * @param namespace  The given namespace
239    */
240   public void requireGlobalPermission(User user, String request, Action perm,
241       String namespace) throws IOException {
242     if (!authorizationEnabled) {
243       return;
244     }
245     AuthResult authResult;
246     if (authManager.authorize(user, perm)) {
247       authResult = AuthResult.allow(request, "Global check allowed", user, perm, null);
248       authResult.getParams().setNamespace(namespace);
249       logResult(authResult);
250     } else {
251       authResult = AuthResult.deny(request, "Global check failed", user, perm, null);
252       authResult.getParams().setNamespace(namespace);
253       logResult(authResult);
254       throw new AccessDeniedException(
255           "Insufficient permissions for user '" + (user != null ? user.getShortName() : "null")
256               + "' (global, action=" + perm.toString() + ")");
257     }
258   }
259 
260   /**
261    * Checks that the user has the given global or namespace permission.
262    * @param namespace  The given namespace
263    * @param permissions Actions being requested
264    */
265   public void requireNamespacePermission(User user, String request, String namespace,
266       Action... permissions) throws IOException {
267     if (!authorizationEnabled) {
268       return;
269     }
270     AuthResult result = null;
271 
272     for (Action permission : permissions) {
273       if (authManager.authorize(user, namespace, permission)) {
274         result =
275             AuthResult.allow(request, "Namespace permission granted", user, permission, namespace);
276         break;
277       } else {
278         // rest of the world
279         result = AuthResult.deny(request, "Insufficient permissions", user, permission, namespace);
280       }
281     }
282     logResult(result);
283     if (!result.isAllowed()) {
284       throw new AccessDeniedException("Insufficient permissions " + result.toContextString());
285     }
286   }
287 
288   /**
289    * Checks that the user has the given global or namespace permission.
290    * @param namespace   The given namespace
291    * @param permissions Actions being requested
292    */
293   public void requireNamespacePermission(User user, String request, String namespace,
294       TableName tableName, Map<byte[], ? extends Collection<byte[]>> familyMap,
295       Action... permissions) throws IOException {
296     if (!authorizationEnabled) {
297       return;
298     }
299     AuthResult result = null;
300 
301     for (Action permission : permissions) {
302       if (authManager.authorize(user, namespace, permission)) {
303         result =
304             AuthResult.allow(request, "Namespace permission granted", user, permission, namespace);
305         result.getParams().setTableName(tableName).setFamilies(familyMap);
306         break;
307       } else {
308         // rest of the world
309         result = AuthResult.deny(request, "Insufficient permissions", user, permission, namespace);
310         result.getParams().setTableName(tableName).setFamilies(familyMap);
311       }
312     }
313     logResult(result);
314     if (!result.isAllowed()) {
315       throw new AccessDeniedException("Insufficient permissions " + result.toContextString());
316     }
317   }
318 }