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