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 static org.junit.jupiter.api.Assertions.assertArrayEquals;
021import static org.junit.jupiter.api.Assertions.assertEquals;
022import static org.junit.jupiter.api.Assertions.assertFalse;
023import static org.junit.jupiter.api.Assertions.assertNotNull;
024import static org.junit.jupiter.api.Assertions.assertNull;
025import static org.junit.jupiter.api.Assertions.assertTrue;
026
027import java.io.IOException;
028import java.util.Arrays;
029import java.util.List;
030import java.util.Map;
031import org.apache.hadoop.conf.Configuration;
032import org.apache.hadoop.hbase.HBaseTestingUtil;
033import org.apache.hadoop.hbase.TableName;
034import org.apache.hadoop.hbase.client.Admin;
035import org.apache.hadoop.hbase.client.Connection;
036import org.apache.hadoop.hbase.client.ConnectionFactory;
037import org.apache.hadoop.hbase.client.Put;
038import org.apache.hadoop.hbase.client.Table;
039import org.apache.hadoop.hbase.security.User;
040import org.apache.hadoop.hbase.testclassification.MediumTests;
041import org.apache.hadoop.hbase.testclassification.SecurityTests;
042import org.apache.hadoop.hbase.util.Bytes;
043import org.junit.jupiter.api.AfterAll;
044import org.junit.jupiter.api.AfterEach;
045import org.junit.jupiter.api.BeforeAll;
046import org.junit.jupiter.api.Tag;
047import org.junit.jupiter.api.Test;
048import org.slf4j.Logger;
049import org.slf4j.LoggerFactory;
050
051import org.apache.hbase.thirdparty.com.google.common.collect.ArrayListMultimap;
052import org.apache.hbase.thirdparty.com.google.common.collect.ListMultimap;
053
054/**
055 * Test the reading and writing of access permissions on {@code _acl_} table.
056 */
057@Tag(SecurityTests.TAG)
058@Tag(MediumTests.TAG)
059public class TestTablePermissions {
060
061  private static final Logger LOG = LoggerFactory.getLogger(TestTablePermissions.class);
062  private static final HBaseTestingUtil UTIL = new HBaseTestingUtil();
063
064  private static String TEST_NAMESPACE = "perms_test_ns";
065  private static String TEST_NAMESPACE2 = "perms_test_ns2";
066  private static TableName TEST_TABLE = TableName.valueOf("perms_test");
067  private static TableName TEST_TABLE2 = TableName.valueOf("perms_test2");
068  private static byte[] TEST_FAMILY = Bytes.toBytes("f1");
069  private static byte[] TEST_QUALIFIER = Bytes.toBytes("col1");
070
071  @BeforeAll
072  public static void beforeClass() throws Exception {
073    // setup configuration
074    Configuration conf = UTIL.getConfiguration();
075    SecureTestUtil.enableSecurity(conf);
076
077    UTIL.startMiniCluster();
078
079    // Wait for the ACL table to become available
080    UTIL.waitTableEnabled(PermissionStorage.ACL_TABLE_NAME);
081    UTIL.waitTableAvailable(TableName.valueOf("hbase:acl"));
082
083    UTIL.createTable(TEST_TABLE, TEST_FAMILY);
084    UTIL.createTable(TEST_TABLE2, TEST_FAMILY);
085  }
086
087  @AfterAll
088  public static void afterClass() throws Exception {
089    UTIL.shutdownMiniCluster();
090  }
091
092  @AfterEach
093  public void tearDown() throws Exception {
094    Configuration conf = UTIL.getConfiguration();
095    try (Connection connection = ConnectionFactory.createConnection(conf);
096      Table table = connection.getTable(PermissionStorage.ACL_TABLE_NAME)) {
097      PermissionStorage.removeTablePermissions(conf, TEST_TABLE, table);
098      PermissionStorage.removeTablePermissions(conf, TEST_TABLE2, table);
099      PermissionStorage.removeTablePermissions(conf, PermissionStorage.ACL_TABLE_NAME, table);
100    }
101  }
102
103  /**
104   * The PermissionStorage.addUserPermission may throw exception before closing the table.
105   */
106  private void addUserPermission(Configuration conf, UserPermission userPerm, Table t)
107    throws IOException {
108    try {
109      PermissionStorage.addUserPermission(conf, userPerm, t);
110    } finally {
111      t.close();
112    }
113  }
114
115  @Test
116  public void testBasicWrite() throws Exception {
117    Configuration conf = UTIL.getConfiguration();
118    try (Connection connection = ConnectionFactory.createConnection(conf)) {
119      // add some permissions
120      addUserPermission(conf,
121        new UserPermission("george",
122          Permission.newBuilder(TEST_TABLE)
123            .withActions(Permission.Action.READ, Permission.Action.WRITE).build()),
124        connection.getTable(PermissionStorage.ACL_TABLE_NAME));
125      addUserPermission(conf,
126        new UserPermission("hubert",
127          Permission.newBuilder(TEST_TABLE).withActions(Permission.Action.READ).build()),
128        connection.getTable(PermissionStorage.ACL_TABLE_NAME));
129      addUserPermission(conf,
130        new UserPermission("humphrey",
131          Permission.newBuilder(TEST_TABLE).withFamily(TEST_FAMILY).withQualifier(TEST_QUALIFIER)
132            .withActions(Permission.Action.READ).build()),
133        connection.getTable(PermissionStorage.ACL_TABLE_NAME));
134    }
135    // retrieve the same
136    ListMultimap<String, UserPermission> perms =
137      PermissionStorage.getTablePermissions(conf, TEST_TABLE);
138    List<UserPermission> userPerms = perms.get("george");
139    assertNotNull(userPerms, "Should have permissions for george");
140    assertEquals(1, userPerms.size(), "Should have 1 permission for george");
141    assertEquals(Permission.Scope.TABLE, userPerms.get(0).getAccessScope());
142    TablePermission permission = (TablePermission) userPerms.get(0).getPermission();
143    assertEquals(TEST_TABLE, permission.getTableName(), "Permission should be for " + TEST_TABLE);
144    assertNull(permission.getFamily(), "Column family should be empty");
145
146    // check actions
147    assertNotNull(permission.getActions());
148    assertEquals(2, permission.getActions().length);
149    List<Permission.Action> actions = Arrays.asList(permission.getActions());
150    assertTrue(actions.contains(TablePermission.Action.READ));
151    assertTrue(actions.contains(TablePermission.Action.WRITE));
152
153    userPerms = perms.get("hubert");
154    assertNotNull(userPerms, "Should have permissions for hubert");
155    assertEquals(1, userPerms.size(), "Should have 1 permission for hubert");
156    assertEquals(Permission.Scope.TABLE, userPerms.get(0).getAccessScope());
157    permission = (TablePermission) userPerms.get(0).getPermission();
158    assertEquals(TEST_TABLE, permission.getTableName(), "Permission should be for " + TEST_TABLE);
159    assertNull(permission.getFamily(), "Column family should be empty");
160
161    // check actions
162    assertNotNull(permission.getActions());
163    assertEquals(1, permission.getActions().length);
164    actions = Arrays.asList(permission.getActions());
165    assertTrue(actions.contains(TablePermission.Action.READ));
166    assertFalse(actions.contains(TablePermission.Action.WRITE));
167
168    userPerms = perms.get("humphrey");
169    assertNotNull(userPerms, "Should have permissions for humphrey");
170    assertEquals(1, userPerms.size(), "Should have 1 permission for humphrey");
171    assertEquals(Permission.Scope.TABLE, userPerms.get(0).getAccessScope());
172    permission = (TablePermission) userPerms.get(0).getPermission();
173    assertEquals(TEST_TABLE, permission.getTableName(), "Permission should be for " + TEST_TABLE);
174    assertTrue(Bytes.equals(TEST_FAMILY, permission.getFamily()),
175      "Permission should be for family " + Bytes.toString(TEST_FAMILY));
176    assertTrue(Bytes.equals(TEST_QUALIFIER, permission.getQualifier()),
177      "Permission should be for qualifier " + Bytes.toString(TEST_QUALIFIER));
178
179    // check actions
180    assertNotNull(permission.getActions());
181    assertEquals(1, permission.getActions().length);
182    actions = Arrays.asList(permission.getActions());
183    assertTrue(actions.contains(TablePermission.Action.READ));
184    assertFalse(actions.contains(TablePermission.Action.WRITE));
185
186    // table 2 permissions
187    try (Connection connection = ConnectionFactory.createConnection(conf);
188      Table table = connection.getTable(PermissionStorage.ACL_TABLE_NAME)) {
189      PermissionStorage.addUserPermission(conf,
190        new UserPermission("hubert", Permission.newBuilder(TEST_TABLE2)
191          .withActions(Permission.Action.READ, Permission.Action.WRITE).build()),
192        table);
193    }
194    // check full load
195    Map<byte[], ListMultimap<String, UserPermission>> allPerms = PermissionStorage.loadAll(conf);
196    assertEquals(2, allPerms.size(),
197      "Full permission map should have entries for both test tables");
198
199    userPerms = allPerms.get(TEST_TABLE.getName()).get("hubert");
200    assertNotNull(userPerms);
201    assertEquals(1, userPerms.size());
202    assertEquals(Permission.Scope.TABLE, userPerms.get(0).getAccessScope());
203    permission = (TablePermission) userPerms.get(0).getPermission();
204    assertEquals(TEST_TABLE, permission.getTableName());
205    assertEquals(1, permission.getActions().length);
206    assertEquals(Permission.Action.READ, permission.getActions()[0]);
207
208    userPerms = allPerms.get(TEST_TABLE2.getName()).get("hubert");
209    assertNotNull(userPerms);
210    assertEquals(1, userPerms.size());
211    assertEquals(Permission.Scope.TABLE, userPerms.get(0).getAccessScope());
212    permission = (TablePermission) userPerms.get(0).getPermission();
213    assertEquals(TEST_TABLE2, permission.getTableName());
214    assertEquals(2, permission.getActions().length);
215    actions = Arrays.asList(permission.getActions());
216    assertTrue(actions.contains(Permission.Action.READ));
217    assertTrue(actions.contains(Permission.Action.WRITE));
218  }
219
220  @Test
221  public void testPersistence() throws Exception {
222    Configuration conf = UTIL.getConfiguration();
223    try (Connection connection = ConnectionFactory.createConnection(conf)) {
224      addUserPermission(conf,
225        new UserPermission("albert",
226          Permission.newBuilder(TEST_TABLE).withActions(Permission.Action.READ).build()),
227        connection.getTable(PermissionStorage.ACL_TABLE_NAME));
228      addUserPermission(conf,
229        new UserPermission("betty",
230          Permission.newBuilder(TEST_TABLE)
231            .withActions(Permission.Action.READ, Permission.Action.WRITE).build()),
232        connection.getTable(PermissionStorage.ACL_TABLE_NAME));
233      addUserPermission(conf,
234        new UserPermission("clark",
235          Permission.newBuilder(TEST_TABLE).withFamily(TEST_FAMILY)
236            .withActions(Permission.Action.READ).build()),
237        connection.getTable(PermissionStorage.ACL_TABLE_NAME));
238      addUserPermission(conf,
239        new UserPermission("dwight",
240          Permission.newBuilder(TEST_TABLE).withFamily(TEST_FAMILY).withQualifier(TEST_QUALIFIER)
241            .withActions(Permission.Action.WRITE).build()),
242        connection.getTable(PermissionStorage.ACL_TABLE_NAME));
243    }
244    // verify permissions survive changes in table metadata
245    ListMultimap<String, UserPermission> preperms =
246      PermissionStorage.getTablePermissions(conf, TEST_TABLE);
247
248    Table table = UTIL.getConnection().getTable(TEST_TABLE);
249    table.put(
250      new Put(Bytes.toBytes("row1")).addColumn(TEST_FAMILY, TEST_QUALIFIER, Bytes.toBytes("v1")));
251    table.put(
252      new Put(Bytes.toBytes("row2")).addColumn(TEST_FAMILY, TEST_QUALIFIER, Bytes.toBytes("v2")));
253    Admin admin = UTIL.getAdmin();
254    try {
255      admin.split(TEST_TABLE);
256    } catch (IOException e) {
257      // although split fail, this may not affect following check
258      // In old Split API without AM2, if region's best split key is not found,
259      // there are not exception thrown. But in current API, exception
260      // will be thrown.
261      LOG.debug("region is not splittable, because " + e);
262    }
263
264    // wait for split
265    Thread.sleep(10000);
266
267    ListMultimap<String, UserPermission> postperms =
268      PermissionStorage.getTablePermissions(conf, TEST_TABLE);
269
270    checkMultimapEqual(preperms, postperms);
271  }
272
273  @Test
274  public void testSerialization() throws Exception {
275    Configuration conf = UTIL.getConfiguration();
276    ListMultimap<String, UserPermission> permissions = createPermissions();
277    byte[] permsData = PermissionStorage.writePermissionsAsBytes(permissions, conf);
278
279    ListMultimap<String, UserPermission> copy =
280      PermissionStorage.readUserPermission(permsData, conf);
281
282    checkMultimapEqual(permissions, copy);
283  }
284
285  private ListMultimap<String, UserPermission> createPermissions() {
286    ListMultimap<String, UserPermission> permissions = ArrayListMultimap.create();
287    permissions.put("george", new UserPermission("george",
288      Permission.newBuilder(TEST_TABLE).withActions(Permission.Action.READ).build()));
289    permissions.put("george", new UserPermission("george", Permission.newBuilder(TEST_TABLE)
290      .withFamily(TEST_FAMILY).withActions(Permission.Action.WRITE).build()));
291    permissions.put("george", new UserPermission("george",
292      Permission.newBuilder(TEST_TABLE2).withActions(Permission.Action.READ).build()));
293    permissions.put("hubert", new UserPermission("hubert", Permission.newBuilder(TEST_TABLE2)
294      .withActions(Permission.Action.READ, Permission.Action.WRITE).build()));
295    permissions.put("bruce", new UserPermission("bruce",
296      Permission.newBuilder(TEST_NAMESPACE).withActions(Permission.Action.READ).build()));
297    return permissions;
298  }
299
300  public void checkMultimapEqual(ListMultimap<String, UserPermission> first,
301    ListMultimap<String, UserPermission> second) {
302    assertEquals(first.size(), second.size());
303    for (String key : first.keySet()) {
304      List<UserPermission> firstPerms = first.get(key);
305      List<UserPermission> secondPerms = second.get(key);
306      assertNotNull(secondPerms);
307      assertEquals(firstPerms.size(), secondPerms.size());
308      LOG.info("First permissions: " + firstPerms.toString());
309      LOG.info("Second permissions: " + secondPerms.toString());
310      for (UserPermission p : firstPerms) {
311        assertTrue(secondPerms.contains(p), "Permission " + p.toString() + " not found");
312      }
313    }
314  }
315
316  @Test
317  public void testEquals() throws Exception {
318    Permission p1 = Permission.newBuilder(TEST_TABLE).withActions(Permission.Action.READ).build();
319    Permission p2 = Permission.newBuilder(TEST_TABLE).withActions(Permission.Action.READ).build();
320    assertTrue(p1.equals(p2));
321    assertTrue(p2.equals(p1));
322
323    p1 = Permission.newBuilder(TEST_TABLE)
324      .withActions(TablePermission.Action.READ, TablePermission.Action.WRITE).build();
325    p2 = Permission.newBuilder(TEST_TABLE)
326      .withActions(TablePermission.Action.WRITE, TablePermission.Action.READ).build();
327    assertTrue(p1.equals(p2));
328    assertTrue(p2.equals(p1));
329
330    p1 = Permission.newBuilder(TEST_TABLE).withFamily(TEST_FAMILY)
331      .withActions(TablePermission.Action.READ, TablePermission.Action.WRITE).build();
332    p2 = Permission.newBuilder(TEST_TABLE).withFamily(TEST_FAMILY)
333      .withActions(TablePermission.Action.WRITE, TablePermission.Action.READ).build();
334    assertTrue(p1.equals(p2));
335    assertTrue(p2.equals(p1));
336
337    p1 = Permission.newBuilder(TEST_TABLE).withFamily(TEST_FAMILY).withQualifier(TEST_QUALIFIER)
338      .withActions(TablePermission.Action.READ, TablePermission.Action.WRITE).build();
339    p2 = Permission.newBuilder(TEST_TABLE).withFamily(TEST_FAMILY).withQualifier(TEST_QUALIFIER)
340      .withActions(TablePermission.Action.WRITE, TablePermission.Action.READ).build();
341    assertTrue(p1.equals(p2));
342    assertTrue(p2.equals(p1));
343
344    p1 = Permission.newBuilder(TEST_TABLE).withActions(TablePermission.Action.READ).build();
345    p2 = Permission.newBuilder(TEST_TABLE).withFamily(TEST_FAMILY)
346      .withActions(TablePermission.Action.READ).build();
347    assertFalse(p1.equals(p2));
348    assertFalse(p2.equals(p1));
349
350    p1 = Permission.newBuilder(TEST_TABLE).withActions(TablePermission.Action.READ).build();
351    p2 = Permission.newBuilder(TEST_TABLE).withActions(TablePermission.Action.WRITE).build();
352    assertFalse(p1.equals(p2));
353    assertFalse(p2.equals(p1));
354    p2 = Permission.newBuilder(TEST_TABLE)
355      .withActions(TablePermission.Action.READ, TablePermission.Action.WRITE).build();
356    assertFalse(p1.equals(p2));
357    assertFalse(p2.equals(p1));
358
359    p1 = Permission.newBuilder(TEST_TABLE).withActions(TablePermission.Action.READ).build();
360    p2 = Permission.newBuilder(TEST_TABLE2).withActions(TablePermission.Action.READ).build();
361    assertFalse(p1.equals(p2));
362    assertFalse(p2.equals(p1));
363
364    p1 = Permission.newBuilder(TEST_NAMESPACE).withActions(TablePermission.Action.READ).build();
365    p2 = Permission.newBuilder(TEST_NAMESPACE).withActions(TablePermission.Action.READ).build();
366    assertEquals(p1, p2);
367
368    p1 = Permission.newBuilder(TEST_NAMESPACE).withActions(TablePermission.Action.READ).build();
369    p2 = Permission.newBuilder(TEST_NAMESPACE2).withActions(TablePermission.Action.READ).build();
370    assertFalse(p1.equals(p2));
371    assertFalse(p2.equals(p1));
372  }
373
374  @Test
375  public void testGlobalPermission() throws Exception {
376    Configuration conf = UTIL.getConfiguration();
377
378    // add some permissions
379    try (Connection connection = ConnectionFactory.createConnection(conf)) {
380      addUserPermission(conf,
381        new UserPermission("user1", Permission.newBuilder()
382          .withActions(Permission.Action.READ, Permission.Action.WRITE).build()),
383        connection.getTable(PermissionStorage.ACL_TABLE_NAME));
384      addUserPermission(conf,
385        new UserPermission("user2",
386          Permission.newBuilder().withActions(Permission.Action.CREATE).build()),
387        connection.getTable(PermissionStorage.ACL_TABLE_NAME));
388      addUserPermission(conf,
389        new UserPermission("user3",
390          Permission.newBuilder()
391            .withActions(Permission.Action.ADMIN, Permission.Action.READ, Permission.Action.CREATE)
392            .build()),
393        connection.getTable(PermissionStorage.ACL_TABLE_NAME));
394    }
395    ListMultimap<String, UserPermission> perms = PermissionStorage.getTablePermissions(conf, null);
396    List<UserPermission> user1Perms = perms.get("user1");
397    assertEquals(1, user1Perms.size(), "Should have 1 permission for user1");
398    assertArrayEquals(new Permission.Action[] { Permission.Action.READ, Permission.Action.WRITE },
399      user1Perms.get(0).getPermission().getActions(), "user1 should have WRITE permission");
400
401    List<UserPermission> user2Perms = perms.get("user2");
402    assertEquals(1, user2Perms.size(), "Should have 1 permission for user2");
403    assertArrayEquals(new Permission.Action[] { Permission.Action.CREATE },
404      user2Perms.get(0).getPermission().getActions(), "user2 should have CREATE permission");
405
406    List<UserPermission> user3Perms = perms.get("user3");
407    assertEquals(1, user3Perms.size(), "Should have 1 permission for user3");
408    assertArrayEquals(
409      new Permission.Action[] { Permission.Action.READ, Permission.Action.CREATE,
410        Permission.Action.ADMIN },
411      user3Perms.get(0).getPermission().getActions(),
412      "user3 should have ADMIN, READ, CREATE permission");
413  }
414
415  @Test
416  public void testAuthManager() throws Exception {
417    Configuration conf = UTIL.getConfiguration();
418    /**
419     * test a race condition causing AuthManager to sometimes fail global permissions checks when
420     * the global cache is being updated
421     */
422    AuthManager authManager = new AuthManager(conf);
423    // currently running user is the system user and should have global admin perms
424    User currentUser = User.getCurrent();
425    assertTrue(authManager.authorizeUserGlobal(currentUser, Permission.Action.ADMIN));
426    try (Connection connection = ConnectionFactory.createConnection(conf)) {
427      for (int i = 1; i <= 50; i++) {
428        addUserPermission(conf,
429          new UserPermission("testauth" + i,
430            Permission.newBuilder()
431              .withActions(Permission.Action.ADMIN, Permission.Action.READ, Permission.Action.WRITE)
432              .build()),
433          connection.getTable(PermissionStorage.ACL_TABLE_NAME));
434        // make sure the system user still shows as authorized
435        assertTrue(authManager.authorizeUserGlobal(currentUser, Permission.Action.ADMIN),
436          "Failed current user auth check on iter " + i);
437      }
438    }
439  }
440}