HADOOP-7964. Deadlock in NetUtils and SecurityUtil class initialization. Contributed by Daryn Sharp.

git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1230249 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Suresh Srinivas 2012-01-11 21:02:04 +00:00
parent c4c6d8ece3
commit 8f83a2355b
7 changed files with 196 additions and 198 deletions

View File

@ -266,6 +266,9 @@ Release 0.23.1 - Unreleased
HADOOP-7963. Fix ViewFS to catch a null canonical service-name and pass HADOOP-7963. Fix ViewFS to catch a null canonical service-name and pass
tests TestViewFileSystem* (Siddharth Seth via vinodkv) tests TestViewFileSystem* (Siddharth Seth via vinodkv)
HADOOP-7964. Deadlock in NetUtils and SecurityUtil class initialization.
(Daryn Sharp via suresh)
Release 0.23.0 - 2011-11-01 Release 0.23.0 - 2011-11-01
INCOMPATIBLE CHANGES INCOMPATIBLE CHANGES

View File

@ -51,12 +51,6 @@ import org.apache.hadoop.ipc.VersionedProtocol;
import org.apache.hadoop.security.SecurityUtil; import org.apache.hadoop.security.SecurityUtil;
import org.apache.hadoop.util.ReflectionUtils; import org.apache.hadoop.util.ReflectionUtils;
import com.google.common.annotations.VisibleForTesting;
//this will need to be replaced someday when there is a suitable replacement
import sun.net.dns.ResolverConfiguration;
import sun.net.util.IPAddressUtil;
@InterfaceAudience.LimitedPrivate({"HDFS", "MapReduce"}) @InterfaceAudience.LimitedPrivate({"HDFS", "MapReduce"})
@InterfaceStability.Unstable @InterfaceStability.Unstable
public class NetUtils { public class NetUtils {
@ -72,26 +66,6 @@ public class NetUtils {
/** Base URL of the Hadoop Wiki: {@value} */ /** Base URL of the Hadoop Wiki: {@value} */
public static final String HADOOP_WIKI = "http://wiki.apache.org/hadoop/"; public static final String HADOOP_WIKI = "http://wiki.apache.org/hadoop/";
private static HostResolver hostResolver;
static {
// SecurityUtils requires a more secure host resolver if tokens are
// using hostnames
setUseQualifiedHostResolver(!SecurityUtil.getTokenServiceUseIp());
}
/**
* This method is intended for use only by SecurityUtils!
* @param flag where the qualified or standard host resolver is used
* to create socket addresses
*/
@InterfaceAudience.Private
public static void setUseQualifiedHostResolver(boolean flag) {
hostResolver = flag
? new QualifiedHostResolver()
: new StandardHostResolver();
}
/** /**
* Get the socket factory for the given class according to its * Get the socket factory for the given class according to its
* configuration parameter * configuration parameter
@ -249,7 +223,7 @@ public class NetUtils {
InetSocketAddress addr; InetSocketAddress addr;
try { try {
InetAddress iaddr = hostResolver.getByName(resolveHost); InetAddress iaddr = SecurityUtil.getByName(resolveHost);
// if there is a static entry for the host, make the returned // if there is a static entry for the host, make the returned
// address look like the original given host // address look like the original given host
if (staticHost != null) { if (staticHost != null) {
@ -262,151 +236,6 @@ public class NetUtils {
return addr; return addr;
} }
interface HostResolver {
InetAddress getByName(String host) throws UnknownHostException;
}
/**
* Uses standard java host resolution
*/
static class StandardHostResolver implements HostResolver {
public InetAddress getByName(String host) throws UnknownHostException {
return InetAddress.getByName(host);
}
}
/**
* This an alternate resolver with important properties that the standard
* java resolver lacks:
* 1) The hostname is fully qualified. This avoids security issues if not
* all hosts in the cluster do not share the same search domains. It
* also prevents other hosts from performing unnecessary dns searches.
* In contrast, InetAddress simply returns the host as given.
* 2) The InetAddress is instantiated with an exact host and IP to prevent
* further unnecessary lookups. InetAddress may perform an unnecessary
* reverse lookup for an IP.
* 3) A call to getHostName() will always return the qualified hostname, or
* more importantly, the IP if instantiated with an IP. This avoids
* unnecessary dns timeouts if the host is not resolvable.
* 4) Point 3 also ensures that if the host is re-resolved, ex. during a
* connection re-attempt, that a reverse lookup to host and forward
* lookup to IP is not performed since the reverse/forward mappings may
* not always return the same IP. If the client initiated a connection
* with an IP, then that IP is all that should ever be contacted.
*
* NOTE: this resolver is only used if:
* hadoop.security.token.service.use_ip=false
*/
protected static class QualifiedHostResolver implements HostResolver {
@SuppressWarnings("unchecked")
private List<String> searchDomains =
ResolverConfiguration.open().searchlist();
/**
* Create an InetAddress with a fully qualified hostname of the given
* hostname. InetAddress does not qualify an incomplete hostname that
* is resolved via the domain search list.
* {@link InetAddress#getCanonicalHostName()} will fully qualify the
* hostname, but it always return the A record whereas the given hostname
* may be a CNAME.
*
* @param host a hostname or ip address
* @return InetAddress with the fully qualified hostname or ip
* @throws UnknownHostException if host does not exist
*/
public InetAddress getByName(String host) throws UnknownHostException {
InetAddress addr = null;
if (IPAddressUtil.isIPv4LiteralAddress(host)) {
// use ipv4 address as-is
byte[] ip = IPAddressUtil.textToNumericFormatV4(host);
addr = InetAddress.getByAddress(host, ip);
} else if (IPAddressUtil.isIPv6LiteralAddress(host)) {
// use ipv6 address as-is
byte[] ip = IPAddressUtil.textToNumericFormatV6(host);
addr = InetAddress.getByAddress(host, ip);
} else if (host.endsWith(".")) {
// a rooted host ends with a dot, ex. "host."
// rooted hosts never use the search path, so only try an exact lookup
addr = getByExactName(host);
} else if (host.contains(".")) {
// the host contains a dot (domain), ex. "host.domain"
// try an exact host lookup, then fallback to search list
addr = getByExactName(host);
if (addr == null) {
addr = getByNameWithSearch(host);
}
} else {
// it's a simple host with no dots, ex. "host"
// try the search list, then fallback to exact host
InetAddress loopback = InetAddress.getByName(null);
if (host.equalsIgnoreCase(loopback.getHostName())) {
addr = InetAddress.getByAddress(host, loopback.getAddress());
} else {
addr = getByNameWithSearch(host);
if (addr == null) {
addr = getByExactName(host);
}
}
}
// unresolvable!
if (addr == null) {
throw new UnknownHostException(host);
}
return addr;
}
InetAddress getByExactName(String host) {
InetAddress addr = null;
// InetAddress will use the search list unless the host is rooted
// with a trailing dot. The trailing dot will disable any use of the
// search path in a lower level resolver. See RFC 1535.
String fqHost = host;
if (!fqHost.endsWith(".")) fqHost += ".";
try {
addr = getInetAddressByName(fqHost);
// can't leave the hostname as rooted or other parts of the system
// malfunction, ex. kerberos principals are lacking proper host
// equivalence for rooted/non-rooted hostnames
addr = InetAddress.getByAddress(host, addr.getAddress());
} catch (UnknownHostException e) {
// ignore, caller will throw if necessary
}
return addr;
}
InetAddress getByNameWithSearch(String host) {
InetAddress addr = null;
if (host.endsWith(".")) { // already qualified?
addr = getByExactName(host);
} else {
for (String domain : searchDomains) {
String dot = !domain.startsWith(".") ? "." : "";
addr = getByExactName(host + dot + domain);
if (addr != null) break;
}
}
return addr;
}
// implemented as a separate method to facilitate unit testing
InetAddress getInetAddressByName(String host) throws UnknownHostException {
return InetAddress.getByName(host);
}
void setSearchDomains(String ... domains) {
searchDomains = Arrays.asList(domains);
}
}
/**
* This is for testing only!
*/
@VisibleForTesting
static void setHostResolver(HostResolver newResolver) {
hostResolver = newResolver;
}
/** /**
* Resolve the uri's hostname and add the default port if not in the uri * Resolve the uri's hostname and add the default port if not in the uri
* @param uri to resolve * @param uri to resolve
@ -447,7 +276,7 @@ public class NetUtils {
String fqHost = canonicalizedHostCache.get(host); String fqHost = canonicalizedHostCache.get(host);
if (fqHost == null) { if (fqHost == null) {
try { try {
fqHost = hostResolver.getByName(host).getHostName(); fqHost = SecurityUtil.getByName(host).getHostName();
// slight race condition, but won't hurt // slight race condition, but won't hurt
canonicalizedHostCache.put(host, fqHost); canonicalizedHostCache.put(host, fqHost);
} catch (UnknownHostException e) { } catch (UnknownHostException e) {

View File

@ -23,6 +23,8 @@ import java.net.URI;
import java.net.URL; import java.net.URL;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.security.AccessController; import java.security.AccessController;
import java.util.Arrays;
import java.util.List;
import java.util.ServiceLoader; import java.util.ServiceLoader;
import java.util.Set; import java.util.Set;
@ -41,6 +43,11 @@ import org.apache.hadoop.net.NetUtils;
import org.apache.hadoop.security.token.Token; import org.apache.hadoop.security.token.Token;
import org.apache.hadoop.security.token.TokenInfo; import org.apache.hadoop.security.token.TokenInfo;
import com.google.common.annotations.VisibleForTesting;
//this will need to be replaced someday when there is a suitable replacement
import sun.net.dns.ResolverConfiguration;
import sun.net.util.IPAddressUtil;
import sun.security.jgss.krb5.Krb5Util; import sun.security.jgss.krb5.Krb5Util;
import sun.security.krb5.Credentials; import sun.security.krb5.Credentials;
import sun.security.krb5.PrincipalName; import sun.security.krb5.PrincipalName;
@ -53,7 +60,10 @@ public class SecurityUtil {
// controls whether buildTokenService will use an ip or host/ip as given // controls whether buildTokenService will use an ip or host/ip as given
// by the user // by the user
private static boolean useIpForTokenService; @VisibleForTesting
static boolean useIpForTokenService;
@VisibleForTesting
static HostResolver hostResolver;
static { static {
boolean useIp = new Configuration().getBoolean( boolean useIp = new Configuration().getBoolean(
@ -68,16 +78,9 @@ public class SecurityUtil {
@InterfaceAudience.Private @InterfaceAudience.Private
static void setTokenServiceUseIp(boolean flag) { static void setTokenServiceUseIp(boolean flag) {
useIpForTokenService = flag; useIpForTokenService = flag;
NetUtils.setUseQualifiedHostResolver(!flag); hostResolver = !useIpForTokenService
} ? new QualifiedHostResolver()
: new StandardHostResolver();
/**
* Intended only for temporary use by NetUtils. Do not use.
* @return whether tokens use an IP address
*/
@InterfaceAudience.Private
public static boolean getTokenServiceUseIp() {
return useIpForTokenService;
} }
/** /**
@ -142,7 +145,7 @@ public class SecurityUtil {
* it will be removed when the Java behavior is changed. * it will be removed when the Java behavior is changed.
* *
* @param remoteHost Target URL the krb-https client will access * @param remoteHost Target URL the krb-https client will access
* @throws IOException * @throws IOException if the service ticket cannot be retrieved
*/ */
public static void fetchServiceTicket(URL remoteHost) throws IOException { public static void fetchServiceTicket(URL remoteHost) throws IOException {
if(!UserGroupInformation.isSecurityEnabled()) if(!UserGroupInformation.isSecurityEnabled())
@ -179,7 +182,7 @@ public class SecurityUtil {
* @param hostname * @param hostname
* the fully-qualified domain name used for substitution * the fully-qualified domain name used for substitution
* @return converted Kerberos principal name * @return converted Kerberos principal name
* @throws IOException * @throws IOException if the client address cannot be determined
*/ */
public static String getServerPrincipal(String principalConfig, public static String getServerPrincipal(String principalConfig,
String hostname) throws IOException { String hostname) throws IOException {
@ -204,7 +207,7 @@ public class SecurityUtil {
* @param addr * @param addr
* InetAddress of the host used for substitution * InetAddress of the host used for substitution
* @return converted Kerberos principal name * @return converted Kerberos principal name
* @throws IOException * @throws IOException if the client address cannot be determined
*/ */
public static String getServerPrincipal(String principalConfig, public static String getServerPrincipal(String principalConfig,
InetAddress addr) throws IOException { InetAddress addr) throws IOException {
@ -251,7 +254,7 @@ public class SecurityUtil {
* the key to look for keytab file in conf * the key to look for keytab file in conf
* @param userNameKey * @param userNameKey
* the key to look for user's Kerberos principal name in conf * the key to look for user's Kerberos principal name in conf
* @throws IOException * @throws IOException if login fails
*/ */
public static void login(final Configuration conf, public static void login(final Configuration conf,
final String keytabFileKey, final String userNameKey) throws IOException { final String keytabFileKey, final String userNameKey) throws IOException {
@ -271,7 +274,7 @@ public class SecurityUtil {
* the key to look for user's Kerberos principal name in conf * the key to look for user's Kerberos principal name in conf
* @param hostname * @param hostname
* hostname to use for substitution * hostname to use for substitution
* @throws IOException * @throws IOException if the config doesn't specify a keytab
*/ */
public static void login(final Configuration conf, public static void login(final Configuration conf,
final String keytabFileKey, final String userNameKey, String hostname) final String keytabFileKey, final String userNameKey, String hostname)
@ -363,7 +366,7 @@ public class SecurityUtil {
* Look up the TokenInfo for a given protocol. It searches all known * Look up the TokenInfo for a given protocol. It searches all known
* SecurityInfo providers. * SecurityInfo providers.
* @param protocol The protocol class to get the information for. * @param protocol The protocol class to get the information for.
* @conf conf Configuration object * @param conf Configuration object
* @return the TokenInfo or null if it has no KerberosInfo defined * @return the TokenInfo or null if it has no KerberosInfo defined
*/ */
public static TokenInfo getTokenInfo(Class<?> protocol, Configuration conf) { public static TokenInfo getTokenInfo(Class<?> protocol, Configuration conf) {
@ -442,4 +445,155 @@ public class SecurityUtil {
public static Text buildTokenService(URI uri) { public static Text buildTokenService(URI uri) {
return buildTokenService(NetUtils.createSocketAddr(uri.getAuthority())); return buildTokenService(NetUtils.createSocketAddr(uri.getAuthority()));
} }
/**
* Resolves a host subject to the security requirements determined by
* hadoop.security.token.service.use_ip.
*
* @param hostname host or ip to resolve
* @return a resolved host
* @throws UnknownHostException if the host doesn't exist
*/
@InterfaceAudience.Private
public static
InetAddress getByName(String hostname) throws UnknownHostException {
return hostResolver.getByName(hostname);
}
interface HostResolver {
InetAddress getByName(String host) throws UnknownHostException;
}
/**
* Uses standard java host resolution
*/
static class StandardHostResolver implements HostResolver {
public InetAddress getByName(String host) throws UnknownHostException {
return InetAddress.getByName(host);
}
}
/**
* This an alternate resolver with important properties that the standard
* java resolver lacks:
* 1) The hostname is fully qualified. This avoids security issues if not
* all hosts in the cluster do not share the same search domains. It
* also prevents other hosts from performing unnecessary dns searches.
* In contrast, InetAddress simply returns the host as given.
* 2) The InetAddress is instantiated with an exact host and IP to prevent
* further unnecessary lookups. InetAddress may perform an unnecessary
* reverse lookup for an IP.
* 3) A call to getHostName() will always return the qualified hostname, or
* more importantly, the IP if instantiated with an IP. This avoids
* unnecessary dns timeouts if the host is not resolvable.
* 4) Point 3 also ensures that if the host is re-resolved, ex. during a
* connection re-attempt, that a reverse lookup to host and forward
* lookup to IP is not performed since the reverse/forward mappings may
* not always return the same IP. If the client initiated a connection
* with an IP, then that IP is all that should ever be contacted.
*
* NOTE: this resolver is only used if:
* hadoop.security.token.service.use_ip=false
*/
protected static class QualifiedHostResolver implements HostResolver {
@SuppressWarnings("unchecked")
private List<String> searchDomains =
ResolverConfiguration.open().searchlist();
/**
* Create an InetAddress with a fully qualified hostname of the given
* hostname. InetAddress does not qualify an incomplete hostname that
* is resolved via the domain search list.
* {@link InetAddress#getCanonicalHostName()} will fully qualify the
* hostname, but it always return the A record whereas the given hostname
* may be a CNAME.
*
* @param host a hostname or ip address
* @return InetAddress with the fully qualified hostname or ip
* @throws UnknownHostException if host does not exist
*/
public InetAddress getByName(String host) throws UnknownHostException {
InetAddress addr = null;
if (IPAddressUtil.isIPv4LiteralAddress(host)) {
// use ipv4 address as-is
byte[] ip = IPAddressUtil.textToNumericFormatV4(host);
addr = InetAddress.getByAddress(host, ip);
} else if (IPAddressUtil.isIPv6LiteralAddress(host)) {
// use ipv6 address as-is
byte[] ip = IPAddressUtil.textToNumericFormatV6(host);
addr = InetAddress.getByAddress(host, ip);
} else if (host.endsWith(".")) {
// a rooted host ends with a dot, ex. "host."
// rooted hosts never use the search path, so only try an exact lookup
addr = getByExactName(host);
} else if (host.contains(".")) {
// the host contains a dot (domain), ex. "host.domain"
// try an exact host lookup, then fallback to search list
addr = getByExactName(host);
if (addr == null) {
addr = getByNameWithSearch(host);
}
} else {
// it's a simple host with no dots, ex. "host"
// try the search list, then fallback to exact host
InetAddress loopback = InetAddress.getByName(null);
if (host.equalsIgnoreCase(loopback.getHostName())) {
addr = InetAddress.getByAddress(host, loopback.getAddress());
} else {
addr = getByNameWithSearch(host);
if (addr == null) {
addr = getByExactName(host);
}
}
}
// unresolvable!
if (addr == null) {
throw new UnknownHostException(host);
}
return addr;
}
InetAddress getByExactName(String host) {
InetAddress addr = null;
// InetAddress will use the search list unless the host is rooted
// with a trailing dot. The trailing dot will disable any use of the
// search path in a lower level resolver. See RFC 1535.
String fqHost = host;
if (!fqHost.endsWith(".")) fqHost += ".";
try {
addr = getInetAddressByName(fqHost);
// can't leave the hostname as rooted or other parts of the system
// malfunction, ex. kerberos principals are lacking proper host
// equivalence for rooted/non-rooted hostnames
addr = InetAddress.getByAddress(host, addr.getAddress());
} catch (UnknownHostException e) {
// ignore, caller will throw if necessary
}
return addr;
}
InetAddress getByNameWithSearch(String host) {
InetAddress addr = null;
if (host.endsWith(".")) { // already qualified?
addr = getByExactName(host);
} else {
for (String domain : searchDomains) {
String dot = !domain.startsWith(".") ? "." : "";
addr = getByExactName(host + dot + domain);
if (addr != null) break;
}
}
return addr;
}
// implemented as a separate method to facilitate unit testing
InetAddress getInetAddressByName(String host) throws UnknownHostException {
return InetAddress.getByName(host);
}
void setSearchDomains(String ... domains) {
searchDomains = Arrays.asList(domains);
}
}
} }

View File

@ -25,7 +25,7 @@ import junit.framework.TestCase;
import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.permission.FsPermission; import org.apache.hadoop.fs.permission.FsPermission;
import org.apache.hadoop.net.NetUtilsTestResolver; import org.apache.hadoop.security.NetUtilsTestResolver;
import org.apache.hadoop.util.Progressable; import org.apache.hadoop.util.Progressable;
import org.junit.Test; import org.junit.Test;

View File

@ -37,6 +37,7 @@ import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.security.NetUtilsTestResolver;
import org.junit.Before; import org.junit.Before;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;

View File

@ -16,7 +16,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.apache.hadoop.net; package org.apache.hadoop.security;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
@ -25,7 +25,7 @@ import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.apache.hadoop.net.NetUtils.QualifiedHostResolver; import org.apache.hadoop.security.SecurityUtil.QualifiedHostResolver;
/** /**
* provides a dummy dns search resolver with a configurable search path * provides a dummy dns search resolver with a configurable search path
@ -41,7 +41,7 @@ public class NetUtilsTestResolver extends QualifiedHostResolver {
resolver.addResolvedHost("host.a.b.", "1.1.1.1"); resolver.addResolvedHost("host.a.b.", "1.1.1.1");
resolver.addResolvedHost("b-host.b.", "2.2.2.2"); resolver.addResolvedHost("b-host.b.", "2.2.2.2");
resolver.addResolvedHost("simple.", "3.3.3.3"); resolver.addResolvedHost("simple.", "3.3.3.3");
NetUtils.setHostResolver(resolver); SecurityUtil.hostResolver = resolver;
return resolver; return resolver;
} }
@ -56,7 +56,8 @@ public class NetUtilsTestResolver extends QualifiedHostResolver {
resolvedHosts.put(host, addr); resolvedHosts.put(host, addr);
} }
InetAddress getInetAddressByName(String host) throws UnknownHostException { @Override
public InetAddress getInetAddressByName(String host) throws UnknownHostException {
hostSearches.add(host); hostSearches.add(host);
if (!resolvedHosts.containsKey(host)) { if (!resolvedHosts.containsKey(host)) {
throw new UnknownHostException(host); throw new UnknownHostException(host);
@ -64,11 +65,21 @@ public class NetUtilsTestResolver extends QualifiedHostResolver {
return resolvedHosts.get(host); return resolvedHosts.get(host);
} }
String[] getHostSearches() { @Override
public InetAddress getByExactName(String host) {
return super.getByExactName(host);
}
@Override
public InetAddress getByNameWithSearch(String host) {
return super.getByNameWithSearch(host);
}
public String[] getHostSearches() {
return hostSearches.toArray(new String[0]); return hostSearches.toArray(new String[0]);
} }
void reset() { public void reset() {
hostSearches.clear(); hostSearches.clear();
} }
} }

View File

@ -225,7 +225,7 @@ public class TestSecurityUtil {
assertTrue(!addr.isUnresolved()); assertTrue(!addr.isUnresolved());
// don't know what the standard resolver will return for hostname. // don't know what the standard resolver will return for hostname.
// should be host for host; host or ip for ip is ambiguous // should be host for host; host or ip for ip is ambiguous
if (!SecurityUtil.getTokenServiceUseIp()) { if (!SecurityUtil.useIpForTokenService) {
assertEquals(host, addr.getHostName()); assertEquals(host, addr.getHostName());
assertEquals(host, addr.getAddress().getHostName()); assertEquals(host, addr.getAddress().getHostName());
} }