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  import static org.junit.Assert.fail;
22  
23  import org.apache.commons.logging.Log;
24  import org.apache.commons.logging.LogFactory;
25  import org.apache.hadoop.conf.Configuration;
26  import org.apache.hadoop.fs.Path;
27  import org.apache.hadoop.hbase.client.Admin;
28  import org.apache.hadoop.hbase.client.Connection;
29  import org.apache.hadoop.hbase.client.ConnectionFactory;
30  import org.apache.hadoop.hbase.client.Table;
31  import org.apache.hadoop.hbase.Coprocessor;
32  import org.apache.hadoop.hbase.coprocessor.BaseRegionObserver;
33  import org.apache.hadoop.hbase.coprocessor.CoprocessorHost;
34  import org.apache.hadoop.hbase.HBaseTestingUtility;
35  import org.apache.hadoop.hbase.HColumnDescriptor;
36  import org.apache.hadoop.hbase.HConstants;
37  import org.apache.hadoop.hbase.HTableDescriptor;
38  import org.apache.hadoop.hbase.TableName;
39  import org.apache.hadoop.hbase.TableNotEnabledException;
40  import org.apache.hadoop.hbase.TableNotFoundException;
41  import org.apache.hadoop.hbase.coprocessor.RegionObserver;
42  import org.apache.hadoop.hbase.testclassification.MediumTests;
43  import org.apache.hadoop.hbase.util.Bytes;
44  
45  import org.junit.Test;
46  import org.junit.experimental.categories.Category;
47  import org.apache.hadoop.hbase.CategoryBasedTimeout;
48  import org.junit.rules.TestRule;
49  import org.junit.After;
50  import org.junit.ClassRule;
51  
52  import java.io.IOException;
53  
54  /**
55   * Performs coprocessor loads for variuos paths and malformed strings
56   */
57  @Category({MediumTests.class})
58  public class TestCoprocessorWhitelistMasterObserver extends SecureTestUtil {
59    private static final Log LOG = LogFactory.getLog(TestCoprocessorWhitelistMasterObserver.class);
60    private static final HBaseTestingUtility UTIL = new HBaseTestingUtility();
61    private static Configuration conf;
62    private static final TableName TEST_TABLE = TableName.valueOf("testTable");
63    private static final byte[] TEST_FAMILY = Bytes.toBytes("fam1");
64  
65    @After
66    public void tearDownTestCoprocessorWhitelistMasterObserver() throws Exception {
67      Admin admin = UTIL.getHBaseAdmin();
68      try {
69        try {
70          admin.disableTable(TEST_TABLE);
71        } catch (TableNotEnabledException ex) {
72          // Table was left disabled by test
73          LOG.info("Table was left disabled by test");
74        }
75        admin.deleteTable(TEST_TABLE);
76      } catch (TableNotFoundException ex) {
77        // Table was not created for some reason?
78        LOG.info("Table was not created for some reason");
79      }
80      UTIL.shutdownMiniCluster();
81    }
82  
83    @ClassRule
84    public static TestRule timeout =
85            CategoryBasedTimeout.builder().withTimeout(TestCoprocessorWhitelistMasterObserver.class).build();
86    /**
87     * Test a table modification adding a coprocessor path
88     * which is not whitelisted
89     * @result An IOException should be thrown and caught
90     *         to show coprocessor is working as desired
91     * @param whitelistedPaths A String array of paths to add in
92     *         for the whitelisting configuration
93     * @param coprocessorPath A String to use as the
94     *         path for a mock coprocessor
95     */
96    private static void positiveTestCase(String[] whitelistedPaths,
97        String coprocessorPath) throws Exception {
98      Configuration conf = UTIL.getConfiguration();
99      // load coprocessor under test
100     conf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY,
101         CoprocessorWhitelistMasterObserver.class.getName());
102     conf.setStrings(
103         CoprocessorWhitelistMasterObserver.CP_COPROCESSOR_WHITELIST_PATHS_KEY,
104         whitelistedPaths);
105     // set retries low to raise exception quickly
106     conf.setInt("hbase.client.retries.number", 1);
107     UTIL.startMiniCluster();
108     Table table = UTIL.createTable(TEST_TABLE,
109         new byte[][] { TEST_FAMILY });
110     UTIL.waitUntilAllRegionsAssigned(TEST_TABLE);
111     Connection connection = ConnectionFactory.createConnection(conf);
112     Table t = connection.getTable(TEST_TABLE);
113     HTableDescriptor htd = new HTableDescriptor(t.getTableDescriptor());
114     htd.addCoprocessor("net.clayb.hbase.coprocessor.NotWhitelisted",
115       new Path(coprocessorPath),
116       Coprocessor.PRIORITY_USER, null);
117     LOG.info("Modifying Table");
118     try {
119       connection.getAdmin().modifyTable(TEST_TABLE, htd);
120       fail("Expected coprocessor to raise IOException");
121     } catch (IOException e) {
122       // swallow exception from coprocessor
123     }
124     LOG.info("Done Modifying Table");
125     assertEquals(0, t.getTableDescriptor().getCoprocessors().size());
126   }
127 
128   /**
129    * Test a table modification adding a coprocessor path
130    * which is whitelisted
131    * @result The coprocessor should be added to the table
132    *         descriptor successfully
133    * @param whitelistedPaths A String array of paths to add in
134    *         for the whitelisting configuration
135    * @param coprocessorPath A String to use as the
136    *         path for a mock coprocessor
137    */
138   private static void negativeTestCase(String[] whitelistedPaths,
139       String coprocessorPath) throws Exception {
140     Configuration conf = UTIL.getConfiguration();
141     // load coprocessor under test
142     conf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY,
143         CoprocessorWhitelistMasterObserver.class.getName());
144     // set retries low to raise exception quickly
145     conf.setInt("hbase.client.retries.number", 1);
146     // set a coprocessor whitelist path for test
147     conf.setStrings(
148         CoprocessorWhitelistMasterObserver.CP_COPROCESSOR_WHITELIST_PATHS_KEY,
149         whitelistedPaths);
150     UTIL.startMiniCluster();
151     Table table = UTIL.createTable(TEST_TABLE,
152         new byte[][] { TEST_FAMILY });
153     UTIL.waitUntilAllRegionsAssigned(TEST_TABLE);
154     Connection connection = ConnectionFactory.createConnection(conf);
155     Admin admin = connection.getAdmin();
156     // disable table so we do not actually try loading non-existant
157     // coprocessor file
158     admin.disableTable(TEST_TABLE);
159     Table t = connection.getTable(TEST_TABLE);
160     HTableDescriptor htd = new HTableDescriptor(t.getTableDescriptor());
161     htd.addCoprocessor("net.clayb.hbase.coprocessor.Whitelisted",
162       new Path(coprocessorPath),
163       Coprocessor.PRIORITY_USER, null);
164     LOG.info("Modifying Table");
165     admin.modifyTable(TEST_TABLE, htd);
166     assertEquals(1, t.getTableDescriptor().getCoprocessors().size());
167     LOG.info("Done Modifying Table");
168   }
169 
170   /**
171    * Test a table modification adding a coprocessor path
172    * which is not whitelisted
173    * @result An IOException should be thrown and caught
174    *         to show coprocessor is working as desired
175    */
176   @Test
177   @Category(MediumTests.class)
178   public void testSubstringNonWhitelisted() throws Exception {
179     positiveTestCase(new String[]{"/permitted/*"},
180         "file:///notpermitted/couldnotpossiblyexist.jar");
181   }
182 
183   /**
184    * Test a table creation including a coprocessor path
185    * which is not whitelisted
186    * @result Coprocessor should be added to table descriptor
187    *         Table is disabled to avoid an IOException due to
188    *         the added coprocessor not actually existing on disk
189    */
190   @Test
191   @Category(MediumTests.class)
192   public void testDifferentFileSystemNonWhitelisted() throws Exception {
193     positiveTestCase(new String[]{"hdfs://foo/bar"},
194         "file:///notpermitted/couldnotpossiblyexist.jar");
195   }
196 
197   /**
198    * Test a table modification adding a coprocessor path
199    * which is whitelisted
200    * @result Coprocessor should be added to table descriptor
201    *         Table is disabled to avoid an IOException due to
202    *         the added coprocessor not actually existing on disk
203    */
204   @Test
205   @Category(MediumTests.class)
206   public void testSchemeAndDirectorywhitelisted() throws Exception {
207     negativeTestCase(new String[]{"/tmp","file:///permitted/*"},
208         "file:///permitted/couldnotpossiblyexist.jar");
209   }
210 
211   /**
212    * Test a table modification adding a coprocessor path
213    * which is whitelisted
214    * @result Coprocessor should be added to table descriptor
215    *         Table is disabled to avoid an IOException due to
216    *         the added coprocessor not actually existing on disk
217    */
218   @Test
219   @Category(MediumTests.class)
220   public void testSchemeWhitelisted() throws Exception {
221     negativeTestCase(new String[]{"file:///"},
222         "file:///permitted/couldnotpossiblyexist.jar");
223   }
224 
225   /**
226    * Test a table modification adding a coprocessor path
227    * which is whitelisted
228    * @result Coprocessor should be added to table descriptor
229    *         Table is disabled to avoid an IOException due to
230    *         the added coprocessor not actually existing on disk
231    */
232   @Test
233   @Category(MediumTests.class)
234   public void testDFSNameWhitelistedWorks() throws Exception {
235     negativeTestCase(new String[]{"hdfs://Your-FileSystem"},
236         "hdfs://Your-FileSystem/permitted/couldnotpossiblyexist.jar");
237   }
238 
239   /**
240    * Test a table modification adding a coprocessor path
241    * which is whitelisted
242    * @result Coprocessor should be added to table descriptor
243    *         Table is disabled to avoid an IOException due to
244    *         the added coprocessor not actually existing on disk
245    */
246   @Test
247   @Category(MediumTests.class)
248   public void testDFSNameNotWhitelistedFails() throws Exception {
249     positiveTestCase(new String[]{"hdfs://Your-FileSystem"},
250         "hdfs://My-FileSystem/permitted/couldnotpossiblyexist.jar");
251   }
252 
253   /**
254    * Test a table modification adding a coprocessor path
255    * which is whitelisted
256    * @result Coprocessor should be added to table descriptor
257    *         Table is disabled to avoid an IOException due to
258    *         the added coprocessor not actually existing on disk
259    */
260   @Test
261   @Category(MediumTests.class)
262   public void testBlanketWhitelist() throws Exception {
263     negativeTestCase(new String[]{"*"},
264         "hdfs:///permitted/couldnotpossiblyexist.jar");
265   }
266 
267   /**
268    * Test a table creation including a coprocessor path
269    * which is not whitelisted
270    * @result Table will not be created due to the offending coprocessor
271    */
272   @Test
273   @Category(MediumTests.class)
274   public void testCreationNonWhitelistedCoprocessorPath() throws Exception {
275     Configuration conf = UTIL.getConfiguration();
276     // load coprocessor under test
277     conf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY,
278         CoprocessorWhitelistMasterObserver.class.getName());
279     conf.setStrings(CoprocessorWhitelistMasterObserver.CP_COPROCESSOR_WHITELIST_PATHS_KEY,
280         new String[]{});
281     // set retries low to raise exception quickly
282     conf.setInt("hbase.client.retries.number", 1);
283     UTIL.startMiniCluster();
284     HTableDescriptor htd = new HTableDescriptor(TEST_TABLE);
285     HColumnDescriptor hcd = new HColumnDescriptor(TEST_FAMILY);
286     htd.addFamily(hcd);
287     htd.addCoprocessor("net.clayb.hbase.coprocessor.NotWhitelisted",
288       new Path("file:///notpermitted/couldnotpossiblyexist.jar"),
289       Coprocessor.PRIORITY_USER, null);
290     Connection connection = ConnectionFactory.createConnection(conf);
291     Admin admin = connection.getAdmin();
292     LOG.info("Creating Table");
293     try {
294       admin.createTable(htd);
295       fail("Expected coprocessor to raise IOException");
296     } catch (IOException e) {
297       // swallow exception from coprocessor
298     }
299     LOG.info("Done Creating Table");
300     // ensure table was not created
301     assertEquals(new HTableDescriptor[0],
302       admin.listTables("^" + TEST_TABLE.getNameAsString() + "$"));
303   }
304 
305 
306   public static class TestRegionObserver extends BaseRegionObserver {}
307 
308   /**
309    * Test a table creation including a coprocessor path
310    * which is on the classpath
311    * @result Table will be created with the coprocessor
312    */
313   @Test
314   @Category(MediumTests.class)
315   public void testCreationClasspathCoprocessor() throws Exception {
316     Configuration conf = UTIL.getConfiguration();
317     // load coprocessor under test
318     conf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY,
319         CoprocessorWhitelistMasterObserver.class.getName());
320     conf.setStrings(CoprocessorWhitelistMasterObserver.CP_COPROCESSOR_WHITELIST_PATHS_KEY,
321         new String[]{});
322     // set retries low to raise exception quickly
323     conf.setInt("hbase.client.retries.number", 1);
324     UTIL.startMiniCluster();
325     HTableDescriptor htd = new HTableDescriptor(TEST_TABLE);
326     HColumnDescriptor hcd = new HColumnDescriptor(TEST_FAMILY);
327     htd.addFamily(hcd);
328     htd.addCoprocessor(TestRegionObserver.class.getName());
329     Connection connection = ConnectionFactory.createConnection(conf);
330     Admin admin = connection.getAdmin();
331     LOG.info("Creating Table");
332     admin.createTable(htd);
333     // ensure table was created and coprocessor is added to table
334     LOG.info("Done Creating Table");
335     Table t = connection.getTable(TEST_TABLE);
336     assertEquals(1, t.getTableDescriptor().getCoprocessors().size());
337   }
338 }