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.zookeeper; 019 020import static org.junit.Assert.assertEquals; 021import static org.junit.Assert.assertFalse; 022import static org.junit.Assert.assertTrue; 023 024import java.io.File; 025import java.io.FileOutputStream; 026import java.io.IOException; 027import java.io.OutputStreamWriter; 028import java.nio.charset.StandardCharsets; 029import java.util.ArrayList; 030import java.util.List; 031import javax.security.auth.login.AppConfigurationEntry; 032import org.apache.hadoop.conf.Configuration; 033import org.apache.hadoop.hbase.HBaseClassTestRule; 034import org.apache.hadoop.hbase.HBaseConfiguration; 035import org.apache.hadoop.hbase.HBaseTestingUtil; 036import org.apache.hadoop.hbase.HConstants; 037import org.apache.hadoop.hbase.ServerName; 038import org.apache.hadoop.hbase.TestZooKeeper; 039import org.apache.hadoop.hbase.testclassification.MediumTests; 040import org.apache.hadoop.hbase.testclassification.ZKTests; 041import org.apache.zookeeper.ZooDefs; 042import org.apache.zookeeper.data.ACL; 043import org.apache.zookeeper.data.Stat; 044import org.junit.AfterClass; 045import org.junit.Before; 046import org.junit.BeforeClass; 047import org.junit.ClassRule; 048import org.junit.Test; 049import org.junit.experimental.categories.Category; 050import org.slf4j.Logger; 051import org.slf4j.LoggerFactory; 052 053@Category({ ZKTests.class, MediumTests.class }) 054public class TestZooKeeperACL { 055 056 @ClassRule 057 public static final HBaseClassTestRule CLASS_RULE = 058 HBaseClassTestRule.forClass(TestZooKeeperACL.class); 059 060 private final static Logger LOG = LoggerFactory.getLogger(TestZooKeeperACL.class); 061 private final static HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil(); 062 063 private static ZKWatcher zkw; 064 private static boolean secureZKAvailable; 065 066 @BeforeClass 067 public static void setUpBeforeClass() throws Exception { 068 File saslConfFile = File.createTempFile("tmp", "jaas.conf"); 069 try (OutputStreamWriter fwriter = 070 new OutputStreamWriter(new FileOutputStream(saslConfFile), StandardCharsets.UTF_8)) { 071 fwriter.write("Server {\n" + "org.apache.zookeeper.server.auth.DigestLoginModule required\n" 072 + "user_hbase=\"secret\";\n" + "};\n" + "Client {\n" 073 + "org.apache.zookeeper.server.auth.DigestLoginModule required\n" + "username=\"hbase\"\n" 074 + "password=\"secret\";\n" + "};" + "\n"); 075 } 076 System.setProperty("java.security.auth.login.config", saslConfFile.getAbsolutePath()); 077 System.setProperty("zookeeper.authProvider.1", 078 "org.apache.zookeeper.server.auth.SASLAuthenticationProvider"); 079 080 TEST_UTIL.getConfiguration().setInt("hbase.zookeeper.property.maxClientCnxns", 1000); 081 082 // If Hadoop is missing HADOOP-7070 the cluster will fail to start due to 083 // the JAAS configuration required by ZK being clobbered by Hadoop 084 try { 085 TEST_UTIL.startMiniCluster(); 086 } catch (IOException e) { 087 LOG.warn("Hadoop is missing HADOOP-7070", e); 088 secureZKAvailable = false; 089 return; 090 } 091 zkw = new ZKWatcher(new Configuration(TEST_UTIL.getConfiguration()), 092 TestZooKeeper.class.getName(), null); 093 } 094 095 @AfterClass 096 public static void tearDownAfterClass() throws Exception { 097 if (!secureZKAvailable) { 098 return; 099 } 100 TEST_UTIL.shutdownMiniCluster(); 101 } 102 103 @Before 104 public void setUp() throws Exception { 105 if (!secureZKAvailable) { 106 return; 107 } 108 TEST_UTIL.ensureSomeRegionServersAvailable(2); 109 } 110 111 /** 112 * Create a node and check its ACL. When authentication is enabled on ZooKeeper, all nodes (except 113 * /hbase/root-region-server, /hbase/master and /hbase/hbaseid) should be created so that only the 114 * hbase server user (master or region server user) that created them can access them, and this 115 * user should have all permissions on this node. For /hbase/root-region-server, /hbase/master, 116 * and /hbase/hbaseid the permissions should be as above, but should also be world-readable. First 117 * we check the general case of /hbase nodes in the following test, and then check the subset of 118 * world-readable nodes in the three tests after that. 119 */ 120 @Test 121 public void testHBaseRootZNodeACL() throws Exception { 122 if (!secureZKAvailable) { 123 return; 124 } 125 126 List<ACL> acls = zkw.getRecoverableZooKeeper().getZooKeeper().getACL("/hbase", new Stat()); 127 assertEquals(1, acls.size()); 128 assertEquals("sasl", acls.get(0).getId().getScheme()); 129 assertEquals("hbase", acls.get(0).getId().getId()); 130 assertEquals(ZooDefs.Perms.ALL, acls.get(0).getPerms()); 131 } 132 133 /** 134 * When authentication is enabled on ZooKeeper, /hbase/root-region-server should be created with 2 135 * ACLs: one specifies that the hbase user has full access to the node; the other, that it is 136 * world-readable. 137 */ 138 @Test 139 public void testHBaseRootRegionServerZNodeACL() throws Exception { 140 if (!secureZKAvailable) { 141 return; 142 } 143 144 List<ACL> acls = 145 zkw.getRecoverableZooKeeper().getZooKeeper().getACL("/hbase/root-region-server", new Stat()); 146 assertEquals(2, acls.size()); 147 148 boolean foundWorldReadableAcl = false; 149 boolean foundHBaseOwnerAcl = false; 150 for (int i = 0; i < 2; i++) { 151 if (acls.get(i).getId().getScheme().equals("world") == true) { 152 assertEquals("anyone", acls.get(0).getId().getId()); 153 assertEquals(ZooDefs.Perms.READ, acls.get(0).getPerms()); 154 foundWorldReadableAcl = true; 155 } else { 156 if (acls.get(i).getId().getScheme().equals("sasl") == true) { 157 assertEquals("hbase", acls.get(1).getId().getId()); 158 assertEquals("sasl", acls.get(1).getId().getScheme()); 159 foundHBaseOwnerAcl = true; 160 } else { // error: should not get here: test fails. 161 assertTrue(false); 162 } 163 } 164 } 165 assertTrue(foundWorldReadableAcl); 166 assertTrue(foundHBaseOwnerAcl); 167 } 168 169 /** 170 * When authentication is enabled on ZooKeeper, /hbase/master should be created with 2 ACLs: one 171 * specifies that the hbase user has full access to the node; the other, that it is 172 * world-readable. 173 */ 174 @Test 175 public void testHBaseMasterServerZNodeACL() throws Exception { 176 if (!secureZKAvailable) { 177 return; 178 } 179 180 List<ACL> acls = 181 zkw.getRecoverableZooKeeper().getZooKeeper().getACL("/hbase/master", new Stat()); 182 assertEquals(2, acls.size()); 183 184 boolean foundWorldReadableAcl = false; 185 boolean foundHBaseOwnerAcl = false; 186 for (int i = 0; i < 2; i++) { 187 if (acls.get(i).getId().getScheme().equals("world") == true) { 188 assertEquals("anyone", acls.get(0).getId().getId()); 189 assertEquals(ZooDefs.Perms.READ, acls.get(0).getPerms()); 190 foundWorldReadableAcl = true; 191 } else { 192 if (acls.get(i).getId().getScheme().equals("sasl") == true) { 193 assertEquals("hbase", acls.get(1).getId().getId()); 194 assertEquals("sasl", acls.get(1).getId().getScheme()); 195 foundHBaseOwnerAcl = true; 196 } else { // error: should not get here: test fails. 197 assertTrue(false); 198 } 199 } 200 } 201 assertTrue(foundWorldReadableAcl); 202 assertTrue(foundHBaseOwnerAcl); 203 } 204 205 /** 206 * When authentication is enabled on ZooKeeper, /hbase/hbaseid should be created with 2 ACLs: one 207 * specifies that the hbase user has full access to the node; the other, that it is 208 * world-readable. 209 */ 210 @Test 211 public void testHBaseIDZNodeACL() throws Exception { 212 if (!secureZKAvailable) { 213 return; 214 } 215 216 List<ACL> acls = 217 zkw.getRecoverableZooKeeper().getZooKeeper().getACL("/hbase/hbaseid", new Stat()); 218 assertEquals(2, acls.size()); 219 220 boolean foundWorldReadableAcl = false; 221 boolean foundHBaseOwnerAcl = false; 222 for (int i = 0; i < 2; i++) { 223 if (acls.get(i).getId().getScheme().equals("world") == true) { 224 assertEquals("anyone", acls.get(0).getId().getId()); 225 assertEquals(ZooDefs.Perms.READ, acls.get(0).getPerms()); 226 foundWorldReadableAcl = true; 227 } else { 228 if (acls.get(i).getId().getScheme().equals("sasl") == true) { 229 assertEquals("hbase", acls.get(1).getId().getId()); 230 assertEquals("sasl", acls.get(1).getId().getScheme()); 231 foundHBaseOwnerAcl = true; 232 } else { // error: should not get here: test fails. 233 assertTrue(false); 234 } 235 } 236 } 237 assertTrue(foundWorldReadableAcl); 238 assertTrue(foundHBaseOwnerAcl); 239 } 240 241 /** 242 * Finally, we check the ACLs of a node outside of the /hbase hierarchy and verify that its ACL is 243 * simply 'hbase:Perms.ALL'. 244 */ 245 @Test 246 public void testOutsideHBaseNodeACL() throws Exception { 247 if (!secureZKAvailable) { 248 return; 249 } 250 251 ZKUtil.createWithParents(zkw, "/testACLNode"); 252 List<ACL> acls = 253 zkw.getRecoverableZooKeeper().getZooKeeper().getACL("/testACLNode", new Stat()); 254 assertEquals(1, acls.size()); 255 assertEquals("sasl", acls.get(0).getId().getScheme()); 256 assertEquals("hbase", acls.get(0).getId().getId()); 257 assertEquals(ZooDefs.Perms.ALL, acls.get(0).getPerms()); 258 } 259 260 /** 261 * Check if ZooKeeper JaasConfiguration is valid. 262 */ 263 @Test 264 public void testIsZooKeeperSecure() throws Exception { 265 boolean testJaasConfig = 266 ZKAuthentication.isSecureZooKeeper(new Configuration(TEST_UTIL.getConfiguration())); 267 assertEquals(testJaasConfig, secureZKAvailable); 268 // Define Jaas configuration without ZooKeeper Jaas config 269 File saslConfFile = File.createTempFile("tmp", "fakeJaas.conf"); 270 try (OutputStreamWriter fwriter = 271 new OutputStreamWriter(new FileOutputStream(saslConfFile), StandardCharsets.UTF_8)) { 272 fwriter.write(""); 273 } 274 275 System.setProperty("java.security.auth.login.config", saslConfFile.getAbsolutePath()); 276 277 testJaasConfig = 278 ZKAuthentication.isSecureZooKeeper(new Configuration(TEST_UTIL.getConfiguration())); 279 assertFalse(testJaasConfig); 280 saslConfFile.delete(); 281 } 282 283 /** 284 * Check if Programmatic way of setting zookeeper security settings is valid. 285 */ 286 @Test 287 public void testIsZooKeeperSecureWithProgrammaticConfig() throws Exception { 288 289 javax.security.auth.login.Configuration.setConfiguration(new DummySecurityConfiguration()); 290 291 Configuration config = new Configuration(HBaseConfiguration.create()); 292 boolean testJaasConfig = ZKAuthentication.isSecureZooKeeper(config); 293 assertFalse(testJaasConfig); 294 295 // Now set authentication scheme to Kerberos still it should return false 296 // because no configuration set 297 config.set("hbase.security.authentication", "kerberos"); 298 testJaasConfig = ZKAuthentication.isSecureZooKeeper(config); 299 assertFalse(testJaasConfig); 300 301 // Now set programmatic options related to security 302 config.set(HConstants.ZK_CLIENT_KEYTAB_FILE, "/dummy/file"); 303 config.set(HConstants.ZK_CLIENT_KERBEROS_PRINCIPAL, "dummy"); 304 config.set(HConstants.ZK_SERVER_KEYTAB_FILE, "/dummy/file"); 305 config.set(HConstants.ZK_SERVER_KERBEROS_PRINCIPAL, "dummy"); 306 testJaasConfig = ZKAuthentication.isSecureZooKeeper(config); 307 assertTrue(testJaasConfig); 308 } 309 310 private static class DummySecurityConfiguration extends javax.security.auth.login.Configuration { 311 @Override 312 public AppConfigurationEntry[] getAppConfigurationEntry(String name) { 313 return null; 314 } 315 } 316 317 @Test 318 public void testAdminDrainAllowedOnSecureZK() throws Exception { 319 if (!secureZKAvailable) { 320 return; 321 } 322 List<ServerName> decommissionedServers = new ArrayList<>(1); 323 decommissionedServers.add(ServerName.parseServerName("ZZZ,123,123")); 324 325 // If unable to connect to secure ZK cluster then this operation would fail. 326 TEST_UTIL.getAdmin().decommissionRegionServers(decommissionedServers, false); 327 328 decommissionedServers = TEST_UTIL.getAdmin().listDecommissionedRegionServers(); 329 assertEquals(1, decommissionedServers.size()); 330 assertEquals(ServerName.parseServerName("ZZZ,123,123"), decommissionedServers.get(0)); 331 332 TEST_UTIL.getAdmin().recommissionRegionServer(decommissionedServers.get(0), null); 333 decommissionedServers = TEST_UTIL.getAdmin().listDecommissionedRegionServers(); 334 assertEquals(0, decommissionedServers.size()); 335 } 336 337}