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}