diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/KerberosAuthException.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/KerberosAuthException.java new file mode 100644 index 0000000000..811c7c969b --- /dev/null +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/KerberosAuthException.java @@ -0,0 +1,118 @@ +/** + * 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 static org.apache.hadoop.security.UGIExceptionMessages.*; + +import java.io.IOException; +import org.apache.hadoop.classification.InterfaceAudience; +import org.apache.hadoop.classification.InterfaceStability; + +/** + * Thrown when {@link UserGroupInformation} failed with an unrecoverable error, + * such as failure in kerberos login/logout, invalid subject etc. + * + * Caller should not retry when catching this exception. + */ +@InterfaceAudience.Public +@InterfaceStability.Unstable +public class KerberosAuthException extends IOException { + static final long serialVersionUID = 31L; + + private String user; + private String principal; + private String keytabFile; + private String ticketCacheFile; + private String initialMessage; + + public KerberosAuthException(String msg) { + super(msg); + } + + public KerberosAuthException(Throwable cause) { + super(cause); + } + + public KerberosAuthException(String initialMsg, Throwable cause) { + this(cause); + initialMessage = initialMsg; + } + + public void setUser(final String u) { + user = u; + } + + public void setPrincipal(final String p) { + principal = p; + } + + public void setKeytabFile(final String k) { + keytabFile = k; + } + + public void setTicketCacheFile(final String t) { + ticketCacheFile = t; + } + + /** @return The initial message, or null if not set. */ + public String getInitialMessage() { + return initialMessage; + } + + /** @return The keytab file path, or null if not set. */ + public String getKeytabFile() { + return keytabFile; + } + + /** @return The principal, or null if not set. */ + public String getPrincipal() { + return principal; + } + + /** @return The ticket cache file path, or null if not set. */ + public String getTicketCacheFile() { + return ticketCacheFile; + } + + /** @return The user, or null if not set. */ + public String getUser() { + return user; + } + + @Override + public String getMessage() { + final StringBuilder sb = new StringBuilder(); + if (initialMessage != null) { + sb.append(initialMessage); + } + if (user != null) { + sb.append(FOR_USER + user); + } + if (principal != null) { + sb.append(FOR_PRINCIPAL + principal); + } + if (keytabFile != null) { + sb.append(FROM_KEYTAB + keytabFile); + } + if (ticketCacheFile != null) { + sb.append(USING_TICKET_CACHE_FILE+ ticketCacheFile); + } + sb.append(" " + super.getMessage()); + return sb.toString(); + } +} \ No newline at end of file diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/UGIExceptionMessages.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/UGIExceptionMessages.java new file mode 100644 index 0000000000..c4d30e509e --- /dev/null +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/UGIExceptionMessages.java @@ -0,0 +1,46 @@ +/* + * 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; + +/** + * Standard strings to use in exception messages + * in {@link KerberosAuthException} when throwing. + */ +final class UGIExceptionMessages { + + public static final String FAILURE_TO_LOGIN = "failure to login:"; + public static final String FOR_USER = " for user: "; + public static final String FOR_PRINCIPAL = " for principal: "; + public static final String FROM_KEYTAB = " from keytab "; + public static final String LOGIN_FAILURE = "Login failure"; + public static final String LOGOUT_FAILURE = "Logout failure"; + public static final String MUST_FIRST_LOGIN = + "login must be done first"; + public static final String MUST_FIRST_LOGIN_FROM_KEYTAB = + "loginUserFromKeyTab must be done first"; + public static final String SUBJECT_MUST_CONTAIN_PRINCIPAL = + "Provided Subject must contain a KerberosPrincipal"; + public static final String SUBJECT_MUST_NOT_BE_NULL = + "Subject must not be null"; + public static final String USING_TICKET_CACHE_FILE = + " using ticket cache file: "; + + //checkstyle: Utility classes should not have a public or default constructor. + private UGIExceptionMessages() { + } +} diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/UserGroupInformation.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/UserGroupInformation.java index 637e3fa4e1..329859d29e 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/UserGroupInformation.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/UserGroupInformation.java @@ -21,6 +21,7 @@ import static org.apache.hadoop.fs.CommonConfigurationKeys.HADOOP_USER_GROUP_MET import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.HADOOP_KERBEROS_MIN_SECONDS_BEFORE_RELOGIN; import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.HADOOP_KERBEROS_MIN_SECONDS_BEFORE_RELOGIN_DEFAULT; import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.HADOOP_TOKEN_FILES; +import static org.apache.hadoop.security.UGIExceptionMessages.*; import static org.apache.hadoop.util.PlatformName.IBM_JAVA; import java.io.File; @@ -755,8 +756,11 @@ public class UserGroupInformation { ugi.setAuthenticationMethod(AuthenticationMethod.KERBEROS); return ugi; } catch (LoginException le) { - throw new IOException("failure to login using ticket cache file " + - ticketCache, le); + KerberosAuthException kae = + new KerberosAuthException(FAILURE_TO_LOGIN, le); + kae.setUser(user); + kae.setTicketCacheFile(ticketCache); + throw kae; } } @@ -765,16 +769,17 @@ public class UserGroupInformation { * * @param subject The KerberosPrincipal to use in UGI * - * @throws IOException if the kerberos login fails + * @throws IOException + * @throws KerberosAuthException if the kerberos login fails */ public static UserGroupInformation getUGIFromSubject(Subject subject) throws IOException { if (subject == null) { - throw new IOException("Subject must not be null"); + throw new KerberosAuthException(SUBJECT_MUST_NOT_BE_NULL); } if (subject.getPrincipals(KerberosPrincipal.class).isEmpty()) { - throw new IOException("Provided Subject must contain a KerberosPrincipal"); + throw new KerberosAuthException(SUBJECT_MUST_CONTAIN_PRINCIPAL); } KerberosPrincipal principal = @@ -894,7 +899,7 @@ public class UserGroupInformation { loginUser.spawnAutoRenewalThreadForUserCreds(); } catch (LoginException le) { LOG.debug("failure to login", le); - throw new IOException("failure to login: " + le, le); + throw new KerberosAuthException(FAILURE_TO_LOGIN, le); } if (LOG.isDebugEnabled()) { LOG.debug("UGI loginUser:"+loginUser); @@ -1001,7 +1006,8 @@ public class UserGroupInformation { * file and logs them in. They become the currently logged-in user. * @param user the principal name to load from the keytab * @param path the path to the keytab file - * @throws IOException if the keytab file can't be read + * @throws IOException + * @throws KerberosAuthException if it's a kerberos login exception. */ @InterfaceAudience.Public @InterfaceStability.Evolving @@ -1030,8 +1036,10 @@ public class UserGroupInformation { if (start > 0) { metrics.loginFailure.add(Time.now() - start); } - throw new IOException("Login failure for " + user + " from keytab " + - path+ ": " + le, le); + KerberosAuthException kae = new KerberosAuthException(LOGIN_FAILURE, le); + kae.setUser(user); + kae.setKeytabFile(path); + throw kae; } LOG.info("Login successful for user " + keytabPrincipal + " using keytab file " + keytabFile); @@ -1042,8 +1050,9 @@ public class UserGroupInformation { * This method assumes that the user logged in by calling * {@link #loginUserFromKeytab(String, String)}. * - * @throws IOException if a failure occurred in logout, or if the user did - * not log in by invoking loginUserFromKeyTab() before. + * @throws IOException + * @throws KerberosAuthException if a failure occurred in logout, + * or if the user did not log in by invoking loginUserFromKeyTab() before. */ @InterfaceAudience.Public @InterfaceStability.Evolving @@ -1054,7 +1063,7 @@ public class UserGroupInformation { } LoginContext login = getLogin(); if (login == null || keytabFile == null) { - throw new IOException("loginUserFromKeytab must be done first"); + throw new KerberosAuthException(MUST_FIRST_LOGIN_FROM_KEYTAB); } try { @@ -1065,9 +1074,10 @@ public class UserGroupInformation { login.logout(); } } catch (LoginException le) { - throw new IOException("Logout failure for " + user + " from keytab " + - keytabFile + ": " + le, - le); + KerberosAuthException kae = new KerberosAuthException(LOGOUT_FAILURE, le); + kae.setUser(user.toString()); + kae.setKeytabFile(keytabFile); + throw kae; } LOG.info("Logout successful for user " + keytabPrincipal @@ -1078,6 +1088,7 @@ public class UserGroupInformation { * Re-login a user from keytab if TGT is expired or is close to expiry. * * @throws IOException + * @throws KerberosAuthException if it's a kerberos login exception. */ public synchronized void checkTGTAndReloginFromKeytab() throws IOException { if (!isSecurityEnabled() @@ -1099,12 +1110,12 @@ public class UserGroupInformation { * happened already. * The Subject field of this UserGroupInformation object is updated to have * the new credentials. - * @throws IOException on a failure + * @throws IOException + * @throws KerberosAuthException on a failure */ @InterfaceAudience.Public @InterfaceStability.Evolving - public synchronized void reloginFromKeytab() - throws IOException { + public synchronized void reloginFromKeytab() throws IOException { if (!isSecurityEnabled() || user.getAuthenticationMethod() != AuthenticationMethod.KERBEROS || !isKeytab) @@ -1124,7 +1135,7 @@ public class UserGroupInformation { LoginContext login = getLogin(); if (login == null || keytabFile == null) { - throw new IOException("loginUserFromKeyTab must be done first"); + throw new KerberosAuthException(MUST_FIRST_LOGIN_FROM_KEYTAB); } long start = 0; @@ -1156,8 +1167,10 @@ public class UserGroupInformation { if (start > 0) { metrics.loginFailure.add(Time.now() - start); } - throw new IOException("Login failure for " + keytabPrincipal + - " from keytab " + keytabFile + ": " + le, le); + KerberosAuthException kae = new KerberosAuthException(LOGIN_FAILURE, le); + kae.setPrincipal(keytabPrincipal); + kae.setKeytabFile(keytabFile); + throw kae; } } @@ -1166,19 +1179,19 @@ public class UserGroupInformation { * method assumes that login had happened already. * The Subject field of this UserGroupInformation object is updated to have * the new credentials. - * @throws IOException on a failure + * @throws IOException + * @throws KerberosAuthException on a failure */ @InterfaceAudience.Public @InterfaceStability.Evolving - public synchronized void reloginFromTicketCache() - throws IOException { + public synchronized void reloginFromTicketCache() throws IOException { if (!isSecurityEnabled() || user.getAuthenticationMethod() != AuthenticationMethod.KERBEROS || !isKrbTkt) return; LoginContext login = getLogin(); if (login == null) { - throw new IOException("login must be done first"); + throw new KerberosAuthException(MUST_FIRST_LOGIN); } long now = Time.now(); if (!hasSufficientTimeElapsed(now)) { @@ -1205,8 +1218,9 @@ public class UserGroupInformation { login.login(); setLogin(login); } catch (LoginException le) { - throw new IOException("Login failure for " + getUserName() + ": " + le, - le); + KerberosAuthException kae = new KerberosAuthException(LOGIN_FAILURE, le); + kae.setUser(getUserName()); + throw kae; } } @@ -1252,8 +1266,10 @@ public class UserGroupInformation { if (start > 0) { metrics.loginFailure.add(Time.now() - start); } - throw new IOException("Login failure for " + user + " from keytab " + - path + ": " + le, le); + KerberosAuthException kae = new KerberosAuthException(LOGIN_FAILURE, le); + kae.setUser(user); + kae.setKeytabFile(path); + throw kae; } finally { if(oldKeytabFile != null) keytabFile = oldKeytabFile; if(oldKeytabPrincipal != null) keytabPrincipal = oldKeytabPrincipal;