View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  package org.apache.hadoop.hbase.security.access;
20  
21  import static org.junit.Assert.assertEquals;
22  import static org.junit.Assert.assertFalse;
23  import static org.junit.Assert.assertNotNull;
24  import static org.junit.Assert.assertNull;
25  import static org.junit.Assert.assertTrue;
26  
27  import java.io.ByteArrayOutputStream;
28  import java.io.DataOutput;
29  import java.io.DataOutputStream;
30  import java.io.IOException;
31  import java.util.Arrays;
32  import java.util.List;
33  import java.util.Map;
34  import java.util.Set;
35  import java.util.concurrent.atomic.AtomicBoolean;
36  
37  import org.apache.commons.logging.Log;
38  import org.apache.commons.logging.LogFactory;
39  import org.apache.hadoop.conf.Configuration;
40  import org.apache.hadoop.hbase.Abortable;
41  import org.apache.hadoop.hbase.TableName;
42  import org.apache.hadoop.hbase.client.Admin;
43  import org.apache.hadoop.hbase.client.Table;
44  import org.apache.hadoop.hbase.exceptions.DeserializationException;
45  import org.apache.hadoop.hbase.HBaseTestingUtility;
46  import org.apache.hadoop.hbase.testclassification.LargeTests;
47  import org.apache.hadoop.hbase.client.HTable;
48  import org.apache.hadoop.hbase.client.Put;
49  import org.apache.hadoop.hbase.security.User;
50  import org.apache.hadoop.hbase.util.Bytes;
51  import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
52  import org.apache.hadoop.io.Text;
53  import org.junit.After;
54  import org.junit.AfterClass;
55  import org.junit.BeforeClass;
56  import org.junit.Test;
57  import org.junit.experimental.categories.Category;
58  
59  import com.google.common.collect.ArrayListMultimap;
60  import com.google.common.collect.ListMultimap;
61  
62  /**
63   * Test the reading and writing of access permissions on {@code _acl_} table.
64   */
65  @Category(LargeTests.class)
66  public class TestTablePermissions {
67    private static final Log LOG = LogFactory.getLog(TestTablePermissions.class);
68    private static final HBaseTestingUtility UTIL = new HBaseTestingUtility();
69    private static ZooKeeperWatcher ZKW;
70    private final static Abortable ABORTABLE = new Abortable() {
71      private final AtomicBoolean abort = new AtomicBoolean(false);
72  
73      @Override
74      public void abort(String why, Throwable e) {
75        LOG.info(why, e);
76        abort.set(true);
77      }
78  
79      @Override
80      public boolean isAborted() {
81        return abort.get();
82      }
83    };
84  
85    private static TableName TEST_TABLE =
86        TableName.valueOf("perms_test");
87    private static TableName TEST_TABLE2 =
88        TableName.valueOf("perms_test2");
89    private static byte[] TEST_FAMILY = Bytes.toBytes("f1");
90    private static byte[] TEST_QUALIFIER = Bytes.toBytes("col1");
91  
92    @BeforeClass
93    public static void beforeClass() throws Exception {
94      // setup configuration
95      Configuration conf = UTIL.getConfiguration();
96      SecureTestUtil.enableSecurity(conf);
97  
98      UTIL.startMiniCluster();
99  
100     // Wait for the ACL table to become available
101     UTIL.waitTableEnabled(AccessControlLists.ACL_TABLE_NAME);
102 
103     ZKW = new ZooKeeperWatcher(UTIL.getConfiguration(),
104       "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     AccessControlLists.removeTablePermissions(conf, TEST_TABLE);
119     AccessControlLists.removeTablePermissions(conf, TEST_TABLE2);
120     AccessControlLists.removeTablePermissions(conf, AccessControlLists.ACL_TABLE_NAME);
121   }
122 
123   /**
124    * Test we can read permissions serialized with Writables.
125    * @throws DeserializationException
126    */
127   @Test
128   public void testMigration() throws DeserializationException {
129     Configuration conf = UTIL.getConfiguration();
130     ListMultimap<String,TablePermission> permissions = createPermissions();
131     byte [] bytes = writePermissionsAsBytes(permissions, conf);
132     AccessControlLists.readPermissions(bytes, conf);
133   }
134 
135   /**
136    * Writes a set of permissions as {@link org.apache.hadoop.io.Writable} instances
137    * and returns the resulting byte array.  Used to verify we can read stuff written
138    * with Writable.
139    */
140   public static byte[] writePermissionsAsBytes(ListMultimap<String,? extends Permission> perms,
141       Configuration conf) {
142     try {
143        ByteArrayOutputStream bos = new ByteArrayOutputStream();
144        writePermissions(new DataOutputStream(bos), perms, conf);
145        return bos.toByteArray();
146     } catch (IOException ioe) {
147       // shouldn't happen here
148       throw new RuntimeException("Error serializing permissions", ioe);
149     }
150   }
151 
152   /**
153    * Writes a set of permissions as {@link org.apache.hadoop.io.Writable} instances
154    * to the given output stream.
155    * @param out
156    * @param perms
157    * @param conf
158    * @throws IOException
159   */
160   public static void writePermissions(DataOutput out,
161       ListMultimap<String,? extends Permission> perms, Configuration conf)
162   throws IOException {
163     Set<String> keys = perms.keySet();
164     out.writeInt(keys.size());
165     for (String key : keys) {
166       Text.writeString(out, key);
167       HbaseObjectWritableFor96Migration.writeObject(out, perms.get(key), List.class, conf);
168     }
169   }
170 
171 
172   @Test
173   public void testBasicWrite() throws Exception {
174     Configuration conf = UTIL.getConfiguration();
175     // add some permissions
176     AccessControlLists.addUserPermission(conf,
177             new UserPermission(Bytes.toBytes("george"), TEST_TABLE, null, (byte[])null,
178             UserPermission.Action.READ, UserPermission.Action.WRITE));
179     AccessControlLists.addUserPermission(conf,
180         new UserPermission(Bytes.toBytes("hubert"), TEST_TABLE, null, (byte[])null,
181             UserPermission.Action.READ));
182     AccessControlLists.addUserPermission(conf,
183         new UserPermission(Bytes.toBytes("humphrey"),
184             TEST_TABLE, TEST_FAMILY, TEST_QUALIFIER,
185             UserPermission.Action.READ));
186 
187     // retrieve the same
188     ListMultimap<String,TablePermission> perms =
189         AccessControlLists.getTablePermissions(conf, TEST_TABLE);
190     List<TablePermission> userPerms = perms.get("george");
191     assertNotNull("Should have permissions for george", userPerms);
192     assertEquals("Should have 1 permission for george", 1, userPerms.size());
193     TablePermission permission = userPerms.get(0);
194     assertEquals("Permission should be for " + TEST_TABLE,
195         TEST_TABLE, permission.getTableName());
196     assertNull("Column family should be empty", permission.getFamily());
197 
198     // check actions
199     assertNotNull(permission.getActions());
200     assertEquals(2, permission.getActions().length);
201     List<TablePermission.Action> actions = Arrays.asList(permission.getActions());
202     assertTrue(actions.contains(TablePermission.Action.READ));
203     assertTrue(actions.contains(TablePermission.Action.WRITE));
204 
205     userPerms = perms.get("hubert");
206     assertNotNull("Should have permissions for hubert", userPerms);
207     assertEquals("Should have 1 permission for hubert", 1, userPerms.size());
208     permission = userPerms.get(0);
209     assertEquals("Permission should be for " + TEST_TABLE,
210         TEST_TABLE, permission.getTableName());
211     assertNull("Column family should be empty", permission.getFamily());
212 
213     // check actions
214     assertNotNull(permission.getActions());
215     assertEquals(1, permission.getActions().length);
216     actions = Arrays.asList(permission.getActions());
217     assertTrue(actions.contains(TablePermission.Action.READ));
218     assertFalse(actions.contains(TablePermission.Action.WRITE));
219 
220     userPerms = perms.get("humphrey");
221     assertNotNull("Should have permissions for humphrey", userPerms);
222     assertEquals("Should have 1 permission for humphrey", 1, userPerms.size());
223     permission = userPerms.get(0);
224     assertEquals("Permission should be for " + TEST_TABLE,
225         TEST_TABLE, permission.getTableName());
226     assertTrue("Permission should be for family " + TEST_FAMILY,
227         Bytes.equals(TEST_FAMILY, permission.getFamily()));
228     assertTrue("Permission should be for qualifier " + TEST_QUALIFIER,
229         Bytes.equals(TEST_QUALIFIER, permission.getQualifier()));
230 
231     // check actions
232     assertNotNull(permission.getActions());
233     assertEquals(1, permission.getActions().length);
234     actions = Arrays.asList(permission.getActions());
235     assertTrue(actions.contains(TablePermission.Action.READ));
236     assertFalse(actions.contains(TablePermission.Action.WRITE));
237 
238     // table 2 permissions
239     AccessControlLists.addUserPermission(conf,
240         new UserPermission(Bytes.toBytes("hubert"), TEST_TABLE2, null, (byte[])null,
241             TablePermission.Action.READ, TablePermission.Action.WRITE));
242 
243     // check full load
244     Map<byte[], ListMultimap<String,TablePermission>> allPerms =
245         AccessControlLists.loadAll(conf);
246     assertEquals("Full permission map should have entries for both test tables",
247         2, allPerms.size());
248 
249     userPerms = allPerms.get(TEST_TABLE.getName()).get("hubert");
250     assertNotNull(userPerms);
251     assertEquals(1, userPerms.size());
252     permission = userPerms.get(0);
253     assertEquals(TEST_TABLE, permission.getTableName());
254     assertEquals(1, permission.getActions().length);
255     assertEquals(TablePermission.Action.READ, permission.getActions()[0]);
256 
257     userPerms = allPerms.get(TEST_TABLE2.getName()).get("hubert");
258     assertNotNull(userPerms);
259     assertEquals(1, userPerms.size());
260     permission = userPerms.get(0);
261     assertEquals(TEST_TABLE2, permission.getTableName());
262     assertEquals(2, permission.getActions().length);
263     actions = Arrays.asList(permission.getActions());
264     assertTrue(actions.contains(TablePermission.Action.READ));
265     assertTrue(actions.contains(TablePermission.Action.WRITE));
266   }
267 
268   @Test
269   public void testPersistence() throws Exception {
270     Configuration conf = UTIL.getConfiguration();
271     AccessControlLists.addUserPermission(conf,
272         new UserPermission(Bytes.toBytes("albert"), TEST_TABLE, null,
273                            (byte[])null, TablePermission.Action.READ));
274     AccessControlLists.addUserPermission(conf,
275         new UserPermission(Bytes.toBytes("betty"), TEST_TABLE, null,
276                            (byte[])null, TablePermission.Action.READ,
277                            TablePermission.Action.WRITE));
278     AccessControlLists.addUserPermission(conf,
279         new UserPermission(Bytes.toBytes("clark"),
280                            TEST_TABLE, TEST_FAMILY,
281                            TablePermission.Action.READ));
282     AccessControlLists.addUserPermission(conf,
283         new UserPermission(Bytes.toBytes("dwight"),
284                            TEST_TABLE, TEST_FAMILY, TEST_QUALIFIER,
285                            TablePermission.Action.WRITE));
286 
287     // verify permissions survive changes in table metadata
288     ListMultimap<String,TablePermission> preperms =
289         AccessControlLists.getTablePermissions(conf, TEST_TABLE);
290 
291     Table table = new HTable(conf, TEST_TABLE);
292     table.put(new Put(Bytes.toBytes("row1"))
293         .add(TEST_FAMILY, TEST_QUALIFIER, Bytes.toBytes("v1")));
294     table.put(new Put(Bytes.toBytes("row2"))
295         .add(TEST_FAMILY, TEST_QUALIFIER, Bytes.toBytes("v2")));
296     Admin admin = UTIL.getHBaseAdmin();
297     admin.split(TEST_TABLE);
298 
299     // wait for split
300     Thread.sleep(10000);
301 
302     ListMultimap<String,TablePermission> postperms =
303         AccessControlLists.getTablePermissions(conf, TEST_TABLE);
304 
305     checkMultimapEqual(preperms, postperms);
306   }
307 
308   @Test
309   public void testSerialization() throws Exception {
310     Configuration conf = UTIL.getConfiguration();
311     ListMultimap<String,TablePermission> permissions = createPermissions();
312     byte[] permsData = AccessControlLists.writePermissionsAsBytes(permissions, conf);
313 
314     ListMultimap<String, TablePermission> copy =
315         AccessControlLists.readPermissions(permsData, conf);
316 
317     checkMultimapEqual(permissions, copy);
318   }
319 
320   private ListMultimap<String,TablePermission> createPermissions() {
321     ListMultimap<String,TablePermission> permissions = ArrayListMultimap.create();
322     permissions.put("george", new TablePermission(TEST_TABLE, null,
323         TablePermission.Action.READ));
324     permissions.put("george", new TablePermission(TEST_TABLE, TEST_FAMILY,
325         TablePermission.Action.WRITE));
326     permissions.put("george", new TablePermission(TEST_TABLE2, null,
327         TablePermission.Action.READ));
328     permissions.put("hubert", new TablePermission(TEST_TABLE2, null,
329         TablePermission.Action.READ, TablePermission.Action.WRITE));
330     return permissions;
331   }
332 
333   public void checkMultimapEqual(ListMultimap<String,TablePermission> first,
334       ListMultimap<String,TablePermission> second) {
335     assertEquals(first.size(), second.size());
336     for (String key : first.keySet()) {
337       List<TablePermission> firstPerms = first.get(key);
338       List<TablePermission> secondPerms = second.get(key);
339       assertNotNull(secondPerms);
340       assertEquals(firstPerms.size(), secondPerms.size());
341       LOG.info("First permissions: "+firstPerms.toString());
342       LOG.info("Second permissions: "+secondPerms.toString());
343       for (TablePermission p : firstPerms) {
344         assertTrue("Permission "+p.toString()+" not found", secondPerms.contains(p));
345       }
346     }
347   }
348 
349   @Test
350   public void testEquals() throws Exception {
351     TablePermission p1 = new TablePermission(TEST_TABLE, null, TablePermission.Action.READ);
352     TablePermission p2 = new TablePermission(TEST_TABLE, null, TablePermission.Action.READ);
353     assertTrue(p1.equals(p2));
354     assertTrue(p2.equals(p1));
355 
356     p1 = new TablePermission(TEST_TABLE, null, TablePermission.Action.READ, TablePermission.Action.WRITE);
357     p2 = new TablePermission(TEST_TABLE, null, TablePermission.Action.WRITE, TablePermission.Action.READ);
358     assertTrue(p1.equals(p2));
359     assertTrue(p2.equals(p1));
360 
361     p1 = new TablePermission(TEST_TABLE, TEST_FAMILY, TablePermission.Action.READ, TablePermission.Action.WRITE);
362     p2 = new TablePermission(TEST_TABLE, TEST_FAMILY, TablePermission.Action.WRITE, TablePermission.Action.READ);
363     assertTrue(p1.equals(p2));
364     assertTrue(p2.equals(p1));
365 
366     p1 = new TablePermission(TEST_TABLE, TEST_FAMILY, TEST_QUALIFIER, TablePermission.Action.READ, TablePermission.Action.WRITE);
367     p2 = new TablePermission(TEST_TABLE, TEST_FAMILY, TEST_QUALIFIER, TablePermission.Action.WRITE, TablePermission.Action.READ);
368     assertTrue(p1.equals(p2));
369     assertTrue(p2.equals(p1));
370 
371     p1 = new TablePermission(TEST_TABLE, null, TablePermission.Action.READ);
372     p2 = new TablePermission(TEST_TABLE, TEST_FAMILY, TablePermission.Action.READ);
373     assertFalse(p1.equals(p2));
374     assertFalse(p2.equals(p1));
375 
376     p1 = new TablePermission(TEST_TABLE, null, TablePermission.Action.READ);
377     p2 = new TablePermission(TEST_TABLE, null, TablePermission.Action.WRITE);
378     assertFalse(p1.equals(p2));
379     assertFalse(p2.equals(p1));
380     p2 = new TablePermission(TEST_TABLE, null, TablePermission.Action.READ, TablePermission.Action.WRITE);
381     assertFalse(p1.equals(p2));
382     assertFalse(p2.equals(p1));
383 
384     p1 = new TablePermission(TEST_TABLE, null, TablePermission.Action.READ);
385     p2 = new TablePermission(TEST_TABLE2, null, TablePermission.Action.READ);
386     assertFalse(p1.equals(p2));
387     assertFalse(p2.equals(p1));
388 
389     p2 = new TablePermission(TEST_TABLE, null);
390     assertFalse(p1.equals(p2));
391     assertFalse(p2.equals(p1));
392   }
393 
394   @Test
395   public void testGlobalPermission() throws Exception {
396     Configuration conf = UTIL.getConfiguration();
397 
398     // add some permissions
399     AccessControlLists.addUserPermission(conf,
400         new UserPermission(Bytes.toBytes("user1"),
401             Permission.Action.READ, Permission.Action.WRITE));
402     AccessControlLists.addUserPermission(conf,
403         new UserPermission(Bytes.toBytes("user2"),
404             Permission.Action.CREATE));
405     AccessControlLists.addUserPermission(conf,
406         new UserPermission(Bytes.toBytes("user3"),
407             Permission.Action.ADMIN, Permission.Action.READ, Permission.Action.CREATE));
408 
409     ListMultimap<String,TablePermission> perms = AccessControlLists.getTablePermissions(conf, null);
410     List<TablePermission> user1Perms = perms.get("user1");
411     assertEquals("Should have 1 permission for user1", 1, user1Perms.size());
412     assertEquals("user1 should have WRITE permission",
413                  new Permission.Action[] { Permission.Action.READ, Permission.Action.WRITE },
414                  user1Perms.get(0).getActions());
415 
416     List<TablePermission> user2Perms = perms.get("user2");
417     assertEquals("Should have 1 permission for user2", 1, user2Perms.size());
418     assertEquals("user2 should have CREATE permission",
419                  new Permission.Action[] { Permission.Action.CREATE },
420                  user2Perms.get(0).getActions());
421 
422     List<TablePermission> user3Perms = perms.get("user3");
423     assertEquals("Should have 1 permission for user3", 1, user3Perms.size());
424     assertEquals("user3 should have ADMIN, READ, CREATE permission",
425                  new Permission.Action[] {
426                     Permission.Action.ADMIN, Permission.Action.READ, Permission.Action.CREATE
427                  },
428                  user3Perms.get(0).getActions());
429   }
430 
431   @Test
432   public void testAuthManager() throws Exception {
433     Configuration conf = UTIL.getConfiguration();
434     /* test a race condition causing TableAuthManager to sometimes fail global permissions checks
435      * when the global cache is being updated
436      */
437     TableAuthManager authManager = TableAuthManager.get(ZKW, conf);
438     // currently running user is the system user and should have global admin perms
439     User currentUser = User.getCurrent();
440     assertTrue(authManager.authorize(currentUser, Permission.Action.ADMIN));
441     for (int i=1; i<=50; i++) {
442       AccessControlLists.addUserPermission(conf, new UserPermission(Bytes.toBytes("testauth"+i),
443           Permission.Action.ADMIN, Permission.Action.READ, Permission.Action.WRITE));
444       // make sure the system user still shows as authorized
445       assertTrue("Failed current user auth check on iter "+i,
446           authManager.authorize(currentUser, Permission.Action.ADMIN));
447     }
448   }
449 }