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..4e89ec9 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 @@ -112,15 +112,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 +149,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 +194,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 +369,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 +400,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 +496,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 +535,7 @@ private void dumpKeytab(File keytabFile) throws IOException { key.getKeyType()); } endln(); + return true; } /** @@ -557,7 +582,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 +590,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 +615,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 +633,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 +705,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 +721,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 +869,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 +889,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 +922,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 +1000,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-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 80b33a3..fb6aa9d 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 @@ -35,6 +35,7 @@ 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 +110,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; @@ -1180,15 +1182,39 @@ 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); + 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 kdiag = new KDiag(conf, binding); + try { + kdiag.execute(); + } catch (IOException e) { + throw e; + } catch (Exception ex) { + throw new IOException(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(); + kdiag.dumpUGI("RM User", rmLoginUGI); + kdiag.validateUGI("RM User", rmLoginUGI); } }