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