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.*;
21  
22  import java.util.UUID;
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.Coprocessor;
28  import org.apache.hadoop.hbase.HBaseTestingUtility;
29  import org.apache.hadoop.hbase.HColumnDescriptor;
30  import org.apache.hadoop.hbase.HTableDescriptor;
31  import org.apache.hadoop.hbase.testclassification.MediumTests;
32  import org.apache.hadoop.hbase.TableNotFoundException;
33  import org.apache.hadoop.hbase.client.HBaseAdmin;
34  import org.apache.hadoop.hbase.client.HTable;
35  import org.apache.hadoop.hbase.client.Put;
36  import org.apache.hadoop.hbase.client.Result;
37  import org.apache.hadoop.hbase.client.Scan;
38  import org.apache.hadoop.hbase.master.MasterCoprocessorHost;
39  import org.apache.hadoop.hbase.regionserver.RegionServerCoprocessorHost;
40  import org.apache.hadoop.hbase.security.User;
41  import org.apache.hadoop.hbase.security.access.Permission.Action;
42  import org.apache.hadoop.hbase.util.Bytes;
43  import org.apache.hadoop.hbase.util.TestTableName;
44  import org.apache.log4j.Level;
45  import org.apache.log4j.Logger;
46  import org.junit.After;
47  import org.junit.AfterClass;
48  import org.junit.Before;
49  import org.junit.BeforeClass;
50  import org.junit.Rule;
51  import org.junit.Test;
52  import org.junit.experimental.categories.Category;
53  
54  @Category(MediumTests.class)
55  public class TestScanEarlyTermination extends SecureTestUtil {
56    private static final Log LOG = LogFactory.getLog(TestScanEarlyTermination.class);
57  
58    static {
59      Logger.getLogger(AccessController.class).setLevel(Level.TRACE);
60      Logger.getLogger(AccessControlFilter.class).setLevel(Level.TRACE);
61      Logger.getLogger(TableAuthManager.class).setLevel(Level.TRACE);
62    }
63  
64    @Rule
65    public TestTableName TEST_TABLE = new TestTableName();
66    private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
67    private static final byte[] TEST_FAMILY1 = Bytes.toBytes("f1");
68    private static final byte[] TEST_FAMILY2 = Bytes.toBytes("f2");
69    private static final byte[] TEST_ROW = Bytes.toBytes("testrow");
70    private static final byte[] TEST_Q1 = Bytes.toBytes("q1");
71    private static final byte[] TEST_Q2 = Bytes.toBytes("q2");
72    private static final byte[] ZERO = Bytes.toBytes(0L);
73  
74    private static Configuration conf;
75  
76    private static User USER_OWNER;
77    private static User USER_OTHER;
78  
79    @BeforeClass
80    public static void setupBeforeClass() throws Exception {
81      // setup configuration
82      conf = TEST_UTIL.getConfiguration();
83      // Enable security
84      enableSecurity(conf);
85      // Verify enableSecurity sets up what we require
86      verifyConfiguration(conf);
87  
88      TEST_UTIL.startMiniCluster();
89      MasterCoprocessorHost cpHost = TEST_UTIL.getMiniHBaseCluster().getMaster()
90          .getCoprocessorHost();
91      cpHost.load(AccessController.class, Coprocessor.PRIORITY_HIGHEST, conf);
92      AccessController ac = (AccessController)
93        cpHost.findCoprocessor(AccessController.class.getName());
94      cpHost.createEnvironment(AccessController.class, ac, Coprocessor.PRIORITY_HIGHEST, 1, conf);
95      RegionServerCoprocessorHost rsHost = TEST_UTIL.getMiniHBaseCluster().getRegionServer(0)
96          .getCoprocessorHost();
97      rsHost.createEnvironment(AccessController.class, ac, Coprocessor.PRIORITY_HIGHEST, 1, conf);
98  
99      // Wait for the ACL table to become available
100     TEST_UTIL.waitTableEnabled(AccessControlLists.ACL_TABLE_NAME.getName());
101 
102     // create a set of test users
103     USER_OWNER = User.createUserForTesting(conf, "owner", new String[0]);
104     USER_OTHER = User.createUserForTesting(conf, "other", new String[0]);
105   }
106 
107   @AfterClass
108   public static void tearDownAfterClass() throws Exception {
109     TEST_UTIL.shutdownMiniCluster();
110   }
111 
112   @Before
113   public void setUp() throws Exception {
114     HBaseAdmin admin = TEST_UTIL.getHBaseAdmin();
115     HTableDescriptor htd = new HTableDescriptor(TEST_TABLE.getTableName());
116     htd.setOwner(USER_OWNER);
117     HColumnDescriptor hcd = new HColumnDescriptor(TEST_FAMILY1);
118     hcd.setMaxVersions(10);
119     htd.addFamily(hcd);
120     hcd = new HColumnDescriptor(TEST_FAMILY2);
121     hcd.setMaxVersions(10);
122     htd.addFamily(hcd);
123 
124     // Enable backwards compatible early termination behavior in the HTD. We
125     // want to confirm that the per-table configuration is properly picked up.
126     htd.setConfiguration(AccessControlConstants.CF_ATTRIBUTE_EARLY_OUT, "true");
127 
128     admin.createTable(htd);
129     TEST_UTIL.waitUntilAllRegionsAssigned(TEST_TABLE.getTableName());
130   }
131 
132   @After
133   public void tearDown() throws Exception {
134     // Clean the _acl_ table
135     try {
136       TEST_UTIL.deleteTable(TEST_TABLE.getTableName());
137     } catch (TableNotFoundException ex) {
138       // Test deleted the table, no problem
139       LOG.info("Test deleted table " + TEST_TABLE.getTableName());
140     }
141     assertEquals(0, AccessControlLists.getTablePermissions(conf, TEST_TABLE.getTableName()).size());
142   }
143 
144   @Test
145   public void testEarlyScanTermination() throws Exception {
146     // Grant USER_OTHER access to TEST_FAMILY1 only
147     grantOnTable(TEST_UTIL, USER_OTHER.getShortName(), TEST_TABLE.getTableName(), TEST_FAMILY1,
148       null, Action.READ);
149 
150     // Set up test data
151     verifyAllowed(new AccessTestAction() {
152       @Override
153       public Object run() throws Exception {
154         // force a new RS connection
155         conf.set("testkey", UUID.randomUUID().toString());
156         HTable t = new HTable(conf, TEST_TABLE.getTableName());
157         try {
158           Put put = new Put(TEST_ROW).add(TEST_FAMILY1, TEST_Q1, ZERO);
159           t.put(put);
160           // Set a READ cell ACL for USER_OTHER on this value in FAMILY2
161           put = new Put(TEST_ROW).add(TEST_FAMILY2, TEST_Q1, ZERO);
162           put.setACL(USER_OTHER.getShortName(), new Permission(Action.READ));
163           t.put(put);
164           // Set an empty cell ACL for USER_OTHER on this other value in FAMILY2
165           put = new Put(TEST_ROW).add(TEST_FAMILY2, TEST_Q2, ZERO);
166           put.setACL(USER_OTHER.getShortName(), new Permission());
167           t.put(put);
168         } finally {
169           t.close();
170         }
171         return null;
172       }
173     }, USER_OWNER);
174 
175     // A scan of FAMILY1 will be allowed
176     verifyAllowed(new AccessTestAction() {
177       @Override
178       public Object run() throws Exception {
179         // force a new RS connection
180         conf.set("testkey", UUID.randomUUID().toString());
181         HTable t = new HTable(conf, TEST_TABLE.getTableName());
182         try {
183           Scan scan = new Scan().addFamily(TEST_FAMILY1);
184           Result result = t.getScanner(scan).next();
185           if (result != null) {
186             assertTrue("Improper exclusion", result.containsColumn(TEST_FAMILY1, TEST_Q1));
187             assertFalse("Improper inclusion", result.containsColumn(TEST_FAMILY2, TEST_Q1));
188             return result.listCells();
189           }
190           return null;
191         } finally {
192           t.close();
193         }
194       }
195     }, USER_OTHER);
196 
197     // A scan of FAMILY1 and FAMILY2 will produce results for FAMILY1 without
198     // throwing an exception, however no cells from FAMILY2 will be returned
199     // because we early out checks at the CF level.
200     verifyAllowed(new AccessTestAction() {
201       @Override
202       public Object run() throws Exception {
203         // force a new RS connection
204         conf.set("testkey", UUID.randomUUID().toString());
205         HTable t = new HTable(conf, TEST_TABLE.getTableName());
206         try {
207           Scan scan = new Scan();
208           Result result = t.getScanner(scan).next();
209           if (result != null) {
210             assertTrue("Improper exclusion", result.containsColumn(TEST_FAMILY1, TEST_Q1));
211             assertFalse("Improper inclusion", result.containsColumn(TEST_FAMILY2, TEST_Q1));
212             return result.listCells();
213           }
214           return null;
215         } finally {
216           t.close();
217         }
218       }
219     }, USER_OTHER);
220 
221     // A scan of FAMILY2 will throw an AccessDeniedException
222     verifyDenied(new AccessTestAction() {
223       @Override
224       public Object run() throws Exception {
225         // force a new RS connection
226         conf.set("testkey", UUID.randomUUID().toString());
227         HTable t = new HTable(conf, TEST_TABLE.getTableName());
228         try {
229           Scan scan = new Scan().addFamily(TEST_FAMILY2);
230           Result result = t.getScanner(scan).next();
231           if (result != null) {
232             return result.listCells();
233           }
234           return null;
235         } finally {
236           t.close();
237         }
238       }
239     }, USER_OTHER);
240 
241     // Now grant USER_OTHER access to TEST_FAMILY2:TEST_Q2
242     grantOnTable(TEST_UTIL, USER_OTHER.getShortName(), TEST_TABLE.getTableName(), TEST_FAMILY2,
243       TEST_Q2, Action.READ);
244 
245     // A scan of FAMILY1 and FAMILY2 will produce combined results. In FAMILY2
246     // we have access granted to Q2 at the CF level. Because we early out
247     // checks at the CF level the cell ACL on Q1 also granting access is ignored.
248     verifyAllowed(new AccessTestAction() {
249       @Override
250       public Object run() throws Exception {
251         // force a new RS connection
252         conf.set("testkey", UUID.randomUUID().toString());
253         HTable t = new HTable(conf, TEST_TABLE.getTableName());
254         try {
255           Scan scan = new Scan();
256           Result result = t.getScanner(scan).next();
257           if (result != null) {
258             assertTrue("Improper exclusion", result.containsColumn(TEST_FAMILY1, TEST_Q1));
259             assertFalse("Improper inclusion", result.containsColumn(TEST_FAMILY2, TEST_Q1));
260             assertTrue("Improper exclusion", result.containsColumn(TEST_FAMILY2, TEST_Q2));
261             return result.listCells();
262           }
263           return null;
264         } finally {
265           t.close();
266         }
267       }
268     }, USER_OTHER);
269   }
270 }