diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/CommonConfigurationKeysPublic.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/CommonConfigurationKeysPublic.java
index 0baca07..2cb390a 100644
--- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/CommonConfigurationKeysPublic.java
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/CommonConfigurationKeysPublic.java
@@ -317,6 +317,10 @@
public static final String HADOOP_SECURITY_DNS_NAMESERVER_KEY =
"hadoop.security.dns.nameserver";
+ /** See core-default.xml */
+ public static final String HADOOP_KERBEROS_DIAGNOSTICS_ENABLED =
+ "hadoop.kerberos.diagnostics.enabled";
+
@Deprecated
/** Only used by HttpServer. */
public static final String HADOOP_SSL_ENABLED_KEY = "hadoop.ssl.enabled";
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/KDiag.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/KDiag.java
index 4c2b0c4..b0a64b4 100644
--- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/KDiag.java
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/KDiag.java
@@ -71,6 +71,7 @@
public class KDiag extends Configured implements Tool, Closeable {
private static final Logger LOG = LoggerFactory.getLogger(KDiag.class);
+
/**
* Location of the kerberos ticket cache as passed down via an environment
* variable. This is what kinit will use by default: {@value}
@@ -112,15 +113,16 @@
public static final String ETC_KRB5_CONF = "/etc/krb5.conf";
public static final String ETC_NTP = "/etc/ntp.conf";
public static final String HADOOP_JAAS_DEBUG = "HADOOP_JAAS_DEBUG";
+ public static final int DEFAULT_MIN_KEY_LENGTH = 256;
private PrintWriter out;
private File keytab;
private String principal;
- private long minKeyLength = 256;
+ private long minKeyLength = DEFAULT_MIN_KEY_LENGTH;
private boolean securityRequired;
- private boolean nofail = false;
- private boolean nologin = false;
- private boolean jaas = false;
+ private boolean raiseExceptions = false;
+ private boolean attemptLogin = false;
+ private boolean validateJaas = false;
/**
* Flag set to true if a {@link #verify(boolean, String, String, Object...)}
@@ -148,30 +150,35 @@
public static final String ARG_SECURE = "--secure";
+ /**
+ * Empty constructor; the {@link #run(String[])} command
+ * will be needed to configure the class from CLI arguments.
+ */
+ public KDiag() {
+ }
+
+ /**
+ * Binding-driven instance
+ * @param conf Hadoop configuration
+ * @param binding binding information
+ */
@SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
- public KDiag(Configuration conf,
- PrintWriter out,
- File keytab,
- String principal,
- long minKeyLength,
- boolean securityRequired) {
+ public KDiag(Configuration conf, KDiagBinding binding) {
super(conf);
- this.keytab = keytab;
- this.principal = principal;
- this.out = out;
- this.minKeyLength = minKeyLength;
- this.securityRequired = securityRequired;
+ this.keytab = binding.keytab;
+ this.principal = binding.principal;
+ this.out = binding.out;
+ this.minKeyLength = binding.minKeyLength;
+ this.securityRequired = binding.securityRequired;
+ this.raiseExceptions = binding.raiseExceptions;
+ this.attemptLogin = binding.attemptLogin;
}
- public KDiag() {
- }
@Override
public void close() throws IOException {
flush();
- if (out != null) {
- out.close();
- }
+ IOUtils.closeQuietly(out);
}
@Override
@@ -188,9 +195,9 @@ public int run(String[] argv) throws Exception {
minKeyLength = Integer.parseInt(mkl);
}
securityRequired = popOption(ARG_SECURE, args);
- nofail = popOption(ARG_NOFAIL, args);
- jaas = popOption(ARG_JAAS, args);
- nologin = popOption(ARG_NOLOGIN, args);
+ raiseExceptions = !popOption(ARG_NOFAIL, args);
+ validateJaas = popOption(ARG_JAAS, args);
+ attemptLogin = !popOption(ARG_NOLOGIN, args);
// look for list of resources
String resource;
@@ -363,24 +370,23 @@ public boolean execute() throws Exception {
validateSasl(DFS_DATA_TRANSFER_SASLPROPERTIES_RESOLVER_CLASS);
}
validateKinitExecutable();
- validateJAAS(jaas);
+ validateJAAS(validateJaas);
validateNTPConf();
-
- if (!nologin) {
+ if (keytab != null) {
+ dumpKeytab(keytab);
+ }
+ if (attemptLogin) {
title("Logging in");
if (keytab != null) {
- dumpKeytab(keytab);
loginFromKeytab();
} else {
UserGroupInformation loginUser = getLoginUser();
dumpUGI("Log in user", loginUser);
validateUGI("Login user", loginUser);
- println("Ticket based login: %b", isLoginTicketBased());
- println("Keytab based login: %b", isLoginKeytabBased());
}
}
- return true;
+ return !probeHasFailed;
} finally {
// restore original system properties
System.setProperty(SUN_SECURITY_KRB5_DEBUG,
@@ -395,7 +401,7 @@ public boolean execute() throws Exception {
* @param conf configuration to check
* @return true if auth is simple (i.e. not kerberos)
*/
- protected boolean isSimpleAuthentication(Configuration conf) {
+ public static boolean isSimpleAuthentication(Configuration conf) {
return SecurityUtil.getAuthenticationMethod(conf)
.equals(AuthenticationMethod.SIMPLE);
}
@@ -491,15 +497,34 @@ private void validateKrb5File() throws IOException {
}
/**
+ * Validate a keytab file by loading and parsing it; the contents
+ * are dumped to the output
+ * @param keytabFile keytab file to examine
+ * @throws IOException on any failure
+ */
+ public void validateKeytab(File keytabFile) throws IOException {
+ boolean oldRaise = raiseExceptions;
+ try {
+ raiseExceptions = true;
+ dumpKeytab(keytabFile);
+ } finally {
+ raiseExceptions = oldRaise;
+ }
+ }
+
+ /**
* Dump a keytab: list all principals.
*
* @param keytabFile the keytab file
+ * @return the outcome of any validation
* @throws IOException IO problems
*/
- private void dumpKeytab(File keytabFile) throws IOException {
+ private boolean dumpKeytab(File keytabFile) throws IOException {
title("Examining keytab %s", keytabFile);
File kt = keytabFile.getCanonicalFile();
- verifyFileIsValid(kt, CAT_KERBEROS, "keytab");
+ if (!verifyFileIsValid(kt, CAT_KERBEROS, "keytab")) {
+ return false;
+ }
List entries = Keytab.read(kt).getEntries();
println("keytab entry count: %d", entries.size());
for (KeytabEntry entry : entries) {
@@ -511,6 +536,7 @@ private void dumpKeytab(File keytabFile) throws IOException {
key.getKeyType());
}
endln();
+ return true;
}
/**
@@ -557,7 +583,7 @@ private void loginFromKeytab() throws IOException {
* @param ugi UGI to dump
* @throws IOException
*/
- private void dumpUGI(String title, UserGroupInformation ugi)
+ public void dumpUGI(String title, UserGroupInformation ugi)
throws IOException {
title(title);
println("UGI instance = %s", ugi);
@@ -565,6 +591,8 @@ private void dumpUGI(String title, UserGroupInformation ugi)
println("Authentication method: %s", ugi.getAuthenticationMethod());
println("Real Authentication method: %s",
ugi.getRealAuthenticationMethod());
+ println("Login mechanism: %s",
+ ugi.isFromKeytab() ? "keytab" : ugi.isTicketBased() ? "ticket": "none");
title("Group names");
for (String name : ugi.getGroupNames()) {
println(name);
@@ -588,7 +616,7 @@ private void dumpUGI(String title, UserGroupInformation ugi)
* @param messagePrefix message in exceptions
* @param user user to validate
*/
- private void validateUGI(String messagePrefix, UserGroupInformation user) {
+ public void validateUGI(String messagePrefix, UserGroupInformation user) {
if (verify(user.getAuthenticationMethod() == AuthenticationMethod.KERBEROS,
CAT_LOGIN, "User %s is not authenticated by Kerberos", user)) {
verify(user.hasKerberosCredentials(),
@@ -606,18 +634,19 @@ private void validateUGI(String messagePrefix, UserGroupInformation user) {
* If it is just a command, it has to be on the path. There's no check
* for that -but the PATH is printed out.
*/
- private void validateKinitExecutable() {
+ private boolean validateKinitExecutable() {
String kinit = getConf().getTrimmed(KERBEROS_KINIT_COMMAND, "");
if (!kinit.isEmpty()) {
File kinitPath = new File(kinit);
println("%s = %s", KERBEROS_KINIT_COMMAND, kinitPath);
if (kinitPath.isAbsolute()) {
- verifyFileIsValid(kinitPath, CAT_KERBEROS, KERBEROS_KINIT_COMMAND);
+ return verifyFileIsValid(kinitPath, CAT_KERBEROS, KERBEROS_KINIT_COMMAND);
} else {
println("Executable %s is relative -must be on the PATH", kinit);
printEnv("PATH");
}
}
+ return true;
}
/**
@@ -677,14 +706,13 @@ private void validateNTPConf() throws IOException {
}
}
-
/**
* Verify that a file is valid: it is a file, non-empty and readable.
* @param file file
* @param category category for exceptions
* @param text text message
* @return true if the validation held; false if it did not and
- * {@link #nofail} has disabled raising exceptions.
+ * {@link #raiseExceptions} has disabled raising exceptions.
*/
private boolean verifyFileIsValid(File file, String category, String text) {
return verify(file.exists(), category,
@@ -694,7 +722,7 @@ private boolean verifyFileIsValid(File file, String category, String text) {
"%s path does not refer to a file: %s", text, file)
&& verify(file.length() != 0, category,
"%s file is empty: %s", text, file)
- && verify(file.canRead(), category,
+ && verify(file.canRead(), category,
"%s file is not readable: %s", text, file);
}
@@ -842,7 +870,7 @@ private void fail(String category, String message, Object... args)
/**
* Assert that a condition must hold.
*
- * If not, an exception is raised, or, if {@link #nofail} is set,
+ * If not, an exception is raised, or, if {@link #raiseExceptions} is clear,
* an error will be logged and the method return false.
*
* @param condition condition which must hold
@@ -862,7 +890,7 @@ private boolean verify(boolean condition,
if (!condition) {
// condition not met: fail or report
probeHasFailed = true;
- if (!nofail) {
+ if (raiseExceptions) {
fail(category, message, args);
} else {
error(category, message, args);
@@ -895,7 +923,7 @@ private void warn(String category, String message, Object...args) {
/**
* Conditional failure with string formatted arguments.
- * There is no chek for the {@link #nofail} value.
+ * There is no check for the {@link #raiseExceptions} value.
* @param condition failure condition
* @param category category for exception
* @param message string formatting message
@@ -973,4 +1001,67 @@ public String getCategory() {
return category;
}
}
+
+ /**
+ * KDiag binding information to pass down. A struct is used
+ * so that it can be extended without breaking existing code.
+ *
+ * For this to work, the default values must be "harmless", not
+ * triggering actions which would break an in-code use of the diagnostics.
+ */
+ public static class KDiagBinding {
+
+ public PrintWriter out;
+ public File keytab;
+ public String keytabConfKey;
+ public String principal;
+ public long minKeyLength = DEFAULT_MIN_KEY_LENGTH;
+ public boolean securityRequired;
+ public boolean raiseExceptions = false;
+ public boolean attemptLogin = false;
+ public boolean validateJaas = false;
+
+ public KDiagBinding withKeytab(File f) {
+ keytab = f;
+ return this;
+ }
+
+ public void withKeytabConfKey(String key) {
+ this.keytabConfKey = key;
+ }
+
+ public KDiagBinding withMinKeyLength(int l) {
+ minKeyLength = l;
+ return this;
+ }
+ public KDiagBinding withRaiseExceptions(boolean b) {
+ raiseExceptions = b;
+ return this;
+ }
+
+ public KDiagBinding withPrincipal(String principal) {
+ this.principal = principal;
+ return this;
+ }
+
+ public KDiagBinding withSecurityRequired(boolean securityRequired) {
+ this.securityRequired = securityRequired;
+ return this;
+ }
+
+ public KDiagBinding withAttemptLogin(boolean attemptLogin) {
+ this.attemptLogin = attemptLogin;
+ return this;
+ }
+
+ public KDiagBinding withValidateJaas(boolean validateJaas) {
+ this.validateJaas = validateJaas;
+ return this;
+ }
+
+ public KDiagBinding withOut(PrintWriter out) {
+ this.out = out;
+ return this;
+ }
+ }
}
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/UserGroupInformation.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/UserGroupInformation.java
index 90d396f..2f7ce64 100644
--- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/UserGroupInformation.java
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/UserGroupInformation.java
@@ -857,7 +857,15 @@ public synchronized static void setLoginUser(UserGroupInformation ugi) {
public boolean isFromKeytab() {
return isKeytab;
}
-
+
+ /**
+ * Is this user logged in from a Kerberos ticket?
+ * @return true if the user logged in from a ticket
+ */
+ public boolean isTicketBased() {
+ return isKrbTkt;
+ }
+
/**
* Get the Kerberos TGT
* @return the user's TGT or null if none was found
diff --git a/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml b/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml
index 746541a..58e0b88 100644
--- a/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml
+++ b/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml
@@ -415,7 +415,15 @@
Maps kerberos principals to local user names
-
+
+ hadoop.kerberos.diagnostics.enabled
+ false
+ If true, print extra Kerberos diagnostics during startup of those
+ services which support the feature.
+
+
+
+
io.file.buffer.size
4096
diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/ResourceManager.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/ResourceManager.java
index 2744bb4..c12da38 100644
--- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/ResourceManager.java
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/ResourceManager.java
@@ -30,11 +30,13 @@
import org.apache.hadoop.ha.HAServiceProtocol;
import org.apache.hadoop.ha.HAServiceProtocol.HAServiceState;
import org.apache.hadoop.http.lib.StaticUserWebFilter;
+import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.metrics2.lib.DefaultMetricsSystem;
import org.apache.hadoop.metrics2.source.JvmMetrics;
import org.apache.hadoop.security.AuthenticationFilterInitializer;
import org.apache.hadoop.security.Groups;
import org.apache.hadoop.security.HttpCrossOriginFilterInitializer;
+import org.apache.hadoop.security.KDiag;
import org.apache.hadoop.security.SecurityUtil;
import org.apache.hadoop.security.UserGroupInformation;
import org.apache.hadoop.security.authentication.server.KerberosAuthenticationHandler;
@@ -109,6 +111,7 @@
import org.apache.hadoop.yarn.webapp.util.WebAppUtils;
import org.apache.zookeeper.server.auth.DigestAuthenticationProvider;
+import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
@@ -121,6 +124,8 @@
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
+import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.HADOOP_KERBEROS_DIAGNOSTICS_ENABLED;
+
/**
* The ResourceManager is the main class that is a set of components.
* "I am the ResourceManager. All your resources belong to us..."
@@ -1181,15 +1186,52 @@ protected void serviceStart() throws Exception {
}
super.serviceStart();
}
-
+
+ /**
+ * Secure login. After this completes, the service is logged in
+ * to Kerberos -if enabled.
+ * @throws IOException
+ */
protected void doSecureLogin() throws IOException {
- InetSocketAddress socAddr = getBindAddress(conf);
- SecurityUtil.login(this.conf, YarnConfiguration.RM_KEYTAB,
- YarnConfiguration.RM_PRINCIPAL, socAddr.getHostName());
+ boolean diagnosticsEnabled =
+ conf.getBoolean(HADOOP_KERBEROS_DIAGNOSTICS_ENABLED, false);
+ KDiag kdiag = null;
- // if security is enable, set rmLoginUGI as UGI of loginUser
- if (UserGroupInformation.isSecurityEnabled()) {
- this.rmLoginUGI = UserGroupInformation.getLoginUser();
+ try {
+ if (diagnosticsEnabled) {
+ // add extra kerberos diagnostics
+ KDiag.KDiagBinding binding = new KDiag.KDiagBinding();
+ binding.withAttemptLogin(false)
+ .withSecurityRequired(!KDiag.isSimpleAuthentication(conf))
+ .withRaiseExceptions(false);
+ String keytabFilename = conf.get(YarnConfiguration.RM_KEYTAB);
+ if (keytabFilename != null) {
+ binding.withKeytab(new File(keytabFilename));
+ }
+
+ kdiag = new KDiag(conf, binding);
+ try {
+ kdiag.execute();
+ } catch (Exception ex) {
+ LOG.warn("In Kerberos Diagnostics", ex);
+ }
+ }
+
+ InetSocketAddress socAddr = getBindAddress(conf);
+ SecurityUtil.login(this.conf, YarnConfiguration.RM_KEYTAB,
+ YarnConfiguration.RM_PRINCIPAL, socAddr.getHostName());
+
+ // if security is enable, set rmLoginUGI as UGI of loginUser
+ if (UserGroupInformation.isSecurityEnabled()) {
+ this.rmLoginUGI = UserGroupInformation.getLoginUser();
+ if (kdiag != null) {
+ // post-login: print out state of user
+ kdiag.dumpUGI("RM User", rmLoginUGI);
+ kdiag.validateUGI("RM User", rmLoginUGI);
+ }
+ }
+ } finally {
+ IOUtils.closeStream(kdiag);
}
}