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}