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}