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