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