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