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 java.io.IOException; 021import java.util.HashMap; 022import java.util.Map; 023import javax.security.auth.login.AppConfigurationEntry; 024import org.apache.hadoop.conf.Configuration; 025import org.apache.hadoop.hbase.HConstants; 026import org.apache.hadoop.security.SecurityUtil; 027import org.apache.hadoop.security.authentication.util.KerberosUtil; 028import org.apache.yetus.audience.InterfaceAudience; 029import org.apache.zookeeper.client.ZooKeeperSaslClient; 030import org.apache.zookeeper.server.ZooKeeperSaslServer; 031import org.slf4j.Logger; 032import org.slf4j.LoggerFactory; 033 034/** 035 * Provides ZooKeeper authentication services for both client and server processes. 036 */ 037@InterfaceAudience.Private 038public final class ZKAuthentication { 039 private static final Logger LOG = LoggerFactory.getLogger(ZKAuthentication.class); 040 041 private ZKAuthentication() { 042 } 043 044 /** 045 * Log in the current zookeeper server process using the given configuration keys for the 046 * credential file and login principal. 047 * <p> 048 * <strong>This is only applicable when running on secure hbase</strong> On regular HBase (without 049 * security features), this will safely be ignored. 050 * </p> 051 * @param conf The configuration data to use 052 * @param keytabFileKey Property key used to configure the path to the credential file 053 * @param userNameKey Property key used to configure the login principal 054 * @param hostname Current hostname to use in any credentials 055 * @throws IOException underlying exception from SecurityUtil.login() call 056 */ 057 public static void loginServer(Configuration conf, String keytabFileKey, String userNameKey, 058 String hostname) throws IOException { 059 login(conf, keytabFileKey, userNameKey, hostname, ZooKeeperSaslServer.LOGIN_CONTEXT_NAME_KEY, 060 JaasConfiguration.SERVER_KEYTAB_KERBEROS_CONFIG_NAME); 061 } 062 063 /** 064 * Log in the current zookeeper client using the given configuration keys for the credential file 065 * and login principal. 066 * <p> 067 * <strong>This is only applicable when running on secure hbase</strong> On regular HBase (without 068 * security features), this will safely be ignored. 069 * </p> 070 * @param conf The configuration data to use 071 * @param keytabFileKey Property key used to configure the path to the credential file 072 * @param userNameKey Property key used to configure the login principal 073 * @param hostname Current hostname to use in any credentials 074 * @throws IOException underlying exception from SecurityUtil.login() call 075 */ 076 public static void loginClient(Configuration conf, String keytabFileKey, String userNameKey, 077 String hostname) throws IOException { 078 login(conf, keytabFileKey, userNameKey, hostname, ZooKeeperSaslClient.LOGIN_CONTEXT_NAME_KEY, 079 JaasConfiguration.CLIENT_KEYTAB_KERBEROS_CONFIG_NAME); 080 } 081 082 /** 083 * Log in the current process using the given configuration keys for the credential file and login 084 * principal. 085 * <p> 086 * <strong>This is only applicable when running on secure hbase</strong> On regular HBase (without 087 * security features), this will safely be ignored. 088 * </p> 089 * @param conf The configuration data to use 090 * @param keytabFileKey Property key used to configure the path to the credential file 091 * @param userNameKey Property key used to configure the login principal 092 * @param hostname Current hostname to use in any credentials 093 * @param loginContextProperty property name to expose the entry name 094 * @param loginContextName jaas entry name 095 * @throws IOException underlying exception from SecurityUtil.login() call 096 */ 097 private static void login(Configuration conf, String keytabFileKey, String userNameKey, 098 String hostname, String loginContextProperty, String loginContextName) throws IOException { 099 if (!isSecureZooKeeper(conf)) { 100 return; 101 } 102 103 // User has specified a jaas.conf, keep this one as the good one. 104 // HBASE_OPTS="-Djava.security.auth.login.config=jaas.conf" 105 if (System.getProperty("java.security.auth.login.config") != null) { 106 return; 107 } 108 109 // No keytab specified, no auth 110 String keytabFilename = conf.get(keytabFileKey); 111 if (keytabFilename == null) { 112 LOG.warn("no keytab specified for: {}", keytabFileKey); 113 return; 114 } 115 116 String principalConfig = conf.get(userNameKey, System.getProperty("user.name")); 117 String principalName = SecurityUtil.getServerPrincipal(principalConfig, hostname); 118 119 // Initialize the "jaas.conf" for keyTab/principal, 120 // If keyTab is not specified use the Ticket Cache. 121 // and set the zookeeper login context name. 122 JaasConfiguration jaasConf = 123 new JaasConfiguration(loginContextName, principalName, keytabFilename); 124 javax.security.auth.login.Configuration.setConfiguration(jaasConf); 125 System.setProperty(loginContextProperty, loginContextName); 126 } 127 128 /** 129 * Returns {@code true} when secure authentication is enabled (whether 130 * {@code hbase.security.authentication} is set to "{@code kerberos}"). 131 */ 132 public static boolean isSecureZooKeeper(Configuration conf) { 133 // Detection for embedded HBase client with jaas configuration 134 // defined for third party programs. 135 try { 136 javax.security.auth.login.Configuration testConfig = 137 javax.security.auth.login.Configuration.getConfiguration(); 138 if ( 139 testConfig.getAppConfigurationEntry("Client") == null 140 && testConfig 141 .getAppConfigurationEntry(JaasConfiguration.CLIENT_KEYTAB_KERBEROS_CONFIG_NAME) == null 142 && testConfig 143 .getAppConfigurationEntry(JaasConfiguration.SERVER_KEYTAB_KERBEROS_CONFIG_NAME) == null 144 && conf.get(HConstants.ZK_CLIENT_KERBEROS_PRINCIPAL) == null 145 && conf.get(HConstants.ZK_SERVER_KERBEROS_PRINCIPAL) == null 146 ) { 147 148 return false; 149 } 150 } catch (Exception e) { 151 // No Jaas configuration defined. 152 return false; 153 } 154 155 // Master & RSs uses hbase.zookeeper.client.* 156 return "kerberos".equalsIgnoreCase(conf.get("hbase.security.authentication")); 157 } 158 159 /** 160 * A JAAS configuration that defines the login modules that we want to use for ZooKeeper login. 161 */ 162 private static class JaasConfiguration extends javax.security.auth.login.Configuration { 163 private static final Logger LOG = LoggerFactory.getLogger(JaasConfiguration.class); 164 165 public static final String SERVER_KEYTAB_KERBEROS_CONFIG_NAME = 166 "zookeeper-server-keytab-kerberos"; 167 public static final String CLIENT_KEYTAB_KERBEROS_CONFIG_NAME = 168 "zookeeper-client-keytab-kerberos"; 169 170 private static final Map<String, String> BASIC_JAAS_OPTIONS = new HashMap<>(); 171 172 static { 173 String jaasEnvVar = System.getenv("HBASE_JAAS_DEBUG"); 174 if ("true".equalsIgnoreCase(jaasEnvVar)) { 175 BASIC_JAAS_OPTIONS.put("debug", "true"); 176 } 177 } 178 179 private static final Map<String, String> KEYTAB_KERBEROS_OPTIONS = new HashMap<>(); 180 181 static { 182 KEYTAB_KERBEROS_OPTIONS.put("doNotPrompt", "true"); 183 KEYTAB_KERBEROS_OPTIONS.put("storeKey", "true"); 184 KEYTAB_KERBEROS_OPTIONS.put("refreshKrb5Config", "true"); 185 KEYTAB_KERBEROS_OPTIONS.putAll(BASIC_JAAS_OPTIONS); 186 } 187 188 private static final AppConfigurationEntry KEYTAB_KERBEROS_LOGIN = 189 new AppConfigurationEntry(KerberosUtil.getKrb5LoginModuleName(), 190 AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, KEYTAB_KERBEROS_OPTIONS); 191 192 private static final AppConfigurationEntry[] KEYTAB_KERBEROS_CONF = 193 new AppConfigurationEntry[] { KEYTAB_KERBEROS_LOGIN }; 194 195 private javax.security.auth.login.Configuration baseConfig; 196 private final String loginContextName; 197 private final boolean useTicketCache; 198 private final String keytabFile; 199 private final String principal; 200 201 public JaasConfiguration(String loginContextName, String principal, String keytabFile) { 202 this(loginContextName, principal, keytabFile, keytabFile == null || keytabFile.length() == 0); 203 } 204 205 private JaasConfiguration(String loginContextName, String principal, String keytabFile, 206 boolean useTicketCache) { 207 try { 208 this.baseConfig = javax.security.auth.login.Configuration.getConfiguration(); 209 } catch (SecurityException e) { 210 this.baseConfig = null; 211 } 212 this.loginContextName = loginContextName; 213 this.useTicketCache = useTicketCache; 214 this.keytabFile = keytabFile; 215 this.principal = principal; 216 LOG.info("JaasConfiguration loginContextName={} principal={} useTicketCache={} keytabFile={}", 217 loginContextName, principal, useTicketCache, keytabFile); 218 } 219 220 @Override 221 public AppConfigurationEntry[] getAppConfigurationEntry(String appName) { 222 if (loginContextName.equals(appName)) { 223 if (!useTicketCache) { 224 KEYTAB_KERBEROS_OPTIONS.put("keyTab", keytabFile); 225 KEYTAB_KERBEROS_OPTIONS.put("useKeyTab", "true"); 226 } 227 KEYTAB_KERBEROS_OPTIONS.put("principal", principal); 228 KEYTAB_KERBEROS_OPTIONS.put("useTicketCache", useTicketCache ? "true" : "false"); 229 return KEYTAB_KERBEROS_CONF; 230 } 231 232 if (baseConfig != null) { 233 return baseConfig.getAppConfigurationEntry(appName); 234 } 235 236 return (null); 237 } 238 } 239}