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