HDFS-4043. Namenode Kerberos Login does not use proper hostname for host qualified hdfs principal name (#4785)

Use the existing DomainNameResolver to leverage the pluggable resolution framework.  This provides a means to perform a reverse lookup if needed.

Update default implementation of DNSDomainNameResolver to protect against returning the IP address as a string from a cached value.

Co-authored-by: Steve Vaughan Jr <s_vaughan@apple.com>
This commit is contained in:
Steve Vaughan 2022-08-22 17:34:33 -04:00 committed by GitHub
parent 2123859d60
commit 1120cc8485
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 91 additions and 1 deletions

View File

@ -486,4 +486,7 @@ public class CommonConfigurationKeys extends CommonConfigurationKeysPublic {
public static final boolean IOSTATISTICS_THREAD_LEVEL_ENABLED_DEFAULT = public static final boolean IOSTATISTICS_THREAD_LEVEL_ENABLED_DEFAULT =
true; true;
public static final String HADOOP_SECURITY_RESOLVER_IMPL =
"hadoop.security.resolver.impl";
} }

View File

@ -18,6 +18,10 @@
package org.apache.hadoop.net; package org.apache.hadoop.net;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.naming.NamingException;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
@ -27,6 +31,10 @@
* fully qualified domain names belonging to the IPs from this host name * fully qualified domain names belonging to the IPs from this host name
*/ */
public class DNSDomainNameResolver implements DomainNameResolver { public class DNSDomainNameResolver implements DomainNameResolver {
private final static Logger LOG =
LoggerFactory.getLogger(DNSDomainNameResolver.class.getName());
@Override @Override
public InetAddress[] getAllByDomainName(String domainName) public InetAddress[] getAllByDomainName(String domainName)
throws UnknownHostException { throws UnknownHostException {
@ -40,6 +48,16 @@ public String getHostnameByIP(InetAddress address) {
&& host.charAt(host.length()-1) == '.') { && host.charAt(host.length()-1) == '.') {
host = host.substring(0, host.length()-1); host = host.substring(0, host.length()-1);
} }
// Protect against the Java behaviour of returning the IP address as a string from a cache
// instead of performing a reverse lookup.
if (host != null && host.equals(address.getHostAddress())) {
LOG.debug("IP address returned for FQDN detected: {}", address.getHostAddress());
try {
return DNS.reverseDns(address, null);
} catch (NamingException lookupFailure) {
LOG.warn("Failed to perform reverse lookup: {}", address);
}
}
return host; return host;
} }

View File

@ -44,6 +44,8 @@
import org.apache.hadoop.fs.CommonConfigurationKeys; import org.apache.hadoop.fs.CommonConfigurationKeys;
import org.apache.hadoop.io.Text; import org.apache.hadoop.io.Text;
import org.apache.hadoop.net.DNS; import org.apache.hadoop.net.DNS;
import org.apache.hadoop.net.DomainNameResolver;
import org.apache.hadoop.net.DomainNameResolverFactory;
import org.apache.hadoop.net.NetUtils; import org.apache.hadoop.net.NetUtils;
import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod; import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod;
import org.apache.hadoop.security.token.Token; import org.apache.hadoop.security.token.Token;
@ -81,6 +83,8 @@ private SecurityUtil() {
@VisibleForTesting @VisibleForTesting
static HostResolver hostResolver; static HostResolver hostResolver;
private static DomainNameResolver domainNameResolver;
private static boolean logSlowLookups; private static boolean logSlowLookups;
private static int slowLookupThresholdMs; private static int slowLookupThresholdMs;
@ -112,6 +116,9 @@ private static void setConfigurationInternal(Configuration conf) {
.HADOOP_SECURITY_DNS_LOG_SLOW_LOOKUPS_THRESHOLD_MS_KEY, .HADOOP_SECURITY_DNS_LOG_SLOW_LOOKUPS_THRESHOLD_MS_KEY,
CommonConfigurationKeys CommonConfigurationKeys
.HADOOP_SECURITY_DNS_LOG_SLOW_LOOKUPS_THRESHOLD_MS_DEFAULT); .HADOOP_SECURITY_DNS_LOG_SLOW_LOOKUPS_THRESHOLD_MS_DEFAULT);
domainNameResolver = DomainNameResolverFactory.newInstance(conf,
CommonConfigurationKeys.HADOOP_SECURITY_RESOLVER_IMPL);
} }
/** /**
@ -212,7 +219,7 @@ public static String getServerPrincipal(String principalConfig,
throw new IOException("Can't replace " + HOSTNAME_PATTERN throw new IOException("Can't replace " + HOSTNAME_PATTERN
+ " pattern since client address is null"); + " pattern since client address is null");
} }
return replacePattern(components, addr.getCanonicalHostName()); return replacePattern(components, domainNameResolver.getHostnameByIP(addr));
} }
} }

View File

@ -121,6 +121,14 @@
</description> </description>
</property> </property>
<property>
<name>hadoop.security.resolver.impl</name>
<value>org.apache.hadoop.net.DNSDomainNameResolver</value>
<description>
The resolver implementation used to resolve FQDN for Kerberos
</description>
</property>
<property> <property>
<name>hadoop.security.dns.log-slow-lookups.enabled</name> <name>hadoop.security.dns.log-slow-lookups.enabled</name>
<value>false</value> <value>false</value>

View File

@ -0,0 +1,54 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.net;
import org.junit.Test;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Objects;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assume.assumeFalse;
public class TestDNSDomainNameResolver {
static DNSDomainNameResolver DNR = new DNSDomainNameResolver();
@Test
public void testGetHostNameByIP() throws UnknownHostException {
InetAddress localhost = InetAddress.getLocalHost();
assumeFalse("IP lookup support required",
Objects.equals(localhost.getCanonicalHostName(), localhost.getHostAddress()));
// Precondition: host name and canonical host name for unresolved returns an IP address.
InetAddress unresolved = InetAddress.getByAddress(localhost.getHostAddress(),
localhost.getAddress());
assertEquals(localhost.getHostAddress(), unresolved.getHostName());
// Test: Get the canonical name despite InetAddress caching
String canonicalHostName = DNR.getHostnameByIP(unresolved);
// Verify: The canonical host name doesn't match the host address but does match the localhost.
assertNotEquals(localhost.getHostAddress(), canonicalHostName);
assertEquals(localhost.getCanonicalHostName(), canonicalHostName);
}
}