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