hadoop KDiag 源码

  • 2022-10-20
  • 浏览 (329)

haddop KDiag 代码

文件路径:/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/KDiag.java

/*
 * 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.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.security.authentication.util.KerberosName;
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.apache.kerby.kerberos.kerb.keytab.Keytab;
import org.apache.kerby.kerberos.kerb.keytab.KeytabEntry;
import org.apache.kerby.kerberos.kerb.type.base.EncryptionKey;
import org.apache.kerby.kerberos.kerb.type.base.PrincipalName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Cipher;

import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
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 java.util.regex.Pattern;

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;
  private boolean checkShortName = false;

  /**
   * A pattern that recognizes simple/non-simple names. Per KerberosName
   */
  private static final Pattern nonSimplePattern = Pattern.compile("[/@]");

  /**
   * 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";

  public static final String ARG_VERIFYSHORTNAME = "--verifyshortname";

  @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);
    checkShortName = popOption(ARG_VERIFYSHORTNAME, 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")
      + arg(ARG_VERIFYSHORTNAME, ARG_PRINCIPAL + " <principal>",
      "Verify the short name of the specific principal does not contain '@' or '/'");
  }

  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);
    println("%s = %s", ARG_VERIFYSHORTNAME, checkShortName);

    // 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 (checkShortName) {
        validateShortName();
      }

      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 when a particular cryptographic algorithm is
   *                          requested but is not available in the environment.
   */

  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);
  }

  /**
   * Verify whether auth_to_local rules transform a principal name
   * <p>
   * Having a local user name "bar@foo.com" may be harmless, so it is noted at
   * info. However if what was intended is a transformation to "bar"
   * it can be difficult to debug, hence this check.
   */
  protected void validateShortName() {
    failif(principal == null, CAT_KERBEROS, "No principal defined");

    try {
      KerberosName kn = new KerberosName(principal);
      String result = kn.getShortName();
      if (nonSimplePattern.matcher(result).find()) {
        warn(CAT_KERBEROS, principal + " short name: " + result +
                " still contains @ or /");
      }
    } catch (IOException e) {
      throw new KerberosDiagsFailure(CAT_KERBEROS, e,
              "Failed to get short name for " + principal, e);
    } catch (IllegalArgumentException e) {
      error(CAT_KERBEROS, "KerberosName(" + principal + ") failed: %s\n%s",
              e, StringUtils.stringifyException(e));
    }
  }

  /**
   * 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");

    Keytab loadKeytab = Keytab.loadKeytab(kt);
    List<PrincipalName> principals = loadKeytab.getPrincipals();
    println("keytab principal count: %d", principals.size());
    int entrySize = 0;
    for (PrincipalName princ : principals) {
      List<KeytabEntry> entries = loadKeytab.getKeytabEntries(princ);
      entrySize = entrySize + entries.size();
      for (KeytabEntry entry : entries) {
        EncryptionKey key = entry.getKey();
        println(" %s: version=%d expires=%s encryption=%s",
                entry.getPrincipal(),
                entry.getKvno(),
                entry.getTimestamp(),
                key.getKeyType());
      }
    }
    println("keytab entry count: %d", entrySize);

    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 (InputStream in = Files.newInputStream(file.toPath())) {
      for (String line : IOUtils.readLines(in, StandardCharsets.UTF_8)) {
        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 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;
    }
  }
}

相关信息

hadoop 源码目录

相关文章

hadoop AccessControlException 源码

hadoop AnnotatedSecurityInfo 源码

hadoop AuthenticationFilterInitializer 源码

hadoop CompositeGroupsMapping 源码

hadoop Credentials 源码

hadoop FastSaslClientFactory 源码

hadoop FastSaslServerFactory 源码

hadoop GroupMappingServiceProvider 源码

hadoop Groups 源码

hadoop HadoopKerberosName 源码

0  赞