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