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