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    private TableAuthManager authManager;
45    /**
46     * if we are active, usually false, only true if "hbase.security.authorization"
47     * has been set to true in site configuration.see HBASE-19483.
48     */
49    private boolean authorizationEnabled;
50  
51    public static boolean isAuthorizationSupported(Configuration conf) {
52      // branch-1.4 and branch-1 use a default of false but we must assume it is true
53      // for compatibility with branch-1.3 tests
54      return conf.getBoolean(User.HBASE_SECURITY_AUTHORIZATION_CONF_KEY, true);
55    }
56  
57    /**
58     * Constructor with existing configuration
59     *
60     * @param conf Existing configuration to use
61     * @param zkw reference to the {@link ZooKeeperWatcher}
62     */
63    public AccessChecker(final Configuration conf, final ZooKeeperWatcher zkw)
64        throws RuntimeException {
65      // If zk is null or IOException while obtaining auth manager,
66      // throw RuntimeException so that the coprocessor is unloaded.
67      if (zkw != null) {
68        try {
69          this.authManager = TableAuthManager.getOrCreate(zkw, conf);
70        } catch (IOException ioe) {
71          throw new RuntimeException("Error obtaining AccessChecker", ioe);
72        }
73      } else {
74        throw new NullPointerException("Error obtaining AccessChecker, zk found null.");
75      }
76      authorizationEnabled = isAuthorizationSupported(conf);
77    }
78  
79    public TableAuthManager getAuthManager() {
80      return authManager;
81    }
82  
83    public void logResult(AuthResult result) {
84      if (AUDITLOG.isTraceEnabled()) {
85        InetAddress remoteAddr = RpcServer.getRemoteAddress();
86        AUDITLOG.trace("Access " + (result.isAllowed() ? "allowed" : "denied") +
87            " for user " + (result.getUser() != null ? result.getUser().getShortName() : "UNKNOWN") +
88            "; reason: " + result.getReason() +
89            "; remote address: " + (remoteAddr != null ? remoteAddr : "") +
90            "; request: " + result.getRequest() +
91            "; context: " + result.toContextString());
92      }
93    }
94  
95    /**
96     * Authorizes that the current user has any of the given permissions for the
97     * given table, column family and column qualifier.
98     * @param tableName Table requested
99     * @param family Column family requested
100    * @param qualifier Column qualifier requested
101    * @throws IOException if obtaining the current user fails
102    * @throws AccessDeniedException if user has no authorization
103    */
104   public void requirePermission(User user, String request, TableName tableName, byte[] family,
105       byte[] qualifier, Action... permissions) throws IOException {
106     AuthResult result = null;
107 
108     for (Action permission : permissions) {
109       if (authManager.authorize(user, tableName, family, qualifier, permission)) {
110         result = AuthResult.allow(request, "Table permission granted", user,
111             permission, tableName, family, qualifier);
112         break;
113       } else {
114         // rest of the world
115         result = AuthResult.deny(request, "Insufficient permissions", user,
116             permission, tableName, family, qualifier);
117       }
118     }
119     logResult(result);
120     if (authorizationEnabled && !result.isAllowed()) {
121       throw new AccessDeniedException("Insufficient permissions " + result.toContextString());
122     }
123   }
124 
125   /**
126    * Authorizes that the current user has any of the given permissions for the
127    * given table, column family and column qualifier.
128    * @param tableName Table requested
129    * @param family Column family param
130    * @param qualifier Column qualifier param
131    * @throws IOException if obtaining the current user fails
132    * @throws AccessDeniedException if user has no authorization
133    */
134   public void requireTablePermission(User user, String request, TableName tableName, byte[] family,
135       byte[] qualifier, Action... permissions) throws IOException {
136     AuthResult result = null;
137 
138     for (Action permission : permissions) {
139       if (authManager.authorize(user, tableName, null, null, permission)) {
140         result = AuthResult.allow(request, "Table permission granted", user,
141             permission, tableName, null, null);
142         result.getParams().setFamily(family).setQualifier(qualifier);
143         break;
144       } else {
145         // rest of the world
146         result = AuthResult.deny(request, "Insufficient permissions", user,
147             permission, tableName, family, qualifier);
148         result.getParams().setFamily(family).setQualifier(qualifier);
149       }
150     }
151     logResult(result);
152     if (authorizationEnabled && !result.isAllowed()) {
153       throw new AccessDeniedException("Insufficient permissions " + result.toContextString());
154     }
155   }
156 
157   /**
158    * Authorizes that the current user has any of the given permissions to access the table.
159    *
160    * @param tableName Table requested
161    * @param permissions Actions being requested
162    * @throws IOException if obtaining the current user fails
163    * @throws AccessDeniedException if user has no authorization
164    */
165   public void requireAccess(User user, String request, TableName tableName,
166       Action... permissions) throws IOException {
167     AuthResult result = null;
168 
169     for (Action permission : permissions) {
170       if (authManager.hasAccess(user, tableName, permission)) {
171         result = AuthResult.allow(request, "Table permission granted", user,
172             permission, tableName, null, null);
173         break;
174       } else {
175         // rest of the world
176         result = AuthResult.deny(request, "Insufficient permissions", user,
177             permission, tableName, null, null);
178       }
179     }
180     logResult(result);
181     if (authorizationEnabled && !result.isAllowed()) {
182       throw new AccessDeniedException("Insufficient permissions " + result.toContextString());
183     }
184   }
185 
186   /**
187    * Authorizes that the current user has global privileges for the given action.
188    * @param perm The action being requested
189    * @throws IOException if obtaining the current user fails
190    * @throws AccessDeniedException if authorization is denied
191    */
192   public void requirePermission(User user, String request, Action perm) throws IOException {
193     requireGlobalPermission(user, request, perm, null, null);
194   }
195 
196   /**
197    * Checks that the user has the given global permission. The generated
198    * audit log message will contain context information for the operation
199    * being authorized, based on the given parameters.
200    * @param perm Action being requested
201    * @param tableName Affected table name.
202    * @param familyMap Affected column families.
203    */
204   public void requireGlobalPermission(User user, String request, Action perm, TableName tableName,
205       Map<byte[], ? extends Collection<byte[]>> familyMap) throws IOException {
206     AuthResult result = null;
207     if (authManager.authorize(user, perm)) {
208       result = AuthResult.allow(request, "Global check allowed", user, perm, tableName, familyMap);
209       result.getParams().setTableName(tableName).setFamilies(familyMap);
210       logResult(result);
211     } else {
212       result = AuthResult.deny(request, "Global check failed", user, perm, tableName, familyMap);
213       result.getParams().setTableName(tableName).setFamilies(familyMap);
214       logResult(result);
215       if (authorizationEnabled) {
216         throw new AccessDeniedException("Insufficient permissions for user '" +
217             (user != null ? user.getShortName() : "null") +"' (global, action=" +
218             perm.toString() + ")");
219       }
220     }
221   }
222 
223   /**
224    * Checks that the user has the given global permission. The generated
225    * audit log message will contain context information for the operation
226    * being authorized, based on the given parameters.
227    * @param perm Action being requested
228    * @param namespace  The given namespace
229    */
230   public void requireGlobalPermission(User user, String request, Action perm,
231       String namespace) throws IOException {
232     AuthResult authResult = null;
233     if (authManager.authorize(user, perm)) {
234       authResult = AuthResult.allow(request, "Global check allowed", user, perm, null);
235       authResult.getParams().setNamespace(namespace);
236       logResult(authResult);
237     } else {
238       authResult = AuthResult.deny(request, "Global check failed", user, perm, null);
239       authResult.getParams().setNamespace(namespace);
240       logResult(authResult);
241       if (authorizationEnabled) {
242         throw new AccessDeniedException("Insufficient permissions for user '" +
243             (user != null ? user.getShortName() : "null") +"' (global, action=" +
244             perm.toString() + ")");
245       }
246     }
247   }
248 
249   /**
250    * Checks that the user has the given global or namespace permission.
251    * @param namespace  The given namespace
252    * @param permissions Actions being requested
253    */
254   public void requireNamespacePermission(User user, String request, String namespace,
255       Action... permissions) throws IOException {
256     AuthResult result = null;
257 
258     for (Action permission : permissions) {
259       if (authManager.authorize(user, namespace, permission)) {
260         result = AuthResult.allow(request, "Namespace permission granted",
261             user, permission, namespace);
262         break;
263       } else {
264         // rest of the world
265         result = AuthResult.deny(request, "Insufficient permissions", user,
266             permission, namespace);
267       }
268     }
269     logResult(result);
270     if (authorizationEnabled && !result.isAllowed()) {
271       throw new AccessDeniedException("Insufficient permissions "
272           + result.toContextString());
273     }
274   }
275 
276   /**
277    * Checks that the user has the given global or namespace permission.
278    * @param namespace   The given namespace
279    * @param permissions Actions being requested
280    */
281   public void requireNamespacePermission(User user, String request, String namespace,
282       TableName tableName,
283       Map<byte[], ? extends Collection<byte[]>> familyMap, Action... permissions)
284       throws IOException {
285     AuthResult result = null;
286 
287     for (Action permission : permissions) {
288       if (authManager.authorize(user, namespace, permission)) {
289         result = AuthResult.allow(request, "Namespace permission granted",
290             user, permission, namespace);
291         result.getParams().setTableName(tableName).setFamilies(familyMap);
292         break;
293       } else {
294         // rest of the world
295         result = AuthResult.deny(request, "Insufficient permissions", user,
296             permission, namespace);
297         result.getParams().setTableName(tableName).setFamilies(familyMap);
298       }
299     }
300     logResult(result);
301     if (authorizationEnabled && !result.isAllowed()) {
302       throw new AccessDeniedException("Insufficient permissions "
303           + result.toContextString());
304     }
305   }
306 }