| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.apache.hadoop.security; |
| |
| import org.apache.commons.io.IOUtils; |
| import org.apache.directory.server.kerberos.shared.keytab.Keytab; |
| import org.apache.directory.server.kerberos.shared.keytab.KeytabEntry; |
| import org.apache.directory.shared.kerberos.components.EncryptionKey; |
| import org.apache.hadoop.conf.Configuration; |
| import org.apache.hadoop.conf.Configured; |
| import org.apache.hadoop.io.Text; |
| import org.apache.hadoop.security.token.Token; |
| import org.apache.hadoop.security.token.TokenIdentifier; |
| import org.apache.hadoop.util.ExitUtil; |
| import org.apache.hadoop.util.Shell; |
| import org.apache.hadoop.util.StringUtils; |
| import org.apache.hadoop.util.Tool; |
| import org.apache.hadoop.util.ToolRunner; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import javax.crypto.Cipher; |
| |
| import java.io.Closeable; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.PrintWriter; |
| import java.lang.reflect.InvocationTargetException; |
| import java.net.InetAddress; |
| import java.security.NoSuchAlgorithmException; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Date; |
| import java.util.LinkedList; |
| import java.util.List; |
| |
| import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.*; |
| import static org.apache.hadoop.security.UserGroupInformation.*; |
| import static org.apache.hadoop.security.authentication.util.KerberosUtil.*; |
| import static org.apache.hadoop.util.StringUtils.popOption; |
| import static org.apache.hadoop.util.StringUtils.popOptionWithArgument; |
| import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.HADOOP_TOKEN_FILES; |
| |
| /** |
| * Kerberos diagnostics |
| * |
| * This operation expands some of the diagnostic output of the security code, |
| * but not all. For completeness |
| * |
| * Set the environment variable {@code HADOOP_JAAS_DEBUG=true} |
| * Set the log level for {@code org.apache.hadoop.security=DEBUG} |
| */ |
| 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} |
| */ |
| public static final String KRB5_CCNAME = "KRB5CCNAME"; |
| /** |
| * Location of main kerberos configuration file as passed down via an |
| * environment variable. |
| */ |
| public static final String KRB5_CONFIG = "KRB5_CONFIG"; |
| public static final String JAVA_SECURITY_KRB5_CONF |
| = "java.security.krb5.conf"; |
| public static final String JAVA_SECURITY_KRB5_REALM |
| = "java.security.krb5.realm"; |
| public static final String JAVA_SECURITY_KRB5_KDC_ADDRESS |
| = "java.security.krb5.kdc"; |
| public static final String SUN_SECURITY_KRB5_DEBUG |
| = "sun.security.krb5.debug"; |
| public static final String SUN_SECURITY_SPNEGO_DEBUG |
| = "sun.security.spnego.debug"; |
| public static final String SUN_SECURITY_JAAS_FILE |
| = "java.security.auth.login.config"; |
| public static final String KERBEROS_KINIT_COMMAND |
| = "hadoop.kerberos.kinit.command"; |
| |
| public static final String HADOOP_AUTHENTICATION_IS_DISABLED |
| = "Hadoop authentication is disabled"; |
| public static final String UNSET = "(unset)"; |
| |
| /** |
| * String seen in {@code getDefaultRealm()} exceptions if the user has |
| * no realm: {@value}. |
| */ |
| public static final String NO_DEFAULT_REALM = "Cannot locate default realm"; |
| |
| /** |
| * The exit code for a failure of the diagnostics: 41 == HTTP 401 == unauth. |
| */ |
| public static final int KDIAG_FAILURE = 41; |
| public static final String DFS_DATA_TRANSFER_SASLPROPERTIES_RESOLVER_CLASS |
| = "dfs.data.transfer.saslproperties.resolver.class"; |
| public static final String DFS_DATA_TRANSFER_PROTECTION |
| = "dfs.data.transfer.protection"; |
| 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"; |
| |
| private PrintWriter out; |
| private File keytab; |
| private String principal; |
| private long minKeyLength = 256; |
| private boolean securityRequired; |
| private boolean nofail = false; |
| private boolean nologin = false; |
| private boolean jaas = false; |
| |
| /** |
| * Flag set to true if a {@link #verify(boolean, String, String, Object...)} |
| * probe failed. |
| */ |
| private boolean probeHasFailed = false; |
| |
| public static final String CAT_CONFIG = "CONFIG"; |
| public static final String CAT_JAAS = "JAAS"; |
| public static final String CAT_JVM = "JVM"; |
| public static final String CAT_KERBEROS = "KERBEROS"; |
| public static final String CAT_LOGIN = "LOGIN"; |
| public static final String CAT_OS = "JAAS"; |
| public static final String CAT_SASL = "SASL"; |
| public static final String CAT_UGI = "UGI"; |
| public static final String CAT_TOKEN = "TOKEN"; |
| |
| public static final String ARG_KEYLEN = "--keylen"; |
| public static final String ARG_KEYTAB = "--keytab"; |
| public static final String ARG_JAAS = "--jaas"; |
| public static final String ARG_NOFAIL = "--nofail"; |
| public static final String ARG_NOLOGIN = "--nologin"; |
| public static final String ARG_OUTPUT = "--out"; |
| public static final String ARG_PRINCIPAL = "--principal"; |
| public static final String ARG_RESOURCE = "--resource"; |
| |
| public static final String ARG_SECURE = "--secure"; |
| |
| @SuppressWarnings("IOResourceOpenedButNotSafelyClosed") |
| public KDiag(Configuration conf, |
| PrintWriter out, |
| File keytab, |
| String principal, |
| long minKeyLength, |
| boolean securityRequired) { |
| super(conf); |
| this.keytab = keytab; |
| this.principal = principal; |
| this.out = out; |
| this.minKeyLength = minKeyLength; |
| this.securityRequired = securityRequired; |
| } |
| |
| public KDiag() { |
| } |
| |
| @Override |
| public void close() throws IOException { |
| flush(); |
| if (out != null) { |
| out.close(); |
| } |
| } |
| |
| @Override |
| public int run(String[] argv) throws Exception { |
| List<String> args = new LinkedList<>(Arrays.asList(argv)); |
| String keytabName = popOptionWithArgument(ARG_KEYTAB, args); |
| if (keytabName != null) { |
| keytab = new File(keytabName); |
| } |
| principal = popOptionWithArgument(ARG_PRINCIPAL, args); |
| String outf = popOptionWithArgument(ARG_OUTPUT, args); |
| String mkl = popOptionWithArgument(ARG_KEYLEN, args); |
| if (mkl != null) { |
| minKeyLength = Integer.parseInt(mkl); |
| } |
| securityRequired = popOption(ARG_SECURE, args); |
| nofail = popOption(ARG_NOFAIL, args); |
| jaas = popOption(ARG_JAAS, args); |
| nologin = popOption(ARG_NOLOGIN, args); |
| |
| // look for list of resources |
| String resource; |
| while (null != (resource = popOptionWithArgument(ARG_RESOURCE, args))) { |
| // loading a resource |
| LOG.info("Loading resource {}", resource); |
| try (InputStream in = |
| getClass().getClassLoader().getResourceAsStream(resource)) { |
| if (verify(in != null, CAT_CONFIG, "No resource %s", resource)) { |
| Configuration.addDefaultResource(resource); |
| } |
| } |
| } |
| // look for any leftovers |
| if (!args.isEmpty()) { |
| println("Unknown arguments in command:"); |
| for (String s : args) { |
| println(" \"%s\"", s); |
| } |
| println(); |
| println(usage()); |
| return -1; |
| } |
| if (outf != null) { |
| println("Printing output to %s", outf); |
| out = new PrintWriter(new File(outf), "UTF-8"); |
| } |
| execute(); |
| return probeHasFailed ? KDIAG_FAILURE : 0; |
| } |
| |
| private String usage() { |
| return "KDiag: Diagnose Kerberos Problems\n" |
| + arg("-D", "key=value", "Define a configuration option") |
| + arg(ARG_JAAS, "", |
| "Require a JAAS file to be defined in " + SUN_SECURITY_JAAS_FILE) |
| + arg(ARG_KEYLEN, "<keylen>", |
| "Require a minimum size for encryption keys supported by the JVM." |
| + " Default value : "+ minKeyLength) |
| + arg(ARG_KEYTAB, "<keytab> " + ARG_PRINCIPAL + " <principal>", |
| "Login from a keytab as a specific principal") |
| + arg(ARG_NOFAIL, "", "Do not fail on the first problem") |
| + arg(ARG_NOLOGIN, "", "Do not attempt to log in") |
| + arg(ARG_OUTPUT, "<file>", "Write output to a file") |
| + arg(ARG_RESOURCE, "<resource>", "Load an XML configuration resource") |
| + arg(ARG_SECURE, "", "Require the hadoop configuration to be secure"); |
| } |
| |
| private String arg(String name, String params, String meaning) { |
| return String.format(" [%s%s%s] : %s", |
| name, (!params.isEmpty() ? " " : ""), params, meaning) + ".\n"; |
| } |
| |
| /** |
| * Execute diagnostics. |
| * <p> |
| * Things it would be nice if UGI made accessible |
| * <ol> |
| * <li>A way to enable JAAS debug programatically</li> |
| * <li>Access to the TGT</li> |
| * </ol> |
| * @return true if security was enabled and all probes were successful |
| * @throws KerberosDiagsFailure explicitly raised failure |
| * @throws Exception other security problems |
| */ |
| @SuppressWarnings("deprecation") |
| public boolean execute() throws Exception { |
| |
| title("Kerberos Diagnostics scan at %s", |
| new Date(System.currentTimeMillis())); |
| |
| // check that the machine has a name |
| println("Hostname = %s", |
| InetAddress.getLocalHost().getCanonicalHostName()); |
| |
| println("%s = %d", ARG_KEYLEN, minKeyLength); |
| println("%s = %s", ARG_KEYTAB, keytab); |
| println("%s = %s", ARG_PRINCIPAL, principal); |
| |
| // Fail fast on a JVM without JCE installed. |
| validateKeyLength(); |
| |
| // look at realm |
| println("JVM Kerberos Login Module = %s", getKrb5LoginModuleName()); |
| |
| title("Core System Properties"); |
| for (String prop : new String[]{ |
| "user.name", |
| "java.version", |
| "java.vendor", |
| JAVA_SECURITY_KRB5_CONF, |
| JAVA_SECURITY_KRB5_REALM, |
| JAVA_SECURITY_KRB5_KDC_ADDRESS, |
| SUN_SECURITY_KRB5_DEBUG, |
| SUN_SECURITY_SPNEGO_DEBUG, |
| SUN_SECURITY_JAAS_FILE |
| }) { |
| printSysprop(prop); |
| } |
| endln(); |
| |
| title("All System Properties"); |
| ArrayList<String> propList = new ArrayList<>( |
| System.getProperties().stringPropertyNames()); |
| Collections.sort(propList, String.CASE_INSENSITIVE_ORDER); |
| for (String s : propList) { |
| printSysprop(s); |
| } |
| endln(); |
| |
| title("Environment Variables"); |
| for (String env : new String[]{ |
| HADOOP_JAAS_DEBUG, |
| KRB5_CCNAME, |
| KRB5_CONFIG, |
| HADOOP_USER_NAME, |
| HADOOP_PROXY_USER, |
| HADOOP_TOKEN_FILE_LOCATION, |
| "HADOOP_SECURE_LOG", |
| "HADOOP_OPTS", |
| "HADOOP_CLIENT_OPTS", |
| }) { |
| printEnv(env); |
| } |
| endln(); |
| |
| title("Configuration Options"); |
| for (String prop : new String[]{ |
| KERBEROS_KINIT_COMMAND, |
| HADOOP_SECURITY_AUTHENTICATION, |
| HADOOP_SECURITY_AUTHORIZATION, |
| "hadoop.kerberos.min.seconds.before.relogin", // not in 2.6 |
| "hadoop.security.dns.interface", // not in 2.6 |
| "hadoop.security.dns.nameserver", // not in 2.6 |
| HADOOP_RPC_PROTECTION, |
| HADOOP_SECURITY_SASL_PROPS_RESOLVER_CLASS, |
| HADOOP_SECURITY_CRYPTO_CODEC_CLASSES_KEY_PREFIX, |
| HADOOP_SECURITY_GROUP_MAPPING, |
| "hadoop.security.impersonation.provider.class", // not in 2.6 |
| DFS_DATA_TRANSFER_PROTECTION, // HDFS |
| DFS_DATA_TRANSFER_SASLPROPERTIES_RESOLVER_CLASS // HDFS |
| }) { |
| printConfOpt(prop); |
| } |
| |
| // check that authentication is enabled |
| Configuration conf = getConf(); |
| if (isSimpleAuthentication(conf)) { |
| println(HADOOP_AUTHENTICATION_IS_DISABLED); |
| failif(securityRequired, CAT_CONFIG, HADOOP_AUTHENTICATION_IS_DISABLED); |
| // no security, warn |
| LOG.warn("Security is not enabled for the Hadoop cluster"); |
| } else { |
| if (isSimpleAuthentication(new Configuration())) { |
| LOG.warn("The default cluster security is insecure"); |
| failif(securityRequired, CAT_CONFIG, HADOOP_AUTHENTICATION_IS_DISABLED); |
| } |
| } |
| |
| |
| // now the big test: login, then try again |
| boolean krb5Debug = getAndSet(SUN_SECURITY_KRB5_DEBUG); |
| boolean spnegoDebug = getAndSet(SUN_SECURITY_SPNEGO_DEBUG); |
| |
| try { |
| UserGroupInformation.setConfiguration(conf); |
| validateHadoopTokenFiles(conf); |
| validateKrb5File(); |
| printDefaultRealm(); |
| validateSasl(HADOOP_SECURITY_SASL_PROPS_RESOLVER_CLASS); |
| if (conf.get(DFS_DATA_TRANSFER_SASLPROPERTIES_RESOLVER_CLASS) != null) { |
| validateSasl(DFS_DATA_TRANSFER_SASLPROPERTIES_RESOLVER_CLASS); |
| } |
| validateKinitExecutable(); |
| validateJAAS(jaas); |
| validateNTPConf(); |
| |
| if (!nologin) { |
| 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; |
| } finally { |
| // restore original system properties |
| System.setProperty(SUN_SECURITY_KRB5_DEBUG, |
| Boolean.toString(krb5Debug)); |
| System.setProperty(SUN_SECURITY_SPNEGO_DEBUG, |
| Boolean.toString(spnegoDebug)); |
| } |
| } |
| |
| /** |
| * Is the authentication method of this configuration "simple"? |
| * @param conf configuration to check |
| * @return true if auth is simple (i.e. not kerberos) |
| */ |
| protected boolean isSimpleAuthentication(Configuration conf) { |
| return SecurityUtil.getAuthenticationMethod(conf) |
| .equals(AuthenticationMethod.SIMPLE); |
| } |
| |
| /** |
| * Fail fast on a JVM without JCE installed. |
| * |
| * This is a recurrent problem |
| * (that is: it keeps creeping back with JVM updates); |
| * a fast failure is the best tactic. |
| * @throws NoSuchAlgorithmException |
| */ |
| |
| protected void validateKeyLength() throws NoSuchAlgorithmException { |
| int aesLen = Cipher.getMaxAllowedKeyLength("AES"); |
| println("Maximum AES encryption key length %d bits", aesLen); |
| verify(minKeyLength <= aesLen, |
| CAT_JVM, |
| "Java Cryptography Extensions are not installed on this JVM." |
| + " Maximum supported key length %s - minimum required %d", |
| aesLen, minKeyLength); |
| } |
| |
| /** |
| * Get the default realm. |
| * <p> |
| * Not having a default realm may be harmless, so is noted at info. |
| * All other invocation failures are downgraded to warn, as |
| * follow-on actions may still work. |
| * Failure to invoke the method via introspection is considered a failure, |
| * as it's a sign of JVM compatibility issues that may have other |
| * consequences |
| */ |
| protected void printDefaultRealm() { |
| try { |
| String defaultRealm = getDefaultRealm(); |
| println("Default Realm = %s", defaultRealm); |
| if (defaultRealm == null) { |
| warn(CAT_KERBEROS, "Host has no default realm"); |
| } |
| } catch (ClassNotFoundException |
| | IllegalAccessException |
| | NoSuchMethodException e) { |
| throw new KerberosDiagsFailure(CAT_JVM, e, |
| "Failed to invoke krb5.Config.getDefaultRealm: %s: " +e, e); |
| } catch (InvocationTargetException e) { |
| Throwable cause = e.getCause() != null ? e.getCause() : e; |
| if (cause.toString().contains(NO_DEFAULT_REALM)) { |
| // exception raised if there is no default realm. This is not |
| // always a problem, so downgrade to a message. |
| warn(CAT_KERBEROS, "Host has no default realm"); |
| LOG.debug(cause.toString(), cause); |
| } else { |
| error(CAT_KERBEROS, "Kerberos.getDefaultRealm() failed: %s\n%s", |
| cause, StringUtils.stringifyException(cause)); |
| } |
| } |
| } |
| |
| /** |
| * Validate that hadoop.token.files (if specified) exist and are valid. |
| * @throws ClassNotFoundException |
| * @throws SecurityException |
| * @throws NoSuchMethodException |
| * @throws KerberosDiagsFailure |
| */ |
| private void validateHadoopTokenFiles(Configuration conf) |
| throws ClassNotFoundException, KerberosDiagsFailure, NoSuchMethodException, |
| SecurityException { |
| title("Locating Hadoop token files"); |
| |
| String tokenFileLocation = System.getProperty(HADOOP_TOKEN_FILES); |
| if(tokenFileLocation != null) { |
| println("Found " + HADOOP_TOKEN_FILES + " in system properties : " |
| + tokenFileLocation); |
| } |
| |
| if(conf.get(HADOOP_TOKEN_FILES) != null) { |
| println("Found " + HADOOP_TOKEN_FILES + " in hadoop configuration : " |
| + conf.get(HADOOP_TOKEN_FILES)); |
| if(System.getProperty(HADOOP_TOKEN_FILES) != null) { |
| println(HADOOP_TOKEN_FILES + " in the system properties overrides the" |
| + " one specified in hadoop configuration"); |
| } else { |
| tokenFileLocation = conf.get(HADOOP_TOKEN_FILES); |
| } |
| } |
| |
| if (tokenFileLocation != null) { |
| for (String tokenFileName: |
| StringUtils.getTrimmedStrings(tokenFileLocation)) { |
| if (tokenFileName.length() > 0) { |
| File tokenFile = new File(tokenFileName); |
| verifyFileIsValid(tokenFile, CAT_TOKEN, "token"); |
| verify(tokenFile, conf, CAT_TOKEN, "token"); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Locate the {@code krb5.conf} file and dump it. |
| * |
| * No-op on windows. |
| * @throws IOException problems reading the file. |
| */ |
| private void validateKrb5File() throws IOException { |
| if (!Shell.WINDOWS) { |
| title("Locating Kerberos configuration file"); |
| String krbPath = ETC_KRB5_CONF; |
| String jvmKrbPath = System.getProperty(JAVA_SECURITY_KRB5_CONF); |
| if (jvmKrbPath != null && !jvmKrbPath.isEmpty()) { |
| println("Setting kerberos path from sysprop %s: \"%s\"", |
| JAVA_SECURITY_KRB5_CONF, jvmKrbPath); |
| krbPath = jvmKrbPath; |
| } |
| |
| String krb5name = System.getenv(KRB5_CONFIG); |
| if (krb5name != null) { |
| println("Setting kerberos path from environment variable %s: \"%s\"", |
| KRB5_CONFIG, krb5name); |
| krbPath = krb5name; |
| if (jvmKrbPath != null) { |
| println("Warning - both %s and %s were set - %s takes priority", |
| JAVA_SECURITY_KRB5_CONF, KRB5_CONFIG, KRB5_CONFIG); |
| } |
| } |
| |
| File krbFile = new File(krbPath); |
| println("Kerberos configuration file = %s", krbFile); |
| dump(krbFile); |
| endln(); |
| } |
| } |
| |
| /** |
| * Dump a keytab: list all principals. |
| * |
| * @param keytabFile the keytab file |
| * @throws IOException IO problems |
| */ |
| private void dumpKeytab(File keytabFile) throws IOException { |
| title("Examining keytab %s", keytabFile); |
| File kt = keytabFile.getCanonicalFile(); |
| verifyFileIsValid(kt, CAT_KERBEROS, "keytab"); |
| List<KeytabEntry> entries = Keytab.read(kt).getEntries(); |
| println("keytab entry count: %d", entries.size()); |
| for (KeytabEntry entry : entries) { |
| EncryptionKey key = entry.getKey(); |
| println(" %s: version=%d expires=%s encryption=%s", |
| entry.getPrincipalName(), |
| entry.getKeyVersion(), |
| entry.getTimeStamp(), |
| key.getKeyType()); |
| } |
| endln(); |
| } |
| |
| /** |
| * Log in from a keytab, dump the UGI, validate it, then try and log in again. |
| * |
| * That second-time login catches JVM/Hadoop compatibility problems. |
| * @throws IOException Keytab loading problems |
| */ |
| private void loginFromKeytab() throws IOException { |
| UserGroupInformation ugi; |
| String identity; |
| if (keytab != null) { |
| File kt = keytab.getCanonicalFile(); |
| println("Using keytab %s principal %s", kt, principal); |
| identity = principal; |
| |
| failif(principal == null, CAT_KERBEROS, "No principal defined"); |
| ugi = loginUserFromKeytabAndReturnUGI(principal, kt.getPath()); |
| dumpUGI(identity, ugi); |
| validateUGI(principal, ugi); |
| |
| title("Attempting to relogin"); |
| try { |
| // package scoped -hence the reason why this class must be in the |
| // hadoop.security package |
| setShouldRenewImmediatelyForTests(true); |
| // attempt a new login |
| ugi.reloginFromKeytab(); |
| } catch (IllegalAccessError e) { |
| // if you've built this class into an independent JAR, package-access |
| // may fail. Downgrade |
| warn(CAT_UGI, "Failed to reset UGI -and so could not try to relogin"); |
| LOG.debug("Failed to reset UGI: {}", e, e); |
| } |
| } else { |
| println("No keytab: attempting to log in is as current user"); |
| } |
| } |
| |
| /** |
| * Dump a UGI. |
| * |
| * @param title title of this section |
| * @param ugi UGI to dump |
| * @throws IOException |
| */ |
| private void dumpUGI(String title, UserGroupInformation ugi) |
| throws IOException { |
| title(title); |
| println("UGI instance = %s", ugi); |
| println("Has kerberos credentials: %b", ugi.hasKerberosCredentials()); |
| println("Authentication method: %s", ugi.getAuthenticationMethod()); |
| println("Real Authentication method: %s", |
| ugi.getRealAuthenticationMethod()); |
| title("Group names"); |
| for (String name : ugi.getGroupNames()) { |
| println(name); |
| } |
| title("Credentials"); |
| List<Text> secretKeys = ugi.getCredentials().getAllSecretKeys(); |
| title("Secret keys"); |
| if (!secretKeys.isEmpty()) { |
| for (Text secret: secretKeys) { |
| println("%s", secret); |
| } |
| } else { |
| println("(none)"); |
| } |
| |
| dumpTokens(ugi); |
| } |
| |
| /** |
| * Validate the UGI: verify it is kerberized. |
| * @param messagePrefix message in exceptions |
| * @param user user to validate |
| */ |
| private 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(), |
| CAT_LOGIN, "%s: No kerberos credentials for %s", messagePrefix, user); |
| verify(user.getAuthenticationMethod() != null, |
| CAT_LOGIN, "%s: Null AuthenticationMethod for %s", messagePrefix, |
| user); |
| } |
| } |
| |
| /** |
| * A cursory look at the {@code kinit} executable. |
| * |
| * If it is an absolute path: it must exist with a size > 0. |
| * 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() { |
| 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); |
| } else { |
| println("Executable %s is relative -must be on the PATH", kinit); |
| printEnv("PATH"); |
| } |
| } |
| } |
| |
| /** |
| * Try to load the SASL resolver. |
| * @param saslPropsResolverKey key for the SASL resolver |
| */ |
| private void validateSasl(String saslPropsResolverKey) { |
| title("Resolving SASL property %s", saslPropsResolverKey); |
| String saslPropsResolver = getConf().getTrimmed(saslPropsResolverKey); |
| try { |
| Class<? extends SaslPropertiesResolver> resolverClass = |
| getConf().getClass( |
| saslPropsResolverKey, |
| SaslPropertiesResolver.class, |
| SaslPropertiesResolver.class); |
| println("Resolver is %s", resolverClass); |
| } catch (RuntimeException e) { |
| throw new KerberosDiagsFailure(CAT_SASL, e, |
| "Failed to load %s class %s", |
| saslPropsResolverKey, saslPropsResolver); |
| } |
| } |
| |
| /** |
| * Validate any JAAS entry referenced in the {@link #SUN_SECURITY_JAAS_FILE} |
| * property. |
| * @param jaasRequired is JAAS required |
| */ |
| private void validateJAAS(boolean jaasRequired) throws IOException { |
| String jaasFilename = System.getProperty(SUN_SECURITY_JAAS_FILE); |
| if (jaasRequired) { |
| verify(jaasFilename != null, CAT_JAAS, |
| "No JAAS file specified in " + SUN_SECURITY_JAAS_FILE); |
| } |
| if (jaasFilename != null) { |
| title("JAAS"); |
| File jaasFile = new File(jaasFilename); |
| println("JAAS file is defined in %s: %s", |
| SUN_SECURITY_JAAS_FILE, jaasFile); |
| verifyFileIsValid(jaasFile, CAT_JAAS, |
| "JAAS file defined in " + SUN_SECURITY_JAAS_FILE); |
| dump(jaasFile); |
| endln(); |
| } |
| } |
| |
| private void validateNTPConf() throws IOException { |
| if (!Shell.WINDOWS) { |
| File ntpfile = new File(ETC_NTP); |
| if (ntpfile.exists() |
| && verifyFileIsValid(ntpfile, CAT_OS, |
| "NTP file: " + ntpfile)) { |
| title("NTP"); |
| dump(ntpfile); |
| endln(); |
| } |
| } |
| } |
| |
| |
| /** |
| * 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 <i>and</i> |
| * {@link #nofail} has disabled raising exceptions. |
| */ |
| private boolean verifyFileIsValid(File file, String category, String text) { |
| return verify(file.exists(), category, |
| "%s file does not exist: %s", |
| text, file) |
| && verify(file.isFile(), category, |
| "%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, |
| "%s file is not readable: %s", text, file); |
| } |
| |
| /** |
| * Dump all tokens of a UGI. |
| * @param ugi UGI to examine |
| */ |
| public void dumpTokens(UserGroupInformation ugi) { |
| Collection<Token<? extends TokenIdentifier>> tokens |
| = ugi.getCredentials().getAllTokens(); |
| title("Token Count: %d", tokens.size()); |
| for (Token<? extends TokenIdentifier> token : tokens) { |
| println("Token %s", token.getKind()); |
| } |
| endln(); |
| } |
| |
| /** |
| * Set the System property to true; return the old value for caching. |
| * |
| * @param sysprop property |
| * @return the previous value |
| */ |
| private boolean getAndSet(String sysprop) { |
| boolean old = Boolean.getBoolean(sysprop); |
| System.setProperty(sysprop, "true"); |
| return old; |
| } |
| |
| /** |
| * Flush all active output channels, including {@Code System.err}, |
| * so as to stay in sync with any JRE log messages. |
| */ |
| private void flush() { |
| if (out != null) { |
| out.flush(); |
| } else { |
| System.out.flush(); |
| } |
| System.err.flush(); |
| } |
| |
| /** |
| * Print a line of output. This goes to any output file, or |
| * is logged at info. The output is flushed before and after, to |
| * try and stay in sync with JRE logging. |
| * |
| * @param format format string |
| * @param args any arguments |
| */ |
| private void println(String format, Object... args) { |
| flush(); |
| String msg = String.format(format, args); |
| if (out != null) { |
| out.println(msg); |
| } else { |
| System.out.println(msg); |
| } |
| flush(); |
| } |
| |
| /** |
| * Print a new line |
| */ |
| private void println() { |
| println(""); |
| } |
| |
| /** |
| * Print something at the end of a section |
| */ |
| private void endln() { |
| println(); |
| println("-----"); |
| } |
| |
| /** |
| * Print a title entry. |
| * |
| * @param format format string |
| * @param args any arguments |
| */ |
| private void title(String format, Object... args) { |
| println(); |
| println(); |
| println("== " + String.format(format, args) + " =="); |
| println(); |
| } |
| |
| /** |
| * Print a system property, or {@link #UNSET} if unset. |
| * @param property property to print |
| */ |
| private void printSysprop(String property) { |
| println("%s = \"%s\"", property, |
| System.getProperty(property, UNSET)); |
| } |
| |
| /** |
| * Print a configuration option, or {@link #UNSET} if unset. |
| * |
| * @param option option to print |
| */ |
| private void printConfOpt(String option) { |
| println("%s = \"%s\"", option, getConf().get(option, UNSET)); |
| } |
| |
| /** |
| * Print an environment variable's name and value; printing |
| * {@link #UNSET} if it is not set. |
| * @param variable environment variable |
| */ |
| private void printEnv(String variable) { |
| String env = System.getenv(variable); |
| println("%s = \"%s\"", variable, env != null ? env : UNSET); |
| } |
| |
| /** |
| * Dump any file to standard out. |
| * @param file file to dump |
| * @throws IOException IO problems |
| */ |
| private void dump(File file) throws IOException { |
| try (FileInputStream in = new FileInputStream(file)) { |
| for (String line : IOUtils.readLines(in)) { |
| println("%s", line); |
| } |
| } |
| } |
| |
| /** |
| * Format and raise a failure. |
| * |
| * @param category category for exception |
| * @param message string formatting message |
| * @param args any arguments for the formatting |
| * @throws KerberosDiagsFailure containing the formatted text |
| */ |
| private void fail(String category, String message, Object... args) |
| throws KerberosDiagsFailure { |
| error(category, message, args); |
| throw new KerberosDiagsFailure(category, message, args); |
| } |
| |
| /** |
| * Assert that a condition must hold. |
| * |
| * If not, an exception is raised, or, if {@link #nofail} is set, |
| * an error will be logged and the method return false. |
| * |
| * @param condition condition which must hold |
| * @param category category for exception |
| * @param message string formatting message |
| * @param args any arguments for the formatting |
| * @return true if the verification succeeded, false if it failed but |
| * an exception was not raised. |
| * @throws KerberosDiagsFailure containing the formatted text |
| * if the condition was met |
| */ |
| private boolean verify(boolean condition, |
| String category, |
| String message, |
| Object... args) |
| throws KerberosDiagsFailure { |
| if (!condition) { |
| // condition not met: fail or report |
| probeHasFailed = true; |
| if (!nofail) { |
| fail(category, message, args); |
| } else { |
| error(category, message, args); |
| } |
| return false; |
| } else { |
| // condition is met |
| return true; |
| } |
| } |
| |
| /** |
| * Verify that tokenFile contains valid Credentials. |
| * |
| * If not, an exception is raised, or, if {@link #nofail} is set, |
| * an error will be logged and the method return false. |
| * |
| */ |
| private boolean verify(File tokenFile, Configuration conf, String category, |
| String message) throws KerberosDiagsFailure { |
| try { |
| Credentials.readTokenStorageFile(tokenFile, conf); |
| } catch(Exception e) { |
| if (!nofail) { |
| fail(category, message); |
| } else { |
| error(category, message); |
| } |
| return false; |
| } |
| return true; |
| } |
| |
| /** |
| * Print a message as an error |
| * @param category error category |
| * @param message format string |
| * @param args list of arguments |
| */ |
| private void error(String category, String message, Object...args) { |
| println("ERROR: %s: %s", category, String.format(message, args)); |
| } |
| /** |
| * Print a message as an warning |
| * @param category error category |
| * @param message format string |
| * @param args list of arguments |
| */ |
| private void warn(String category, String message, Object...args) { |
| println("WARNING: %s: %s", category, String.format(message, args)); |
| } |
| |
| /** |
| * Conditional failure with string formatted arguments. |
| * There is no chek for the {@link #nofail} value. |
| * @param condition failure condition |
| * @param category category for exception |
| * @param message string formatting message |
| * @param args any arguments for the formatting |
| * @throws KerberosDiagsFailure containing the formatted text |
| * if the condition was met |
| */ |
| private void failif(boolean condition, |
| String category, |
| String message, |
| Object... args) |
| throws KerberosDiagsFailure { |
| if (condition) { |
| fail(category, message, args); |
| } |
| } |
| |
| /** |
| * Inner entry point, with no logging or system exits. |
| * |
| * @param conf configuration |
| * @param argv argument list |
| * @return an exception |
| * @throws Exception |
| */ |
| public static int exec(Configuration conf, String... argv) throws Exception { |
| try(KDiag kdiag = new KDiag()) { |
| return ToolRunner.run(conf, kdiag, argv); |
| } |
| } |
| |
| /** |
| * Main entry point. |
| * @param argv args list |
| */ |
| public static void main(String[] argv) { |
| try { |
| ExitUtil.terminate(exec(new Configuration(), argv)); |
| } catch (ExitUtil.ExitException e) { |
| LOG.error(e.toString()); |
| System.exit(e.status); |
| } catch (Exception e) { |
| LOG.error(e.toString(), e); |
| ExitUtil.halt(-1, e); |
| } |
| } |
| |
| /** |
| * Diagnostics failures return the exit code 41, "unauthorized". |
| * |
| * They have a category, initially for testing: the category can be |
| * validated without having to match on the entire string. |
| */ |
| public static class KerberosDiagsFailure extends ExitUtil.ExitException { |
| private final String category; |
| |
| public KerberosDiagsFailure(String category, String message) { |
| super(KDIAG_FAILURE, category + ": " + message); |
| this.category = category; |
| } |
| |
| public KerberosDiagsFailure(String category, |
| String message, |
| Object... args) { |
| this(category, String.format(message, args)); |
| } |
| |
| public KerberosDiagsFailure(String category, Throwable throwable, |
| String message, Object... args) { |
| this(category, message, args); |
| initCause(throwable); |
| } |
| |
| public String getCategory() { |
| return category; |
| } |
| } |
| } |