|
@@ -23,6 +23,8 @@ import java.net.URI;
|
|
|
import java.net.URL;
|
|
|
import java.net.UnknownHostException;
|
|
|
import java.security.AccessController;
|
|
|
+import java.util.Arrays;
|
|
|
+import java.util.List;
|
|
|
import java.util.ServiceLoader;
|
|
|
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.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.krb5.Credentials;
|
|
|
import sun.security.krb5.PrincipalName;
|
|
@@ -53,7 +60,10 @@ public class SecurityUtil {
|
|
|
|
|
|
// controls whether buildTokenService will use an ip or host/ip as given
|
|
|
// by the user
|
|
|
- private static boolean useIpForTokenService;
|
|
|
+ @VisibleForTesting
|
|
|
+ static boolean useIpForTokenService;
|
|
|
+ @VisibleForTesting
|
|
|
+ static HostResolver hostResolver;
|
|
|
|
|
|
static {
|
|
|
boolean useIp = new Configuration().getBoolean(
|
|
@@ -68,16 +78,9 @@ public class SecurityUtil {
|
|
|
@InterfaceAudience.Private
|
|
|
static void setTokenServiceUseIp(boolean flag) {
|
|
|
useIpForTokenService = flag;
|
|
|
- NetUtils.setUseQualifiedHostResolver(!flag);
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 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;
|
|
|
+ hostResolver = !useIpForTokenService
|
|
|
+ ? new QualifiedHostResolver()
|
|
|
+ : new StandardHostResolver();
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -142,7 +145,7 @@ public class SecurityUtil {
|
|
|
* it will be removed when the Java behavior is changed.
|
|
|
*
|
|
|
* @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 {
|
|
|
if(!UserGroupInformation.isSecurityEnabled())
|
|
@@ -179,7 +182,7 @@ public class SecurityUtil {
|
|
|
* @param hostname
|
|
|
* the fully-qualified domain name used for substitution
|
|
|
* @return converted Kerberos principal name
|
|
|
- * @throws IOException
|
|
|
+ * @throws IOException if the client address cannot be determined
|
|
|
*/
|
|
|
public static String getServerPrincipal(String principalConfig,
|
|
|
String hostname) throws IOException {
|
|
@@ -204,7 +207,7 @@ public class SecurityUtil {
|
|
|
* @param addr
|
|
|
* InetAddress of the host used for substitution
|
|
|
* @return converted Kerberos principal name
|
|
|
- * @throws IOException
|
|
|
+ * @throws IOException if the client address cannot be determined
|
|
|
*/
|
|
|
public static String getServerPrincipal(String principalConfig,
|
|
|
InetAddress addr) throws IOException {
|
|
@@ -251,7 +254,7 @@ public class SecurityUtil {
|
|
|
* the key to look for keytab file in conf
|
|
|
* @param userNameKey
|
|
|
* 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,
|
|
|
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
|
|
|
* @param hostname
|
|
|
* hostname to use for substitution
|
|
|
- * @throws IOException
|
|
|
+ * @throws IOException if the config doesn't specify a keytab
|
|
|
*/
|
|
|
public static void login(final Configuration conf,
|
|
|
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
|
|
|
* SecurityInfo providers.
|
|
|
* @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
|
|
|
*/
|
|
|
public static TokenInfo getTokenInfo(Class<?> protocol, Configuration conf) {
|
|
@@ -442,4 +445,155 @@ public class SecurityUtil {
|
|
|
public static Text buildTokenService(URI uri) {
|
|
|
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);
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|