001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019package org.apache.hadoop.security; 020 021import org.apache.commons.io.IOUtils; 022import org.apache.directory.server.kerberos.shared.keytab.Keytab; 023import org.apache.directory.server.kerberos.shared.keytab.KeytabEntry; 024import org.apache.directory.shared.kerberos.components.EncryptionKey; 025import org.apache.hadoop.conf.Configuration; 026import org.apache.hadoop.conf.Configured; 027import org.apache.hadoop.io.Text; 028import org.apache.hadoop.security.authentication.util.KerberosName; 029import org.apache.hadoop.security.token.Token; 030import org.apache.hadoop.security.token.TokenIdentifier; 031import org.apache.hadoop.util.ExitUtil; 032import org.apache.hadoop.util.Shell; 033import org.apache.hadoop.util.StringUtils; 034import org.apache.hadoop.util.Tool; 035import org.apache.hadoop.util.ToolRunner; 036import org.slf4j.Logger; 037import org.slf4j.LoggerFactory; 038 039import javax.crypto.Cipher; 040import java.io.Closeable; 041import java.io.File; 042import java.io.FileInputStream; 043import java.io.IOException; 044import java.io.InputStream; 045import java.io.PrintWriter; 046import java.lang.reflect.InvocationTargetException; 047import java.net.InetAddress; 048import java.security.NoSuchAlgorithmException; 049import java.util.ArrayList; 050import java.util.Arrays; 051import java.util.Collection; 052import java.util.Collections; 053import java.util.Date; 054import java.util.LinkedList; 055import java.util.List; 056import java.util.regex.Pattern; 057 058import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.*; 059import static org.apache.hadoop.security.UserGroupInformation.*; 060import static org.apache.hadoop.security.authentication.util.KerberosUtil.*; 061import static org.apache.hadoop.util.StringUtils.popOption; 062import static org.apache.hadoop.util.StringUtils.popOptionWithArgument; 063 064/** 065 * Kerberos diagnostics 066 * 067 * This operation expands some of the diagnostic output of the security code, 068 * but not all. For completeness 069 * 070 * Set the environment variable {@code HADOOP_JAAS_DEBUG=true} 071 * Set the log level for {@code org.apache.hadoop.security=DEBUG} 072 */ 073public class KDiag extends Configured implements Tool, Closeable { 074 075 private static final Logger LOG = LoggerFactory.getLogger(KDiag.class); 076 /** 077 * Location of the kerberos ticket cache as passed down via an environment 078 * variable. This is what kinit will use by default: {@value} 079 */ 080 public static final String KRB5_CCNAME = "KRB5CCNAME"; 081 public static final String JAVA_SECURITY_KRB5_CONF 082 = "java.security.krb5.conf"; 083 public static final String JAVA_SECURITY_KRB5_REALM 084 = "java.security.krb5.realm"; 085 public static final String JAVA_SECURITY_KRB5_KDC_ADDRESS 086 = "java.security.krb5.kdc"; 087 public static final String SUN_SECURITY_KRB5_DEBUG 088 = "sun.security.krb5.debug"; 089 public static final String SUN_SECURITY_SPNEGO_DEBUG 090 = "sun.security.spnego.debug"; 091 public static final String SUN_SECURITY_JAAS_FILE 092 = "java.security.auth.login.config"; 093 public static final String KERBEROS_KINIT_COMMAND 094 = "hadoop.kerberos.kinit.command"; 095 096 public static final String HADOOP_AUTHENTICATION_IS_DISABLED 097 = "Hadoop authentication is disabled"; 098 public static final String UNSET = "(unset)"; 099 100 /** 101 * String seen in {@code getDefaultRealm()} exceptions if the user has 102 * no realm: {@value}. 103 */ 104 public static final String NO_DEFAULT_REALM = "Cannot locate default realm"; 105 106 /** 107 * The exit code for a failure of the diagnostics: 41 == HTTP 401 == unauth. 108 */ 109 public static final int KDIAG_FAILURE = 41; 110 public static final String DFS_DATA_TRANSFER_SASLPROPERTIES_RESOLVER_CLASS 111 = "dfs.data.transfer.saslproperties.resolver.class"; 112 public static final String DFS_DATA_TRANSFER_PROTECTION 113 = "dfs.data.transfer.protection"; 114 public static final String ETC_KRB5_CONF = "/etc/krb5.conf"; 115 public static final String ETC_NTP = "/etc/ntp.conf"; 116 public static final String HADOOP_JAAS_DEBUG = "HADOOP_JAAS_DEBUG"; 117 118 private PrintWriter out; 119 private File keytab; 120 private String principal; 121 private long minKeyLength = 256; 122 private boolean securityRequired; 123 private boolean nofail = false; 124 private boolean nologin = false; 125 private boolean jaas = false; 126 private boolean checkShortName = false; 127 128 /** 129 * A pattern that recognizes simple/non-simple names. Per KerberosName 130 */ 131 private static final Pattern nonSimplePattern = Pattern.compile("[/@]"); 132 133 /** 134 * Flag set to true if a {@link #verify(boolean, String, String, Object...)} 135 * probe failed. 136 */ 137 private boolean probeHasFailed = false; 138 139 public static final String CAT_CONFIG = "CONFIG"; 140 public static final String CAT_JAAS = "JAAS"; 141 public static final String CAT_JVM = "JVM"; 142 public static final String CAT_KERBEROS = "KERBEROS"; 143 public static final String CAT_LOGIN = "LOGIN"; 144 public static final String CAT_OS = "JAAS"; 145 public static final String CAT_SASL = "SASL"; 146 public static final String CAT_UGI = "UGI"; 147 148 public static final String ARG_KEYLEN = "--keylen"; 149 public static final String ARG_KEYTAB = "--keytab"; 150 public static final String ARG_JAAS = "--jaas"; 151 public static final String ARG_NOFAIL = "--nofail"; 152 public static final String ARG_NOLOGIN = "--nologin"; 153 public static final String ARG_OUTPUT = "--out"; 154 public static final String ARG_PRINCIPAL = "--principal"; 155 public static final String ARG_RESOURCE = "--resource"; 156 157 public static final String ARG_SECURE = "--secure"; 158 159 public static final String ARG_VERIFYSHORTNAME = "--verifyshortname"; 160 161 @SuppressWarnings("IOResourceOpenedButNotSafelyClosed") 162 public KDiag(Configuration conf, 163 PrintWriter out, 164 File keytab, 165 String principal, 166 long minKeyLength, 167 boolean securityRequired) { 168 super(conf); 169 this.keytab = keytab; 170 this.principal = principal; 171 this.out = out; 172 this.minKeyLength = minKeyLength; 173 this.securityRequired = securityRequired; 174 } 175 176 public KDiag() { 177 } 178 179 @Override 180 public void close() throws IOException { 181 flush(); 182 if (out != null) { 183 out.close(); 184 } 185 } 186 187 @Override 188 public int run(String[] argv) throws Exception { 189 List<String> args = new LinkedList<>(Arrays.asList(argv)); 190 String keytabName = popOptionWithArgument(ARG_KEYTAB, args); 191 if (keytabName != null) { 192 keytab = new File(keytabName); 193 } 194 principal = popOptionWithArgument(ARG_PRINCIPAL, args); 195 String outf = popOptionWithArgument(ARG_OUTPUT, args); 196 String mkl = popOptionWithArgument(ARG_KEYLEN, args); 197 if (mkl != null) { 198 minKeyLength = Integer.parseInt(mkl); 199 } 200 securityRequired = popOption(ARG_SECURE, args); 201 nofail = popOption(ARG_NOFAIL, args); 202 jaas = popOption(ARG_JAAS, args); 203 nologin = popOption(ARG_NOLOGIN, args); 204 checkShortName = popOption(ARG_VERIFYSHORTNAME, args); 205 206 // look for list of resources 207 String resource; 208 while (null != (resource = popOptionWithArgument(ARG_RESOURCE, args))) { 209 // loading a resource 210 LOG.info("Loading resource {}", resource); 211 try (InputStream in = 212 getClass().getClassLoader().getResourceAsStream(resource)) { 213 if (verify(in != null, CAT_CONFIG, "No resource %s", resource)) { 214 Configuration.addDefaultResource(resource); 215 } 216 } 217 } 218 // look for any leftovers 219 if (!args.isEmpty()) { 220 println("Unknown arguments in command:"); 221 for (String s : args) { 222 println(" \"%s\"", s); 223 } 224 println(); 225 println(usage()); 226 return -1; 227 } 228 if (outf != null) { 229 println("Printing output to %s", outf); 230 out = new PrintWriter(new File(outf), "UTF-8"); 231 } 232 execute(); 233 return probeHasFailed ? KDIAG_FAILURE : 0; 234 } 235 236 private String usage() { 237 return "KDiag: Diagnose Kerberos Problems\n" 238 + arg("-D", "key=value", "Define a configuration option") 239 + arg(ARG_JAAS, "", 240 "Require a JAAS file to be defined in " + SUN_SECURITY_JAAS_FILE) 241 + arg(ARG_KEYLEN, "<keylen>", 242 "Require a minimum size for encryption keys supported by the JVM." 243 + " Default value : "+ minKeyLength) 244 + arg(ARG_KEYTAB, "<keytab> " + ARG_PRINCIPAL + " <principal>", 245 "Login from a keytab as a specific principal") 246 + arg(ARG_NOFAIL, "", "Do not fail on the first problem") 247 + arg(ARG_NOLOGIN, "", "Do not attempt to log in") 248 + arg(ARG_OUTPUT, "<file>", "Write output to a file") 249 + arg(ARG_RESOURCE, "<resource>", "Load an XML configuration resource") 250 + arg(ARG_SECURE, "", "Require the hadoop configuration to be secure") 251 + arg(ARG_VERIFYSHORTNAME, ARG_PRINCIPAL + " <principal>", 252 "Verify the short name of the specific principal does not contain '@' or '/'"); 253 } 254 255 private String arg(String name, String params, String meaning) { 256 return String.format(" [%s%s%s] : %s", 257 name, (!params.isEmpty() ? " " : ""), params, meaning) + ".\n"; 258 } 259 260 /** 261 * Execute diagnostics. 262 * <p> 263 * Things it would be nice if UGI made accessible 264 * <ol> 265 * <li>A way to enable JAAS debug programatically</li> 266 * <li>Access to the TGT</li> 267 * </ol> 268 * @return true if security was enabled and all probes were successful 269 * @throws KerberosDiagsFailure explicitly raised failure 270 * @throws Exception other security problems 271 */ 272 @SuppressWarnings("deprecation") 273 public boolean execute() throws Exception { 274 275 title("Kerberos Diagnostics scan at %s", 276 new Date(System.currentTimeMillis())); 277 278 // check that the machine has a name 279 println("Hostname = %s", 280 InetAddress.getLocalHost().getCanonicalHostName()); 281 282 println("%s = %d", ARG_KEYLEN, minKeyLength); 283 println("%s = %s", ARG_KEYTAB, keytab); 284 println("%s = %s", ARG_PRINCIPAL, principal); 285 println("%s = %s", ARG_VERIFYSHORTNAME, checkShortName); 286 287 // Fail fast on a JVM without JCE installed. 288 validateKeyLength(); 289 290 // look at realm 291 println("JVM Kerberos Login Module = %s", getKrb5LoginModuleName()); 292 293 title("Core System Properties"); 294 for (String prop : new String[]{ 295 "user.name", 296 "java.version", 297 "java.vendor", 298 JAVA_SECURITY_KRB5_CONF, 299 JAVA_SECURITY_KRB5_REALM, 300 JAVA_SECURITY_KRB5_KDC_ADDRESS, 301 SUN_SECURITY_KRB5_DEBUG, 302 SUN_SECURITY_SPNEGO_DEBUG, 303 SUN_SECURITY_JAAS_FILE 304 }) { 305 printSysprop(prop); 306 } 307 endln(); 308 309 title("All System Properties"); 310 ArrayList<String> propList = new ArrayList<>( 311 System.getProperties().stringPropertyNames()); 312 Collections.sort(propList, String.CASE_INSENSITIVE_ORDER); 313 for (String s : propList) { 314 printSysprop(s); 315 } 316 endln(); 317 318 title("Environment Variables"); 319 for (String env : new String[]{ 320 HADOOP_JAAS_DEBUG, 321 KRB5_CCNAME, 322 HADOOP_USER_NAME, 323 HADOOP_PROXY_USER, 324 HADOOP_TOKEN_FILE_LOCATION, 325 "HADOOP_SECURE_LOG", 326 "HADOOP_OPTS", 327 "HADOOP_CLIENT_OPTS", 328 }) { 329 printEnv(env); 330 } 331 endln(); 332 333 title("Configuration Options"); 334 for (String prop : new String[]{ 335 KERBEROS_KINIT_COMMAND, 336 HADOOP_SECURITY_AUTHENTICATION, 337 HADOOP_SECURITY_AUTHORIZATION, 338 "hadoop.kerberos.min.seconds.before.relogin", // not in 2.6 339 "hadoop.security.dns.interface", // not in 2.6 340 "hadoop.security.dns.nameserver", // not in 2.6 341 HADOOP_RPC_PROTECTION, 342 HADOOP_SECURITY_SASL_PROPS_RESOLVER_CLASS, 343 HADOOP_SECURITY_CRYPTO_CODEC_CLASSES_KEY_PREFIX, 344 HADOOP_SECURITY_GROUP_MAPPING, 345 "hadoop.security.impersonation.provider.class", // not in 2.6 346 DFS_DATA_TRANSFER_PROTECTION, // HDFS 347 DFS_DATA_TRANSFER_SASLPROPERTIES_RESOLVER_CLASS // HDFS 348 }) { 349 printConfOpt(prop); 350 } 351 352 // check that authentication is enabled 353 Configuration conf = getConf(); 354 if (isSimpleAuthentication(conf)) { 355 println(HADOOP_AUTHENTICATION_IS_DISABLED); 356 failif(securityRequired, CAT_CONFIG, HADOOP_AUTHENTICATION_IS_DISABLED); 357 // no security, warn 358 LOG.warn("Security is not enabled for the Hadoop cluster"); 359 } else { 360 if (isSimpleAuthentication(new Configuration())) { 361 LOG.warn("The default cluster security is insecure"); 362 failif(securityRequired, CAT_CONFIG, HADOOP_AUTHENTICATION_IS_DISABLED); 363 } 364 } 365 366 367 // now the big test: login, then try again 368 boolean krb5Debug = getAndSet(SUN_SECURITY_KRB5_DEBUG); 369 boolean spnegoDebug = getAndSet(SUN_SECURITY_SPNEGO_DEBUG); 370 371 try { 372 UserGroupInformation.setConfiguration(conf); 373 validateKrb5File(); 374 printDefaultRealm(); 375 validateSasl(HADOOP_SECURITY_SASL_PROPS_RESOLVER_CLASS); 376 if (conf.get(DFS_DATA_TRANSFER_SASLPROPERTIES_RESOLVER_CLASS) != null) { 377 validateSasl(DFS_DATA_TRANSFER_SASLPROPERTIES_RESOLVER_CLASS); 378 } 379 validateKinitExecutable(); 380 validateJAAS(jaas); 381 validateNTPConf(); 382 383 if (checkShortName) { 384 validateShortName(); 385 } 386 387 if (!nologin) { 388 title("Logging in"); 389 if (keytab != null) { 390 dumpKeytab(keytab); 391 loginFromKeytab(); 392 } else { 393 UserGroupInformation loginUser = getLoginUser(); 394 dumpUGI("Log in user", loginUser); 395 validateUGI("Login user", loginUser); 396 println("Ticket based login: %b", isLoginTicketBased()); 397 println("Keytab based login: %b", isLoginKeytabBased()); 398 } 399 } 400 401 return true; 402 } finally { 403 // restore original system properties 404 System.setProperty(SUN_SECURITY_KRB5_DEBUG, 405 Boolean.toString(krb5Debug)); 406 System.setProperty(SUN_SECURITY_SPNEGO_DEBUG, 407 Boolean.toString(spnegoDebug)); 408 } 409 } 410 411 /** 412 * Is the authentication method of this configuration "simple"? 413 * @param conf configuration to check 414 * @return true if auth is simple (i.e. not kerberos) 415 */ 416 protected boolean isSimpleAuthentication(Configuration conf) { 417 return SecurityUtil.getAuthenticationMethod(conf) 418 .equals(AuthenticationMethod.SIMPLE); 419 } 420 421 /** 422 * Fail fast on a JVM without JCE installed. 423 * 424 * This is a recurrent problem 425 * (that is: it keeps creeping back with JVM updates); 426 * a fast failure is the best tactic. 427 * @throws NoSuchAlgorithmException 428 */ 429 430 protected void validateKeyLength() throws NoSuchAlgorithmException { 431 int aesLen = Cipher.getMaxAllowedKeyLength("AES"); 432 println("Maximum AES encryption key length %d bits", aesLen); 433 verify(minKeyLength <= aesLen, 434 CAT_JVM, 435 "Java Cryptography Extensions are not installed on this JVM." 436 + " Maximum supported key length %s - minimum required %d", 437 aesLen, minKeyLength); 438 } 439 440 /** 441 * Verify whether auth_to_local rules transform a principal name 442 * <p> 443 * Having a local user name "bar@foo.com" may be harmless, so it is noted at 444 * info. However if what was intended is a transformation to "bar" 445 * it can be difficult to debug, hence this check. 446 */ 447 protected void validateShortName() { 448 failif(principal == null, CAT_KERBEROS, "No principal defined"); 449 450 try { 451 KerberosName kn = new KerberosName(principal); 452 String result = kn.getShortName(); 453 if (nonSimplePattern.matcher(result).find()) { 454 warn(CAT_KERBEROS, principal + " short name: " + result 455 + " still contains @ or /"); 456 } 457 } catch (IOException e) { 458 throw new KerberosDiagsFailure(CAT_KERBEROS, e, 459 "Failed to get short name for " + principal, e); 460 } catch (IllegalArgumentException e) { 461 error(CAT_KERBEROS, "KerberosName(" + principal + ") failed: %s\n%s", 462 e, StringUtils.stringifyException(e)); 463 } 464 } 465 466 /** 467 * Get the default realm. 468 * <p> 469 * Not having a default realm may be harmless, so is noted at info. 470 * All other invocation failures are downgraded to warn, as 471 * follow-on actions may still work. 472 * Failure to invoke the method via introspection is considered a failure, 473 * as it's a sign of JVM compatibility issues that may have other 474 * consequences 475 */ 476 protected void printDefaultRealm() { 477 try { 478 String defaultRealm = getDefaultRealm(); 479 println("Default Realm = %s", defaultRealm); 480 if (defaultRealm == null) { 481 warn(CAT_KERBEROS, "Host has no default realm"); 482 } 483 } catch (ClassNotFoundException 484 | IllegalAccessException 485 | NoSuchMethodException e) { 486 throw new KerberosDiagsFailure(CAT_JVM, e, 487 "Failed to invoke krb5.Config.getDefaultRealm: %s: " +e, e); 488 } catch (InvocationTargetException e) { 489 Throwable cause = e.getCause() != null ? e.getCause() : e; 490 if (cause.toString().contains(NO_DEFAULT_REALM)) { 491 // exception raised if there is no default realm. This is not 492 // always a problem, so downgrade to a message. 493 warn(CAT_KERBEROS, "Host has no default realm"); 494 LOG.debug(cause.toString(), cause); 495 } else { 496 error(CAT_KERBEROS, "Kerberos.getDefaultRealm() failed: %s\n%s", 497 cause, StringUtils.stringifyException(cause)); 498 } 499 } 500 } 501 502 /** 503 * Locate the {@code krb5.conf} file and dump it. 504 * 505 * No-op on windows. 506 * @throws IOException problems reading the file. 507 */ 508 private void validateKrb5File() throws IOException { 509 if (!Shell.WINDOWS) { 510 title("Locating Kerberos configuration file"); 511 String krbPath = ETC_KRB5_CONF; 512 String jvmKrbPath = System.getProperty(JAVA_SECURITY_KRB5_CONF); 513 if (jvmKrbPath != null && !jvmKrbPath.isEmpty()) { 514 println("Setting kerberos path from sysprop %s: \"%s\"", 515 JAVA_SECURITY_KRB5_CONF, jvmKrbPath); 516 krbPath = jvmKrbPath; 517 } 518 519 String krb5name = System.getenv(KRB5_CCNAME); 520 if (krb5name != null) { 521 println("Setting kerberos path from environment variable %s: \"%s\"", 522 KRB5_CCNAME, krb5name); 523 krbPath = krb5name; 524 if (jvmKrbPath != null) { 525 println("Warning - both %s and %s were set - %s takes priority", 526 JAVA_SECURITY_KRB5_CONF, KRB5_CCNAME, KRB5_CCNAME); 527 } 528 } 529 530 File krbFile = new File(krbPath); 531 println("Kerberos configuration file = %s", krbFile); 532 dump(krbFile); 533 endln(); 534 } 535 } 536 537 /** 538 * Dump a keytab: list all principals. 539 * 540 * @param keytabFile the keytab file 541 * @throws IOException IO problems 542 */ 543 private void dumpKeytab(File keytabFile) throws IOException { 544 title("Examining keytab %s", keytabFile); 545 File kt = keytabFile.getCanonicalFile(); 546 verifyFileIsValid(kt, CAT_KERBEROS, "keytab"); 547 List<KeytabEntry> entries = Keytab.read(kt).getEntries(); 548 println("keytab entry count: %d", entries.size()); 549 for (KeytabEntry entry : entries) { 550 EncryptionKey key = entry.getKey(); 551 println(" %s: version=%d expires=%s encryption=%s", 552 entry.getPrincipalName(), 553 entry.getKeyVersion(), 554 entry.getTimeStamp(), 555 key.getKeyType()); 556 } 557 endln(); 558 } 559 560 /** 561 * Log in from a keytab, dump the UGI, validate it, then try and log in again. 562 * 563 * That second-time login catches JVM/Hadoop compatibility problems. 564 * @throws IOException Keytab loading problems 565 */ 566 private void loginFromKeytab() throws IOException { 567 UserGroupInformation ugi; 568 String identity; 569 if (keytab != null) { 570 File kt = keytab.getCanonicalFile(); 571 println("Using keytab %s principal %s", kt, principal); 572 identity = principal; 573 574 failif(principal == null, CAT_KERBEROS, "No principal defined"); 575 ugi = loginUserFromKeytabAndReturnUGI(principal, kt.getPath()); 576 dumpUGI(identity, ugi); 577 validateUGI(principal, ugi); 578 579 title("Attempting to relogin"); 580 try { 581 // package scoped -hence the reason why this class must be in the 582 // hadoop.security package 583 setShouldRenewImmediatelyForTests(true); 584 // attempt a new login 585 ugi.reloginFromKeytab(); 586 } catch (IllegalAccessError e) { 587 // if you've built this class into an independent JAR, package-access 588 // may fail. Downgrade 589 warn(CAT_UGI, "Failed to reset UGI -and so could not try to relogin"); 590 LOG.debug("Failed to reset UGI: {}", e, e); 591 } 592 } else { 593 println("No keytab: attempting to log in is as current user"); 594 } 595 } 596 597 /** 598 * Dump a UGI. 599 * 600 * @param title title of this section 601 * @param ugi UGI to dump 602 * @throws IOException 603 */ 604 private void dumpUGI(String title, UserGroupInformation ugi) 605 throws IOException { 606 title(title); 607 println("UGI instance = %s", ugi); 608 println("Has kerberos credentials: %b", ugi.hasKerberosCredentials()); 609 println("Authentication method: %s", ugi.getAuthenticationMethod()); 610 println("Real Authentication method: %s", 611 ugi.getRealAuthenticationMethod()); 612 title("Group names"); 613 for (String name : ugi.getGroupNames()) { 614 println(name); 615 } 616 title("Credentials"); 617 List<Text> secretKeys = ugi.getCredentials().getAllSecretKeys(); 618 title("Secret keys"); 619 if (!secretKeys.isEmpty()) { 620 for (Text secret: secretKeys) { 621 println("%s", secret); 622 } 623 } else { 624 println("(none)"); 625 } 626 627 dumpTokens(ugi); 628 } 629 630 /** 631 * Validate the UGI: verify it is kerberized. 632 * @param messagePrefix message in exceptions 633 * @param user user to validate 634 */ 635 private void validateUGI(String messagePrefix, UserGroupInformation user) { 636 if (verify(user.getAuthenticationMethod() == AuthenticationMethod.KERBEROS, 637 CAT_LOGIN, "User %s is not authenticated by Kerberos", user)) { 638 verify(user.hasKerberosCredentials(), 639 CAT_LOGIN, "%s: No kerberos credentials for %s", messagePrefix, user); 640 verify(user.getAuthenticationMethod() != null, 641 CAT_LOGIN, "%s: Null AuthenticationMethod for %s", messagePrefix, 642 user); 643 } 644 } 645 646 /** 647 * A cursory look at the {@code kinit} executable. 648 * 649 * If it is an absolute path: it must exist with a size > 0. 650 * If it is just a command, it has to be on the path. There's no check 651 * for that -but the PATH is printed out. 652 */ 653 private void validateKinitExecutable() { 654 String kinit = getConf().getTrimmed(KERBEROS_KINIT_COMMAND, ""); 655 if (!kinit.isEmpty()) { 656 File kinitPath = new File(kinit); 657 println("%s = %s", KERBEROS_KINIT_COMMAND, kinitPath); 658 if (kinitPath.isAbsolute()) { 659 verifyFileIsValid(kinitPath, CAT_KERBEROS, KERBEROS_KINIT_COMMAND); 660 } else { 661 println("Executable %s is relative -must be on the PATH", kinit); 662 printEnv("PATH"); 663 } 664 } 665 } 666 667 /** 668 * Try to load the SASL resolver. 669 * @param saslPropsResolverKey key for the SASL resolver 670 */ 671 private void validateSasl(String saslPropsResolverKey) { 672 title("Resolving SASL property %s", saslPropsResolverKey); 673 String saslPropsResolver = getConf().getTrimmed(saslPropsResolverKey); 674 try { 675 Class<? extends SaslPropertiesResolver> resolverClass = 676 getConf().getClass( 677 saslPropsResolverKey, 678 SaslPropertiesResolver.class, 679 SaslPropertiesResolver.class); 680 println("Resolver is %s", resolverClass); 681 } catch (RuntimeException e) { 682 throw new KerberosDiagsFailure(CAT_SASL, e, 683 "Failed to load %s class %s", 684 saslPropsResolverKey, saslPropsResolver); 685 } 686 } 687 688 /** 689 * Validate any JAAS entry referenced in the {@link #SUN_SECURITY_JAAS_FILE} 690 * property. 691 * @param jaasRequired is JAAS required 692 */ 693 private void validateJAAS(boolean jaasRequired) throws IOException { 694 String jaasFilename = System.getProperty(SUN_SECURITY_JAAS_FILE); 695 if (jaasRequired) { 696 verify(jaasFilename != null, CAT_JAAS, 697 "No JAAS file specified in " + SUN_SECURITY_JAAS_FILE); 698 } 699 if (jaasFilename != null) { 700 title("JAAS"); 701 File jaasFile = new File(jaasFilename); 702 println("JAAS file is defined in %s: %s", 703 SUN_SECURITY_JAAS_FILE, jaasFile); 704 verifyFileIsValid(jaasFile, CAT_JAAS, 705 "JAAS file defined in " + SUN_SECURITY_JAAS_FILE); 706 dump(jaasFile); 707 endln(); 708 } 709 } 710 711 private void validateNTPConf() throws IOException { 712 if (!Shell.WINDOWS) { 713 File ntpfile = new File(ETC_NTP); 714 if (ntpfile.exists() 715 && verifyFileIsValid(ntpfile, CAT_OS, 716 "NTP file: " + ntpfile)) { 717 title("NTP"); 718 dump(ntpfile); 719 endln(); 720 } 721 } 722 } 723 724 725 /** 726 * Verify that a file is valid: it is a file, non-empty and readable. 727 * @param file file 728 * @param category category for exceptions 729 * @param text text message 730 * @return true if the validation held; false if it did not <i>and</i> 731 * {@link #nofail} has disabled raising exceptions. 732 */ 733 private boolean verifyFileIsValid(File file, String category, String text) { 734 return verify(file.exists(), category, 735 "%s file does not exist: %s", 736 text, file) 737 && verify(file.isFile(), category, 738 "%s path does not refer to a file: %s", text, file) 739 && verify(file.length() != 0, category, 740 "%s file is empty: %s", text, file) 741 && verify(file.canRead(), category, 742 "%s file is not readable: %s", text, file); 743 } 744 745 /** 746 * Dump all tokens of a UGI. 747 * @param ugi UGI to examine 748 */ 749 public void dumpTokens(UserGroupInformation ugi) { 750 Collection<Token<? extends TokenIdentifier>> tokens 751 = ugi.getCredentials().getAllTokens(); 752 title("Token Count: %d", tokens.size()); 753 for (Token<? extends TokenIdentifier> token : tokens) { 754 println("Token %s", token.getKind()); 755 } 756 endln(); 757 } 758 759 /** 760 * Set the System property to true; return the old value for caching. 761 * 762 * @param sysprop property 763 * @return the previous value 764 */ 765 private boolean getAndSet(String sysprop) { 766 boolean old = Boolean.getBoolean(sysprop); 767 System.setProperty(sysprop, "true"); 768 return old; 769 } 770 771 /** 772 * Flush all active output channels, including {@Code System.err}, 773 * so as to stay in sync with any JRE log messages. 774 */ 775 private void flush() { 776 if (out != null) { 777 out.flush(); 778 } else { 779 System.out.flush(); 780 } 781 System.err.flush(); 782 } 783 784 /** 785 * Print a line of output. This goes to any output file, or 786 * is logged at info. The output is flushed before and after, to 787 * try and stay in sync with JRE logging. 788 * 789 * @param format format string 790 * @param args any arguments 791 */ 792 private void println(String format, Object... args) { 793 flush(); 794 String msg = String.format(format, args); 795 if (out != null) { 796 out.println(msg); 797 } else { 798 System.out.println(msg); 799 } 800 flush(); 801 } 802 803 /** 804 * Print a new line 805 */ 806 private void println() { 807 println(""); 808 } 809 810 /** 811 * Print something at the end of a section 812 */ 813 private void endln() { 814 println(); 815 println("-----"); 816 } 817 818 /** 819 * Print a title entry. 820 * 821 * @param format format string 822 * @param args any arguments 823 */ 824 private void title(String format, Object... args) { 825 println(); 826 println(); 827 println("== " + String.format(format, args) + " =="); 828 println(); 829 } 830 831 /** 832 * Print a system property, or {@link #UNSET} if unset. 833 * @param property property to print 834 */ 835 private void printSysprop(String property) { 836 println("%s = \"%s\"", property, 837 System.getProperty(property, UNSET)); 838 } 839 840 /** 841 * Print a configuration option, or {@link #UNSET} if unset. 842 * 843 * @param option option to print 844 */ 845 private void printConfOpt(String option) { 846 println("%s = \"%s\"", option, getConf().get(option, UNSET)); 847 } 848 849 /** 850 * Print an environment variable's name and value; printing 851 * {@link #UNSET} if it is not set. 852 * @param variable environment variable 853 */ 854 private void printEnv(String variable) { 855 String env = System.getenv(variable); 856 println("%s = \"%s\"", variable, env != null ? env : UNSET); 857 } 858 859 /** 860 * Dump any file to standard out. 861 * @param file file to dump 862 * @throws IOException IO problems 863 */ 864 private void dump(File file) throws IOException { 865 try (FileInputStream in = new FileInputStream(file)) { 866 for (String line : IOUtils.readLines(in)) { 867 println(line); 868 } 869 } 870 } 871 872 /** 873 * Format and raise a failure. 874 * 875 * @param category category for exception 876 * @param message string formatting message 877 * @param args any arguments for the formatting 878 * @throws KerberosDiagsFailure containing the formatted text 879 */ 880 private void fail(String category, String message, Object... args) 881 throws KerberosDiagsFailure { 882 error(category, message, args); 883 throw new KerberosDiagsFailure(category, message, args); 884 } 885 886 /** 887 * Assert that a condition must hold. 888 * 889 * If not, an exception is raised, or, if {@link #nofail} is set, 890 * an error will be logged and the method return false. 891 * 892 * @param condition condition which must hold 893 * @param category category for exception 894 * @param message string formatting message 895 * @param args any arguments for the formatting 896 * @return true if the verification succeeded, false if it failed but 897 * an exception was not raised. 898 * @throws KerberosDiagsFailure containing the formatted text 899 * if the condition was met 900 */ 901 private boolean verify(boolean condition, 902 String category, 903 String message, 904 Object... args) 905 throws KerberosDiagsFailure { 906 if (!condition) { 907 // condition not met: fail or report 908 probeHasFailed = true; 909 if (!nofail) { 910 fail(category, message, args); 911 } else { 912 error(category, message, args); 913 } 914 return false; 915 } else { 916 // condition is met 917 return true; 918 } 919 } 920 921 /** 922 * Print a message as an error 923 * @param category error category 924 * @param message format string 925 * @param args list of arguments 926 */ 927 private void error(String category, String message, Object...args) { 928 println("ERROR: %s: %s", category, String.format(message, args)); 929 } 930 /** 931 * Print a message as an warning 932 * @param category error category 933 * @param message format string 934 * @param args list of arguments 935 */ 936 private void warn(String category, String message, Object...args) { 937 println("WARNING: %s: %s", category, String.format(message, args)); 938 } 939 940 /** 941 * Conditional failure with string formatted arguments. 942 * There is no chek for the {@link #nofail} value. 943 * @param condition failure condition 944 * @param category category for exception 945 * @param message string formatting message 946 * @param args any arguments for the formatting 947 * @throws KerberosDiagsFailure containing the formatted text 948 * if the condition was met 949 */ 950 private void failif(boolean condition, 951 String category, 952 String message, 953 Object... args) 954 throws KerberosDiagsFailure { 955 if (condition) { 956 fail(category, message, args); 957 } 958 } 959 960 /** 961 * Inner entry point, with no logging or system exits. 962 * 963 * @param conf configuration 964 * @param argv argument list 965 * @return an exception 966 * @throws Exception 967 */ 968 public static int exec(Configuration conf, String... argv) throws Exception { 969 try(KDiag kdiag = new KDiag()) { 970 return ToolRunner.run(conf, kdiag, argv); 971 } 972 } 973 974 /** 975 * Main entry point. 976 * @param argv args list 977 */ 978 public static void main(String[] argv) { 979 try { 980 ExitUtil.terminate(exec(new Configuration(), argv)); 981 } catch (ExitUtil.ExitException e) { 982 LOG.error(e.toString()); 983 System.exit(e.status); 984 } catch (Exception e) { 985 LOG.error(e.toString(), e); 986 ExitUtil.halt(-1, e); 987 } 988 } 989 990 /** 991 * Diagnostics failures return the exit code 41, "unauthorized". 992 * 993 * They have a category, initially for testing: the category can be 994 * validated without having to match on the entire string. 995 */ 996 public static class KerberosDiagsFailure extends ExitUtil.ExitException { 997 private final String category; 998 999 public KerberosDiagsFailure(String category, String message) { 1000 super(KDIAG_FAILURE, category + ": " + message); 1001 this.category = category; 1002 } 1003 1004 public KerberosDiagsFailure(String category, 1005 String message, 1006 Object... args) { 1007 this(category, String.format(message, args)); 1008 } 1009 1010 public KerberosDiagsFailure(String category, Throwable throwable, 1011 String message, Object... args) { 1012 this(category, message, args); 1013 initCause(throwable); 1014 } 1015 1016 public String getCategory() { 1017 return category; 1018 } 1019 } 1020}