HADOOP-12617. SPNEGO authentication request to non-default realm gets default realm name inserted in target server principal. (mattf)

This commit is contained in:
mattf 2015-12-08 17:35:25 -08:00
parent 7013f9d6cd
commit 472541291b
3 changed files with 104 additions and 11 deletions

View File

@ -84,15 +84,77 @@ public class KerberosUtil {
return (String)getDefaultRealmMethod.invoke(kerbConf, new Object[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 */ /* Return fqdn of the current host */
static String getLocalHostName() throws UnknownHostException { static String getLocalHostName() throws UnknownHostException {
return InetAddress.getLocalHost().getCanonicalHostName(); 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 * hostname to lower case. If hostname is null or "0.0.0.0", it uses
* dynamically looked-up fqdn of the current host instead. * 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 * @param service
* Service for which you want to generate the principal. * Service for which you want to generate the principal.
@ -102,15 +164,26 @@ public class KerberosUtil {
* @throws UnknownHostException * @throws UnknownHostException
* If no IP address for the local host could be found. * 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 { throws UnknownHostException {
String fqdn = hostname; String fqdn = hostname;
String shortprinc = null;
String realmString = null;
if (null == fqdn || fqdn.equals("") || fqdn.equals("0.0.0.0")) { if (null == fqdn || fqdn.equals("") || fqdn.equals("0.0.0.0")) {
fqdn = getLocalHostName(); fqdn = getLocalHostName();
} }
// convert hostname to lowercase as kerberos does not work with hostnames // convert hostname to lowercase as kerberos does not work with hostnames
// with uppercase characters. // 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;
}
} }
/** /**

View File

@ -18,6 +18,7 @@ package org.apache.hadoop.security.authentication.util;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.UnknownHostException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@ -52,30 +53,46 @@ public class TestKerberosUtil {
} }
@Test @Test
public void testGetServerPrincipal() throws IOException { public void testGetServerPrincipal()
throws IOException, UnknownHostException {
String service = "TestKerberosUtil"; String service = "TestKerberosUtil";
String localHostname = KerberosUtil.getLocalHostName(); String localHostname = KerberosUtil.getLocalHostName();
String testHost = "FooBar"; 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 // send null hostname
Assert.assertEquals("When no hostname is sent", Assert.assertEquals("When no hostname is sent",
service + "/" + localHostname.toLowerCase(Locale.ENGLISH), service + "/" + localHostname.toLowerCase(Locale.ENGLISH) + atDefaultRealm,
KerberosUtil.getServicePrincipal(service, null)); KerberosUtil.getServicePrincipal(service, null));
// send empty hostname // send empty hostname
Assert.assertEquals("When empty hostname is sent", Assert.assertEquals("When empty hostname is sent",
service + "/" + localHostname.toLowerCase(Locale.ENGLISH), service + "/" + localHostname.toLowerCase(Locale.ENGLISH) + atDefaultRealm,
KerberosUtil.getServicePrincipal(service, "")); KerberosUtil.getServicePrincipal(service, ""));
// send 0.0.0.0 hostname // send 0.0.0.0 hostname
Assert.assertEquals("When 0.0.0.0 hostname is sent", 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")); KerberosUtil.getServicePrincipal(service, "0.0.0.0"));
// send uppercase hostname // send uppercase hostname
Assert.assertEquals("When uppercase hostname is sent", Assert.assertEquals("When uppercase hostname is sent",
service + "/" + testHost.toLowerCase(Locale.ENGLISH), service + "/" + testHost.toLowerCase(Locale.ENGLISH) + atDefaultRealm,
KerberosUtil.getServicePrincipal(service, testHost)); KerberosUtil.getServicePrincipal(service, testHost));
// send lowercase hostname // send lowercase hostname
Assert.assertEquals("When lowercase hostname is sent", Assert.assertEquals("When lowercase hostname is sent",
service + "/" + testHost.toLowerCase(Locale.ENGLISH), service + "/" + testHost.toLowerCase(Locale.ENGLISH) + atDefaultRealm,
KerberosUtil.getServicePrincipal( KerberosUtil.getServicePrincipal(
service, testHost.toLowerCase(Locale.ENGLISH))); service, testHost.toLowerCase(Locale.ENGLISH)));
} }

View File

@ -412,6 +412,9 @@ Release 2.8.0 - UNRELEASED
BUG FIXES 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 HADOOP-12352. Delay in checkpointing Trash can leave trash for 2 intervals
before deleting (Casey Brotherton via harsh) before deleting (Casey Brotherton via harsh)