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.hadoop.hbase.HConstants;
026import org.apache.hadoop.hbase.HTableDescriptor;
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.yetus.audience.InterfaceAudience;
032import org.apache.hadoop.hbase.client.Admin;
033import org.apache.hadoop.hbase.client.Connection;
034import org.apache.hadoop.hbase.client.Table;
035import org.apache.hadoop.hbase.client.security.SecurityCapability;
036import org.apache.hadoop.hbase.ipc.CoprocessorRpcChannel;
037import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos;
038import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos.AccessControlService.BlockingInterface;
039import org.apache.hadoop.hbase.util.Bytes;
040
041/**
042 * Utility client for doing access control admin operations.
043 */
044@InterfaceAudience.Public
045public class AccessControlClient {
046  public static final TableName ACL_TABLE_NAME =
047      TableName.valueOf(NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR, "acl");
048
049  /**
050   * Return true if authorization is supported and enabled
051   * @param connection The connection to use
052   * @return true if authorization is supported and enabled, false otherwise
053   * @throws IOException
054   */
055  public static boolean isAuthorizationEnabled(Connection connection) throws IOException {
056    return connection.getAdmin().getSecurityCapabilities()
057        .contains(SecurityCapability.AUTHORIZATION);
058  }
059
060  /**
061   * Return true if cell authorization is supported and enabled
062   * @param connection The connection to use
063   * @return true if cell authorization is supported and enabled, false otherwise
064   * @throws IOException
065   */
066  public static boolean isCellAuthorizationEnabled(Connection connection) throws IOException {
067    return connection.getAdmin().getSecurityCapabilities()
068        .contains(SecurityCapability.CELL_AUTHORIZATION);
069  }
070
071  private static BlockingInterface getAccessControlServiceStub(Table ht)
072      throws IOException {
073    CoprocessorRpcChannel service = ht.coprocessorService(HConstants.EMPTY_START_ROW);
074    BlockingInterface protocol =
075        AccessControlProtos.AccessControlService.newBlockingStub(service);
076    return protocol;
077  }
078
079  /**
080   * Grants permission on the specified table for the specified user
081   * @param connection The Connection instance to use
082   * @param tableName
083   * @param userName
084   * @param family
085   * @param qual
086   * @param mergeExistingPermissions If set to false, later granted permissions will override
087   *          previous granted permissions. otherwise, it'll merge with previous granted
088   *          permissions.
089   * @param actions
090   * @throws Throwable
091   */
092  private static void grant(Connection connection, final TableName tableName,
093      final String userName, final byte[] family, final byte[] qual, boolean mergeExistingPermissions,
094      final Permission.Action... actions) throws Throwable {
095    // TODO: Priority is not used.
096    try (Table table = connection.getTable(ACL_TABLE_NAME)) {
097      AccessControlUtil.grant(null, getAccessControlServiceStub(table), userName, tableName,
098        family, qual, mergeExistingPermissions, actions);
099    }
100  }
101
102  /**
103   * Grants permission on the specified table for the specified user.
104   * If permissions for a specified user exists, later granted permissions will override previous granted permissions.
105   * @param connection The Connection instance to use
106   * @param tableName
107   * @param userName
108   * @param family
109   * @param qual
110   * @param actions
111   * @throws Throwable
112   */
113  public static void grant(Connection connection, final TableName tableName, final String userName,
114      final byte[] family, final byte[] qual, final Permission.Action... actions) throws Throwable {
115    grant(connection, tableName, userName, family, qual, true, actions);
116  }
117
118  /**
119   * Grants permission on the specified namespace for the specified user.
120   * @param connection
121   * @param namespace
122   * @param userName
123   * @param mergeExistingPermissions If set to false, later granted permissions will override
124   *          previous granted permissions. otherwise, it'll merge with previous granted
125   *          permissions.
126   * @param actions
127   * @throws Throwable
128   */
129  private static void grant(Connection connection, final String namespace, final String userName,
130      boolean mergeExistingPermissions, final Permission.Action... actions) throws Throwable {
131    // TODO: Pass an rpcController.
132    try (Table table = connection.getTable(ACL_TABLE_NAME)) {
133      AccessControlUtil.grant(null, getAccessControlServiceStub(table), userName, namespace,
134        mergeExistingPermissions, actions);
135    }
136  }
137
138  /**
139   * Grants permission on the specified namespace for the specified user.
140   * If permissions on the specified namespace exists, later granted permissions will override previous granted
141   * permissions.
142   * @param connection The Connection instance to use
143   * @param namespace
144   * @param userName
145   * @param actions
146   * @throws Throwable
147   */
148  public static void grant(Connection connection, final String namespace, final String userName,
149      final Permission.Action... actions) throws Throwable {
150    grant(connection, namespace, userName, true, actions);
151  }
152
153  /**
154   * Grants permission on the specified namespace for the specified user.
155   * @param connection
156   * @param userName
157   * @param mergeExistingPermissions If set to false, later granted permissions will override
158   *          previous granted permissions. otherwise, it'll merge with previous granted
159   *          permissions.
160   * @param actions
161   * @throws Throwable
162   */
163  private static void grant(Connection connection, final String userName,
164      boolean mergeExistingPermissions, final Permission.Action... actions) throws Throwable {
165    // TODO: Pass an rpcController
166    try (Table table = connection.getTable(ACL_TABLE_NAME)) {
167      AccessControlUtil.grant(null, getAccessControlServiceStub(table), userName,
168              mergeExistingPermissions, actions);
169    }
170  }
171
172  /**
173   * Grant global permissions for the specified user.
174   * If permissions for the specified user exists, later granted permissions will override previous granted
175   * permissions.
176   * @param connection
177   * @param userName
178   * @param actions
179   * @throws Throwable
180   */
181  public static void grant(Connection connection, final String userName,
182      final Permission.Action... actions) throws Throwable {
183    grant(connection, userName, true, actions);
184  }
185
186  public static boolean isAccessControllerRunning(Connection connection)
187      throws MasterNotRunningException, ZooKeeperConnectionException, IOException {
188    try (Admin admin = connection.getAdmin()) {
189      return admin.isTableAvailable(ACL_TABLE_NAME);
190    }
191  }
192
193  /**
194   * Revokes the permission on the table
195   * @param connection The Connection instance to use
196   * @param tableName
197   * @param username
198   * @param family
199   * @param qualifier
200   * @param actions
201   * @throws Throwable
202   */
203  public static void revoke(Connection connection, final TableName tableName,
204      final String username, final byte[] family, final byte[] qualifier,
205      final Permission.Action... actions) throws Throwable {
206    /** TODO: Pass an rpcController
207    HBaseRpcController controller
208      = ((ClusterConnection) connection).getRpcControllerFactory().newController();
209    controller.setPriority(tableName);
210    */
211    try (Table table = connection.getTable(ACL_TABLE_NAME)) {
212      AccessControlUtil.revoke(null, getAccessControlServiceStub(table), username, tableName,
213        family, qualifier, actions);
214    }
215  }
216
217  /**
218   * Revokes the permission on the table for the specified user.
219   * @param connection The Connection instance to use
220   * @param namespace
221   * @param userName
222   * @param actions
223   * @throws Throwable
224   */
225  public static void revoke(Connection connection, final String namespace,
226      final String userName, final Permission.Action... actions) throws Throwable {
227    /** TODO: Pass an rpcController
228    HBaseRpcController controller
229      = ((ClusterConnection) connection).getRpcControllerFactory().newController();
230      */
231    try (Table table = connection.getTable(ACL_TABLE_NAME)) {
232      AccessControlUtil.revoke(null, getAccessControlServiceStub(table), userName, namespace,
233        actions);
234    }
235  }
236
237  /**
238   * Revoke global permissions for the specified user.
239   * @param connection The Connection instance to use
240   */
241  public static void revoke(Connection connection, final String userName,
242      final Permission.Action... actions) throws Throwable {
243    /** TODO: Pass an rpc controller.
244    HBaseRpcController controller
245      = ((ClusterConnection) connection).getRpcControllerFactory().newController();
246      */
247    try (Table table = connection.getTable(ACL_TABLE_NAME)) {
248      AccessControlUtil.revoke(null, getAccessControlServiceStub(table), userName, actions);
249    }
250  }
251
252  /**
253   * List all the userPermissions matching the given pattern. If pattern is null, the behavior is
254   * dependent on whether user has global admin privileges or not. If yes, the global permissions
255   * along with the list of superusers would be returned. Else, no rows get returned.
256   * @param connection The Connection instance to use
257   * @param tableRegex The regular expression string to match against
258   * @return - returns an array of UserPermissions
259   * @throws Throwable
260   */
261  public static List<UserPermission> getUserPermissions(Connection connection, String tableRegex)
262      throws Throwable {
263    /** TODO: Pass an rpcController
264    HBaseRpcController controller
265      = ((ClusterConnection) connection).getRpcControllerFactory().newController();
266      */
267    List<UserPermission> permList = new ArrayList<>();
268    try (Table table = connection.getTable(ACL_TABLE_NAME)) {
269      try (Admin admin = connection.getAdmin()) {
270        CoprocessorRpcChannel service = table.coprocessorService(HConstants.EMPTY_START_ROW);
271        BlockingInterface protocol =
272            AccessControlProtos.AccessControlService.newBlockingStub(service);
273        HTableDescriptor[] htds = null;
274        if (tableRegex == null || tableRegex.isEmpty()) {
275          permList = AccessControlUtil.getUserPermissions(null, protocol);
276        } else if (tableRegex.charAt(0) == '@') {  // Namespaces
277          String namespaceRegex = tableRegex.substring(1);
278          for (NamespaceDescriptor nsds : admin.listNamespaceDescriptors()) {  // Read out all namespaces
279            String namespace = nsds.getName();
280            if (namespace.matches(namespaceRegex)) {  // Match the given namespace regex?
281              permList.addAll(AccessControlUtil.getUserPermissions(null, protocol,
282                Bytes.toBytes(namespace)));
283            }
284          }
285        } else {  // Tables
286          htds = admin.listTables(Pattern.compile(tableRegex), true);
287          for (HTableDescriptor hd : htds) {
288            permList.addAll(AccessControlUtil.getUserPermissions(null, protocol,
289              hd.getTableName()));
290          }
291        }
292      }
293    }
294    return permList;
295  }
296}