Skip to content

Commit ea97e1e

Browse files
committed
HBASE-26616 Refactor code related to ZooKeeper authentication
This refactor reduces the size and scope of the `ZKUtil` class. The core of this refactor is moving the `login*` methods from `ZKUtil` into their own class, `ZKAuthentication`. The class `JaasConfiguration` is also moved along with them. Signed-off-by: Andrew Purtell <[email protected]> Signed-off-by: Duo Zhang <[email protected]>
1 parent 70cb9b0 commit ea97e1e

File tree

9 files changed

+323
-300
lines changed

9 files changed

+323
-300
lines changed

hbase-server/src/main/java/org/apache/hadoop/hbase/HBaseServerBase.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
import org.apache.hadoop.hbase.util.Pair;
6565
import org.apache.hadoop.hbase.util.Sleeper;
6666
import org.apache.hadoop.hbase.zookeeper.ClusterStatusTracker;
67-
import org.apache.hadoop.hbase.zookeeper.ZKUtil;
67+
import org.apache.hadoop.hbase.zookeeper.ZKAuthentication;
6868
import org.apache.hadoop.hbase.zookeeper.ZKWatcher;
6969
import org.apache.yetus.audience.InterfaceAudience;
7070
import org.slf4j.Logger;
@@ -245,7 +245,7 @@ public HBaseServerBase(Configuration conf, String name)
245245
this.useThisHostnameInstead;
246246
serverName = ServerName.valueOf(hostName, addr.getPort(), this.startcode);
247247
// login the zookeeper client principal (if using security)
248-
ZKUtil.loginClient(this.conf, HConstants.ZK_CLIENT_KEYTAB_FILE,
248+
ZKAuthentication.loginClient(this.conf, HConstants.ZK_CLIENT_KEYTAB_FILE,
249249
HConstants.ZK_CLIENT_KERBEROS_PRINCIPAL, hostName);
250250
// login the server principal (if using secure Hadoop)
251251
login(userProvider, hostName);

hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMasterCommandLine.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
import org.apache.hadoop.hbase.util.JVMClusterUtil;
3535
import org.apache.hadoop.hbase.util.ServerCommandLine;
3636
import org.apache.hadoop.hbase.zookeeper.MiniZooKeeperCluster;
37-
import org.apache.hadoop.hbase.zookeeper.ZKUtil;
37+
import org.apache.hadoop.hbase.zookeeper.ZKAuthentication;
3838
import org.apache.hadoop.metrics2.lib.DefaultMetricsSystem;
3939
import org.apache.yetus.audience.InterfaceAudience;
4040
import org.apache.zookeeper.KeeperException;
@@ -214,7 +214,7 @@ private int startMaster() {
214214
}
215215

216216
// login the zookeeper server principal (if using security)
217-
ZKUtil.loginServer(conf, HConstants.ZK_SERVER_KEYTAB_FILE,
217+
ZKAuthentication.loginServer(conf, HConstants.ZK_SERVER_KEYTAB_FILE,
218218
HConstants.ZK_SERVER_KERBEROS_PRINCIPAL, null);
219219
int localZKClusterSessionTimeout =
220220
conf.getInt(HConstants.ZK_SESSION_TIMEOUT + ".localHBaseCluster", 10*1000);

hbase-server/src/test/java/org/apache/hadoop/hbase/zookeeper/TestZooKeeperACL.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ public void testOutsideHBaseNodeACL() throws Exception {
277277
@Test
278278
public void testIsZooKeeperSecure() throws Exception {
279279
boolean testJaasConfig =
280-
ZKUtil.isSecureZooKeeper(new Configuration(TEST_UTIL.getConfiguration()));
280+
ZKAuthentication.isSecureZooKeeper(new Configuration(TEST_UTIL.getConfiguration()));
281281
assertEquals(testJaasConfig, secureZKAvailable);
282282
// Define Jaas configuration without ZooKeeper Jaas config
283283
File saslConfFile = File.createTempFile("tmp", "fakeJaas.conf");
@@ -289,7 +289,7 @@ public void testIsZooKeeperSecure() throws Exception {
289289
System.setProperty("java.security.auth.login.config",
290290
saslConfFile.getAbsolutePath());
291291

292-
testJaasConfig = ZKUtil.isSecureZooKeeper(new Configuration(TEST_UTIL.getConfiguration()));
292+
testJaasConfig = ZKAuthentication.isSecureZooKeeper(new Configuration(TEST_UTIL.getConfiguration()));
293293
assertFalse(testJaasConfig);
294294
saslConfFile.delete();
295295
}
@@ -303,21 +303,21 @@ public void testIsZooKeeperSecureWithProgrammaticConfig() throws Exception {
303303
javax.security.auth.login.Configuration.setConfiguration(new DummySecurityConfiguration());
304304

305305
Configuration config = new Configuration(HBaseConfiguration.create());
306-
boolean testJaasConfig = ZKUtil.isSecureZooKeeper(config);
306+
boolean testJaasConfig = ZKAuthentication.isSecureZooKeeper(config);
307307
assertFalse(testJaasConfig);
308308

309309
// Now set authentication scheme to Kerberos still it should return false
310310
// because no configuration set
311311
config.set("hbase.security.authentication", "kerberos");
312-
testJaasConfig = ZKUtil.isSecureZooKeeper(config);
312+
testJaasConfig = ZKAuthentication.isSecureZooKeeper(config);
313313
assertFalse(testJaasConfig);
314314

315315
// Now set programmatic options related to security
316316
config.set(HConstants.ZK_CLIENT_KEYTAB_FILE, "/dummy/file");
317317
config.set(HConstants.ZK_CLIENT_KERBEROS_PRINCIPAL, "dummy");
318318
config.set(HConstants.ZK_SERVER_KEYTAB_FILE, "/dummy/file");
319319
config.set(HConstants.ZK_SERVER_KERBEROS_PRINCIPAL, "dummy");
320-
testJaasConfig = ZKUtil.isSecureZooKeeper(config);
320+
testJaasConfig = ZKAuthentication.isSecureZooKeeper(config);
321321
assertTrue(testJaasConfig);
322322
}
323323

hbase-zookeeper/src/main/java/org/apache/hadoop/hbase/zookeeper/HQuorumPeer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ public static void main(String[] args) {
7676
zkConfig.parseProperties(zkProperties);
7777

7878
// login the zookeeper server principal (if using security)
79-
ZKUtil.loginServer(conf, HConstants.ZK_SERVER_KEYTAB_FILE,
79+
ZKAuthentication.loginServer(conf, HConstants.ZK_SERVER_KEYTAB_FILE,
8080
HConstants.ZK_SERVER_KERBEROS_PRINCIPAL,
8181
zkConfig.getClientPortAddress().getHostName());
8282

hbase-zookeeper/src/main/java/org/apache/hadoop/hbase/zookeeper/ZKAclReset.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
package org.apache.hadoop.hbase.zookeeper;
2121

2222
import java.util.List;
23-
2423
import org.apache.hadoop.conf.Configuration;
2524
import org.apache.hadoop.conf.Configured;
2625
import org.apache.hadoop.hbase.HBaseConfiguration;
@@ -61,7 +60,7 @@ private static void resetAcls(final ZKWatcher zkw, final String znode,
6160
zk.setACL(znode, ZooDefs.Ids.OPEN_ACL_UNSAFE, -1);
6261
} else {
6362
LOG.info(" - set ACLs for {}", znode);
64-
zk.setACL(znode, ZKUtil.createACL(zkw, znode, true), -1);
63+
zk.setACL(znode, zkw.createACL(znode, true), -1);
6564
}
6665
}
6766

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
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.zookeeper;
20+
21+
import java.io.IOException;
22+
import java.util.HashMap;
23+
import java.util.Map;
24+
import javax.security.auth.login.AppConfigurationEntry;
25+
import org.apache.hadoop.conf.Configuration;
26+
import org.apache.hadoop.hbase.HConstants;
27+
import org.apache.hadoop.security.SecurityUtil;
28+
import org.apache.hadoop.security.authentication.util.KerberosUtil;
29+
import org.apache.yetus.audience.InterfaceAudience;
30+
import org.apache.zookeeper.client.ZooKeeperSaslClient;
31+
import org.apache.zookeeper.server.ZooKeeperSaslServer;
32+
import org.slf4j.Logger;
33+
import org.slf4j.LoggerFactory;
34+
35+
/**
36+
* Provides ZooKeeper authentication services for both client and server processes.
37+
*/
38+
@InterfaceAudience.Private
39+
public final class ZKAuthentication {
40+
private static final Logger LOG = LoggerFactory.getLogger(ZKAuthentication.class);
41+
42+
private ZKAuthentication() {}
43+
44+
/**
45+
* Log in the current zookeeper server process using the given configuration
46+
* keys for the credential file and login principal.
47+
*
48+
* <p><strong>This is only applicable when running on secure hbase</strong>
49+
* On regular HBase (without security features), this will safely be ignored.
50+
* </p>
51+
*
52+
* @param conf The configuration data to use
53+
* @param keytabFileKey Property key used to configure the path to the credential file
54+
* @param userNameKey Property key used to configure the login principal
55+
* @param hostname Current hostname to use in any credentials
56+
* @throws IOException underlying exception from SecurityUtil.login() call
57+
*/
58+
public static void loginServer(Configuration conf, String keytabFileKey,
59+
String userNameKey, String hostname) throws IOException {
60+
login(conf, keytabFileKey, userNameKey, hostname,
61+
ZooKeeperSaslServer.LOGIN_CONTEXT_NAME_KEY,
62+
JaasConfiguration.SERVER_KEYTAB_KERBEROS_CONFIG_NAME);
63+
}
64+
65+
/**
66+
* Log in the current zookeeper client using the given configuration
67+
* keys for the credential file and login principal.
68+
*
69+
* <p><strong>This is only applicable when running on secure hbase</strong>
70+
* On regular HBase (without security features), this will safely be ignored.
71+
* </p>
72+
*
73+
* @param conf The configuration data to use
74+
* @param keytabFileKey Property key used to configure the path to the credential file
75+
* @param userNameKey Property key used to configure the login principal
76+
* @param hostname Current hostname to use in any credentials
77+
* @throws IOException underlying exception from SecurityUtil.login() call
78+
*/
79+
public static void loginClient(Configuration conf, String keytabFileKey,
80+
String userNameKey, String hostname) throws IOException {
81+
login(conf, keytabFileKey, userNameKey, hostname,
82+
ZooKeeperSaslClient.LOGIN_CONTEXT_NAME_KEY,
83+
JaasConfiguration.CLIENT_KEYTAB_KERBEROS_CONFIG_NAME);
84+
}
85+
86+
/**
87+
* Log in the current process using the given configuration keys for the
88+
* credential file and login principal.
89+
*
90+
* <p><strong>This is only applicable when running on secure hbase</strong>
91+
* On regular HBase (without security features), this will safely be ignored.
92+
* </p>
93+
*
94+
* @param conf The configuration data to use
95+
* @param keytabFileKey Property key used to configure the path to the credential file
96+
* @param userNameKey Property key used to configure the login principal
97+
* @param hostname Current hostname to use in any credentials
98+
* @param loginContextProperty property name to expose the entry name
99+
* @param loginContextName jaas entry name
100+
* @throws IOException underlying exception from SecurityUtil.login() call
101+
*/
102+
private static void login(Configuration conf, String keytabFileKey,
103+
String userNameKey, String hostname,
104+
String loginContextProperty, String loginContextName)
105+
throws IOException {
106+
if (!isSecureZooKeeper(conf)) {
107+
return;
108+
}
109+
110+
// User has specified a jaas.conf, keep this one as the good one.
111+
// HBASE_OPTS="-Djava.security.auth.login.config=jaas.conf"
112+
if (System.getProperty("java.security.auth.login.config") != null) {
113+
return;
114+
}
115+
116+
// No keytab specified, no auth
117+
String keytabFilename = conf.get(keytabFileKey);
118+
if (keytabFilename == null) {
119+
LOG.warn("no keytab specified for: {}", keytabFileKey);
120+
return;
121+
}
122+
123+
String principalConfig = conf.get(userNameKey, System.getProperty("user.name"));
124+
String principalName = SecurityUtil.getServerPrincipal(principalConfig, hostname);
125+
126+
// Initialize the "jaas.conf" for keyTab/principal,
127+
// If keyTab is not specified use the Ticket Cache.
128+
// and set the zookeeper login context name.
129+
JaasConfiguration jaasConf = new JaasConfiguration(loginContextName,
130+
principalName, keytabFilename);
131+
javax.security.auth.login.Configuration.setConfiguration(jaasConf);
132+
System.setProperty(loginContextProperty, loginContextName);
133+
}
134+
135+
/**
136+
* Returns {@code true} when secure authentication is enabled
137+
* (whether {@code hbase.security.authentication} is set to
138+
* "{@code kerberos}").
139+
*/
140+
public static boolean isSecureZooKeeper(Configuration conf) {
141+
// Detection for embedded HBase client with jaas configuration
142+
// defined for third party programs.
143+
try {
144+
javax.security.auth.login.Configuration testConfig =
145+
javax.security.auth.login.Configuration.getConfiguration();
146+
if (testConfig.getAppConfigurationEntry("Client") == null
147+
&& testConfig.getAppConfigurationEntry(
148+
JaasConfiguration.CLIENT_KEYTAB_KERBEROS_CONFIG_NAME) == null
149+
&& testConfig.getAppConfigurationEntry(
150+
JaasConfiguration.SERVER_KEYTAB_KERBEROS_CONFIG_NAME) == null
151+
&& conf.get(HConstants.ZK_CLIENT_KERBEROS_PRINCIPAL) == null
152+
&& conf.get(HConstants.ZK_SERVER_KERBEROS_PRINCIPAL) == null) {
153+
154+
return false;
155+
}
156+
} catch(Exception e) {
157+
// No Jaas configuration defined.
158+
return false;
159+
}
160+
161+
// Master & RSs uses hbase.zookeeper.client.*
162+
return "kerberos".equalsIgnoreCase(conf.get("hbase.security.authentication"));
163+
}
164+
165+
/**
166+
* A JAAS configuration that defines the login modules that we want to use for ZooKeeper login.
167+
*/
168+
private static class JaasConfiguration extends javax.security.auth.login.Configuration {
169+
private static final Logger LOG = LoggerFactory.getLogger(JaasConfiguration.class);
170+
171+
public static final String SERVER_KEYTAB_KERBEROS_CONFIG_NAME =
172+
"zookeeper-server-keytab-kerberos";
173+
public static final String CLIENT_KEYTAB_KERBEROS_CONFIG_NAME =
174+
"zookeeper-client-keytab-kerberos";
175+
176+
private static final Map<String, String> BASIC_JAAS_OPTIONS = new HashMap<>();
177+
178+
static {
179+
String jaasEnvVar = System.getenv("HBASE_JAAS_DEBUG");
180+
if ("true".equalsIgnoreCase(jaasEnvVar)) {
181+
BASIC_JAAS_OPTIONS.put("debug", "true");
182+
}
183+
}
184+
185+
private static final Map<String, String> KEYTAB_KERBEROS_OPTIONS = new HashMap<>();
186+
187+
static {
188+
KEYTAB_KERBEROS_OPTIONS.put("doNotPrompt", "true");
189+
KEYTAB_KERBEROS_OPTIONS.put("storeKey", "true");
190+
KEYTAB_KERBEROS_OPTIONS.put("refreshKrb5Config", "true");
191+
KEYTAB_KERBEROS_OPTIONS.putAll(BASIC_JAAS_OPTIONS);
192+
}
193+
194+
private static final AppConfigurationEntry KEYTAB_KERBEROS_LOGIN =
195+
new AppConfigurationEntry(KerberosUtil.getKrb5LoginModuleName(),
196+
AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, KEYTAB_KERBEROS_OPTIONS);
197+
198+
private static final AppConfigurationEntry[] KEYTAB_KERBEROS_CONF =
199+
new AppConfigurationEntry[] { KEYTAB_KERBEROS_LOGIN };
200+
201+
private javax.security.auth.login.Configuration baseConfig;
202+
private final String loginContextName;
203+
private final boolean useTicketCache;
204+
private final String keytabFile;
205+
private final String principal;
206+
207+
public JaasConfiguration(String loginContextName, String principal, String keytabFile) {
208+
this(loginContextName, principal, keytabFile, keytabFile == null || keytabFile.length() == 0);
209+
}
210+
211+
private JaasConfiguration(String loginContextName, String principal, String keytabFile,
212+
boolean useTicketCache) {
213+
try {
214+
this.baseConfig = javax.security.auth.login.Configuration.getConfiguration();
215+
} catch (SecurityException e) {
216+
this.baseConfig = null;
217+
}
218+
this.loginContextName = loginContextName;
219+
this.useTicketCache = useTicketCache;
220+
this.keytabFile = keytabFile;
221+
this.principal = principal;
222+
LOG.info(
223+
"JaasConfiguration loginContextName={} principal={} useTicketCache={} keytabFile={}",
224+
loginContextName, principal, useTicketCache, keytabFile);
225+
}
226+
227+
@Override public AppConfigurationEntry[] getAppConfigurationEntry(String appName) {
228+
if (loginContextName.equals(appName)) {
229+
if (!useTicketCache) {
230+
KEYTAB_KERBEROS_OPTIONS.put("keyTab", keytabFile);
231+
KEYTAB_KERBEROS_OPTIONS.put("useKeyTab", "true");
232+
}
233+
KEYTAB_KERBEROS_OPTIONS.put("principal", principal);
234+
KEYTAB_KERBEROS_OPTIONS.put("useTicketCache", useTicketCache ? "true" : "false");
235+
return KEYTAB_KERBEROS_CONF;
236+
}
237+
238+
if (baseConfig != null) {
239+
return baseConfig.getAppConfigurationEntry(appName);
240+
}
241+
242+
return (null);
243+
}
244+
}
245+
}

0 commit comments

Comments
 (0)