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.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 
130     TEST_UTIL.waitTableEnabled(TEST_TABLE.getTableName().getName());
131   }
132 
133   @After
134   public void tearDown() throws Exception {
135     // Clean the _acl_ table
136     try {
137       TEST_UTIL.deleteTable(TEST_TABLE.getTableName());
138     } catch (TableNotFoundException ex) {
139       // Test deleted the table, no problem
140       LOG.info("Test deleted table " + TEST_TABLE.getTableName());
141     }
142     assertEquals(0, AccessControlLists.getTablePermissions(conf, TEST_TABLE.getTableName()).size());
143   }
144 
145   @Test
146   public void testEarlyScanTermination() throws Exception {
147     // Grant USER_OTHER access to TEST_FAMILY1 only
148     grantOnTable(TEST_UTIL, USER_OTHER.getShortName(), TEST_TABLE.getTableName(), TEST_FAMILY1,
149       null, Action.READ);
150 
151     // Set up test data
152     verifyAllowed(new AccessTestAction() {
153       @Override
154       public Object run() throws Exception {
155         // force a new RS connection
156         conf.set("testkey", UUID.randomUUID().toString());
157         HTable t = new HTable(conf, TEST_TABLE.getTableName());
158         try {
159           Put put = new Put(TEST_ROW).add(TEST_FAMILY1, TEST_Q1, ZERO);
160           t.put(put);
161           // Set a READ cell ACL for USER_OTHER on this value in FAMILY2
162           put = new Put(TEST_ROW).add(TEST_FAMILY2, TEST_Q1, ZERO);
163           put.setACL(USER_OTHER.getShortName(), new Permission(Action.READ));
164           t.put(put);
165           // Set an empty cell ACL for USER_OTHER on this other value in FAMILY2
166           put = new Put(TEST_ROW).add(TEST_FAMILY2, TEST_Q2, ZERO);
167           put.setACL(USER_OTHER.getShortName(), new Permission());
168           t.put(put);
169         } finally {
170           t.close();
171         }
172         return null;
173       }
174     }, USER_OWNER);
175 
176     // A scan of FAMILY1 will be allowed
177     verifyAllowed(new AccessTestAction() {
178       @Override
179       public Object run() throws Exception {
180         // force a new RS connection
181         conf.set("testkey", UUID.randomUUID().toString());
182         HTable t = new HTable(conf, TEST_TABLE.getTableName());
183         try {
184           Scan scan = new Scan().addFamily(TEST_FAMILY1);
185           Result result = t.getScanner(scan).next();
186           if (result != null) {
187             assertTrue("Improper exclusion", result.containsColumn(TEST_FAMILY1, TEST_Q1));
188             assertFalse("Improper inclusion", result.containsColumn(TEST_FAMILY2, TEST_Q1));
189             return result.listCells();
190           }
191           return null;
192         } finally {
193           t.close();
194         }
195       }
196     }, USER_OTHER);
197 
198     // A scan of FAMILY1 and FAMILY2 will produce results for FAMILY1 without
199     // throwing an exception, however no cells from FAMILY2 will be returned
200     // because we early out checks at the CF level.
201     verifyAllowed(new AccessTestAction() {
202       @Override
203       public Object run() throws Exception {
204         // force a new RS connection
205         conf.set("testkey", UUID.randomUUID().toString());
206         HTable t = new HTable(conf, TEST_TABLE.getTableName());
207         try {
208           Scan scan = new Scan();
209           Result result = t.getScanner(scan).next();
210           if (result != null) {
211             assertTrue("Improper exclusion", result.containsColumn(TEST_FAMILY1, TEST_Q1));
212             assertFalse("Improper inclusion", result.containsColumn(TEST_FAMILY2, TEST_Q1));
213             return result.listCells();
214           }
215           return null;
216         } finally {
217           t.close();
218         }
219       }
220     }, USER_OTHER);
221 
222     // A scan of FAMILY2 will throw an AccessDeniedException
223     verifyDeniedWithException(new AccessTestAction() {
224       @Override
225       public Object run() throws Exception {
226         // force a new RS connection
227         conf.set("testkey", UUID.randomUUID().toString());
228         HTable t = new HTable(conf, TEST_TABLE.getTableName());
229         try {
230           Scan scan = new Scan().addFamily(TEST_FAMILY2);
231           Result result = t.getScanner(scan).next();
232           if (result != null) {
233             return result.listCells();
234           }
235           return null;
236         } finally {
237           t.close();
238         }
239       }
240     }, USER_OTHER);
241 
242     // Now grant USER_OTHER access to TEST_FAMILY2:TEST_Q2
243     grantOnTable(TEST_UTIL, USER_OTHER.getShortName(), TEST_TABLE.getTableName(), TEST_FAMILY2,
244       TEST_Q2, Action.READ);
245 
246     // A scan of FAMILY1 and FAMILY2 will produce combined results. In FAMILY2
247     // we have access granted to Q2 at the CF level. Because we early out
248     // checks at the CF level the cell ACL on Q1 also granting access is ignored.
249     verifyAllowed(new AccessTestAction() {
250       @Override
251       public Object run() throws Exception {
252         // force a new RS connection
253         conf.set("testkey", UUID.randomUUID().toString());
254         HTable t = new HTable(conf, TEST_TABLE.getTableName());
255         try {
256           Scan scan = new Scan();
257           Result result = t.getScanner(scan).next();
258           if (result != null) {
259             assertTrue("Improper exclusion", result.containsColumn(TEST_FAMILY1, TEST_Q1));
260             assertFalse("Improper inclusion", result.containsColumn(TEST_FAMILY2, TEST_Q1));
261             assertTrue("Improper exclusion", result.containsColumn(TEST_FAMILY2, TEST_Q2));
262             return result.listCells();
263           }
264           return null;
265         } finally {
266           t.close();
267         }
268       }
269     }, USER_OTHER);
270   }
271 }