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 */ 018 019package org.apache.hadoop.hbase.test; 020 021import static org.junit.Assert.assertEquals; 022import static org.junit.Assert.assertTrue; 023import static org.junit.Assert.fail; 024 025import java.io.IOException; 026import java.util.List; 027import org.apache.hadoop.conf.Configuration; 028import org.apache.hadoop.fs.FileSystem; 029import org.apache.hadoop.fs.Path; 030import org.apache.hadoop.fs.permission.FsPermission; 031import org.apache.hadoop.hbase.HBaseConfiguration; 032import org.apache.hadoop.hbase.IntegrationTestingUtility; 033import org.apache.hadoop.hbase.security.SecurityConstants; 034import org.apache.hadoop.hbase.testclassification.IntegrationTests; 035import org.apache.hadoop.hbase.util.AbstractHBaseTool; 036import org.apache.hadoop.hbase.util.CommonFSUtils; 037import org.apache.hadoop.hbase.zookeeper.RecoverableZooKeeper; 038import org.apache.hadoop.hbase.zookeeper.ZKUtil; 039import org.apache.hadoop.hbase.zookeeper.ZKWatcher; 040import org.apache.hadoop.hbase.zookeeper.ZNodePaths; 041import org.apache.hadoop.util.ToolRunner; 042import org.apache.zookeeper.KeeperException; 043import org.apache.zookeeper.KeeperException.Code; 044import org.apache.zookeeper.KeeperException.NoNodeException; 045import org.apache.zookeeper.ZooDefs.Ids; 046import org.apache.zookeeper.ZooDefs.Perms; 047import org.apache.zookeeper.data.ACL; 048import org.apache.zookeeper.data.Id; 049import org.apache.zookeeper.data.Stat; 050import org.junit.experimental.categories.Category; 051import org.slf4j.Logger; 052import org.slf4j.LoggerFactory; 053 054import org.apache.hbase.thirdparty.org.apache.commons.cli.CommandLine; 055 056/** 057 * An integration test which checks that the znodes in zookeeper and data in the FileSystem 058 * are protected for secure HBase deployments. 059 * This test is intended to be run on clusters with kerberos authorization for HBase and ZooKeeper. 060 * 061 * If hbase.security.authentication is not set to kerberos, the test does not run unless -f is 062 * specified which bypasses the check. It is recommended to always run with -f on secure clusters 063 * so that the test checks the actual end result, not the configuration. 064 * 065 * The test should be run as hbase user with kinit / TGT cached since it accesses HDFS. 066 * <p> 067 * Example usage: 068 * hbase org.apache.hadoop.hbase.test.IntegrationTestZnodeACLs -h 069 */ 070@Category(IntegrationTests.class) 071public class IntegrationTestZKAndFSPermissions extends AbstractHBaseTool { 072 private static final Logger LOG = 073 LoggerFactory.getLogger(IntegrationTestZKAndFSPermissions.class); 074 075 private String superUser; 076 private String masterPrincipal; 077 private boolean isForce; 078 private String fsPerms; 079 private boolean skipFSCheck; 080 private boolean skipZKCheck; 081 082 public static final String FORCE_CHECK_ARG = "f"; 083 public static final String PRINCIPAL_ARG = "p"; 084 public static final String SUPERUSER_ARG = "s"; 085 public static final String FS_PERMS = "fs_perms"; 086 public static final String SKIP_CHECK_FS = "skip_fs_check"; 087 public static final String SKIP_CHECK_ZK = "skip_zk_check"; 088 089 @Override 090 public void setConf(Configuration conf) { 091 super.setConf(conf); 092 } 093 094 @Override 095 protected void addOptions() { 096 addOptNoArg(FORCE_CHECK_ARG, "Whether to skip configuration lookup and assume a secure setup"); 097 addOptWithArg(PRINCIPAL_ARG, "The principal for zk authorization"); 098 addOptWithArg(SUPERUSER_ARG, "The principal for super user"); 099 addOptWithArg(FS_PERMS, "FS permissions, ex. 700, 750, etc. Defaults to 700"); 100 addOptNoArg(SKIP_CHECK_FS, "Whether to skip checking FS permissions"); 101 addOptNoArg(SKIP_CHECK_ZK, "Whether to skip checking ZK permissions"); 102 } 103 104 @Override 105 protected void processOptions(CommandLine cmd) { 106 isForce = cmd.hasOption(FORCE_CHECK_ARG); 107 masterPrincipal = getShortUserName(conf.get(SecurityConstants.MASTER_KRB_PRINCIPAL)); 108 superUser = cmd.getOptionValue(SUPERUSER_ARG, conf.get("hbase.superuser")); 109 masterPrincipal = cmd.getOptionValue(PRINCIPAL_ARG, masterPrincipal); 110 fsPerms = cmd.getOptionValue(FS_PERMS, "700"); 111 skipFSCheck = cmd.hasOption(SKIP_CHECK_FS); 112 skipZKCheck = cmd.hasOption(SKIP_CHECK_ZK); 113 } 114 115 private String getShortUserName(String principal) { 116 for (int i = 0; i < principal.length(); i++) { 117 if (principal.charAt(i) == '/' || principal.charAt(i) == '@') { 118 return principal.substring(0, i); 119 } 120 } 121 return principal; 122 } 123 124 @Override 125 protected int doWork() throws Exception { 126 if (!isForce) { 127 if (!"kerberos".equalsIgnoreCase(conf.get("hbase.security.authentication"))) { 128 LOG.warn("hbase.security.authentication is not kerberos, and -f is not supplied. Skip " 129 + "running the test"); 130 return 0; 131 } 132 } 133 134 if (!skipZKCheck) { 135 testZNodeACLs(); 136 } if (!skipFSCheck) { 137 testFSPerms(); 138 } 139 return 0; 140 } 141 142 private void testZNodeACLs() throws IOException, KeeperException, InterruptedException { 143 144 ZKWatcher watcher = new ZKWatcher(conf, "IntegrationTestZnodeACLs", null); 145 RecoverableZooKeeper zk = ZKUtil.connect(this.conf, watcher); 146 147 String baseZNode = watcher.getZNodePaths().baseZNode; 148 149 LOG.info(""); 150 LOG.info("***********************************************************************************"); 151 LOG.info("Checking ZK permissions, root znode: " + baseZNode); 152 LOG.info("***********************************************************************************"); 153 LOG.info(""); 154 155 checkZnodePermsRecursive(watcher, zk, baseZNode); 156 157 LOG.info("Checking ZK permissions: SUCCESS"); 158 } 159 160 private void checkZnodePermsRecursive(ZKWatcher watcher, 161 RecoverableZooKeeper zk, String znode) throws KeeperException, InterruptedException { 162 163 boolean expectedWorldReadable = watcher.getZNodePaths().isClientReadable(znode); 164 165 assertZnodePerms(zk, znode, expectedWorldReadable); 166 167 try { 168 List<String> children = zk.getChildren(znode, false); 169 170 for (String child : children) { 171 checkZnodePermsRecursive(watcher, zk, ZNodePaths.joinZNode(znode, child)); 172 } 173 } catch (KeeperException ke) { 174 // if we are not authenticated for listChildren, it is fine. 175 if (ke.code() != Code.NOAUTH && ke.code() != Code.NONODE) { 176 throw ke; 177 } 178 } 179 } 180 181 private void assertZnodePerms(RecoverableZooKeeper zk, String znode, 182 boolean expectedWorldReadable) throws KeeperException, InterruptedException { 183 Stat stat = new Stat(); 184 List<ACL> acls; 185 try { 186 acls = zk.getZooKeeper().getACL(znode, stat); 187 } catch (NoNodeException ex) { 188 LOG.debug("Caught exception for missing znode", ex); 189 // the znode is deleted. Probably it was a temporary znode (like RIT). 190 return; 191 } 192 String[] superUsers = superUser == null ? null : superUser.split(","); 193 194 LOG.info("Checking ACLs for znode znode:" + znode + " acls:" + acls); 195 196 for (ACL acl : acls) { 197 int perms = acl.getPerms(); 198 Id id = acl.getId(); 199 // We should only set at most 3 possible ACL for 3 Ids. One for everyone, one for superuser 200 // and one for the hbase user 201 if (Ids.ANYONE_ID_UNSAFE.equals(id)) { 202 // everyone should be set only if we are expecting this znode to be world readable 203 assertTrue(expectedWorldReadable); 204 // assert that anyone can only read 205 assertEquals(perms, Perms.READ); 206 } else if (superUsers != null && ZKWatcher.isSuperUserId(superUsers, id)) { 207 // assert that super user has all the permissions 208 assertEquals(perms, Perms.ALL); 209 } else if (new Id("sasl", masterPrincipal).equals(id)) { 210 // hbase.master.kerberos.principal? 211 assertEquals(perms, Perms.ALL); 212 } else { 213 fail("An ACL is found which is not expected for the znode:" + znode + " , ACL:" + acl); 214 } 215 } 216 } 217 218 private void testFSPerms() throws IOException { 219 Path rootDir = CommonFSUtils.getRootDir(conf); 220 221 LOG.info(""); 222 LOG.info("***********************************************************************************"); 223 LOG.info("Checking FS permissions for root dir:" + rootDir); 224 LOG.info("***********************************************************************************"); 225 LOG.info(""); 226 FileSystem fs = rootDir.getFileSystem(conf); 227 228 short expectedPerms = Short.valueOf(fsPerms, 8); 229 230 assertEquals( 231 FsPermission.createImmutable(expectedPerms), 232 fs.getFileStatus(rootDir).getPermission()); 233 234 LOG.info("Checking FS permissions: SUCCESS"); 235 } 236 237 public static void main(String[] args) throws Exception { 238 Configuration configuration = HBaseConfiguration.create(); 239 IntegrationTestingUtility.setUseDistributedCluster(configuration); 240 IntegrationTestZKAndFSPermissions tool = new IntegrationTestZKAndFSPermissions(); 241 int ret = ToolRunner.run(configuration, tool, args); 242 System.exit(ret); 243 } 244}