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, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase.security.access;
19  
20  import java.io.IOException;
21  import java.util.ArrayList;
22  import java.util.List;
23  import java.util.Map;
24  import java.util.regex.Pattern;
25  
26  import org.apache.hadoop.conf.Configuration;
27  import org.apache.hadoop.hbase.HConstants;
28  import org.apache.hadoop.hbase.HTableDescriptor;
29  import org.apache.hadoop.hbase.MasterNotRunningException;
30  import org.apache.hadoop.hbase.NamespaceDescriptor;
31  import org.apache.hadoop.hbase.TableName;
32  import org.apache.hadoop.hbase.ZooKeeperConnectionException;
33  import org.apache.hadoop.hbase.classification.InterfaceAudience;
34  import org.apache.hadoop.hbase.classification.InterfaceStability;
35  import org.apache.hadoop.hbase.client.Admin;
36  import org.apache.hadoop.hbase.client.Connection;
37  import org.apache.hadoop.hbase.client.ConnectionFactory;
38  import org.apache.hadoop.hbase.client.HTable;
39  import org.apache.hadoop.hbase.client.Table;
40  import org.apache.hadoop.hbase.client.security.SecurityCapability;
41  import org.apache.hadoop.hbase.client.coprocessor.Batch;
42  import org.apache.hadoop.hbase.ipc.BlockingRpcCallback;
43  import org.apache.hadoop.hbase.ipc.CoprocessorRpcChannel;
44  import org.apache.hadoop.hbase.ipc.ServerRpcController;
45  import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
46  import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos;
47  import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos.AccessControlService;
48  import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos.AccessControlService.BlockingInterface;
49  import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos.GrantRequest;
50  import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos.GrantResponse;
51  import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos.RevokeRequest;
52  import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos.RevokeResponse;
53  import org.apache.hadoop.hbase.util.ByteStringer;
54  import org.apache.hadoop.hbase.util.Bytes;
55  
56  import com.google.protobuf.ByteString;
57  
58  /**
59   * Utility client for doing access control admin operations.
60   */
61  @InterfaceAudience.Public
62  @InterfaceStability.Evolving
63  public class AccessControlClient {
64    public static final TableName ACL_TABLE_NAME =
65        TableName.valueOf(NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR, "acl");
66  
67    /**
68     * Return true if authorization is supported and enabled
69     * @param connection The connection to use
70     * @return true if authorization is supported and enabled, false otherwise
71     * @throws IOException
72     */
73    public static boolean isAuthorizationEnabled(Connection connection) throws IOException {
74      return connection.getAdmin().getSecurityCapabilities()
75          .contains(SecurityCapability.AUTHORIZATION);
76    }
77  
78    /**
79     * Return true if cell authorization is supported and enabled
80     * @param connection The connection to use
81     * @return true if cell authorization is supported and enabled, false otherwise
82     * @throws IOException
83     */
84    public static boolean isCellAuthorizationEnabled(Connection connection) throws IOException {
85      return connection.getAdmin().getSecurityCapabilities()
86          .contains(SecurityCapability.CELL_AUTHORIZATION);
87    }
88  
89    private static BlockingInterface getAccessControlServiceStub(Table ht)
90        throws IOException {
91      CoprocessorRpcChannel service = ht.coprocessorService(HConstants.EMPTY_START_ROW);
92      BlockingInterface protocol =
93          AccessControlProtos.AccessControlService.newBlockingStub(service);
94      return protocol;
95    }
96  
97    /**
98     * Grants permission on the specified table for the specified user
99     * @param connection The Connection instance to use
100    * @param tableName
101    * @param userName
102    * @param family
103    * @param qual
104    * @param actions
105    * @throws Throwable
106    */
107   public static void grant(final Connection connection, final TableName tableName,
108       final String userName, final byte[] family, final byte[] qual,
109       final Permission.Action... actions) throws Throwable {
110     try (Table table = connection.getTable(ACL_TABLE_NAME)) {
111       ProtobufUtil.grant(getAccessControlServiceStub(table), userName, tableName, family, qual,
112           actions);
113     }
114   }
115 
116   /**
117    * Grants permission on the specified namespace for the specified user.
118    * @param connection The Connection instance to use
119    * @param namespace
120    * @param userName
121    * @param actions
122    * @throws Throwable
123    */
124   public static void grant(final Connection connection, final String namespace,
125       final String userName, final Permission.Action... actions) throws Throwable {
126     try (Table table = connection.getTable(ACL_TABLE_NAME)) {
127       ProtobufUtil.grant(getAccessControlServiceStub(table), userName, namespace, actions);
128     }
129   }
130 
131   /**
132    * @param connection The Connection instance to use
133    * Grant global permissions for the specified user.
134    */
135   public static void grant(final Connection connection, final String userName,
136        final Permission.Action... actions) throws Throwable {
137     try (Table table = connection.getTable(ACL_TABLE_NAME)) {
138       ProtobufUtil.grant(getAccessControlServiceStub(table), userName, actions);
139     }
140   }
141 
142   public static boolean isAccessControllerRunning(final Connection connection)
143       throws MasterNotRunningException, ZooKeeperConnectionException, IOException {
144     try (Admin admin = connection.getAdmin()) {
145       return admin.isTableAvailable(ACL_TABLE_NAME);
146     }
147   }
148 
149   /**
150    * Revokes the permission on the table
151    * @param connection The Connection instance to use
152    * @param tableName
153    * @param username
154    * @param family
155    * @param qualifier
156    * @param actions
157    * @throws Throwable
158    */
159   public static void revoke(final Connection connection, final TableName tableName,
160       final String username, final byte[] family, final byte[] qualifier,
161       final Permission.Action... actions) throws Throwable {
162     try (Table table = connection.getTable(ACL_TABLE_NAME)) {
163       ProtobufUtil.revoke(getAccessControlServiceStub(table), username, tableName, family,
164           qualifier, actions);
165     }
166   }
167 
168   /**
169    * Revokes the permission on the table for the specified user.
170    * @param connection The Connection instance to use
171    * @param namespace
172    * @param userName
173    * @param actions
174    * @throws Throwable
175    */
176   public static void revoke(final Connection connection, final String namespace,
177       final String userName, final Permission.Action... actions) throws Throwable {
178     try (Table table = connection.getTable(ACL_TABLE_NAME)) {
179       ProtobufUtil.revoke(getAccessControlServiceStub(table), userName, namespace, actions);
180     }
181   }
182 
183   /**
184    * Revoke global permissions for the specified user.
185    * @param connection The Connection instance to use
186    */
187   public static void revoke(final Connection connection, final String userName,
188       final Permission.Action... actions) throws Throwable {
189     try (Table table = connection.getTable(ACL_TABLE_NAME)) {
190       ProtobufUtil.revoke(getAccessControlServiceStub(table), userName, actions);
191     }
192 
193   }
194 
195   /**
196    * List all the userPermissions matching the given pattern.
197    * @param connection The Connection instance to use
198    * @param tableRegex The regular expression string to match against
199    * @return - returns an array of UserPermissions
200    * @throws Throwable
201    */
202   public static List<UserPermission> getUserPermissions(Connection connection, String tableRegex)
203       throws Throwable {
204     List<UserPermission> permList = new ArrayList<UserPermission>();
205     try (Table table = connection.getTable(ACL_TABLE_NAME)) {
206       try (Admin admin = connection.getAdmin()) {
207         CoprocessorRpcChannel service = table.coprocessorService(HConstants.EMPTY_START_ROW);
208         BlockingInterface protocol =
209             AccessControlProtos.AccessControlService.newBlockingStub(service);
210         HTableDescriptor[] htds = null;
211         if (tableRegex == null || tableRegex.isEmpty()) {
212           permList = ProtobufUtil.getUserPermissions(protocol);
213         } else if (tableRegex.charAt(0) == '@') {
214           String namespace = tableRegex.substring(1);
215           permList = ProtobufUtil.getUserPermissions(protocol, Bytes.toBytes(namespace));
216         } else {
217           htds = admin.listTables(Pattern.compile(tableRegex), true);
218           for (HTableDescriptor hd : htds) {
219             permList.addAll(ProtobufUtil.getUserPermissions(protocol, hd.getTableName()));
220           }
221         }
222       }
223     }
224     return permList;
225   }
226 
227   /**
228    * Grants permission on the specified table for the specified user
229    * @param conf
230    * @param tableName
231    * @param userName
232    * @param family
233    * @param qual
234    * @param actions
235    * @throws Throwable
236    * @deprecated Use {@link #grant(Connection, TableName, String, byte[], byte[],
237    * Permission.Action...)} instead.
238    */
239   @Deprecated
240   public static void grant(Configuration conf, final TableName tableName,
241       final String userName, final byte[] family, final byte[] qual,
242       final Permission.Action... actions) throws Throwable {
243     try (Connection connection = ConnectionFactory.createConnection(conf)) {
244       grant(connection, tableName, userName, family, qual, actions);
245     }
246   }
247 
248   /**
249    * Grants permission on the specified namespace for the specified user.
250    * @param conf
251    * @param namespace
252    * @param userName
253    * @param actions
254    * @throws Throwable
255    * @deprecated Use {@link #grant(Connection, String, String, Permission.Action...)}
256    * instead.
257    */
258   @Deprecated
259   public static void grant(Configuration conf, final String namespace,
260       final String userName, final Permission.Action... actions) throws Throwable {
261     try (Connection connection = ConnectionFactory.createConnection(conf)) {
262       grant(connection, namespace, userName, actions);
263     }
264   }
265 
266   /**
267    * Grant global permissions for the specified user.
268    * @deprecated Use {@link #grant(Connection, String, Permission.Action...)} instead.
269    */
270   @Deprecated
271   public static void grant(Configuration conf, final String userName,
272       final Permission.Action... actions) throws Throwable {
273     try (Connection connection = ConnectionFactory.createConnection(conf)) {
274       grant(connection, userName, actions);
275     }
276   }
277 
278   /**
279    * @deprecated Use {@link #isAccessControllerRunning(Connection)} instead.
280    */
281   @Deprecated
282   public static boolean isAccessControllerRunning(Configuration conf)
283       throws MasterNotRunningException, ZooKeeperConnectionException, IOException {
284     try (Connection connection = ConnectionFactory.createConnection(conf)) {
285       return isAccessControllerRunning(connection);
286     }
287   }
288 
289   /**
290    * Grants permission on the specified table for the specified user
291    * @param conf
292    * @param tableName
293    * @param userName
294    * @param family
295    * @param qual
296    * @param actions
297    * @return GrantResponse
298    * @throws Throwable
299    * @deprecated Use {@link #grant(Configuration, TableName, String, byte[], byte[], Permission.Action...)}  instead.
300    */
301   @Deprecated
302   public static GrantResponse grant(Configuration conf, final TableName tableName,
303                                     final String userName, final byte[] family, final byte[] qual,
304                                     final AccessControlProtos.Permission.Action... actions) throws Throwable {
305     HTable ht = null;
306     try {
307       TableName aclTableName =
308           TableName.valueOf(NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR, "acl");
309       ht = new HTable(conf, aclTableName.getName());
310       Batch.Call<AccessControlService, GrantResponse> callable =
311           new Batch.Call<AccessControlService, GrantResponse>() {
312             ServerRpcController controller = new ServerRpcController();
313             BlockingRpcCallback<GrantResponse> rpcCallback =
314                 new BlockingRpcCallback<GrantResponse>();
315             @Override
316             public GrantResponse call(AccessControlService service) throws IOException {
317               GrantRequest.Builder builder = GrantRequest.newBuilder();
318               AccessControlProtos.Permission.Builder ret =
319                   AccessControlProtos.Permission.newBuilder();
320               AccessControlProtos.TablePermission.Builder permissionBuilder =
321                   AccessControlProtos.TablePermission
322                       .newBuilder();
323               for (AccessControlProtos.Permission.Action a : actions) {
324                 permissionBuilder.addAction(a);
325               }
326               permissionBuilder.setTableName(ProtobufUtil.toProtoTableName(tableName));
327               if (family != null) {
328                 permissionBuilder.setFamily(ByteStringer.wrap(family));
329               }
330               if (qual != null) {
331                 permissionBuilder.setQualifier(ByteStringer.wrap(qual));
332               }
333               ret.setType(AccessControlProtos.Permission.Type.Table).setTablePermission(
334                   permissionBuilder);
335               builder.setUserPermission(AccessControlProtos.UserPermission.newBuilder()
336                   .setUser(ByteString.copyFromUtf8(userName)).setPermission(ret));
337               service.grant(controller, builder.build(), rpcCallback);
338               return rpcCallback.get();
339             }
340           };
341       Map<byte[], GrantResponse> result = ht.coprocessorService(AccessControlService.class,
342           HConstants.EMPTY_BYTE_ARRAY, HConstants.EMPTY_BYTE_ARRAY, callable);
343       return result.values().iterator().next(); // There will be exactly one
344       // region for labels
345       // table and so one entry in
346       // result Map.
347     } finally {
348       if (ht != null) {
349         ht.close();
350       }
351     }
352   }
353 
354   /**
355    * Revokes the permission on the table
356    * @param conf
357    * @param tableName
358    * @param username
359    * @param family
360    * @param qualifier
361    * @param actions
362    * @throws Throwable
363    * @deprecated Use {@link #revoke(Connection, TableName, String, byte[], byte[],
364    * Permission.Action...)} instead.
365    */
366   @Deprecated
367   public static void revoke(Configuration conf, final TableName tableName,
368       final String username, final byte[] family, final byte[] qualifier,
369       final Permission.Action... actions) throws Throwable {
370     try (Connection connection = ConnectionFactory.createConnection(conf)) {
371       revoke(connection, tableName, username, family, qualifier, actions);
372     }
373   }
374 
375   /**
376    * Revokes the permission on the table for the specified user.
377    * @param conf
378    * @param namespace
379    * @param userName
380    * @param actions
381    * @throws Throwable
382    * @deprecated Use {@link #revoke(Connection, String, String, Permission.Action...)} instead.
383    */
384   @Deprecated
385   public static void revoke(Configuration conf, final String namespace,
386       final String userName, final Permission.Action... actions) throws Throwable {
387     try (Connection connection = ConnectionFactory.createConnection(conf)) {
388       revoke(connection, namespace, userName, actions);
389     }
390   }
391 
392   /**
393    * Revoke global permissions for the specified user.
394    * @deprecated Use {@link #revoke(Connection, String, Permission.Action...)} instead.
395    */
396   @Deprecated
397   public static void revoke(Configuration conf, final String userName,
398       final Permission.Action... actions) throws Throwable {
399     try (Connection connection = ConnectionFactory.createConnection(conf)) {
400       revoke(connection, userName, actions);
401     }
402   }
403 
404   /**
405    * List all the userPermissions matching the given pattern.
406    * @param conf
407    * @param tableRegex The regular expression string to match against
408    * @return - returns an array of UserPermissions
409    * @throws Throwable
410    * @deprecated Use {@link #getUserPermissions(Connection, String)} instead.
411    */
412   @Deprecated
413   public static List<UserPermission> getUserPermissions(Configuration conf, String tableRegex)
414   throws Throwable {
415     try (Connection connection = ConnectionFactory.createConnection(conf)) {
416       return getUserPermissions(connection, tableRegex);
417     }
418   }
419 
420   /**
421    * Revokes the permission on the table
422    * @param conf
423    * @param username
424    * @param tableName
425    * @param family
426    * @param qualifier
427    * @param actions
428    * @return RevokeResponse
429    * @throws Throwable
430    * @deprecated Use {@link #revoke(Configuration, TableName, String, byte[], byte[], Permission.Action...)} instead
431    */
432   @Deprecated
433   public static RevokeResponse revoke(Configuration conf, final String username,
434                                       final TableName tableName, final byte[] family, final byte[] qualifier,
435                                       final AccessControlProtos.Permission.Action... actions) throws Throwable {
436     HTable ht = null;
437     try {
438       TableName aclTableName = TableName.valueOf(NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR,
439           "acl");
440       ht = new HTable(conf, aclTableName.getName());
441       Batch.Call<AccessControlService, AccessControlProtos.RevokeResponse> callable =
442           new Batch.Call<AccessControlService, AccessControlProtos.RevokeResponse>() {
443             ServerRpcController controller = new ServerRpcController();
444             BlockingRpcCallback<AccessControlProtos.RevokeResponse> rpcCallback =
445                 new BlockingRpcCallback<AccessControlProtos.RevokeResponse>();
446             @Override
447             public RevokeResponse call(AccessControlService service) throws IOException {
448               AccessControlProtos.Permission.Builder ret =
449                   AccessControlProtos.Permission.newBuilder();
450               AccessControlProtos.TablePermission.Builder permissionBuilder =
451                   AccessControlProtos.TablePermission.newBuilder();
452               for (AccessControlProtos.Permission.Action a : actions) {
453                 permissionBuilder.addAction(a);
454               }
455               if (tableName != null) {
456                 permissionBuilder.setTableName(ProtobufUtil.toProtoTableName(tableName));
457               }
458               if (family != null) {
459                 permissionBuilder.setFamily(ByteStringer.wrap(family));
460               }
461               if (qualifier != null) {
462                 permissionBuilder.setQualifier(ByteStringer.wrap(qualifier));
463               }
464               ret.setType(AccessControlProtos.Permission.Type.Table).setTablePermission(
465                   permissionBuilder);
466               RevokeRequest builder = AccessControlProtos.RevokeRequest
467                   .newBuilder()
468                   .setUserPermission(
469                       AccessControlProtos.UserPermission.newBuilder()
470                           .setUser(ByteString.copyFromUtf8(username)).setPermission(ret)).build();
471               service.revoke(controller, builder, rpcCallback);
472               return rpcCallback.get();
473             }
474           };
475       Map<byte[], RevokeResponse> result = ht.coprocessorService(AccessControlService.class,
476           HConstants.EMPTY_BYTE_ARRAY, HConstants.EMPTY_BYTE_ARRAY, callable);
477       return result.values().iterator().next();
478     } finally {
479       if (ht != null) {
480         ht.close();
481       }
482     }
483   }
484 }