001/** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.security.access; 019 020import static org.junit.Assert.assertEquals; 021import static org.junit.Assert.fail; 022 023import java.io.IOException; 024import java.util.Optional; 025import org.apache.hadoop.conf.Configuration; 026import org.apache.hadoop.fs.Path; 027import org.apache.hadoop.hbase.Coprocessor; 028import org.apache.hadoop.hbase.HBaseClassTestRule; 029import org.apache.hadoop.hbase.HBaseTestingUtility; 030import org.apache.hadoop.hbase.HColumnDescriptor; 031import org.apache.hadoop.hbase.HTableDescriptor; 032import org.apache.hadoop.hbase.TableName; 033import org.apache.hadoop.hbase.TableNotEnabledException; 034import org.apache.hadoop.hbase.TableNotFoundException; 035import org.apache.hadoop.hbase.client.Admin; 036import org.apache.hadoop.hbase.client.Connection; 037import org.apache.hadoop.hbase.client.ConnectionFactory; 038import org.apache.hadoop.hbase.client.Table; 039import org.apache.hadoop.hbase.coprocessor.CoprocessorHost; 040import org.apache.hadoop.hbase.coprocessor.RegionCoprocessor; 041import org.apache.hadoop.hbase.coprocessor.RegionObserver; 042import org.apache.hadoop.hbase.testclassification.LargeTests; 043import org.apache.hadoop.hbase.testclassification.SecurityTests; 044import org.apache.hadoop.hbase.util.Bytes; 045import org.junit.After; 046import org.junit.ClassRule; 047import org.junit.Test; 048import org.junit.experimental.categories.Category; 049import org.slf4j.Logger; 050import org.slf4j.LoggerFactory; 051 052/** 053 * Performs coprocessor loads for various paths and malformed strings 054 */ 055@Category({SecurityTests.class, LargeTests.class}) 056public class TestCoprocessorWhitelistMasterObserver extends SecureTestUtil { 057 058 @ClassRule 059 public static final HBaseClassTestRule CLASS_RULE = 060 HBaseClassTestRule.forClass(TestCoprocessorWhitelistMasterObserver.class); 061 062 private static final Logger LOG = 063 LoggerFactory.getLogger(TestCoprocessorWhitelistMasterObserver.class); 064 private static final HBaseTestingUtility UTIL = new HBaseTestingUtility(); 065 private static final TableName TEST_TABLE = TableName.valueOf("testTable"); 066 private static final byte[] TEST_FAMILY = Bytes.toBytes("fam1"); 067 068 @After 069 public void tearDownTestCoprocessorWhitelistMasterObserver() throws Exception { 070 Admin admin = UTIL.getAdmin(); 071 try { 072 try { 073 admin.disableTable(TEST_TABLE); 074 } catch (TableNotEnabledException ex) { 075 // Table was left disabled by test 076 LOG.info("Table was left disabled by test"); 077 } 078 admin.deleteTable(TEST_TABLE); 079 } catch (TableNotFoundException ex) { 080 // Table was not created for some reason? 081 LOG.info("Table was not created for some reason"); 082 } 083 UTIL.shutdownMiniCluster(); 084 } 085 086 /** 087 * Test a table modification adding a coprocessor path 088 * which is not whitelisted 089 * @result An IOException should be thrown and caught 090 * to show coprocessor is working as desired 091 * @param whitelistedPaths A String array of paths to add in 092 * for the whitelisting configuration 093 * @param coprocessorPath A String to use as the 094 * path for a mock coprocessor 095 */ 096 private static void positiveTestCase(String[] whitelistedPaths, 097 String coprocessorPath) throws Exception { 098 Configuration conf = UTIL.getConfiguration(); 099 // 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", 5); 107 UTIL.startMiniCluster(); 108 UTIL.createTable(TEST_TABLE, new byte[][] { TEST_FAMILY }); 109 UTIL.waitUntilAllRegionsAssigned(TEST_TABLE); 110 Connection connection = ConnectionFactory.createConnection(conf); 111 Table t = connection.getTable(TEST_TABLE); 112 HTableDescriptor htd = new HTableDescriptor(t.getTableDescriptor()); 113 htd.addCoprocessor("net.clayb.hbase.coprocessor.NotWhitelisted", 114 new Path(coprocessorPath), 115 Coprocessor.PRIORITY_USER, null); 116 LOG.info("Modifying Table"); 117 try { 118 connection.getAdmin().modifyTable(TEST_TABLE, htd); 119 fail("Expected coprocessor to raise IOException"); 120 } catch (IOException e) { 121 // swallow exception from coprocessor 122 } 123 LOG.info("Done Modifying Table"); 124 assertEquals(0, t.getTableDescriptor().getCoprocessors().size()); 125 } 126 127 /** 128 * Test a table modification adding a coprocessor path 129 * which is whitelisted 130 * @result The coprocessor should be added to the table 131 * descriptor successfully 132 * @param whitelistedPaths A String array of paths to add in 133 * for the whitelisting configuration 134 * @param coprocessorPath A String to use as the 135 * path for a mock coprocessor 136 */ 137 private static void negativeTestCase(String[] whitelistedPaths, 138 String coprocessorPath) throws Exception { 139 Configuration conf = UTIL.getConfiguration(); 140 conf.setInt("hbase.client.retries.number", 5); 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 // set a coprocessor whitelist path for test 146 conf.setStrings( 147 CoprocessorWhitelistMasterObserver.CP_COPROCESSOR_WHITELIST_PATHS_KEY, 148 whitelistedPaths); 149 UTIL.startMiniCluster(); 150 UTIL.createTable(TEST_TABLE, new byte[][] { TEST_FAMILY }); 151 UTIL.waitUntilAllRegionsAssigned(TEST_TABLE); 152 Connection connection = ConnectionFactory.createConnection(conf); 153 Admin admin = connection.getAdmin(); 154 // disable table so we do not actually try loading non-existant 155 // coprocessor file 156 admin.disableTable(TEST_TABLE); 157 Table t = connection.getTable(TEST_TABLE); 158 HTableDescriptor htd = new HTableDescriptor(t.getTableDescriptor()); 159 htd.addCoprocessor("net.clayb.hbase.coprocessor.Whitelisted", 160 new Path(coprocessorPath), 161 Coprocessor.PRIORITY_USER, null); 162 LOG.info("Modifying Table"); 163 admin.modifyTable(TEST_TABLE, htd); 164 assertEquals(1, t.getTableDescriptor().getCoprocessors().size()); 165 LOG.info("Done Modifying Table"); 166 } 167 168 /** 169 * Test a table modification adding a coprocessor path 170 * which is not whitelisted 171 * @result An IOException should be thrown and caught 172 * to show coprocessor is working as desired 173 */ 174 @Test 175 public void testSubstringNonWhitelisted() throws Exception { 176 positiveTestCase(new String[]{"/permitted/*"}, 177 "file:///notpermitted/couldnotpossiblyexist.jar"); 178 } 179 180 /** 181 * Test a table creation including a coprocessor path 182 * which is not whitelisted 183 * @result Coprocessor should be added to table descriptor 184 * Table is disabled to avoid an IOException due to 185 * the added coprocessor not actually existing on disk 186 */ 187 @Test 188 public void testDifferentFileSystemNonWhitelisted() throws Exception { 189 positiveTestCase(new String[]{"hdfs://foo/bar"}, 190 "file:///notpermitted/couldnotpossiblyexist.jar"); 191 } 192 193 /** 194 * Test a table modification adding a coprocessor path 195 * which is whitelisted 196 * @result Coprocessor should be added to table descriptor 197 * Table is disabled to avoid an IOException due to 198 * the added coprocessor not actually existing on disk 199 */ 200 @Test 201 public void testSchemeAndDirectorywhitelisted() throws Exception { 202 negativeTestCase(new String[]{"/tmp","file:///permitted/*"}, 203 "file:///permitted/couldnotpossiblyexist.jar"); 204 } 205 206 /** 207 * Test a table modification adding a coprocessor path 208 * which is whitelisted 209 * @result Coprocessor should be added to table descriptor 210 * Table is disabled to avoid an IOException due to 211 * the added coprocessor not actually existing on disk 212 */ 213 @Test 214 public void testSchemeWhitelisted() throws Exception { 215 negativeTestCase(new String[]{"file:///"}, 216 "file:///permitted/couldnotpossiblyexist.jar"); 217 } 218 219 /** 220 * Test a table modification adding a coprocessor path 221 * which is whitelisted 222 * @result Coprocessor should be added to table descriptor 223 * Table is disabled to avoid an IOException due to 224 * the added coprocessor not actually existing on disk 225 */ 226 @Test 227 public void testDFSNameWhitelistedWorks() throws Exception { 228 negativeTestCase(new String[]{"hdfs://Your-FileSystem"}, 229 "hdfs://Your-FileSystem/permitted/couldnotpossiblyexist.jar"); 230 } 231 232 /** 233 * Test a table modification adding a coprocessor path 234 * which is whitelisted 235 * @result Coprocessor should be added to table descriptor 236 * Table is disabled to avoid an IOException due to 237 * the added coprocessor not actually existing on disk 238 */ 239 @Test 240 public void testDFSNameNotWhitelistedFails() throws Exception { 241 positiveTestCase(new String[]{"hdfs://Your-FileSystem"}, 242 "hdfs://My-FileSystem/permitted/couldnotpossiblyexist.jar"); 243 } 244 245 /** 246 * Test a table modification adding a coprocessor path 247 * which is whitelisted 248 * @result Coprocessor should be added to table descriptor 249 * Table is disabled to avoid an IOException due to 250 * the added coprocessor not actually existing on disk 251 */ 252 @Test 253 public void testBlanketWhitelist() throws Exception { 254 negativeTestCase(new String[]{"*"}, 255 "hdfs:///permitted/couldnotpossiblyexist.jar"); 256 } 257 258 /** 259 * Test a table creation including a coprocessor path 260 * which is not whitelisted 261 * @result Table will not be created due to the offending coprocessor 262 */ 263 @Test 264 public void testCreationNonWhitelistedCoprocessorPath() throws Exception { 265 Configuration conf = UTIL.getConfiguration(); 266 // load coprocessor under test 267 conf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY, 268 CoprocessorWhitelistMasterObserver.class.getName()); 269 conf.setStrings(CoprocessorWhitelistMasterObserver.CP_COPROCESSOR_WHITELIST_PATHS_KEY, 270 new String[]{}); 271 // set retries low to raise exception quickly 272 conf.setInt("hbase.client.retries.number", 5); 273 UTIL.startMiniCluster(); 274 HTableDescriptor htd = new HTableDescriptor(TEST_TABLE); 275 HColumnDescriptor hcd = new HColumnDescriptor(TEST_FAMILY); 276 htd.addFamily(hcd); 277 htd.addCoprocessor("net.clayb.hbase.coprocessor.NotWhitelisted", 278 new Path("file:///notpermitted/couldnotpossiblyexist.jar"), 279 Coprocessor.PRIORITY_USER, null); 280 Connection connection = ConnectionFactory.createConnection(conf); 281 Admin admin = connection.getAdmin(); 282 LOG.info("Creating Table"); 283 try { 284 admin.createTable(htd); 285 fail("Expected coprocessor to raise IOException"); 286 } catch (IOException e) { 287 // swallow exception from coprocessor 288 } 289 LOG.info("Done Creating Table"); 290 // ensure table was not created 291 assertEquals(new HTableDescriptor[0], 292 admin.listTables("^" + TEST_TABLE.getNameAsString() + "$")); 293 } 294 295 public static class TestRegionObserver implements RegionCoprocessor, RegionObserver { 296 @Override 297 public Optional<RegionObserver> getRegionObserver() { 298 return Optional.of(this); 299 } 300 301 } 302 303 /** 304 * Test a table creation including a coprocessor path 305 * which is on the classpath 306 * @result Table will be created with the coprocessor 307 */ 308 @Test 309 public void testCreationClasspathCoprocessor() throws Exception { 310 Configuration conf = UTIL.getConfiguration(); 311 // load coprocessor under test 312 conf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY, 313 CoprocessorWhitelistMasterObserver.class.getName()); 314 conf.setStrings(CoprocessorWhitelistMasterObserver.CP_COPROCESSOR_WHITELIST_PATHS_KEY, 315 new String[]{}); 316 // set retries low to raise exception quickly 317 conf.setInt("hbase.client.retries.number", 5); 318 UTIL.startMiniCluster(); 319 HTableDescriptor htd = new HTableDescriptor(TEST_TABLE); 320 HColumnDescriptor hcd = new HColumnDescriptor(TEST_FAMILY); 321 htd.addFamily(hcd); 322 htd.addCoprocessor(TestRegionObserver.class.getName()); 323 Connection connection = ConnectionFactory.createConnection(conf); 324 Admin admin = connection.getAdmin(); 325 LOG.info("Creating Table"); 326 admin.createTable(htd); 327 // ensure table was created and coprocessor is added to table 328 LOG.info("Done Creating Table"); 329 Table t = connection.getTable(TEST_TABLE); 330 assertEquals(1, t.getTableDescriptor().getCoprocessors().size()); 331 } 332}