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