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