View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase.zookeeper;
19  
20  import static org.junit.Assert.assertEquals;
21  import static org.junit.Assert.assertTrue;
22  
23  import java.io.File;
24  import java.io.FileWriter;
25  import java.io.IOException;
26  import java.util.List;
27  
28  import javax.security.auth.login.AppConfigurationEntry;
29  
30  import org.apache.commons.logging.Log;
31  import org.apache.commons.logging.LogFactory;
32  import org.apache.hadoop.conf.Configuration;
33  import org.apache.hadoop.hbase.*;
34  import org.apache.hadoop.hbase.testclassification.MediumTests;
35  import org.apache.zookeeper.ZooDefs;
36  import org.apache.zookeeper.data.ACL;
37  import org.apache.zookeeper.data.Stat;
38  import org.junit.AfterClass;
39  import org.junit.Before;
40  import org.junit.BeforeClass;
41  import org.junit.Test;
42  import org.junit.experimental.categories.Category;
43  
44  @Category(MediumTests.class)
45  public class TestZooKeeperACL {
46    private final static Log LOG = LogFactory.getLog(TestZooKeeperACL.class);
47    private final static HBaseTestingUtility TEST_UTIL =
48        new HBaseTestingUtility();
49  
50    private static ZooKeeperWatcher zkw;
51    private static boolean secureZKAvailable;
52    
53    @BeforeClass
54    public static void setUpBeforeClass() throws Exception {
55      File saslConfFile = File.createTempFile("tmp", "jaas.conf");
56      FileWriter fwriter = new FileWriter(saslConfFile);
57  
58      fwriter.write("" +
59        "Server {\n" +
60          "org.apache.zookeeper.server.auth.DigestLoginModule required\n" +
61          "user_hbase=\"secret\";\n" +
62        "};\n" +
63        "Client {\n" +
64          "org.apache.zookeeper.server.auth.DigestLoginModule required\n" +
65          "username=\"hbase\"\n" +
66          "password=\"secret\";\n" +
67        "};" + "\n");
68      fwriter.close();
69      System.setProperty("java.security.auth.login.config",
70          saslConfFile.getAbsolutePath());
71      System.setProperty("zookeeper.authProvider.1",
72          "org.apache.zookeeper.server.auth.SASLAuthenticationProvider");
73  
74      TEST_UTIL.getConfiguration().setBoolean("dfs.support.append", true);
75      TEST_UTIL.getConfiguration().setInt("hbase.zookeeper.property.maxClientCnxns", 1000);
76  
77      // If Hadoop is missing HADOOP-7070 the cluster will fail to start due to
78      // the JAAS configuration required by ZK being clobbered by Hadoop 
79      try {
80        TEST_UTIL.startMiniCluster();
81      } catch (IOException e) {
82        LOG.warn("Hadoop is missing HADOOP-7070", e);
83        secureZKAvailable = false;
84        return;
85      }
86      zkw = new ZooKeeperWatcher(
87        new Configuration(TEST_UTIL.getConfiguration()),
88          TestZooKeeper.class.getName(), null);
89    }
90  
91    /**
92     * @throws java.lang.Exception
93     */
94    @AfterClass
95    public static void tearDownAfterClass() throws Exception {
96      if (!secureZKAvailable) {
97        return;
98      }
99      TEST_UTIL.shutdownMiniCluster();
100   }
101 
102   /**
103    * @throws java.lang.Exception
104    */
105   @Before
106   public void setUp() throws Exception {
107     if (!secureZKAvailable) {
108       return;
109     }
110     TEST_UTIL.ensureSomeRegionServersAvailable(2);
111   }
112 
113   /**
114    * Create a node and check its ACL. When authentication is enabled on 
115    * Zookeeper, all nodes (except /hbase/root-region-server, /hbase/master
116    * and /hbase/hbaseid) should be created so that only the hbase server user
117    * (master or region server user) that created them can access them, and
118    * this user should have all permissions on this node. For
119    * /hbase/root-region-server, /hbase/master, and /hbase/hbaseid the
120    * permissions should be as above, but should also be world-readable. First
121    * we check the general case of /hbase nodes in the following test, and
122    * then check the subset of world-readable nodes in the three tests after
123    * that.
124    */
125   @Test (timeout=30000)
126   public void testHBaseRootZNodeACL() throws Exception {
127     if (!secureZKAvailable) {
128       return;
129     }
130 
131     List<ACL> acls = zkw.getRecoverableZooKeeper().getZooKeeper()
132         .getACL("/hbase", new Stat());
133     assertEquals(acls.size(),1);
134     assertEquals(acls.get(0).getId().getScheme(),"sasl");
135     assertEquals(acls.get(0).getId().getId(),"hbase");
136     assertEquals(acls.get(0).getPerms(), ZooDefs.Perms.ALL);
137   }
138 
139   /**
140    * When authentication is enabled on Zookeeper, /hbase/root-region-server
141    * should be created with 2 ACLs: one specifies that the hbase user has
142    * full access to the node; the other, that it is world-readable.
143    */
144   @Test (timeout=30000)
145   public void testHBaseRootRegionServerZNodeACL() throws Exception {
146     if (!secureZKAvailable) {
147       return;
148     }
149 
150     List<ACL> acls = zkw.getRecoverableZooKeeper().getZooKeeper()
151         .getACL("/hbase/root-region-server", new Stat());
152     assertEquals(acls.size(),2);
153 
154     boolean foundWorldReadableAcl = false;
155     boolean foundHBaseOwnerAcl = false;
156     for(int i = 0; i < 2; i++) {
157       if (acls.get(i).getId().getScheme().equals("world") == true) {
158         assertEquals(acls.get(0).getId().getId(),"anyone");
159         assertEquals(acls.get(0).getPerms(), ZooDefs.Perms.READ);
160         foundWorldReadableAcl = true;
161       }
162       else {
163         if (acls.get(i).getId().getScheme().equals("sasl") == true) {
164           assertEquals(acls.get(1).getId().getId(),"hbase");
165           assertEquals(acls.get(1).getId().getScheme(),"sasl");
166           foundHBaseOwnerAcl = true;
167         } else { // error: should not get here: test fails.
168           assertTrue(false);
169         }
170       }
171     }
172     assertTrue(foundWorldReadableAcl);
173     assertTrue(foundHBaseOwnerAcl);
174   }
175 
176   /**
177    * When authentication is enabled on Zookeeper, /hbase/master should be
178    * created with 2 ACLs: one specifies that the hbase user has full access
179    * to the node; the other, that it is world-readable.
180    */
181   @Test (timeout=30000)
182   public void testHBaseMasterServerZNodeACL() throws Exception {
183     if (!secureZKAvailable) {
184       return;
185     }
186 
187     List<ACL> acls = zkw.getRecoverableZooKeeper().getZooKeeper()
188         .getACL("/hbase/master", new Stat());
189     assertEquals(acls.size(),2);
190 
191     boolean foundWorldReadableAcl = false;
192     boolean foundHBaseOwnerAcl = false;
193     for(int i = 0; i < 2; i++) {
194       if (acls.get(i).getId().getScheme().equals("world") == true) {
195         assertEquals(acls.get(0).getId().getId(),"anyone");
196         assertEquals(acls.get(0).getPerms(), ZooDefs.Perms.READ);
197         foundWorldReadableAcl = true;
198       } else {
199         if (acls.get(i).getId().getScheme().equals("sasl") == true) {
200           assertEquals(acls.get(1).getId().getId(),"hbase");
201           assertEquals(acls.get(1).getId().getScheme(),"sasl");
202           foundHBaseOwnerAcl = true;
203         } else { // error: should not get here: test fails.
204           assertTrue(false);
205         }
206       }
207     }
208     assertTrue(foundWorldReadableAcl);
209     assertTrue(foundHBaseOwnerAcl);
210   }
211 
212   /**
213    * When authentication is enabled on Zookeeper, /hbase/hbaseid should be
214    * created with 2 ACLs: one specifies that the hbase user has full access
215    * to the node; the other, that it is world-readable.
216    */
217   @Test (timeout=30000)
218   public void testHBaseIDZNodeACL() throws Exception {
219     if (!secureZKAvailable) {
220       return;
221     }
222 
223     List<ACL> acls = zkw.getRecoverableZooKeeper().getZooKeeper()
224         .getACL("/hbase/hbaseid", new Stat());
225     assertEquals(acls.size(),2);
226 
227     boolean foundWorldReadableAcl = false;
228     boolean foundHBaseOwnerAcl = false;
229     for(int i = 0; i < 2; i++) {
230       if (acls.get(i).getId().getScheme().equals("world") == true) {
231         assertEquals(acls.get(0).getId().getId(),"anyone");
232         assertEquals(acls.get(0).getPerms(), ZooDefs.Perms.READ);
233         foundWorldReadableAcl = true;
234       } else {
235         if (acls.get(i).getId().getScheme().equals("sasl") == true) {
236           assertEquals(acls.get(1).getId().getId(),"hbase");
237           assertEquals(acls.get(1).getId().getScheme(),"sasl");
238           foundHBaseOwnerAcl = true;
239         } else { // error: should not get here: test fails.
240           assertTrue(false);
241         }
242       }
243     }
244     assertTrue(foundWorldReadableAcl);
245     assertTrue(foundHBaseOwnerAcl);
246   }
247 
248   /**
249    * Finally, we check the ACLs of a node outside of the /hbase hierarchy and
250    * verify that its ACL is simply 'hbase:Perms.ALL'.
251    */
252   @Test
253   public void testOutsideHBaseNodeACL() throws Exception {
254     if (!secureZKAvailable) {
255       return;
256     }
257 
258     ZKUtil.createWithParents(zkw, "/testACLNode");
259     List<ACL> acls = zkw.getRecoverableZooKeeper().getZooKeeper()
260         .getACL("/testACLNode", new Stat());
261     assertEquals(acls.size(),1);
262     assertEquals(acls.get(0).getId().getScheme(),"sasl");
263     assertEquals(acls.get(0).getId().getId(),"hbase");
264     assertEquals(acls.get(0).getPerms(), ZooDefs.Perms.ALL);
265   }
266 
267   /**
268    * Check if ZooKeeper JaasConfiguration is valid.
269    */
270   @Test
271   public void testIsZooKeeperSecure() throws Exception {
272     boolean testJaasConfig = ZKUtil.isSecureZooKeeper(new Configuration(TEST_UTIL.getConfiguration()));
273     assertEquals(testJaasConfig, secureZKAvailable);
274     // Define Jaas configuration without ZooKeeper Jaas config
275     File saslConfFile = File.createTempFile("tmp", "fakeJaas.conf");
276     FileWriter fwriter = new FileWriter(saslConfFile);
277 
278     fwriter.write("");
279     fwriter.close();
280     System.setProperty("java.security.auth.login.config",
281         saslConfFile.getAbsolutePath());
282 
283     testJaasConfig = ZKUtil.isSecureZooKeeper(new Configuration(TEST_UTIL.getConfiguration()));
284     assertEquals(testJaasConfig, false);
285     saslConfFile.delete();
286   }
287   
288   /**
289    * Check if Programmatic way of setting zookeeper security settings is valid.
290    */
291   @Test
292   public void testIsZooKeeperSecureWithProgrammaticConfig() throws Exception {
293 
294     javax.security.auth.login.Configuration.setConfiguration(new DummySecurityConfiguration());
295 
296     Configuration config = new Configuration(HBaseConfiguration.create());
297     boolean testJaasConfig = ZKUtil.isSecureZooKeeper(config);
298     assertEquals(testJaasConfig, false);
299 
300     // Now set authentication scheme to Kerberos still it should return false
301     // because no configuration set
302     config.set("hbase.security.authentication", "kerberos");
303     testJaasConfig = ZKUtil.isSecureZooKeeper(config);
304     assertEquals(testJaasConfig, false);
305 
306     // Now set programmatic options related to security
307     config.set(HConstants.ZK_CLIENT_KEYTAB_FILE, "/dummy/file");
308     config.set(HConstants.ZK_CLIENT_KERBEROS_PRINCIPAL, "dummy");
309     config.set(HConstants.ZK_SERVER_KEYTAB_FILE, "/dummy/file");
310     config.set(HConstants.ZK_SERVER_KERBEROS_PRINCIPAL, "dummy");
311     testJaasConfig = ZKUtil.isSecureZooKeeper(config);
312     assertEquals(true, testJaasConfig);
313   }
314 
315   private static class DummySecurityConfiguration extends javax.security.auth.login.Configuration {
316     @Override
317     public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
318       return null;
319     }
320   }
321 
322 }
323