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.Collections; 025import java.util.Optional; 026import java.util.regex.Pattern; 027import org.apache.hadoop.conf.Configuration; 028import org.apache.hadoop.hbase.Coprocessor; 029import org.apache.hadoop.hbase.HBaseClassTestRule; 030import org.apache.hadoop.hbase.HBaseTestingUtil; 031import org.apache.hadoop.hbase.TableName; 032import org.apache.hadoop.hbase.TableNotEnabledException; 033import org.apache.hadoop.hbase.TableNotFoundException; 034import org.apache.hadoop.hbase.client.Admin; 035import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; 036import org.apache.hadoop.hbase.client.Connection; 037import org.apache.hadoop.hbase.client.ConnectionFactory; 038import org.apache.hadoop.hbase.client.CoprocessorDescriptorBuilder; 039import org.apache.hadoop.hbase.client.Table; 040import org.apache.hadoop.hbase.client.TableDescriptor; 041import org.apache.hadoop.hbase.client.TableDescriptorBuilder; 042import org.apache.hadoop.hbase.coprocessor.CoprocessorHost; 043import org.apache.hadoop.hbase.coprocessor.RegionCoprocessor; 044import org.apache.hadoop.hbase.coprocessor.RegionObserver; 045import org.apache.hadoop.hbase.testclassification.LargeTests; 046import org.apache.hadoop.hbase.testclassification.SecurityTests; 047import org.apache.hadoop.hbase.util.Bytes; 048import org.junit.After; 049import org.junit.ClassRule; 050import org.junit.Test; 051import org.junit.experimental.categories.Category; 052import org.slf4j.Logger; 053import org.slf4j.LoggerFactory; 054 055/** 056 * Performs coprocessor loads for various paths and malformed strings 057 */ 058@Category({ SecurityTests.class, LargeTests.class }) 059public class TestCoprocessorWhitelistMasterObserver extends SecureTestUtil { 060 061 @ClassRule 062 public static final HBaseClassTestRule CLASS_RULE = 063 HBaseClassTestRule.forClass(TestCoprocessorWhitelistMasterObserver.class); 064 065 private static final Logger LOG = 066 LoggerFactory.getLogger(TestCoprocessorWhitelistMasterObserver.class); 067 private static final HBaseTestingUtil UTIL = new HBaseTestingUtil(); 068 private static final TableName TEST_TABLE = TableName.valueOf("testTable"); 069 private static final byte[] TEST_FAMILY = Bytes.toBytes("fam1"); 070 071 @After 072 public void tearDownTestCoprocessorWhitelistMasterObserver() throws Exception { 073 Admin admin = UTIL.getAdmin(); 074 try { 075 try { 076 admin.disableTable(TEST_TABLE); 077 } catch (TableNotEnabledException ex) { 078 // Table was left disabled by test 079 LOG.info("Table was left disabled by test"); 080 } 081 admin.deleteTable(TEST_TABLE); 082 } catch (TableNotFoundException ex) { 083 // Table was not created for some reason? 084 LOG.info("Table was not created for some reason"); 085 } 086 UTIL.shutdownMiniCluster(); 087 } 088 089 /** 090 * Test a table modification adding a coprocessor path which is not whitelisted. 091 * @exception Exception should be thrown and caught to show coprocessor is working as desired 092 * @param whitelistedPaths A String array of paths to add in for the whitelisting configuration 093 * @param coprocessorPath A String to use as the path for a mock coprocessor 094 */ 095 private static void positiveTestCase(String[] whitelistedPaths, String coprocessorPath) 096 throws Exception { 097 Configuration conf = UTIL.getConfiguration(); 098 // load coprocessor under test 099 conf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY, 100 CoprocessorWhitelistMasterObserver.class.getName()); 101 conf.setStrings(CoprocessorWhitelistMasterObserver.CP_COPROCESSOR_WHITELIST_PATHS_KEY, 102 whitelistedPaths); 103 // set retries low to raise exception quickly 104 conf.setInt("hbase.client.retries.number", 5); 105 UTIL.startMiniCluster(); 106 UTIL.createTable(TEST_TABLE, new byte[][] { TEST_FAMILY }); 107 UTIL.waitUntilAllRegionsAssigned(TEST_TABLE); 108 Connection connection = ConnectionFactory.createConnection(conf); 109 Table t = connection.getTable(TEST_TABLE); 110 TableDescriptor htd = TableDescriptorBuilder.newBuilder(t.getDescriptor()) 111 .setCoprocessor( 112 CoprocessorDescriptorBuilder.newBuilder("net.clayb.hbase.coprocessor.NotWhitelisted") 113 .setJarPath(coprocessorPath).setPriority(Coprocessor.PRIORITY_USER).build()) 114 .build(); 115 LOG.info("Modifying Table"); 116 try { 117 connection.getAdmin().modifyTable(htd); 118 fail("Expected coprocessor to raise IOException"); 119 } catch (IOException e) { 120 // swallow exception from coprocessor 121 } 122 LOG.info("Done Modifying Table"); 123 assertEquals(0, t.getDescriptor().getCoprocessorDescriptors().size()); 124 } 125 126 /** 127 * Test a table modification adding a coprocessor path which is whitelisted. The coprocessor 128 * should be added to the table descriptor successfully. 129 * @param whitelistedPaths A String array of paths to add in for the whitelisting configuration 130 * @param coprocessorPath A String to use as the path for a mock coprocessor 131 */ 132 private static void negativeTestCase(String[] whitelistedPaths, String coprocessorPath) 133 throws Exception { 134 Configuration conf = UTIL.getConfiguration(); 135 conf.setInt("hbase.client.retries.number", 5); 136 // load coprocessor under test 137 conf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY, 138 CoprocessorWhitelistMasterObserver.class.getName()); 139 // set retries low to raise exception quickly 140 // set a coprocessor whitelist path for test 141 conf.setStrings(CoprocessorWhitelistMasterObserver.CP_COPROCESSOR_WHITELIST_PATHS_KEY, 142 whitelistedPaths); 143 UTIL.startMiniCluster(); 144 UTIL.createTable(TEST_TABLE, new byte[][] { TEST_FAMILY }); 145 UTIL.waitUntilAllRegionsAssigned(TEST_TABLE); 146 Connection connection = ConnectionFactory.createConnection(conf); 147 Admin admin = connection.getAdmin(); 148 // disable table so we do not actually try loading non-existant 149 // coprocessor file 150 admin.disableTable(TEST_TABLE); 151 Table t = connection.getTable(TEST_TABLE); 152 TableDescriptor htd = TableDescriptorBuilder.newBuilder(t.getDescriptor()) 153 .setCoprocessor( 154 CoprocessorDescriptorBuilder.newBuilder("net.clayb.hbase.coprocessor.Whitelisted") 155 .setJarPath(coprocessorPath).setPriority(Coprocessor.PRIORITY_USER).build()) 156 .build(); 157 LOG.info("Modifying Table"); 158 admin.modifyTable(htd); 159 assertEquals(1, t.getDescriptor().getCoprocessorDescriptors().size()); 160 LOG.info("Done Modifying Table"); 161 } 162 163 /** 164 * Test a table modification adding a coprocessor path which is not whitelisted. 165 * @exception Exception should be thrown and caught to show coprocessor is working as desired 166 */ 167 @Test 168 public void testSubstringNonWhitelisted() throws Exception { 169 positiveTestCase(new String[] { "/permitted/*" }, 170 "file:///notpermitted/couldnotpossiblyexist.jar"); 171 } 172 173 /** 174 * Test a table creation including a coprocessor path which is not whitelisted. Coprocessor should 175 * be added to table descriptor. Table is disabled to avoid an IOException due to the added 176 * coprocessor not actually existing on disk. 177 */ 178 @Test 179 public void testDifferentFileSystemNonWhitelisted() throws Exception { 180 positiveTestCase(new String[] { "hdfs://foo/bar" }, 181 "file:///notpermitted/couldnotpossiblyexist.jar"); 182 } 183 184 /** 185 * Test a table modification adding a coprocessor path which is whitelisted. Coprocessor should be 186 * added to table descriptor. Table is disabled to avoid an IOException due to the added 187 * coprocessor not actually existing on disk. 188 */ 189 @Test 190 public void testSchemeAndDirectorywhitelisted() throws Exception { 191 negativeTestCase(new String[] { "/tmp", "file:///permitted/*" }, 192 "file:///permitted/couldnotpossiblyexist.jar"); 193 } 194 195 /** 196 * Test a table modification adding a coprocessor path which is whitelisted. Coprocessor should be 197 * added to table descriptor. Table is disabled to avoid an IOException due to the added 198 * coprocessor not actually existing on disk. 199 */ 200 @Test 201 public void testSchemeWhitelisted() throws Exception { 202 negativeTestCase(new String[] { "file:///" }, "file:///permitted/couldnotpossiblyexist.jar"); 203 } 204 205 /** 206 * Test a table modification adding a coprocessor path which is whitelisted. Coprocessor should be 207 * added to table descriptor. Table is disabled to avoid an IOException due to the added 208 * coprocessor not actually existing on disk. 209 */ 210 @Test 211 public void testDFSNameWhitelistedWorks() throws Exception { 212 negativeTestCase(new String[] { "hdfs://Your-FileSystem" }, 213 "hdfs://Your-FileSystem/permitted/couldnotpossiblyexist.jar"); 214 } 215 216 /** 217 * Test a table modification adding a coprocessor path which is whitelisted. Coprocessor should be 218 * added to table descriptor. Table is disabled to avoid an IOException due to the added 219 * coprocessor not actually existing on disk. 220 */ 221 @Test 222 public void testDFSNameNotWhitelistedFails() throws Exception { 223 positiveTestCase(new String[] { "hdfs://Your-FileSystem" }, 224 "hdfs://My-FileSystem/permitted/couldnotpossiblyexist.jar"); 225 } 226 227 /** 228 * Test a table modification adding a coprocessor path which is whitelisted. Coprocessor should be 229 * added to table descriptor. Table is disabled to avoid an IOException due to the added 230 * coprocessor not actually existing on disk. 231 */ 232 @Test 233 public void testBlanketWhitelist() throws Exception { 234 negativeTestCase(new String[] { "*" }, "hdfs:///permitted/couldnotpossiblyexist.jar"); 235 } 236 237 /** 238 * Test a table creation including a coprocessor path which is not whitelisted. Table will not be 239 * created due to the offending coprocessor. 240 */ 241 @Test 242 public void testCreationNonWhitelistedCoprocessorPath() throws Exception { 243 Configuration conf = UTIL.getConfiguration(); 244 // load coprocessor under test 245 conf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY, 246 CoprocessorWhitelistMasterObserver.class.getName()); 247 conf.setStrings(CoprocessorWhitelistMasterObserver.CP_COPROCESSOR_WHITELIST_PATHS_KEY, 248 new String[] {}); 249 // set retries low to raise exception quickly 250 conf.setInt("hbase.client.retries.number", 5); 251 UTIL.startMiniCluster(); 252 TableDescriptor tableDescriptor = TableDescriptorBuilder.newBuilder(TEST_TABLE) 253 .setColumnFamily(ColumnFamilyDescriptorBuilder.of(TEST_FAMILY)) 254 .setCoprocessor( 255 CoprocessorDescriptorBuilder.newBuilder("net.clayb.hbase.coprocessor.NotWhitelisted") 256 .setJarPath("file:///notpermitted/couldnotpossiblyexist.jar") 257 .setPriority(Coprocessor.PRIORITY_USER).setProperties(Collections.emptyMap()).build()) 258 .build(); 259 Connection connection = ConnectionFactory.createConnection(conf); 260 Admin admin = connection.getAdmin(); 261 LOG.info("Creating Table"); 262 try { 263 admin.createTable(tableDescriptor); 264 fail("Expected coprocessor to raise IOException"); 265 } catch (IOException e) { 266 // swallow exception from coprocessor 267 } 268 LOG.info("Done Creating Table"); 269 // ensure table was not created 270 assertEquals(0, 271 admin.listTableDescriptors(Pattern.compile("^" + TEST_TABLE.getNameAsString() + "$")).size()); 272 } 273 274 public static class TestRegionObserver implements RegionCoprocessor, RegionObserver { 275 @Override 276 public Optional<RegionObserver> getRegionObserver() { 277 return Optional.of(this); 278 } 279 280 } 281 282 /** 283 * Test a table creation including a coprocessor path which is on the classpath. Table will be 284 * created with the coprocessor. 285 */ 286 @Test 287 public void testCreationClasspathCoprocessor() throws Exception { 288 Configuration conf = UTIL.getConfiguration(); 289 // load coprocessor under test 290 conf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY, 291 CoprocessorWhitelistMasterObserver.class.getName()); 292 conf.setStrings(CoprocessorWhitelistMasterObserver.CP_COPROCESSOR_WHITELIST_PATHS_KEY, 293 new String[] {}); 294 // set retries low to raise exception quickly 295 conf.setInt("hbase.client.retries.number", 5); 296 UTIL.startMiniCluster(); 297 TableDescriptor tableDescriptor = TableDescriptorBuilder.newBuilder(TEST_TABLE) 298 .setColumnFamily(ColumnFamilyDescriptorBuilder.of(TEST_FAMILY)) 299 .setCoprocessor(TestRegionObserver.class.getName()).build(); 300 Connection connection = ConnectionFactory.createConnection(conf); 301 Admin admin = connection.getAdmin(); 302 LOG.info("Creating Table"); 303 admin.createTable(tableDescriptor); 304 // ensure table was created and coprocessor is added to table 305 LOG.info("Done Creating Table"); 306 Table t = connection.getTable(TEST_TABLE); 307 assertEquals(1, t.getDescriptor().getCoprocessorDescriptors().size()); 308 } 309}