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