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, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.security.access; 019 020import java.io.IOException; 021import java.util.ArrayList; 022import java.util.List; 023import java.util.regex.Pattern; 024 025import org.apache.commons.lang3.StringUtils; 026import org.apache.hadoop.hbase.HConstants; 027import org.apache.hadoop.hbase.MasterNotRunningException; 028import org.apache.hadoop.hbase.NamespaceDescriptor; 029import org.apache.hadoop.hbase.TableName; 030import org.apache.hadoop.hbase.ZooKeeperConnectionException; 031import org.apache.hadoop.hbase.client.TableDescriptor; 032import org.apache.yetus.audience.InterfaceAudience; 033import org.apache.hadoop.hbase.client.Admin; 034import org.apache.hadoop.hbase.client.Connection; 035import org.apache.hadoop.hbase.client.Table; 036import org.apache.hadoop.hbase.client.security.SecurityCapability; 037import org.apache.hadoop.hbase.ipc.CoprocessorRpcChannel; 038import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos; 039import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos.AccessControlService.BlockingInterface; 040import org.apache.hadoop.hbase.util.Bytes; 041 042/** 043 * Utility client for doing access control admin operations. 044 */ 045@InterfaceAudience.Public 046public class AccessControlClient { 047 public static final TableName ACL_TABLE_NAME = 048 TableName.valueOf(NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR, "acl"); 049 050 /** 051 * Return true if authorization is supported and enabled 052 * @param connection The connection to use 053 * @return true if authorization is supported and enabled, false otherwise 054 * @throws IOException 055 */ 056 public static boolean isAuthorizationEnabled(Connection connection) throws IOException { 057 return connection.getAdmin().getSecurityCapabilities() 058 .contains(SecurityCapability.AUTHORIZATION); 059 } 060 061 /** 062 * Return true if cell authorization is supported and enabled 063 * @param connection The connection to use 064 * @return true if cell authorization is supported and enabled, false otherwise 065 * @throws IOException 066 */ 067 public static boolean isCellAuthorizationEnabled(Connection connection) throws IOException { 068 return connection.getAdmin().getSecurityCapabilities() 069 .contains(SecurityCapability.CELL_AUTHORIZATION); 070 } 071 072 private static BlockingInterface getAccessControlServiceStub(Table ht) 073 throws IOException { 074 CoprocessorRpcChannel service = ht.coprocessorService(HConstants.EMPTY_START_ROW); 075 BlockingInterface protocol = 076 AccessControlProtos.AccessControlService.newBlockingStub(service); 077 return protocol; 078 } 079 080 /** 081 * Grants permission on the specified table for the specified user 082 * @param connection The Connection instance to use 083 * @param tableName 084 * @param userName 085 * @param family 086 * @param qual 087 * @param mergeExistingPermissions If set to false, later granted permissions will override 088 * previous granted permissions. otherwise, it'll merge with previous granted 089 * permissions. 090 * @param actions 091 * @throws Throwable 092 */ 093 private static void grant(Connection connection, final TableName tableName, 094 final String userName, final byte[] family, final byte[] qual, boolean mergeExistingPermissions, 095 final Permission.Action... actions) throws Throwable { 096 connection.getAdmin().grant(new UserPermission(userName, Permission.newBuilder(tableName) 097 .withFamily(family).withQualifier(qual).withActions(actions).build()), 098 mergeExistingPermissions); 099 } 100 101 /** 102 * Grants permission on the specified table for the specified user. 103 * If permissions for a specified user exists, later granted permissions will override previous granted permissions. 104 * @param connection The Connection instance to use 105 * @param tableName 106 * @param userName 107 * @param family 108 * @param qual 109 * @param actions 110 * @throws Throwable 111 */ 112 public static void grant(Connection connection, final TableName tableName, final String userName, 113 final byte[] family, final byte[] qual, final Permission.Action... actions) throws Throwable { 114 grant(connection, tableName, userName, family, qual, true, actions); 115 } 116 117 /** 118 * Grants permission on the specified namespace for the specified user. 119 * @param connection 120 * @param namespace 121 * @param userName 122 * @param mergeExistingPermissions If set to false, later granted permissions will override 123 * previous granted permissions. otherwise, it'll merge with previous granted 124 * permissions. 125 * @param actions 126 * @throws Throwable 127 */ 128 private static void grant(Connection connection, final String namespace, final String userName, 129 boolean mergeExistingPermissions, final Permission.Action... actions) throws Throwable { 130 connection.getAdmin().grant( 131 new UserPermission(userName, Permission.newBuilder(namespace).withActions(actions).build()), 132 mergeExistingPermissions); 133 } 134 135 /** 136 * Grants permission on the specified namespace for the specified user. 137 * If permissions on the specified namespace exists, later granted permissions will override previous granted 138 * permissions. 139 * @param connection The Connection instance to use 140 * @param namespace 141 * @param userName 142 * @param actions 143 * @throws Throwable 144 */ 145 public static void grant(Connection connection, final String namespace, final String userName, 146 final Permission.Action... actions) throws Throwable { 147 grant(connection, namespace, userName, true, actions); 148 } 149 150 /** 151 * Grant global permissions for the specified user. 152 * @param connection 153 * @param userName 154 * @param mergeExistingPermissions If set to false, later granted permissions will override 155 * previous granted permissions. otherwise, it'll merge with previous granted 156 * permissions. 157 * @param actions 158 * @throws Throwable 159 */ 160 private static void grant(Connection connection, final String userName, 161 boolean mergeExistingPermissions, final Permission.Action... actions) throws Throwable { 162 connection.getAdmin().grant( 163 new UserPermission(userName, Permission.newBuilder().withActions(actions).build()), 164 mergeExistingPermissions); 165 } 166 167 /** 168 * Grant global permissions for the specified user. 169 * If permissions for the specified user exists, later granted permissions will override previous granted 170 * permissions. 171 * @param connection 172 * @param userName 173 * @param actions 174 * @throws Throwable 175 */ 176 public static void grant(Connection connection, final String userName, 177 final Permission.Action... actions) throws Throwable { 178 grant(connection, userName, true, actions); 179 } 180 181 public static boolean isAccessControllerRunning(Connection connection) 182 throws MasterNotRunningException, ZooKeeperConnectionException, IOException { 183 try (Admin admin = connection.getAdmin()) { 184 return admin.isTableAvailable(ACL_TABLE_NAME); 185 } 186 } 187 188 /** 189 * Revokes the permission on the table 190 * @param connection The Connection instance to use 191 * @param tableName 192 * @param username 193 * @param family 194 * @param qualifier 195 * @param actions 196 * @throws Throwable 197 */ 198 public static void revoke(Connection connection, final TableName tableName, 199 final String username, final byte[] family, final byte[] qualifier, 200 final Permission.Action... actions) throws Throwable { 201 connection.getAdmin().revoke(new UserPermission(username, Permission.newBuilder(tableName) 202 .withFamily(family).withQualifier(qualifier).withActions(actions).build())); 203 } 204 205 /** 206 * Revokes the permission on the namespace for the specified user. 207 * @param connection The Connection instance to use 208 * @param namespace 209 * @param userName 210 * @param actions 211 * @throws Throwable 212 */ 213 public static void revoke(Connection connection, final String namespace, 214 final String userName, final Permission.Action... actions) throws Throwable { 215 connection.getAdmin().revoke( 216 new UserPermission(userName, Permission.newBuilder(namespace).withActions(actions).build())); 217 } 218 219 /** 220 * Revoke global permissions for the specified user. 221 * @param connection The Connection instance to use 222 */ 223 public static void revoke(Connection connection, final String userName, 224 final Permission.Action... actions) throws Throwable { 225 connection.getAdmin() 226 .revoke(new UserPermission(userName, Permission.newBuilder().withActions(actions).build())); 227 } 228 229 /** 230 * List all the userPermissions matching the given pattern. If pattern is null, the behavior is 231 * dependent on whether user has global admin privileges or not. If yes, the global permissions 232 * along with the list of superusers would be returned. Else, no rows get returned. 233 * @param connection The Connection instance to use 234 * @param tableRegex The regular expression string to match against 235 * @return List of UserPermissions 236 * @throws Throwable 237 */ 238 public static List<UserPermission> getUserPermissions(Connection connection, String tableRegex) 239 throws Throwable { 240 return getUserPermissions(connection, tableRegex, HConstants.EMPTY_STRING); 241 } 242 243 /** 244 * List all the userPermissions matching the given table pattern and user name. 245 * @param connection Connection 246 * @param tableRegex The regular expression string to match against 247 * @param userName User name, if empty then all user permissions will be retrieved. 248 * @return List of UserPermissions 249 * @throws Throwable on failure 250 */ 251 public static List<UserPermission> getUserPermissions(Connection connection, String tableRegex, 252 String userName) throws Throwable { 253 List<UserPermission> permList = new ArrayList<>(); 254 try (Admin admin = connection.getAdmin()) { 255 if (tableRegex == null || tableRegex.isEmpty()) { 256 permList = admin.getUserPermissions( 257 GetUserPermissionsRequest.newBuilder().withUserName(userName).build()); 258 } else if (tableRegex.charAt(0) == '@') { // Namespaces 259 String namespaceRegex = tableRegex.substring(1); 260 for (NamespaceDescriptor nsds : admin.listNamespaceDescriptors()) { // Read out all 261 // namespaces 262 String namespace = nsds.getName(); 263 if (namespace.matches(namespaceRegex)) { // Match the given namespace regex? 264 permList.addAll(admin.getUserPermissions( 265 GetUserPermissionsRequest.newBuilder(namespace).withUserName(userName).build())); 266 } 267 } 268 } else { // Tables 269 List<TableDescriptor> htds = admin.listTableDescriptors(Pattern.compile(tableRegex), true); 270 for (TableDescriptor htd : htds) { 271 permList.addAll(admin.getUserPermissions(GetUserPermissionsRequest 272 .newBuilder(htd.getTableName()).withUserName(userName).build())); 273 } 274 } 275 } 276 return permList; 277 } 278 279 /** 280 * List all the userPermissions matching the given table pattern and column family. 281 * @param connection Connection 282 * @param tableRegex The regular expression string to match against. It shouldn't be null, empty 283 * or a namespace regular expression. 284 * @param columnFamily Column family 285 * @return List of UserPermissions 286 * @throws Throwable on failure 287 */ 288 public static List<UserPermission> getUserPermissions(Connection connection, String tableRegex, 289 byte[] columnFamily) throws Throwable { 290 return getUserPermissions(connection, tableRegex, columnFamily, null, HConstants.EMPTY_STRING); 291 } 292 293 /** 294 * List all the userPermissions matching the given table pattern, column family and user name. 295 * @param connection Connection 296 * @param tableRegex The regular expression string to match against. It shouldn't be null, empty 297 * or a namespace regular expression. 298 * @param columnFamily Column family 299 * @param userName User name, if empty then all user permissions will be retrieved. 300 * @return List of UserPermissions 301 * @throws Throwable on failure 302 */ 303 public static List<UserPermission> getUserPermissions(Connection connection, String tableRegex, 304 byte[] columnFamily, String userName) throws Throwable { 305 return getUserPermissions(connection, tableRegex, columnFamily, null, userName); 306 } 307 308 /** 309 * List all the userPermissions matching the given table pattern, column family and column 310 * qualifier. 311 * @param connection Connection 312 * @param tableRegex The regular expression string to match against. It shouldn't be null, empty 313 * or a namespace regular expression. 314 * @param columnFamily Column family 315 * @param columnQualifier Column qualifier 316 * @return List of UserPermissions 317 * @throws Throwable on failure 318 */ 319 public static List<UserPermission> getUserPermissions(Connection connection, String tableRegex, 320 byte[] columnFamily, byte[] columnQualifier) throws Throwable { 321 return getUserPermissions(connection, tableRegex, columnFamily, columnQualifier, 322 HConstants.EMPTY_STRING); 323 } 324 325 /** 326 * List all the userPermissions matching the given table pattern, column family and column 327 * qualifier. 328 * @param connection Connection 329 * @param tableRegex The regular expression string to match against. It shouldn't be null, empty 330 * or a namespace regular expression. 331 * @param columnFamily Column family 332 * @param columnQualifier Column qualifier 333 * @param userName User name, if empty then all user permissions will be retrieved. 334 * @return List of UserPermissions 335 * @throws Throwable on failure 336 */ 337 public static List<UserPermission> getUserPermissions(Connection connection, String tableRegex, 338 byte[] columnFamily, byte[] columnQualifier, String userName) throws Throwable { 339 if (tableRegex == null || tableRegex.isEmpty() || tableRegex.charAt(0) == '@') { 340 throw new IllegalArgumentException("Table name can't be null or empty or a namespace."); 341 } 342 List<UserPermission> permList = new ArrayList<UserPermission>(); 343 try (Admin admin = connection.getAdmin()) { 344 List<TableDescriptor> htds = admin.listTableDescriptors(Pattern.compile(tableRegex), true); 345 // Retrieve table permissions 346 for (TableDescriptor htd : htds) { 347 permList.addAll(admin.getUserPermissions( 348 GetUserPermissionsRequest.newBuilder(htd.getTableName()).withFamily(columnFamily) 349 .withQualifier(columnQualifier).withUserName(userName).build())); 350 } 351 } 352 return permList; 353 } 354 355 /** 356 * Validates whether specified user has permission to perform actions on the mentioned table, 357 * column family or column qualifier. 358 * @param connection Connection 359 * @param tableName Table name, it shouldn't be null or empty. 360 * @param columnFamily The column family. Optional argument, can be empty. If empty then 361 * validation will happen at table level. 362 * @param columnQualifier The column qualifier. Optional argument, can be empty. If empty then 363 * validation will happen at table and column family level. columnQualifier will not be 364 * considered if columnFamily is passed as null or empty. 365 * @param userName User name, it shouldn't be null or empty. 366 * @param actions Actions 367 * @return true if access allowed to the specified user, otherwise false. 368 * @throws Throwable on failure 369 */ 370 public static boolean hasPermission(Connection connection, String tableName, String columnFamily, 371 String columnQualifier, String userName, Permission.Action... actions) throws Throwable { 372 return hasPermission(connection, tableName, Bytes.toBytes(columnFamily), 373 Bytes.toBytes(columnQualifier), userName, actions); 374 } 375 376 /** 377 * Validates whether specified user has permission to perform actions on the mentioned table, 378 * column family or column qualifier. 379 * @param connection Connection 380 * @param tableName Table name, it shouldn't be null or empty. 381 * @param columnFamily The column family. Optional argument, can be empty. If empty then 382 * validation will happen at table level. 383 * @param columnQualifier The column qualifier. Optional argument, can be empty. If empty then 384 * validation will happen at table and column family level. columnQualifier will not be 385 * considered if columnFamily is passed as null or empty. 386 * @param userName User name, it shouldn't be null or empty. 387 * @param actions Actions 388 * @return true if access allowed to the specified user, otherwise false. 389 * @throws Throwable on failure 390 */ 391 public static boolean hasPermission(Connection connection, String tableName, byte[] columnFamily, 392 byte[] columnQualifier, String userName, Permission.Action... actions) throws Throwable { 393 if (StringUtils.isEmpty(tableName) || StringUtils.isEmpty(userName)) { 394 throw new IllegalArgumentException("Table and user name can't be null or empty."); 395 } 396 List<Permission> permissions = new ArrayList<>(1); 397 permissions.add(Permission.newBuilder(TableName.valueOf(tableName)).withFamily(columnFamily) 398 .withQualifier(columnQualifier).withActions(actions).build()); 399 return connection.getAdmin().hasUserPermissions(userName, permissions).get(0); 400 } 401}