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.ByteArrayInputStream;
28  import java.io.ByteArrayOutputStream;
29  import java.io.DataInputStream;
30  import java.io.DataOutputStream;
31  import java.util.Arrays;
32  import java.util.List;
33  import java.util.Map;
34  import java.util.concurrent.atomic.AtomicBoolean;
35  
36  import org.apache.commons.logging.Log;
37  import org.apache.commons.logging.LogFactory;
38  import org.apache.hadoop.conf.Configuration;
39  import org.apache.hadoop.hbase.Abortable;
40  import org.apache.hadoop.hbase.HBaseTestingUtility;
41  import org.apache.hadoop.hbase.LargeTests;
42  import org.apache.hadoop.hbase.client.HBaseAdmin;
43  import org.apache.hadoop.hbase.client.HTable;
44  import org.apache.hadoop.hbase.client.Put;
45  import org.apache.hadoop.hbase.security.User;
46  import org.apache.hadoop.hbase.util.Bytes;
47  import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
48  import org.junit.AfterClass;
49  import org.junit.BeforeClass;
50  import org.junit.Test;
51  import org.junit.experimental.categories.Category;
52  
53  import com.google.common.collect.ArrayListMultimap;
54  import com.google.common.collect.ListMultimap;
55  
56  /**
57   * Test the reading and writing of access permissions on {@code _acl_} table.
58   */
59  @Category(LargeTests.class)
60  public class TestTablePermissions {
61    private static final Log LOG = LogFactory.getLog(TestTablePermissions.class);
62    private static final HBaseTestingUtility UTIL = new HBaseTestingUtility();
63    private static ZooKeeperWatcher ZKW;
64    private final static Abortable ABORTABLE = new Abortable() {
65      private final AtomicBoolean abort = new AtomicBoolean(false);
66  
67      @Override
68      public void abort(String why, Throwable e) {
69        LOG.info(why, e);
70        abort.set(true);
71      }
72  
73      @Override
74      public boolean isAborted() {
75        return abort.get();
76      }
77    };
78  
79    private static byte[] TEST_TABLE = Bytes.toBytes("perms_test");
80    private static byte[] TEST_TABLE2 = Bytes.toBytes("perms_test2");
81    private static byte[] TEST_FAMILY = Bytes.toBytes("f1");
82    private static byte[] TEST_QUALIFIER = Bytes.toBytes("col1");
83  
84    @BeforeClass
85    public static void beforeClass() throws Exception {
86      // setup configuration
87      Configuration conf = UTIL.getConfiguration();
88      SecureTestUtil.enableSecurity(conf);
89  
90      UTIL.startMiniCluster();
91  
92      // Wait for the ACL table to become available
93      UTIL.waitTableAvailable(AccessControlLists.ACL_TABLE_NAME, 5000);
94  
95      ZKW = new ZooKeeperWatcher(UTIL.getConfiguration(),
96        "TestTablePermissions", ABORTABLE);
97  
98      UTIL.createTable(TEST_TABLE, TEST_FAMILY);
99      UTIL.createTable(TEST_TABLE2, TEST_FAMILY);
100   }
101 
102   @AfterClass
103   public static void afterClass() throws Exception {
104     UTIL.shutdownMiniCluster();
105   }
106 
107   @Test
108   public void testBasicWrite() throws Exception {
109     Configuration conf = UTIL.getConfiguration();
110     // add some permissions
111     AccessControlLists.addUserPermission(conf,
112             new UserPermission(Bytes.toBytes("george"), TEST_TABLE, null, (byte[])null,
113             UserPermission.Action.READ, UserPermission.Action.WRITE));
114     AccessControlLists.addUserPermission(conf,
115         new UserPermission(Bytes.toBytes("hubert"), TEST_TABLE, null, (byte[])null,
116             UserPermission.Action.READ));
117     AccessControlLists.addUserPermission(conf,
118         new UserPermission(Bytes.toBytes("humphrey"),
119             TEST_TABLE, TEST_FAMILY, TEST_QUALIFIER,
120             UserPermission.Action.READ));
121 
122     // retrieve the same
123     ListMultimap<String,TablePermission> perms =
124         AccessControlLists.getTablePermissions(conf, TEST_TABLE);
125     List<TablePermission> userPerms = perms.get("george");
126     assertNotNull("Should have permissions for george", userPerms);
127     assertEquals("Should have 1 permission for george", 1, userPerms.size());
128     TablePermission permission = userPerms.get(0);
129     assertTrue("Permission should be for " + TEST_TABLE,
130         Bytes.equals(TEST_TABLE, permission.getTable()));
131     assertNull("Column family should be empty", permission.getFamily());
132 
133     // check actions
134     assertNotNull(permission.getActions());
135     assertEquals(2, permission.getActions().length);
136     List<TablePermission.Action> actions = Arrays.asList(permission.getActions());
137     assertTrue(actions.contains(TablePermission.Action.READ));
138     assertTrue(actions.contains(TablePermission.Action.WRITE));
139 
140     userPerms = perms.get("hubert");
141     assertNotNull("Should have permissions for hubert", userPerms);
142     assertEquals("Should have 1 permission for hubert", 1, userPerms.size());
143     permission = userPerms.get(0);
144     assertTrue("Permission should be for " + TEST_TABLE,
145         Bytes.equals(TEST_TABLE, permission.getTable()));
146     assertNull("Column family should be empty", permission.getFamily());
147 
148     // check actions
149     assertNotNull(permission.getActions());
150     assertEquals(1, permission.getActions().length);
151     actions = Arrays.asList(permission.getActions());
152     assertTrue(actions.contains(TablePermission.Action.READ));
153     assertFalse(actions.contains(TablePermission.Action.WRITE));
154 
155     userPerms = perms.get("humphrey");
156     assertNotNull("Should have permissions for humphrey", userPerms);
157     assertEquals("Should have 1 permission for humphrey", 1, userPerms.size());
158     permission = userPerms.get(0);
159     assertTrue("Permission should be for " + TEST_TABLE,
160         Bytes.equals(TEST_TABLE, permission.getTable()));
161     assertTrue("Permission should be for family " + TEST_FAMILY,
162         Bytes.equals(TEST_FAMILY, permission.getFamily()));
163     assertTrue("Permission should be for qualifier " + TEST_QUALIFIER,
164         Bytes.equals(TEST_QUALIFIER, permission.getQualifier()));
165 
166     // check actions
167     assertNotNull(permission.getActions());
168     assertEquals(1, permission.getActions().length);
169     actions = Arrays.asList(permission.getActions());
170     assertTrue(actions.contains(TablePermission.Action.READ));
171     assertFalse(actions.contains(TablePermission.Action.WRITE));
172 
173     // table 2 permissions
174     AccessControlLists.addUserPermission(conf,
175         new UserPermission(Bytes.toBytes("hubert"), TEST_TABLE2, null, (byte[])null,
176             TablePermission.Action.READ, TablePermission.Action.WRITE));
177 
178     // check full load
179     Map<byte[],ListMultimap<String,TablePermission>> allPerms =
180         AccessControlLists.loadAll(conf);
181     assertEquals("Full permission map should have entries for both test tables",
182         2, allPerms.size());
183 
184     userPerms = allPerms.get(TEST_TABLE).get("hubert");
185     assertNotNull(userPerms);
186     assertEquals(1, userPerms.size());
187     permission = userPerms.get(0);
188     assertTrue(Bytes.equals(TEST_TABLE, permission.getTable()));
189     assertEquals(1, permission.getActions().length);
190     assertEquals(TablePermission.Action.READ, permission.getActions()[0]);
191 
192     userPerms = allPerms.get(TEST_TABLE2).get("hubert");
193     assertNotNull(userPerms);
194     assertEquals(1, userPerms.size());
195     permission = userPerms.get(0);
196     assertTrue(Bytes.equals(TEST_TABLE2, permission.getTable()));
197     assertEquals(2, permission.getActions().length);
198     actions = Arrays.asList(permission.getActions());
199     assertTrue(actions.contains(TablePermission.Action.READ));
200     assertTrue(actions.contains(TablePermission.Action.WRITE));
201   }
202 
203   @Test
204   public void testPersistence() throws Exception {
205     Configuration conf = UTIL.getConfiguration();
206     AccessControlLists.addUserPermission(conf,
207         new UserPermission(Bytes.toBytes("albert"), TEST_TABLE, null,
208                            (byte[])null, TablePermission.Action.READ));
209     AccessControlLists.addUserPermission(conf,
210         new UserPermission(Bytes.toBytes("betty"), TEST_TABLE, null,
211                            (byte[])null, TablePermission.Action.READ,
212                            TablePermission.Action.WRITE));
213     AccessControlLists.addUserPermission(conf,
214         new UserPermission(Bytes.toBytes("clark"),
215                            TEST_TABLE, TEST_FAMILY,
216                            TablePermission.Action.READ));
217     AccessControlLists.addUserPermission(conf,
218         new UserPermission(Bytes.toBytes("dwight"),
219                            TEST_TABLE, TEST_FAMILY, TEST_QUALIFIER,
220                            TablePermission.Action.WRITE));
221 
222     // verify permissions survive changes in table metadata
223     ListMultimap<String,TablePermission> preperms =
224         AccessControlLists.getTablePermissions(conf, TEST_TABLE);
225 
226     HTable table = new HTable(conf, TEST_TABLE);
227     table.put(new Put(Bytes.toBytes("row1"))
228         .add(TEST_FAMILY, TEST_QUALIFIER, Bytes.toBytes("v1")));
229     table.put(new Put(Bytes.toBytes("row2"))
230         .add(TEST_FAMILY, TEST_QUALIFIER, Bytes.toBytes("v2")));
231     HBaseAdmin admin = UTIL.getHBaseAdmin();
232     admin.split(TEST_TABLE);
233 
234     // wait for split
235     Thread.sleep(10000);
236 
237     ListMultimap<String,TablePermission> postperms =
238         AccessControlLists.getTablePermissions(conf, TEST_TABLE);
239 
240     checkMultimapEqual(preperms, postperms);
241   }
242 
243   @Test
244   public void testSerialization() throws Exception {
245     Configuration conf = UTIL.getConfiguration();
246     ListMultimap<String,TablePermission> permissions = ArrayListMultimap.create();
247     permissions.put("george", new TablePermission(TEST_TABLE, null,
248         TablePermission.Action.READ));
249     permissions.put("george", new TablePermission(TEST_TABLE, TEST_FAMILY,
250         TablePermission.Action.WRITE));
251     permissions.put("george", new TablePermission(TEST_TABLE2, null,
252         TablePermission.Action.READ));
253     permissions.put("hubert", new TablePermission(TEST_TABLE2, null,
254         TablePermission.Action.READ, TablePermission.Action.WRITE));
255 
256     ByteArrayOutputStream bos = new ByteArrayOutputStream();
257     AccessControlLists.writePermissions(new DataOutputStream(bos),
258         permissions, conf);
259 
260     ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
261     ListMultimap<String,TablePermission> copy =
262         AccessControlLists.readPermissions(new DataInputStream(bis), conf);
263 
264     checkMultimapEqual(permissions, copy);
265   }
266 
267   public void checkMultimapEqual(ListMultimap<String,TablePermission> first,
268       ListMultimap<String,TablePermission> second) {
269     assertEquals(first.size(), second.size());
270     for (String key : first.keySet()) {
271       List<TablePermission> firstPerms = first.get(key);
272       List<TablePermission> secondPerms = second.get(key);
273       assertNotNull(secondPerms);
274       assertEquals(firstPerms.size(), secondPerms.size());
275       LOG.info("First permissions: "+firstPerms.toString());
276       LOG.info("Second permissions: "+secondPerms.toString());
277       for (TablePermission p : firstPerms) {
278         assertTrue("Permission "+p.toString()+" not found", secondPerms.contains(p));
279       }
280     }
281   }
282 
283   @Test
284   public void testEquals() throws Exception {
285     TablePermission p1 = new TablePermission(TEST_TABLE, null, TablePermission.Action.READ);
286     TablePermission p2 = new TablePermission(TEST_TABLE, null, TablePermission.Action.READ);
287     assertTrue(p1.equals(p2));
288     assertTrue(p2.equals(p1));
289 
290     p1 = new TablePermission(TEST_TABLE, null, TablePermission.Action.READ, TablePermission.Action.WRITE);
291     p2 = new TablePermission(TEST_TABLE, null, TablePermission.Action.WRITE, TablePermission.Action.READ);
292     assertTrue(p1.equals(p2));
293     assertTrue(p2.equals(p1));
294 
295     p1 = new TablePermission(TEST_TABLE, TEST_FAMILY, TablePermission.Action.READ, TablePermission.Action.WRITE);
296     p2 = new TablePermission(TEST_TABLE, TEST_FAMILY, TablePermission.Action.WRITE, TablePermission.Action.READ);
297     assertTrue(p1.equals(p2));
298     assertTrue(p2.equals(p1));
299 
300     p1 = new TablePermission(TEST_TABLE, TEST_FAMILY, TEST_QUALIFIER, TablePermission.Action.READ, TablePermission.Action.WRITE);
301     p2 = new TablePermission(TEST_TABLE, TEST_FAMILY, TEST_QUALIFIER, TablePermission.Action.WRITE, TablePermission.Action.READ);
302     assertTrue(p1.equals(p2));
303     assertTrue(p2.equals(p1));
304 
305     p1 = new TablePermission(TEST_TABLE, null, TablePermission.Action.READ);
306     p2 = new TablePermission(TEST_TABLE, TEST_FAMILY, TablePermission.Action.READ);
307     assertFalse(p1.equals(p2));
308     assertFalse(p2.equals(p1));
309 
310     p1 = new TablePermission(TEST_TABLE, null, TablePermission.Action.READ);
311     p2 = new TablePermission(TEST_TABLE, null, TablePermission.Action.WRITE);
312     assertFalse(p1.equals(p2));
313     assertFalse(p2.equals(p1));
314     p2 = new TablePermission(TEST_TABLE, null, TablePermission.Action.READ, TablePermission.Action.WRITE);
315     assertFalse(p1.equals(p2));
316     assertFalse(p2.equals(p1));
317 
318     p1 = new TablePermission(TEST_TABLE, null, TablePermission.Action.READ);
319     p2 = new TablePermission(TEST_TABLE2, null, TablePermission.Action.READ);
320     assertFalse(p1.equals(p2));
321     assertFalse(p2.equals(p1));
322 
323     p2 = new TablePermission(TEST_TABLE, null);
324     assertFalse(p1.equals(p2));
325     assertFalse(p2.equals(p1));
326   }
327 
328   @Test
329   public void testGlobalPermission() throws Exception {
330     Configuration conf = UTIL.getConfiguration();
331 
332     // add some permissions
333     AccessControlLists.addUserPermission(conf,
334         new UserPermission(Bytes.toBytes("user1"),
335             Permission.Action.READ, Permission.Action.WRITE));
336     AccessControlLists.addUserPermission(conf,
337         new UserPermission(Bytes.toBytes("user2"),
338             Permission.Action.CREATE));
339     AccessControlLists.addUserPermission(conf,
340         new UserPermission(Bytes.toBytes("user3"),
341             Permission.Action.ADMIN, Permission.Action.READ, Permission.Action.CREATE));
342 
343     ListMultimap<String,TablePermission> perms = AccessControlLists.getTablePermissions(conf, null);
344     List<TablePermission> user1Perms = perms.get("user1");
345     assertEquals("Should have 1 permission for user1", 1, user1Perms.size());
346     assertEquals("user1 should have WRITE permission",
347                  new Permission.Action[] { Permission.Action.READ, Permission.Action.WRITE },
348                  user1Perms.get(0).getActions());
349 
350     List<TablePermission> user2Perms = perms.get("user2");
351     assertEquals("Should have 1 permission for user2", 1, user2Perms.size());
352     assertEquals("user2 should have CREATE permission",
353                  new Permission.Action[] { Permission.Action.CREATE },
354                  user2Perms.get(0).getActions());
355 
356     List<TablePermission> user3Perms = perms.get("user3");
357     assertEquals("Should have 1 permission for user3", 1, user3Perms.size());
358     assertEquals("user3 should have ADMIN, READ, CREATE permission",
359                  new Permission.Action[] {
360                     Permission.Action.ADMIN, Permission.Action.READ, Permission.Action.CREATE
361                  },
362                  user3Perms.get(0).getActions());
363   }
364 
365   @Test
366   public void testAuthManager() throws Exception {
367     Configuration conf = UTIL.getConfiguration();
368     /* test a race condition causing TableAuthManager to sometimes fail global permissions checks
369      * when the global cache is being updated
370      */
371     TableAuthManager authManager = TableAuthManager.get(ZKW, conf);
372     // currently running user is the system user and should have global admin perms
373     User currentUser = User.getCurrent();
374     assertTrue(authManager.authorize(currentUser, Permission.Action.ADMIN));
375     for (int i=1; i<=50; i++) {
376       AccessControlLists.addUserPermission(conf, new UserPermission(Bytes.toBytes("testauth"+i),
377           Permission.Action.ADMIN, Permission.Action.READ, Permission.Action.WRITE));
378       // make sure the system user still shows as authorized
379       assertTrue("Failed current user auth check on iter "+i,
380           authManager.authorize(currentUser, Permission.Action.ADMIN));
381     }
382   }
383 }