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:27:50 -08:00
parent 7e4715186d
commit ada9c2c410
3 changed files with 107 additions and 14 deletions

View File

@ -42,7 +42,7 @@ public class KerberosUtil {
/* Return the Kerberos login module name */
public static String getKrb5LoginModuleName() {
return System.getProperty("java.vendor").contains("IBM")
return (IBM_JAVA)
? "com.ibm.security.auth.module.Krb5LoginModule"
: "com.sun.security.auth.module.Krb5LoginModule";
}
@ -72,7 +72,7 @@ public static String getDefaultRealm()
Class<?> classRef;
Method getInstanceMethod;
Method getDefaultRealmMethod;
if (System.getProperty("java.vendor").contains("IBM")) {
if (IBM_JAVA) {
classRef = Class.forName("com.ibm.security.krb5.internal.Config");
} else {
classRef = Class.forName("sun.security.krb5.Config");
@ -84,15 +84,77 @@ public static String getDefaultRealm()
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 (IBM_JAVA) {
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.
@ -102,15 +164,26 @@ static String getLocalHostName() throws UnknownHostException {
* @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.US);
fqdn = fqdn.toLowerCase(Locale.US);
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 @@
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,32 +53,48 @@ public void deleteKeytab() {
}
@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.US)),
defaultRealm);
Assert.assertEquals("testGetServerPrincipal assumes realm of testHost 'FooBar' is default",
KerberosUtil.getDomainRealm(service + "/" + testHost.toLowerCase(Locale.US)),
defaultRealm);
// send null hostname
Assert.assertEquals("When no hostname is sent",
service + "/" + localHostname.toLowerCase(Locale.ENGLISH),
service + "/" + localHostname.toLowerCase(Locale.US) + atDefaultRealm,
KerberosUtil.getServicePrincipal(service, null));
// send empty hostname
Assert.assertEquals("When empty hostname is sent",
service + "/" + localHostname.toLowerCase(Locale.ENGLISH),
service + "/" + localHostname.toLowerCase(Locale.US) + 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.US) + 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.US) + atDefaultRealm,
KerberosUtil.getServicePrincipal(service, testHost));
// send lowercase hostname
Assert.assertEquals("When lowercase hostname is sent",
service + "/" + testHost.toLowerCase(Locale.ENGLISH),
service + "/" + testHost.toLowerCase(Locale.US) + atDefaultRealm,
KerberosUtil.getServicePrincipal(
service, testHost.toLowerCase(Locale.ENGLISH)));
service, testHost.toLowerCase(Locale.US)));
}
@Test

View File

@ -264,6 +264,9 @@ Trunk (Unreleased)
BUG FIXES
HADOOP-12617. SPNEGO authentication request to non-default realm gets
default realm name inserted in target server principal. (mattf)
HADOOP-11473. test-patch says "-1 overall" even when all checks are +1
(Jason Lowe via raviprak)