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