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