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  package org.apache.hadoop.hbase.security.access;
19  
20  import static org.junit.Assert.assertEquals;
21  
22  import java.util.HashMap;
23  import java.util.List;
24  import java.util.Map;
25  
26  import org.apache.commons.logging.Log;
27  import org.apache.commons.logging.LogFactory;
28  import org.apache.hadoop.conf.Configuration;
29  import org.apache.hadoop.hbase.AuthUtil;
30  import org.apache.hadoop.hbase.Cell;
31  import org.apache.hadoop.hbase.Coprocessor;
32  import org.apache.hadoop.hbase.HBaseTestingUtility;
33  import org.apache.hadoop.hbase.HColumnDescriptor;
34  import org.apache.hadoop.hbase.HTableDescriptor;
35  import org.apache.hadoop.hbase.testclassification.MediumTests;
36  import org.apache.hadoop.hbase.TableNotFoundException;
37  import org.apache.hadoop.hbase.client.Delete;
38  import org.apache.hadoop.hbase.client.Get;
39  import org.apache.hadoop.hbase.client.HBaseAdmin;
40  import org.apache.hadoop.hbase.client.HTable;
41  import org.apache.hadoop.hbase.client.Increment;
42  import org.apache.hadoop.hbase.client.Put;
43  import org.apache.hadoop.hbase.client.Result;
44  import org.apache.hadoop.hbase.client.ResultScanner;
45  import org.apache.hadoop.hbase.client.Scan;
46  import org.apache.hadoop.hbase.master.MasterCoprocessorHost;
47  import org.apache.hadoop.hbase.regionserver.RegionServerCoprocessorHost;
48  import org.apache.hadoop.hbase.security.User;
49  import org.apache.hadoop.hbase.security.access.Permission.Action;
50  import org.apache.hadoop.hbase.util.Bytes;
51  import org.apache.hadoop.hbase.util.TestTableName;
52  import org.apache.log4j.Level;
53  import org.apache.log4j.Logger;
54  import org.junit.After;
55  import org.junit.AfterClass;
56  import org.junit.Before;
57  import org.junit.BeforeClass;
58  import org.junit.Rule;
59  import org.junit.Test;
60  import org.junit.experimental.categories.Category;
61  
62  import com.google.common.collect.Lists;
63  
64  @Category(MediumTests.class)
65  public class TestCellACLs extends SecureTestUtil {
66    private static final Log LOG = LogFactory.getLog(TestCellACLs.class);
67  
68    static {
69      Logger.getLogger(AccessController.class).setLevel(Level.TRACE);
70      Logger.getLogger(AccessControlFilter.class).setLevel(Level.TRACE);
71      Logger.getLogger(TableAuthManager.class).setLevel(Level.TRACE);
72    }
73  
74    @Rule
75    public TestTableName TEST_TABLE = new TestTableName();
76    private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
77    private static final byte[] TEST_FAMILY = Bytes.toBytes("f1");
78    private static final byte[] TEST_ROW = Bytes.toBytes("cellpermtest");
79    private static final byte[] TEST_Q1 = Bytes.toBytes("q1");
80    private static final byte[] TEST_Q2 = Bytes.toBytes("q2");
81    private static final byte[] TEST_Q3 = Bytes.toBytes("q3");
82    private static final byte[] TEST_Q4 = Bytes.toBytes("q4");
83    private static final byte[] ZERO = Bytes.toBytes(0L);
84    private static final byte[] ONE = Bytes.toBytes(1L);
85  
86    private static Configuration conf;
87  
88    private static final String GROUP = "group";
89    private static User GROUP_USER;
90    private static User USER_OWNER;
91    private static User USER_OTHER;
92    private static String[] usersAndGroups;
93  
94    @BeforeClass
95    public static void setupBeforeClass() throws Exception {
96      // setup configuration
97      conf = TEST_UTIL.getConfiguration();
98      // Enable security
99      enableSecurity(conf);
100     // Verify enableSecurity sets up what we require
101     verifyConfiguration(conf);
102 
103     // We expect 0.98 cell ACL semantics
104     conf.setBoolean(AccessControlConstants.CF_ATTRIBUTE_EARLY_OUT, false);
105 
106     TEST_UTIL.startMiniCluster();
107     MasterCoprocessorHost cpHost = TEST_UTIL.getMiniHBaseCluster().getMaster()
108         .getCoprocessorHost();
109     cpHost.load(AccessController.class, Coprocessor.PRIORITY_HIGHEST, conf);
110     AccessController ac = (AccessController)
111       cpHost.findCoprocessor(AccessController.class.getName());
112     cpHost.createEnvironment(AccessController.class, ac, Coprocessor.PRIORITY_HIGHEST, 1, conf);
113     RegionServerCoprocessorHost rsHost = TEST_UTIL.getMiniHBaseCluster().getRegionServer(0)
114         .getCoprocessorHost();
115     rsHost.createEnvironment(AccessController.class, ac, Coprocessor.PRIORITY_HIGHEST, 1, conf);
116 
117     // Wait for the ACL table to become available
118     TEST_UTIL.waitTableEnabled(AccessControlLists.ACL_TABLE_NAME.getName());
119 
120     // create a set of test users
121     USER_OWNER = User.createUserForTesting(conf, "owner", new String[0]);
122     USER_OTHER = User.createUserForTesting(conf, "other", new String[0]);
123     GROUP_USER = User.createUserForTesting(conf, "group_user", new String[] { GROUP });
124 
125     usersAndGroups = new String[] { USER_OTHER.getShortName(), AuthUtil.toGroupEntry(GROUP) };
126   }
127 
128   @AfterClass
129   public static void tearDownAfterClass() throws Exception {
130     TEST_UTIL.shutdownMiniCluster();
131   }
132 
133   @Before
134   public void setUp() throws Exception {
135     // Create the test table (owner added to the _acl_ table)
136     HBaseAdmin admin = TEST_UTIL.getHBaseAdmin();
137     HTableDescriptor htd = new HTableDescriptor(TEST_TABLE.getTableName());
138     HColumnDescriptor hcd = new HColumnDescriptor(TEST_FAMILY);
139     hcd.setMaxVersions(4);
140     htd.setOwner(USER_OWNER);
141     htd.addFamily(hcd);
142     admin.createTable(htd, new byte[][] { Bytes.toBytes("s") });
143     TEST_UTIL.waitTableEnabled(TEST_TABLE.getTableName().getName());
144   }
145 
146   @Test
147   public void testCellPermissions() throws Exception {
148     // store two sets of values, one store with a cell level ACL, and one without
149     verifyAllowed(new AccessTestAction() {
150       @Override
151       public Object run() throws Exception {
152         HTable t = new HTable(conf, TEST_TABLE.getTableName());
153         try {
154           Put p;
155           // with ro ACL
156           p = new Put(TEST_ROW).add(TEST_FAMILY, TEST_Q1, ZERO);
157           p.setACL(prepareCellPermissions(usersAndGroups, Action.READ));
158           t.put(p);
159           // with rw ACL
160           p = new Put(TEST_ROW).add(TEST_FAMILY, TEST_Q2, ZERO);
161           p.setACL(prepareCellPermissions(usersAndGroups, Action.READ, Action.WRITE));
162           t.put(p);
163           // no ACL
164           p = new Put(TEST_ROW)
165             .add(TEST_FAMILY, TEST_Q3, ZERO)
166             .add(TEST_FAMILY, TEST_Q4, ZERO);
167           t.put(p);
168         } finally {
169           t.close();
170         }
171         return null;
172       }
173     }, USER_OWNER);
174 
175     /* ---- Gets ---- */
176 
177     AccessTestAction getQ1 = new AccessTestAction() {
178       @Override
179       public Object run() throws Exception {
180         Get get = new Get(TEST_ROW).addColumn(TEST_FAMILY, TEST_Q1);
181         HTable t = new HTable(conf, TEST_TABLE.getTableName());
182         try {
183           return t.get(get).listCells();
184         } finally {
185           t.close();
186         }
187       }
188     };
189 
190     AccessTestAction getQ2 = new AccessTestAction() {
191       @Override
192       public Object run() throws Exception {
193         Get get = new Get(TEST_ROW).addColumn(TEST_FAMILY, TEST_Q2);
194         HTable t = new HTable(conf, TEST_TABLE.getTableName());
195         try {
196           return t.get(get).listCells();
197         } finally {
198           t.close();
199         }
200       }
201     };
202 
203     AccessTestAction getQ3 = new AccessTestAction() {
204       @Override
205       public Object run() throws Exception {
206         Get get = new Get(TEST_ROW).addColumn(TEST_FAMILY, TEST_Q3);
207         HTable t = new HTable(conf, TEST_TABLE.getTableName());
208         try {
209           return t.get(get).listCells();
210         } finally {
211           t.close();
212         }
213       }
214     };
215 
216     AccessTestAction getQ4 = new AccessTestAction() {
217       @Override
218       public Object run() throws Exception {
219         Get get = new Get(TEST_ROW).addColumn(TEST_FAMILY, TEST_Q4);
220         HTable t = new HTable(conf, TEST_TABLE.getTableName());
221         try {
222           return t.get(get).listCells();
223         } finally {
224           t.close();
225         }
226       }
227     };
228 
229     // Confirm special read access set at cell level
230 
231     verifyAllowed(getQ1, USER_OTHER, GROUP_USER);
232     verifyAllowed(getQ2, USER_OTHER, GROUP_USER);
233 
234     // Confirm this access does not extend to other cells
235 
236     verifyIfNull(getQ3, USER_OTHER, GROUP_USER);
237     verifyIfNull(getQ4, USER_OTHER, GROUP_USER);
238 
239     /* ---- Scans ---- */
240 
241     // check that a scan over the test data returns the expected number of KVs
242 
243     final List<Cell> scanResults = Lists.newArrayList();
244 
245     AccessTestAction scanAction = new AccessTestAction() {
246       @Override
247       public List<Cell> run() throws Exception {
248         Scan scan = new Scan();
249         scan.setStartRow(TEST_ROW);
250         scan.setStopRow(Bytes.add(TEST_ROW, new byte[]{ 0 } ));
251         scan.addFamily(TEST_FAMILY);
252         HTable t = new HTable(conf, TEST_TABLE.getTableName());
253         try {
254           ResultScanner scanner = t.getScanner(scan);
255           Result result = null;
256           do {
257             result = scanner.next();
258             if (result != null) {
259               scanResults.addAll(result.listCells());
260             }
261           } while (result != null);
262         } finally {
263           t.close();
264         }
265         return scanResults;
266       }
267     };
268 
269     // owner will see all values
270     scanResults.clear();
271     verifyAllowed(scanAction, USER_OWNER);
272     assertEquals(4, scanResults.size());
273 
274     // other user will see 2 values
275     scanResults.clear();
276     verifyAllowed(scanAction, USER_OTHER);
277     assertEquals(2, scanResults.size());
278 
279     scanResults.clear();
280     verifyAllowed(scanAction, GROUP_USER);
281     assertEquals(2, scanResults.size());
282 
283     /* ---- Increments ---- */
284 
285     AccessTestAction incrementQ1 = new AccessTestAction() {
286       @Override
287       public Object run() throws Exception {
288         Increment i = new Increment(TEST_ROW).addColumn(TEST_FAMILY, TEST_Q1, 1L);
289         HTable t = new HTable(conf, TEST_TABLE.getTableName());
290         try {
291           t.increment(i);
292         } finally {
293           t.close();
294         }
295         return null;
296       }
297     };
298 
299     AccessTestAction incrementQ2 = new AccessTestAction() {
300       @Override
301       public Object run() throws Exception {
302         Increment i = new Increment(TEST_ROW).addColumn(TEST_FAMILY, TEST_Q2, 1L);
303         HTable t = new HTable(conf, TEST_TABLE.getTableName());
304         try {
305           t.increment(i);
306         } finally {
307           t.close();
308         }
309         return null;
310       }
311     };
312 
313     AccessTestAction incrementQ2newDenyACL = new AccessTestAction() {
314       @Override
315       public Object run() throws Exception {
316         Increment i = new Increment(TEST_ROW).addColumn(TEST_FAMILY, TEST_Q2, 1L);
317         // Tag this increment with an ACL that denies write permissions to USER_OTHER
318         i.setACL(USER_OTHER.getShortName(), new Permission(Action.READ));
319         HTable t = new HTable(conf, TEST_TABLE.getTableName());
320         try {
321           t.increment(i);
322         } finally {
323           t.close();
324         }
325         return null;
326       }
327     };
328 
329     AccessTestAction incrementQ3 = new AccessTestAction() {
330       @Override
331       public Object run() throws Exception {
332         Increment i = new Increment(TEST_ROW).addColumn(TEST_FAMILY, TEST_Q3, 1L);
333         HTable t = new HTable(conf, TEST_TABLE.getTableName());
334         try {
335           t.increment(i);
336         } finally {
337           t.close();
338         }
339         return null;
340       }
341     };
342 
343     verifyDenied(incrementQ1, USER_OTHER, GROUP_USER);
344     verifyDenied(incrementQ3, USER_OTHER, GROUP_USER);
345 
346     // We should be able to increment until the permissions are revoked (including the action in
347     // which permissions are revoked, the previous ACL will be carried forward)
348     verifyAllowed(incrementQ2, USER_OTHER, GROUP_USER);
349     verifyAllowed(incrementQ2newDenyACL, USER_OTHER);
350     // But not again after we denied ourselves write permission with an ACL
351     // update
352     verifyDenied(incrementQ2, USER_OTHER, GROUP_USER);
353 
354     /* ---- Deletes ---- */
355 
356     AccessTestAction deleteFamily = new AccessTestAction() {
357       @Override
358       public Object run() throws Exception {
359         Delete delete = new Delete(TEST_ROW).deleteFamily(TEST_FAMILY);
360         HTable t = new HTable(conf, TEST_TABLE.getTableName());
361         try {
362           t.delete(delete);
363         } finally {
364           t.close();
365         }
366         return null;
367       }
368     };
369 
370     AccessTestAction deleteQ1 = new AccessTestAction() {
371       @Override
372       public Object run() throws Exception {
373         Delete delete = new Delete(TEST_ROW).deleteColumn(TEST_FAMILY, TEST_Q1);
374         HTable t = new HTable(conf, TEST_TABLE.getTableName());
375         try {
376           t.delete(delete);
377         } finally {
378           t.close();
379         }
380         return null;
381       }
382     };
383 
384     verifyDenied(deleteFamily, USER_OTHER, GROUP_USER);
385     verifyDenied(deleteQ1, USER_OTHER, GROUP_USER);
386     verifyAllowed(deleteQ1, USER_OWNER);
387   }
388 
389   /**
390    * Insure we are not granting access in the absence of any cells found
391    * when scanning for covered cells.
392    */
393   @Test
394   public void testCoveringCheck() throws Exception {
395     // Grant read access to USER_OTHER
396     grantOnTable(TEST_UTIL, USER_OTHER.getShortName(), TEST_TABLE.getTableName(), TEST_FAMILY,
397       null, Action.READ);
398     // Grant read access to GROUP
399     grantOnTable(TEST_UTIL, AuthUtil.toGroupEntry(GROUP), TEST_TABLE.getTableName(), TEST_FAMILY,
400       null, Action.READ);
401 
402     // A write by USER_OTHER should be denied.
403     // This is where we could have a big problem if there is an error in the
404     // covering check logic.
405     verifyUserDeniedForWrite(USER_OTHER, ZERO);
406     // A write by GROUP_USER from group GROUP should be denied.
407     verifyUserDeniedForWrite(GROUP_USER, ZERO);
408 
409     // Add the cell
410     verifyAllowed(new AccessTestAction() {
411       @Override
412       public Object run() throws Exception {
413         HTable t = new HTable(conf, TEST_TABLE.getTableName());
414         try {
415           Put p;
416           p = new Put(TEST_ROW).add(TEST_FAMILY, TEST_Q1, ZERO);
417           t.put(p);
418         } finally {
419           t.close();
420         }
421         return null;
422       }
423     }, USER_OWNER);
424 
425     // A write by USER_OTHER should still be denied, just to make sure
426     verifyUserDeniedForWrite(USER_OTHER, ONE);
427     // A write by GROUP_USER from group GROUP should still be denied
428     verifyUserDeniedForWrite(GROUP_USER, ONE);
429 
430     // A read by USER_OTHER should be allowed, just to make sure
431     verifyUserAllowedForRead(USER_OTHER);
432     // A read by GROUP_USER from group GROUP should be allowed
433     verifyUserAllowedForRead(GROUP_USER);
434   }
435 
436   private void verifyUserDeniedForWrite(final User user, final byte[] value) throws Exception {
437     verifyDenied(new AccessTestAction() {
438       @Override
439       public Object run() throws Exception {
440         HTable t = new HTable(conf, TEST_TABLE.getTableName());
441         try {
442           Put p;
443           p = new Put(TEST_ROW).add(TEST_FAMILY, TEST_Q1, value);
444           t.put(p);
445         } finally {
446           t.close();
447         }
448         return null;
449       }
450     }, user);
451   }
452 
453   private void verifyUserAllowedForRead(final User user) throws Exception {
454     verifyAllowed(new AccessTestAction() {
455       @Override
456       public Object run() throws Exception {
457         HTable t = new HTable(conf, TEST_TABLE.getTableName());
458         try {
459           return t.get(new Get(TEST_ROW).addColumn(TEST_FAMILY, TEST_Q1));
460         } finally {
461           t.close();
462         }
463       }
464     }, user);
465   }
466 
467   private Map<String, Permission> prepareCellPermissions(String[] users, Action... action) {
468     Map<String, Permission> perms = new HashMap<String, Permission>(2);
469     for (String user : users) {
470       perms.put(user, new Permission(action));
471     }
472     return perms;
473   }
474 
475   @After
476   public void tearDown() throws Exception {
477     // Clean the _acl_ table
478     try {
479       TEST_UTIL.deleteTable(TEST_TABLE.getTableName());
480     } catch (TableNotFoundException ex) {
481       // Test deleted the table, no problem
482       LOG.info("Test deleted table " + TEST_TABLE.getTableName());
483     }
484     assertEquals(0, AccessControlLists.getTablePermissions(conf, TEST_TABLE.getTableName()).size());
485   }
486 }