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  
19  package org.apache.hadoop.hbase.test;
20  
21  import static org.junit.Assert.assertEquals;
22  import static org.junit.Assert.assertTrue;
23  import static org.junit.Assert.fail;
24  
25  import java.io.IOException;
26  import java.util.List;
27  
28  import org.apache.commons.cli.CommandLine;
29  import org.apache.commons.logging.Log;
30  import org.apache.commons.logging.LogFactory;
31  import org.apache.hadoop.conf.Configuration;
32  import org.apache.hadoop.fs.FileSystem;
33  import org.apache.hadoop.fs.Path;
34  import org.apache.hadoop.fs.permission.FsPermission;
35  import org.apache.hadoop.hbase.HBaseConfiguration;
36  import org.apache.hadoop.hbase.IntegrationTestingUtility;
37  import org.apache.hadoop.hbase.testclassification.IntegrationTests;
38  import org.apache.hadoop.hbase.util.AbstractHBaseTool;
39  import org.apache.hadoop.hbase.util.FSUtils;
40  import org.apache.hadoop.hbase.zookeeper.RecoverableZooKeeper;
41  import org.apache.hadoop.hbase.zookeeper.ZKUtil;
42  import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
43  import org.apache.hadoop.util.ToolRunner;
44  import org.apache.zookeeper.KeeperException;
45  import org.apache.zookeeper.KeeperException.Code;
46  import org.apache.zookeeper.KeeperException.NoNodeException;
47  import org.apache.zookeeper.ZooDefs.Ids;
48  import org.apache.zookeeper.ZooDefs.Perms;
49  import org.apache.zookeeper.data.ACL;
50  import org.apache.zookeeper.data.Id;
51  import org.apache.zookeeper.data.Stat;
52  import org.junit.experimental.categories.Category;
53  
54  /**
55   * An integration test which checks that the znodes in zookeeper and data in the FileSystem
56   * are protected for secure HBase deployments.
57   * This test is intended to be run on clusters with kerberos authorization for HBase and Zookeeper.
58   *
59   * If hbase.security.authentication is not set to kerberos, the test does not run unless -f is
60   * specified which bypasses the check. It is recommended to always run with -f on secure clusters
61   * so that the test checks the actual end result, not the configuration.
62   *
63   * The test should be run as hbase user with kinit / TGT cached since it accesses HDFS.
64   * <p>
65   * Example usage:
66   *   hbase org.apache.hadoop.hbase.test.IntegrationTestZnodeACLs -h
67   */
68  @Category(IntegrationTests.class)
69  public class IntegrationTestZKAndFSPermissions extends AbstractHBaseTool {
70  
71    private static final Log LOG = LogFactory.getLog(IntegrationTestZKAndFSPermissions.class);
72    private String superUser;
73    private String masterPrincipal;
74    private boolean isForce;
75    private String fsPerms;
76    private boolean skipFSCheck;
77    private boolean skipZKCheck;
78  
79    public static final String FORCE_CHECK_ARG = "f";
80    public static final String PRINCIPAL_ARG = "p";
81    public static final String SUPERUSER_ARG = "s";
82    public static final String FS_PERMS = "fs_perms";
83    public static final String SKIP_CHECK_FS = "skip_fs_check";
84    public static final String SKIP_CHECK_ZK = "skip_zk_check";
85  
86    @Override
87    public void setConf(Configuration conf) {
88      super.setConf(conf);
89    }
90  
91    @Override
92    protected void addOptions() {
93      addOptNoArg(FORCE_CHECK_ARG, "Whether to skip configuration lookup and assume a secure setup");
94      addOptWithArg(PRINCIPAL_ARG, "The principal for zk authorization");
95      addOptWithArg(SUPERUSER_ARG, "The principal for super user");
96      addOptWithArg(FS_PERMS,      "FS permissions, ex. 700, 750, etc. Defaults to 700");
97      addOptNoArg(SKIP_CHECK_FS, "Whether to skip checking FS permissions");
98      addOptNoArg(SKIP_CHECK_ZK,   "Whether to skip checking ZK permissions");
99    }
100 
101   @Override
102   protected void processOptions(CommandLine cmd) {
103     isForce = cmd.hasOption(FORCE_CHECK_ARG);
104     masterPrincipal = getShortUserName(conf.get("hbase.master.kerberos.principal"));
105     superUser = cmd.getOptionValue(SUPERUSER_ARG, conf.get("hbase.superuser"));
106     masterPrincipal = cmd.getOptionValue(PRINCIPAL_ARG, masterPrincipal);
107     fsPerms = cmd.getOptionValue(FS_PERMS, "700");
108     skipFSCheck = cmd.hasOption(SKIP_CHECK_FS);
109     skipZKCheck = cmd.hasOption(SKIP_CHECK_ZK);
110   }
111 
112   private String getShortUserName(String principal) {
113     for (int i = 0; i < principal.length(); i++) {
114       if (principal.charAt(i) == '/' || principal.charAt(i) == '@') {
115         return principal.substring(0, i);
116       }
117     }
118     return principal;
119   }
120 
121   @Override
122   protected int doWork() throws Exception {
123     if (!isForce) {
124       if (!"kerberos".equalsIgnoreCase(conf.get("hbase.security.authentication"))) {
125         LOG.warn("hbase.security.authentication is not kerberos, and -f is not supplied. Skip "
126             + "running the test");
127         return 0;
128       }
129     }
130 
131     if (!skipZKCheck) {
132       testZNodeACLs();
133     } if (!skipFSCheck) {
134       testFSPerms();
135     }
136     return 0;
137   }
138 
139   private void testZNodeACLs() throws IOException, KeeperException, InterruptedException {
140 
141     ZooKeeperWatcher watcher = new ZooKeeperWatcher(conf, "IntegrationTestZnodeACLs", null);
142     RecoverableZooKeeper zk = ZKUtil.connect(this.conf, watcher);
143 
144     String baseZNode = watcher.baseZNode;
145 
146     LOG.info("");
147     LOG.info("***********************************************************************************");
148     LOG.info("Checking ZK permissions, root znode: " + baseZNode);
149     LOG.info("***********************************************************************************");
150     LOG.info("");
151 
152     checkZnodePermsRecursive(watcher, zk, baseZNode);
153 
154     LOG.info("Checking ZK permissions: SUCCESS");
155   }
156 
157   private void checkZnodePermsRecursive(ZooKeeperWatcher watcher,
158       RecoverableZooKeeper zk, String znode) throws KeeperException, InterruptedException {
159 
160     boolean expectedWorldReadable = watcher.isClientReadable(znode);
161 
162     assertZnodePerms(zk, znode, expectedWorldReadable);
163 
164     try {
165       List<String> children = zk.getChildren(znode, false);
166 
167       for (String child : children) {
168         checkZnodePermsRecursive(watcher, zk, ZKUtil.joinZNode(znode, child));
169       }
170     } catch (KeeperException ke) {
171       // if we are not authenticated for listChildren, it is fine.
172       if (ke.code() != Code.NOAUTH && ke.code() != Code.NONODE) {
173         throw ke;
174       }
175     }
176   }
177 
178   private void assertZnodePerms(RecoverableZooKeeper zk, String znode,
179       boolean expectedWorldReadable) throws KeeperException, InterruptedException {
180     Stat stat = new Stat();
181     List<ACL> acls;
182     try {
183       acls = zk.getZooKeeper().getACL(znode, stat);
184     } catch (NoNodeException ex) {
185       LOG.debug("Caught exception for missing znode", ex);
186       // the znode is deleted. Probably it was a temporary znode (like RIT).
187       return;
188     }
189     String[] superUsers = superUser == null ? null : superUser.split(",");
190 
191     LOG.info("Checking ACLs for znode znode:" + znode + " acls:" + acls);
192 
193     for (ACL acl : acls) {
194       int perms = acl.getPerms();
195       Id id = acl.getId();
196       // We should only set at most 3 possible ACL for 3 Ids. One for everyone, one for superuser
197       // and one for the hbase user
198       if (Ids.ANYONE_ID_UNSAFE.equals(id)) {
199         // everyone should be set only if we are expecting this znode to be world readable
200         assertTrue(expectedWorldReadable);
201         // assert that anyone can only read
202         assertEquals(perms, Perms.READ);
203       } else if (superUsers != null && ZooKeeperWatcher.isSuperUserId(superUsers, id)) {
204         // assert that super user has all the permissions
205         assertEquals(perms, Perms.ALL);
206       } else if (new Id("sasl", masterPrincipal).equals(id)) {
207         // hbase.master.kerberos.principal?
208         assertEquals(perms, Perms.ALL);
209       } else {
210         fail("An ACL is found which is not expected for the znode:" + znode + " , ACL:" + acl);
211       }
212     }
213   }
214 
215   private void testFSPerms() throws IOException {
216     Path rootDir = FSUtils.getRootDir(conf);
217 
218     LOG.info("");
219     LOG.info("***********************************************************************************");
220     LOG.info("Checking FS permissions for root dir:" + rootDir);
221     LOG.info("***********************************************************************************");
222     LOG.info("");
223     FileSystem fs = rootDir.getFileSystem(conf);
224 
225     short expectedPerms = Short.valueOf(fsPerms, 8);
226 
227     assertEquals(
228       FsPermission.createImmutable(expectedPerms),
229       fs.getFileStatus(rootDir).getPermission());
230 
231     LOG.info("Checking FS permissions: SUCCESS");
232   }
233 
234   public static void main(String[] args) throws Exception {
235     Configuration configuration = HBaseConfiguration.create();
236     IntegrationTestingUtility.setUseDistributedCluster(configuration);
237     IntegrationTestZKAndFSPermissions tool = new IntegrationTestZKAndFSPermissions();
238     int ret = ToolRunner.run(configuration, tool, args);
239     System.exit(ret);
240   }
241 }