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(Bytes.toBytes("george"), TEST_TABLE, null, (byte[])null,
147              UserPermission.Action.READ, UserPermission.Action.WRITE),
148              connection.getTable(AccessControlLists.ACL_TABLE_NAME));
149      addUserPermission(conf,
150          new UserPermission(Bytes.toBytes("hubert"), TEST_TABLE, null, (byte[])null,
151              UserPermission.Action.READ),
152          connection.getTable(AccessControlLists.ACL_TABLE_NAME));
153      addUserPermission(conf,
154          new UserPermission(Bytes.toBytes("humphrey"),
155              TEST_TABLE, TEST_FAMILY, TEST_QUALIFIER,
156              UserPermission.Action.READ),
157          connection.getTable(AccessControlLists.ACL_TABLE_NAME));
158    }
159    // retrieve the same
160    ListMultimap<String,TablePermission> perms =
161        AccessControlLists.getTablePermissions(conf, TEST_TABLE);
162    List<TablePermission> userPerms = perms.get("george");
163    assertNotNull("Should have permissions for george", userPerms);
164    assertEquals("Should have 1 permission for george", 1, userPerms.size());
165    TablePermission permission = userPerms.get(0);
166    assertEquals("Permission should be for " + TEST_TABLE,
167        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<TablePermission.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    permission = userPerms.get(0);
181    assertEquals("Permission should be for " + TEST_TABLE,
182        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    permission = userPerms.get(0);
196    assertEquals("Permission should be for " + TEST_TABLE,
197        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(AccessControlLists.ACL_TABLE_NAME)) {
213      AccessControlLists.addUserPermission(conf,
214          new UserPermission(Bytes.toBytes("hubert"), TEST_TABLE2, null, (byte[])null,
215              TablePermission.Action.READ, TablePermission.Action.WRITE), table);
216    }
217    // check full load
218    Map<byte[], ListMultimap<String,TablePermission>> allPerms =
219        AccessControlLists.loadAll(conf);
220    assertEquals("Full permission map should have entries for both test tables",
221        2, allPerms.size());
222
223    userPerms = allPerms.get(TEST_TABLE.getName()).get("hubert");
224    assertNotNull(userPerms);
225    assertEquals(1, userPerms.size());
226    permission = userPerms.get(0);
227    assertEquals(TEST_TABLE, permission.getTableName());
228    assertEquals(1, permission.getActions().length);
229    assertEquals(TablePermission.Action.READ, permission.getActions()[0]);
230
231    userPerms = allPerms.get(TEST_TABLE2.getName()).get("hubert");
232    assertNotNull(userPerms);
233    assertEquals(1, userPerms.size());
234    permission = userPerms.get(0);
235    assertEquals(TEST_TABLE2, permission.getTableName());
236    assertEquals(2, permission.getActions().length);
237    actions = Arrays.asList(permission.getActions());
238    assertTrue(actions.contains(TablePermission.Action.READ));
239    assertTrue(actions.contains(TablePermission.Action.WRITE));
240  }
241
242  @Test
243  public void testPersistence() throws Exception {
244    Configuration conf = UTIL.getConfiguration();
245    try (Connection connection = ConnectionFactory.createConnection(conf)) {
246      addUserPermission(conf,
247          new UserPermission(Bytes.toBytes("albert"), TEST_TABLE, null,
248              (byte[])null, TablePermission.Action.READ), connection.getTable(AccessControlLists.ACL_TABLE_NAME));
249      addUserPermission(conf,
250          new UserPermission(Bytes.toBytes("betty"), TEST_TABLE, null,
251              (byte[])null, TablePermission.Action.READ,
252              TablePermission.Action.WRITE), connection.getTable(AccessControlLists.ACL_TABLE_NAME));
253      addUserPermission(conf,
254          new UserPermission(Bytes.toBytes("clark"),
255              TEST_TABLE, TEST_FAMILY,
256              TablePermission.Action.READ), connection.getTable(AccessControlLists.ACL_TABLE_NAME));
257      addUserPermission(conf,
258          new UserPermission(Bytes.toBytes("dwight"),
259              TEST_TABLE, TEST_FAMILY, TEST_QUALIFIER,
260              TablePermission.Action.WRITE), connection.getTable(AccessControlLists.ACL_TABLE_NAME));
261    }
262    // verify permissions survive changes in table metadata
263    ListMultimap<String,TablePermission> preperms =
264        AccessControlLists.getTablePermissions(conf, TEST_TABLE);
265
266    Table table = UTIL.getConnection().getTable(TEST_TABLE);
267    table.put(new Put(Bytes.toBytes("row1"))
268            .addColumn(TEST_FAMILY, TEST_QUALIFIER, Bytes.toBytes("v1")));
269    table.put(new Put(Bytes.toBytes("row2"))
270            .addColumn(TEST_FAMILY, TEST_QUALIFIER, Bytes.toBytes("v2")));
271    Admin admin = UTIL.getAdmin();
272    try {
273      admin.split(TEST_TABLE);
274    }
275    catch (IOException e) {
276      //although split fail, this may not affect following check
277      //In old Split API without AM2, if region's best split key is not found,
278      //there are not exception thrown. But in current API, exception
279      //will be thrown.
280      LOG.debug("region is not splittable, because " + e);
281    }
282
283    // wait for split
284    Thread.sleep(10000);
285
286    ListMultimap<String,TablePermission> postperms =
287        AccessControlLists.getTablePermissions(conf, TEST_TABLE);
288
289    checkMultimapEqual(preperms, postperms);
290  }
291
292  @Test
293  public void testSerialization() throws Exception {
294    Configuration conf = UTIL.getConfiguration();
295    ListMultimap<String,TablePermission> permissions = createPermissions();
296    byte[] permsData = AccessControlLists.writePermissionsAsBytes(permissions, conf);
297
298    ListMultimap<String, TablePermission> copy =
299        AccessControlLists.readPermissions(permsData, conf);
300
301    checkMultimapEqual(permissions, copy);
302  }
303
304  private ListMultimap<String,TablePermission> createPermissions() {
305    ListMultimap<String,TablePermission> permissions = ArrayListMultimap.create();
306    permissions.put("george", new TablePermission(TEST_TABLE, null,
307        TablePermission.Action.READ));
308    permissions.put("george", new TablePermission(TEST_TABLE, TEST_FAMILY,
309        TablePermission.Action.WRITE));
310    permissions.put("george", new TablePermission(TEST_TABLE2, null,
311        TablePermission.Action.READ));
312    permissions.put("hubert", new TablePermission(TEST_TABLE2, null,
313        TablePermission.Action.READ, TablePermission.Action.WRITE));
314    permissions.put("bruce",new TablePermission(TEST_NAMESPACE,
315        TablePermission.Action.READ));
316    return permissions;
317  }
318
319  public void checkMultimapEqual(ListMultimap<String,TablePermission> first,
320      ListMultimap<String,TablePermission> second) {
321    assertEquals(first.size(), second.size());
322    for (String key : first.keySet()) {
323      List<TablePermission> firstPerms = first.get(key);
324      List<TablePermission> secondPerms = second.get(key);
325      assertNotNull(secondPerms);
326      assertEquals(firstPerms.size(), secondPerms.size());
327      LOG.info("First permissions: "+firstPerms.toString());
328      LOG.info("Second permissions: "+secondPerms.toString());
329      for (TablePermission p : firstPerms) {
330        assertTrue("Permission "+p.toString()+" not found", secondPerms.contains(p));
331      }
332    }
333  }
334
335  @Test
336  public void testEquals() throws Exception {
337    TablePermission p1 = new TablePermission(TEST_TABLE, null, TablePermission.Action.READ);
338    TablePermission p2 = new TablePermission(TEST_TABLE, null, TablePermission.Action.READ);
339    assertTrue(p1.equals(p2));
340    assertTrue(p2.equals(p1));
341
342    p1 = new TablePermission(TEST_TABLE, null, TablePermission.Action.READ, TablePermission.Action.WRITE);
343    p2 = new TablePermission(TEST_TABLE, null, TablePermission.Action.WRITE, TablePermission.Action.READ);
344    assertTrue(p1.equals(p2));
345    assertTrue(p2.equals(p1));
346
347    p1 = new TablePermission(TEST_TABLE, TEST_FAMILY, TablePermission.Action.READ, TablePermission.Action.WRITE);
348    p2 = new TablePermission(TEST_TABLE, TEST_FAMILY, TablePermission.Action.WRITE, TablePermission.Action.READ);
349    assertTrue(p1.equals(p2));
350    assertTrue(p2.equals(p1));
351
352    p1 = new TablePermission(TEST_TABLE, TEST_FAMILY, TEST_QUALIFIER, TablePermission.Action.READ, TablePermission.Action.WRITE);
353    p2 = new TablePermission(TEST_TABLE, TEST_FAMILY, TEST_QUALIFIER, TablePermission.Action.WRITE, TablePermission.Action.READ);
354    assertTrue(p1.equals(p2));
355    assertTrue(p2.equals(p1));
356
357    p1 = new TablePermission(TEST_TABLE, null, TablePermission.Action.READ);
358    p2 = new TablePermission(TEST_TABLE, TEST_FAMILY, TablePermission.Action.READ);
359    assertFalse(p1.equals(p2));
360    assertFalse(p2.equals(p1));
361
362    p1 = new TablePermission(TEST_TABLE, null, TablePermission.Action.READ);
363    p2 = new TablePermission(TEST_TABLE, null, TablePermission.Action.WRITE);
364    assertFalse(p1.equals(p2));
365    assertFalse(p2.equals(p1));
366    p2 = new TablePermission(TEST_TABLE, null, TablePermission.Action.READ, TablePermission.Action.WRITE);
367    assertFalse(p1.equals(p2));
368    assertFalse(p2.equals(p1));
369
370    p1 = new TablePermission(TEST_TABLE, null, TablePermission.Action.READ);
371    p2 = new TablePermission(TEST_TABLE2, null, TablePermission.Action.READ);
372    assertFalse(p1.equals(p2));
373    assertFalse(p2.equals(p1));
374
375    p2 = new TablePermission(TEST_TABLE, null);
376    assertFalse(p1.equals(p2));
377    assertFalse(p2.equals(p1));
378
379    p1 = new TablePermission(TEST_NAMESPACE, TablePermission.Action.READ);
380    p2 = new TablePermission(TEST_NAMESPACE, TablePermission.Action.READ);
381    assertEquals(p1, p2);
382
383    p1 = new TablePermission(TEST_NAMESPACE, TablePermission.Action.READ);
384    p2 = new TablePermission(TEST_NAMESPACE2, TablePermission.Action.READ);
385    assertFalse(p1.equals(p2));
386    assertFalse(p2.equals(p1));
387  }
388
389  @Test
390  public void testGlobalPermission() throws Exception {
391    Configuration conf = UTIL.getConfiguration();
392
393    // add some permissions
394    try (Connection connection = ConnectionFactory.createConnection(conf)) {
395      addUserPermission(conf,
396          new UserPermission(Bytes.toBytes("user1"),
397              Permission.Action.READ, Permission.Action.WRITE), connection.getTable(AccessControlLists.ACL_TABLE_NAME));
398      addUserPermission(conf,
399          new UserPermission(Bytes.toBytes("user2"),
400              Permission.Action.CREATE), connection.getTable(AccessControlLists.ACL_TABLE_NAME));
401      addUserPermission(conf,
402          new UserPermission(Bytes.toBytes("user3"),
403              Permission.Action.ADMIN, Permission.Action.READ, Permission.Action.CREATE),
404          connection.getTable(AccessControlLists.ACL_TABLE_NAME));
405    }
406    ListMultimap<String,TablePermission> perms = AccessControlLists.getTablePermissions(conf, null);
407    List<TablePermission> user1Perms = perms.get("user1");
408    assertEquals("Should have 1 permission for user1", 1, user1Perms.size());
409    assertEquals("user1 should have WRITE permission",
410                 new Permission.Action[] { Permission.Action.READ, Permission.Action.WRITE },
411                 user1Perms.get(0).getActions());
412
413    List<TablePermission> user2Perms = perms.get("user2");
414    assertEquals("Should have 1 permission for user2", 1, user2Perms.size());
415    assertEquals("user2 should have CREATE permission",
416                 new Permission.Action[] { Permission.Action.CREATE },
417                 user2Perms.get(0).getActions());
418
419    List<TablePermission> user3Perms = perms.get("user3");
420    assertEquals("Should have 1 permission for user3", 1, user3Perms.size());
421    assertEquals("user3 should have ADMIN, READ, CREATE permission",
422                 new Permission.Action[] {
423                    Permission.Action.READ, Permission.Action.CREATE, Permission.Action.ADMIN
424                 },
425                 user3Perms.get(0).getActions());
426  }
427
428  @Test
429  public void testAuthManager() throws Exception {
430    Configuration conf = UTIL.getConfiguration();
431    /* test a race condition causing TableAuthManager to sometimes fail global permissions checks
432     * when the global cache is being updated
433     */
434    TableAuthManager authManager = TableAuthManager.getOrCreate(ZKW, conf);
435    // currently running user is the system user and should have global admin perms
436    User currentUser = User.getCurrent();
437    assertTrue(authManager.authorize(currentUser, Permission.Action.ADMIN));
438    try (Connection connection = ConnectionFactory.createConnection(conf)) {
439      for (int i=1; i<=50; i++) {
440        addUserPermission(conf, new UserPermission(Bytes.toBytes("testauth"+i),
441            Permission.Action.ADMIN, Permission.Action.READ, Permission.Action.WRITE),
442            connection.getTable(AccessControlLists.ACL_TABLE_NAME));
443        // make sure the system user still shows as authorized
444        assertTrue("Failed current user auth check on iter "+i,
445            authManager.authorize(currentUser, Permission.Action.ADMIN));
446      }
447    }
448  }
449}