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