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