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 which is not whitelisted 088 * @result An IOException should be thrown and caught to show coprocessor is working as desired 089 * @param whitelistedPaths A String array of paths to add in for the whitelisting configuration 090 * @param coprocessorPath A String to use as the path for a mock coprocessor 091 */ 092 private static void positiveTestCase(String[] whitelistedPaths, String coprocessorPath) 093 throws Exception { 094 Configuration conf = UTIL.getConfiguration(); 095 // load coprocessor under test 096 conf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY, 097 CoprocessorWhitelistMasterObserver.class.getName()); 098 conf.setStrings(CoprocessorWhitelistMasterObserver.CP_COPROCESSOR_WHITELIST_PATHS_KEY, 099 whitelistedPaths); 100 // set retries low to raise exception quickly 101 conf.setInt("hbase.client.retries.number", 5); 102 UTIL.startMiniCluster(); 103 UTIL.createTable(TEST_TABLE, new byte[][] { TEST_FAMILY }); 104 UTIL.waitUntilAllRegionsAssigned(TEST_TABLE); 105 Connection connection = ConnectionFactory.createConnection(conf); 106 Table t = connection.getTable(TEST_TABLE); 107 HTableDescriptor htd = new HTableDescriptor(t.getTableDescriptor()); 108 htd.addCoprocessor("net.clayb.hbase.coprocessor.NotWhitelisted", new Path(coprocessorPath), 109 Coprocessor.PRIORITY_USER, null); 110 LOG.info("Modifying Table"); 111 try { 112 connection.getAdmin().modifyTable(TEST_TABLE, htd); 113 fail("Expected coprocessor to raise IOException"); 114 } catch (IOException e) { 115 // swallow exception from coprocessor 116 } 117 LOG.info("Done Modifying Table"); 118 assertEquals(0, t.getTableDescriptor().getCoprocessors().size()); 119 } 120 121 /** 122 * Test a table modification adding a coprocessor path which is whitelisted 123 * @result The coprocessor should be added to the table descriptor successfully 124 * @param whitelistedPaths A String array of paths to add in for the whitelisting configuration 125 * @param coprocessorPath A String to use as the path for a mock coprocessor 126 */ 127 private static void negativeTestCase(String[] whitelistedPaths, String coprocessorPath) 128 throws Exception { 129 Configuration conf = UTIL.getConfiguration(); 130 conf.setInt("hbase.client.retries.number", 5); 131 // load coprocessor under test 132 conf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY, 133 CoprocessorWhitelistMasterObserver.class.getName()); 134 // set retries low to raise exception quickly 135 // set a coprocessor whitelist path for test 136 conf.setStrings(CoprocessorWhitelistMasterObserver.CP_COPROCESSOR_WHITELIST_PATHS_KEY, 137 whitelistedPaths); 138 UTIL.startMiniCluster(); 139 UTIL.createTable(TEST_TABLE, new byte[][] { TEST_FAMILY }); 140 UTIL.waitUntilAllRegionsAssigned(TEST_TABLE); 141 Connection connection = ConnectionFactory.createConnection(conf); 142 Admin admin = connection.getAdmin(); 143 // disable table so we do not actually try loading non-existant 144 // coprocessor file 145 admin.disableTable(TEST_TABLE); 146 Table t = connection.getTable(TEST_TABLE); 147 HTableDescriptor htd = new HTableDescriptor(t.getTableDescriptor()); 148 htd.addCoprocessor("net.clayb.hbase.coprocessor.Whitelisted", new Path(coprocessorPath), 149 Coprocessor.PRIORITY_USER, null); 150 LOG.info("Modifying Table"); 151 admin.modifyTable(TEST_TABLE, htd); 152 assertEquals(1, t.getTableDescriptor().getCoprocessors().size()); 153 LOG.info("Done Modifying Table"); 154 } 155 156 /** 157 * Test a table modification adding a coprocessor path which is not whitelisted 158 * @result An IOException should be thrown and caught to show coprocessor is working as desired 159 */ 160 @Test 161 public void testSubstringNonWhitelisted() throws Exception { 162 positiveTestCase(new String[] { "/permitted/*" }, 163 "file:///notpermitted/couldnotpossiblyexist.jar"); 164 } 165 166 /** 167 * Test a table creation including a coprocessor path which is not whitelisted 168 * @result Coprocessor should be added to table descriptor Table is disabled to avoid an 169 * IOException due to the added coprocessor not actually existing on disk 170 */ 171 @Test 172 public void testDifferentFileSystemNonWhitelisted() throws Exception { 173 positiveTestCase(new String[] { "hdfs://foo/bar" }, 174 "file:///notpermitted/couldnotpossiblyexist.jar"); 175 } 176 177 /** 178 * Test a table modification adding a coprocessor path which is whitelisted 179 * @result Coprocessor should be added to table descriptor Table is disabled to avoid an 180 * IOException due to the added coprocessor not actually existing on disk 181 */ 182 @Test 183 public void testSchemeAndDirectorywhitelisted() throws Exception { 184 negativeTestCase(new String[] { "/tmp", "file:///permitted/*" }, 185 "file:///permitted/couldnotpossiblyexist.jar"); 186 } 187 188 /** 189 * Test a table modification adding a coprocessor path which is whitelisted 190 * @result Coprocessor should be added to table descriptor Table is disabled to avoid an 191 * IOException due to the added coprocessor not actually existing on disk 192 */ 193 @Test 194 public void testSchemeWhitelisted() throws Exception { 195 negativeTestCase(new String[] { "file:///" }, "file:///permitted/couldnotpossiblyexist.jar"); 196 } 197 198 /** 199 * Test a table modification adding a coprocessor path which is whitelisted 200 * @result Coprocessor should be added to table descriptor Table is disabled to avoid an 201 * IOException due to the added coprocessor not actually existing on disk 202 */ 203 @Test 204 public void testDFSNameWhitelistedWorks() throws Exception { 205 negativeTestCase(new String[] { "hdfs://Your-FileSystem" }, 206 "hdfs://Your-FileSystem/permitted/couldnotpossiblyexist.jar"); 207 } 208 209 /** 210 * Test a table modification adding a coprocessor path which is whitelisted 211 * @result Coprocessor should be added to table descriptor Table is disabled to avoid an 212 * IOException due to the added coprocessor not actually existing on disk 213 */ 214 @Test 215 public void testDFSNameNotWhitelistedFails() throws Exception { 216 positiveTestCase(new String[] { "hdfs://Your-FileSystem" }, 217 "hdfs://My-FileSystem/permitted/couldnotpossiblyexist.jar"); 218 } 219 220 /** 221 * Test a table modification adding a coprocessor path which is whitelisted 222 * @result Coprocessor should be added to table descriptor Table is disabled to avoid an 223 * IOException due to the added coprocessor not actually existing on disk 224 */ 225 @Test 226 public void testBlanketWhitelist() throws Exception { 227 negativeTestCase(new String[] { "*" }, "hdfs:///permitted/couldnotpossiblyexist.jar"); 228 } 229 230 /** 231 * Test a table creation including a coprocessor path which is not whitelisted 232 * @result Table will not be created due to the offending coprocessor 233 */ 234 @Test 235 public void testCreationNonWhitelistedCoprocessorPath() throws Exception { 236 Configuration conf = UTIL.getConfiguration(); 237 // load coprocessor under test 238 conf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY, 239 CoprocessorWhitelistMasterObserver.class.getName()); 240 conf.setStrings(CoprocessorWhitelistMasterObserver.CP_COPROCESSOR_WHITELIST_PATHS_KEY, 241 new String[] {}); 242 // set retries low to raise exception quickly 243 conf.setInt("hbase.client.retries.number", 5); 244 UTIL.startMiniCluster(); 245 HTableDescriptor htd = new HTableDescriptor(TEST_TABLE); 246 HColumnDescriptor hcd = new HColumnDescriptor(TEST_FAMILY); 247 htd.addFamily(hcd); 248 htd.addCoprocessor("net.clayb.hbase.coprocessor.NotWhitelisted", 249 new Path("file:///notpermitted/couldnotpossiblyexist.jar"), Coprocessor.PRIORITY_USER, null); 250 Connection connection = ConnectionFactory.createConnection(conf); 251 Admin admin = connection.getAdmin(); 252 LOG.info("Creating Table"); 253 try { 254 admin.createTable(htd); 255 fail("Expected coprocessor to raise IOException"); 256 } catch (IOException e) { 257 // swallow exception from coprocessor 258 } 259 LOG.info("Done Creating Table"); 260 // ensure table was not created 261 assertEquals(new HTableDescriptor[0], 262 admin.listTables("^" + TEST_TABLE.getNameAsString() + "$")); 263 } 264 265 public static class TestRegionObserver implements RegionCoprocessor, RegionObserver { 266 @Override 267 public Optional<RegionObserver> getRegionObserver() { 268 return Optional.of(this); 269 } 270 271 } 272 273 /** 274 * Test a table creation including a coprocessor path which is on the classpath 275 * @result Table will be created with the coprocessor 276 */ 277 @Test 278 public void testCreationClasspathCoprocessor() throws Exception { 279 Configuration conf = UTIL.getConfiguration(); 280 // load coprocessor under test 281 conf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY, 282 CoprocessorWhitelistMasterObserver.class.getName()); 283 conf.setStrings(CoprocessorWhitelistMasterObserver.CP_COPROCESSOR_WHITELIST_PATHS_KEY, 284 new String[] {}); 285 // set retries low to raise exception quickly 286 conf.setInt("hbase.client.retries.number", 5); 287 UTIL.startMiniCluster(); 288 HTableDescriptor htd = new HTableDescriptor(TEST_TABLE); 289 HColumnDescriptor hcd = new HColumnDescriptor(TEST_FAMILY); 290 htd.addFamily(hcd); 291 htd.addCoprocessor(TestRegionObserver.class.getName()); 292 Connection connection = ConnectionFactory.createConnection(conf); 293 Admin admin = connection.getAdmin(); 294 LOG.info("Creating Table"); 295 admin.createTable(htd); 296 // ensure table was created and coprocessor is added to table 297 LOG.info("Done Creating Table"); 298 Table t = connection.getTable(TEST_TABLE); 299 assertEquals(1, t.getTableDescriptor().getCoprocessors().size()); 300 } 301}