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