diff --git a/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/util/KerberosUtil.java b/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/util/KerberosUtil.java index f7f5f63e829..fec7f018d1d 100644 --- a/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/util/KerberosUtil.java +++ b/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/util/KerberosUtil.java @@ -83,17 +83,79 @@ public class KerberosUtil { new Class[0]); return (String)getDefaultRealmMethod.invoke(kerbConf, new Object[0]); } - + + public static String getDefaultRealmProtected() { + String realmString = null; + try { + realmString = getDefaultRealm(); + } catch (RuntimeException rte) { + //silently catch everything + } catch (Exception e) { + //silently return null + } + return realmString; + } + + /* + * For a Service Host Principal specification, map the host's domain + * to kerberos realm, as specified by krb5.conf [domain_realm] mappings. + * Unfortunately the mapping routines are private to the security.krb5 + * package, so have to construct a PrincipalName instance to derive the realm. + * + * Many things can go wrong with Kerberos configuration, and this is not + * the place to be throwing exceptions to help debug them. Nor do we choose + * to make potentially voluminous logs on every call to a communications API. + * So we simply swallow all exceptions from the underlying libraries and + * return null if we can't get a good value for the realmString. + * + * @param shortprinc A service principal name with host fqdn as instance, e.g. + * "HTTP/myhost.mydomain" + * @return String value of Kerberos realm, mapped from host fqdn + * May be default realm, or may be null. + */ + public static String getDomainRealm(String shortprinc) { + Class classRef; + Object principalName; //of type sun.security.krb5.PrincipalName or IBM equiv + String realmString = null; + try { + if (System.getProperty("java.vendor").contains("IBM")) { + classRef = Class.forName("com.ibm.security.krb5.PrincipalName"); + } else { + classRef = Class.forName("sun.security.krb5.PrincipalName"); + } + int tKrbNtSrvHst = classRef.getField("KRB_NT_SRV_HST").getInt(null); + principalName = classRef.getConstructor(String.class, int.class). + newInstance(shortprinc, tKrbNtSrvHst); + realmString = (String)classRef.getMethod("getRealmString", new Class[0]). + invoke(principalName, new Object[0]); + } catch (RuntimeException rte) { + //silently catch everything + } catch (Exception e) { + //silently return default realm (which may itself be null) + } + if (null == realmString || realmString.equals("")) { + return getDefaultRealmProtected(); + } else { + return realmString; + } + } + /* Return fqdn of the current host */ static String getLocalHostName() throws UnknownHostException { return InetAddress.getLocalHost().getCanonicalHostName(); } /** - * Create Kerberos principal for a given service and hostname. It converts + * Create Kerberos principal for a given service and hostname, + * inferring realm from the fqdn of the hostname. It converts * hostname to lower case. If hostname is null or "0.0.0.0", it uses * dynamically looked-up fqdn of the current host instead. - * + * If domain_realm mappings are inadequately specified, it will + * use default_realm, per usual Kerberos behavior. + * If default_realm also gives a null value, then a principal + * without realm will be returned, which by Kerberos definitions is + * just another way to specify default realm. + * * @param service * Service for which you want to generate the principal. * @param hostname @@ -102,15 +164,26 @@ public class KerberosUtil { * @throws UnknownHostException * If no IP address for the local host could be found. */ - public static final String getServicePrincipal(String service, String hostname) + public static final String getServicePrincipal(String service, + String hostname) throws UnknownHostException { String fqdn = hostname; + String shortprinc = null; + String realmString = null; if (null == fqdn || fqdn.equals("") || fqdn.equals("0.0.0.0")) { fqdn = getLocalHostName(); } // convert hostname to lowercase as kerberos does not work with hostnames // with uppercase characters. - return service + "/" + fqdn.toLowerCase(Locale.ENGLISH); + fqdn = fqdn.toLowerCase(Locale.ENGLISH); + shortprinc = service + "/" + fqdn; + // Obtain the realm name inferred from the domain of the host + realmString = getDomainRealm(shortprinc); + if (null == realmString || realmString.equals("")) { + return shortprinc; + } else { + return shortprinc + "@" + realmString; + } } /** diff --git a/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/util/TestKerberosUtil.java b/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/util/TestKerberosUtil.java index 89e07d1a5f0..97ef025c850 100644 --- a/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/util/TestKerberosUtil.java +++ b/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/util/TestKerberosUtil.java @@ -18,6 +18,7 @@ package org.apache.hadoop.security.authentication.util; import java.io.File; import java.io.IOException; +import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -52,30 +53,46 @@ public class TestKerberosUtil { } @Test - public void testGetServerPrincipal() throws IOException { + public void testGetServerPrincipal() + throws IOException, UnknownHostException { String service = "TestKerberosUtil"; String localHostname = KerberosUtil.getLocalHostName(); String testHost = "FooBar"; + String defaultRealm = KerberosUtil.getDefaultRealmProtected(); + + String atDefaultRealm; + if (defaultRealm == null || defaultRealm.equals("")) { + atDefaultRealm = ""; + } else { + atDefaultRealm = "@" + defaultRealm; + } + // check that the test environment is as expected + Assert.assertEquals("testGetServerPrincipal assumes localhost realm is default", + KerberosUtil.getDomainRealm(service + "/" + localHostname.toLowerCase(Locale.ENGLISH)), + defaultRealm); + Assert.assertEquals("testGetServerPrincipal assumes realm of testHost 'FooBar' is default", + KerberosUtil.getDomainRealm(service + "/" + testHost.toLowerCase(Locale.ENGLISH)), + defaultRealm); // send null hostname Assert.assertEquals("When no hostname is sent", - service + "/" + localHostname.toLowerCase(Locale.ENGLISH), + service + "/" + localHostname.toLowerCase(Locale.ENGLISH) + atDefaultRealm, KerberosUtil.getServicePrincipal(service, null)); // send empty hostname Assert.assertEquals("When empty hostname is sent", - service + "/" + localHostname.toLowerCase(Locale.ENGLISH), + service + "/" + localHostname.toLowerCase(Locale.ENGLISH) + atDefaultRealm, KerberosUtil.getServicePrincipal(service, "")); // send 0.0.0.0 hostname Assert.assertEquals("When 0.0.0.0 hostname is sent", - service + "/" + localHostname.toLowerCase(Locale.ENGLISH), + service + "/" + localHostname.toLowerCase(Locale.ENGLISH) + atDefaultRealm, KerberosUtil.getServicePrincipal(service, "0.0.0.0")); // send uppercase hostname Assert.assertEquals("When uppercase hostname is sent", - service + "/" + testHost.toLowerCase(Locale.ENGLISH), + service + "/" + testHost.toLowerCase(Locale.ENGLISH) + atDefaultRealm, KerberosUtil.getServicePrincipal(service, testHost)); // send lowercase hostname Assert.assertEquals("When lowercase hostname is sent", - service + "/" + testHost.toLowerCase(Locale.ENGLISH), + service + "/" + testHost.toLowerCase(Locale.ENGLISH) + atDefaultRealm, KerberosUtil.getServicePrincipal( service, testHost.toLowerCase(Locale.ENGLISH))); } diff --git a/hadoop-common-project/hadoop-common/CHANGES.txt b/hadoop-common-project/hadoop-common/CHANGES.txt index 04b16b9aa9b..81708bfcbc3 100644 --- a/hadoop-common-project/hadoop-common/CHANGES.txt +++ b/hadoop-common-project/hadoop-common/CHANGES.txt @@ -397,6 +397,9 @@ Release 2.8.0 - UNRELEASED BUG FIXES + HADOOP-12617. SPNEGO authentication request to non-default realm gets + default realm name inserted in target server principal. (mattf) + HADOOP-12352. Delay in checkpointing Trash can leave trash for 2 intervals before deleting (Casey Brotherton via harsh)