HADOOP-7104. Remove unnecessary DNS reverse lookups from RPC layer. Contributed by Kan Zhang

git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1059235 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Todd Lipcon 2011-01-15 02:40:51 +00:00
parent 3d1b9ca1e3
commit c3fdd289cf
6 changed files with 104 additions and 65 deletions

View File

@ -42,6 +42,9 @@ Trunk (unreleased changes)
HADOOP-6995. Allow wildcards to be used in ProxyUsers configurations. HADOOP-6995. Allow wildcards to be used in ProxyUsers configurations.
(todd) (todd)
HADOOP-7104. Remove unnecessary DNS reverse lookups from RPC layer
(Kan Zhang via todd)
OPTIMIZATIONS OPTIMIZATIONS
BUG FIXES BUG FIXES

View File

@ -1268,7 +1268,7 @@ private static String getRemotePrincipal(Configuration conf,
+ protocol.getCanonicalName()); + protocol.getCanonicalName());
} }
return SecurityUtil.getServerPrincipal(conf.get(serverKey), address return SecurityUtil.getServerPrincipal(conf.get(serverKey), address
.getAddress().getCanonicalHostName()); .getAddress());
} }
return null; return null;
} }

View File

@ -851,8 +851,8 @@ public class Connection {
// Cache the remote host & port info so that even if the socket is // Cache the remote host & port info so that even if the socket is
// disconnected, we can say where it used to connect to. // disconnected, we can say where it used to connect to.
private String hostAddress; private String hostAddress;
private String hostName;
private int remotePort; private int remotePort;
private InetAddress addr;
ConnectionHeader header = new ConnectionHeader(); ConnectionHeader header = new ConnectionHeader();
Class<?> protocol; Class<?> protocol;
@ -889,12 +889,11 @@ public Connection(SelectionKey key, SocketChannel channel,
this.unwrappedData = null; this.unwrappedData = null;
this.unwrappedDataLengthBuffer = ByteBuffer.allocate(4); this.unwrappedDataLengthBuffer = ByteBuffer.allocate(4);
this.socket = channel.socket(); this.socket = channel.socket();
InetAddress addr = socket.getInetAddress(); this.addr = socket.getInetAddress();
if (addr == null) { if (addr == null) {
this.hostAddress = "*Unknown*"; this.hostAddress = "*Unknown*";
} else { } else {
this.hostAddress = addr.getHostAddress(); this.hostAddress = addr.getHostAddress();
this.hostName = addr.getCanonicalHostName();
} }
this.remotePort = socket.getPort(); this.remotePort = socket.getPort();
this.responseQueue = new LinkedList<Call>(); this.responseQueue = new LinkedList<Call>();
@ -917,8 +916,8 @@ public String getHostAddress() {
return hostAddress; return hostAddress;
} }
public String getHostName() { public InetAddress getHostInetAddress() {
return hostName; return addr;
} }
public void setLastContact(long lastContact) { public void setLastContact(long lastContact) {
@ -1326,7 +1325,7 @@ private boolean authorizeConnection() throws IOException {
&& (authMethod != AuthMethod.DIGEST)) { && (authMethod != AuthMethod.DIGEST)) {
ProxyUsers.authorize(user, this.getHostAddress(), conf); ProxyUsers.authorize(user, this.getHostAddress(), conf);
} }
authorize(user, header, getHostName()); authorize(user, header, getHostInetAddress());
if (LOG.isDebugEnabled()) { if (LOG.isDebugEnabled()) {
LOG.debug("Successfully authorized " + header); LOG.debug("Successfully authorized " + header);
} }
@ -1666,12 +1665,12 @@ public abstract Writable call(Class<?> protocol,
* *
* @param user client user * @param user client user
* @param connection incoming connection * @param connection incoming connection
* @param hostname fully-qualified domain name of incoming connection * @param addr InetAddress of incoming connection
* @throws AuthorizationException when the client isn't authorized to talk the protocol * @throws AuthorizationException when the client isn't authorized to talk the protocol
*/ */
public void authorize(UserGroupInformation user, public void authorize(UserGroupInformation user,
ConnectionHeader connection, ConnectionHeader connection,
String hostname InetAddress addr
) throws AuthorizationException { ) throws AuthorizationException {
if (authorize) { if (authorize) {
Class<?> protocol = null; Class<?> protocol = null;
@ -1681,7 +1680,7 @@ public void authorize(UserGroupInformation user,
throw new AuthorizationException("Unknown protocol: " + throw new AuthorizationException("Unknown protocol: " +
connection.getProtocol()); connection.getProtocol());
} }
serviceAuthorizationManager.authorize(user, protocol, getConf(), hostname); serviceAuthorizationManager.authorize(user, protocol, getConf(), addr);
} }
} }

View File

@ -135,8 +135,8 @@ public static void fetchServiceTicket(URL remoteHost) throws IOException {
} }
/** /**
* Convert Kerberos principal name conf values to valid Kerberos principal * Convert Kerberos principal name pattern to valid Kerberos principal
* names. It replaces $host in the conf values with hostname, which should be * names. It replaces hostname pattern with hostname, which should be
* fully-qualified domain name. If hostname is null or "0.0.0.0", it uses * fully-qualified domain name. 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.
* *
@ -149,24 +149,57 @@ public static void fetchServiceTicket(URL remoteHost) throws IOException {
*/ */
public static String getServerPrincipal(String principalConfig, public static String getServerPrincipal(String principalConfig,
String hostname) throws IOException { String hostname) throws IOException {
String[] components = getComponents(principalConfig);
if (components == null || components.length != 3
|| !components[1].equals(HOSTNAME_PATTERN)) {
return principalConfig;
} else {
return replacePattern(components, hostname);
}
}
/**
* Convert Kerberos principal name pattern to valid Kerberos principal names.
* This method is similar to {@link #getServerPrincipal(String, String)},
* except 1) the reverse DNS lookup from addr to hostname is done only when
* necessary, 2) param addr can't be null (no default behavior of using local
* hostname when addr is null).
*
* @param principalConfig
* Kerberos principal name pattern to convert
* @param addr
* InetAddress of the host used for substitution
* @return converted Kerberos principal name
* @throws IOException
*/
public static String getServerPrincipal(String principalConfig,
InetAddress addr) throws IOException {
String[] components = getComponents(principalConfig);
if (components == null || components.length != 3
|| !components[1].equals(HOSTNAME_PATTERN)) {
return principalConfig;
} else {
if (addr == null) {
throw new IOException("Can't replace " + HOSTNAME_PATTERN
+ " pattern since client address is null");
}
return replacePattern(components, addr.getCanonicalHostName());
}
}
private static String[] getComponents(String principalConfig) {
if (principalConfig == null) if (principalConfig == null)
return null; return null;
String[] components = principalConfig.split("[/@]"); return principalConfig.split("[/@]");
if (components.length != 3) { }
throw new IOException(
"Kerberos service principal name isn't configured properly "
+ "(should have 3 parts): " + principalConfig);
}
if (components[1].equals(HOSTNAME_PATTERN)) { private static String replacePattern(String[] components, String hostname)
String fqdn = hostname; throws IOException {
if (fqdn == null || fqdn.equals("") || fqdn.equals("0.0.0.0")) { String fqdn = hostname;
fqdn = getLocalHostName(); if (fqdn == null || fqdn.equals("") || fqdn.equals("0.0.0.0")) {
} fqdn = getLocalHostName();
return components[0] + "/" + fqdn + "@" + components[2];
} else {
return principalConfig;
} }
return components[0] + "/" + fqdn + "@" + components[2];
} }
static String getLocalHostName() throws UnknownHostException { static String getLocalHostName() throws UnknownHostException {

View File

@ -18,6 +18,7 @@
package org.apache.hadoop.security.authorize; package org.apache.hadoop.security.authorize;
import java.io.IOException; import java.io.IOException;
import java.net.InetAddress;
import java.util.IdentityHashMap; import java.util.IdentityHashMap;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -30,7 +31,6 @@
import org.apache.hadoop.fs.CommonConfigurationKeys; import org.apache.hadoop.fs.CommonConfigurationKeys;
import org.apache.hadoop.security.KerberosInfo; import org.apache.hadoop.security.KerberosInfo;
import org.apache.hadoop.security.SecurityUtil; import org.apache.hadoop.security.SecurityUtil;
import org.apache.hadoop.security.KerberosName;
import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.UserGroupInformation;
/** /**
@ -71,13 +71,13 @@ public class ServiceAuthorizationManager {
* @param user user accessing the service * @param user user accessing the service
* @param protocol service being accessed * @param protocol service being accessed
* @param conf configuration to use * @param conf configuration to use
* @param hostname fully qualified domain name of the client * @param addr InetAddress of the client
* @throws AuthorizationException on authorization failure * @throws AuthorizationException on authorization failure
*/ */
public void authorize(UserGroupInformation user, public void authorize(UserGroupInformation user,
Class<?> protocol, Class<?> protocol,
Configuration conf, Configuration conf,
String hostname InetAddress addr
) throws AuthorizationException { ) throws AuthorizationException {
AccessControlList acl = protocolToAcl.get(protocol); AccessControlList acl = protocolToAcl.get(protocol);
if (acl == null) { if (acl == null) {
@ -91,41 +91,24 @@ public void authorize(UserGroupInformation user,
if (krbInfo != null) { if (krbInfo != null) {
String clientKey = krbInfo.clientPrincipal(); String clientKey = krbInfo.clientPrincipal();
if (clientKey != null && !clientKey.equals("")) { if (clientKey != null && !clientKey.equals("")) {
if (hostname == null) {
throw new AuthorizationException(
"Can't authorize client when client hostname is null");
}
try { try {
clientPrincipal = SecurityUtil.getServerPrincipal( clientPrincipal = SecurityUtil.getServerPrincipal(
conf.get(clientKey), hostname); conf.get(clientKey), addr);
} catch (IOException e) { } catch (IOException e) {
throw (AuthorizationException) new AuthorizationException( throw (AuthorizationException) new AuthorizationException(
"Can't figure out Kerberos principal name for connection from " "Can't figure out Kerberos principal name for connection from "
+ hostname + " for user=" + user + " protocol=" + protocol) + addr + " for user=" + user + " protocol=" + protocol)
.initCause(e); .initCause(e);
} }
} }
} }
// when authorizing use the short name only if((clientPrincipal != null && !clientPrincipal.equals(user.getUserName())) ||
String shortName = clientPrincipal;
if(clientPrincipal != null ) {
try {
shortName = new KerberosName(clientPrincipal).getShortName();
} catch (IOException e) {
LOG.warn("couldn't get short name from " + clientPrincipal, e);
// just keep going
}
}
if(LOG.isDebugEnabled()) {
LOG.debug("for protocol authorization compare (" + clientPrincipal +
"): " + shortName + " with " + user.getShortUserName());
}
if((shortName != null && !shortName.equals(user.getShortUserName())) ||
!acl.isUserAllowed(user)) { !acl.isUserAllowed(user)) {
AUDITLOG.warn(AUTHZ_FAILED_FOR + user + " for protocol="+protocol); AUDITLOG.warn(AUTHZ_FAILED_FOR + user + " for protocol=" + protocol
+ ", expected client Kerberos principal is " + clientPrincipal);
throw new AuthorizationException("User " + user + throw new AuthorizationException("User " + user +
" is not authorized for protocol " + " is not authorized for protocol " + protocol +
protocol); ", expected client Kerberos principal is " + clientPrincipal);
} }
AUDITLOG.info(AUTHZ_SUCCESSFULL_FOR + user + " for protocol="+protocol); AUDITLOG.info(AUTHZ_SUCCESSFULL_FOR + user + " for protocol="+protocol);
} }

View File

@ -18,14 +18,16 @@
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
import java.io.IOException; import java.io.IOException;
import java.net.InetAddress;
import javax.security.auth.kerberos.KerberosPrincipal; import javax.security.auth.kerberos.KerberosPrincipal;
import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.conf.Configuration;
import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import org.mockito.Mockito;
public class TestSecurityUtil { public class TestSecurityUtil {
@Test @Test
@ -50,28 +52,47 @@ public void isOriginalTGTReturnsCorrectValues() {
private void verify(String original, String hostname, String expected) private void verify(String original, String hostname, String expected)
throws IOException { throws IOException {
assertTrue(SecurityUtil.getServerPrincipal(original, hostname).equals( assertEquals(expected,
expected)); SecurityUtil.getServerPrincipal(original, hostname));
assertTrue(SecurityUtil.getServerPrincipal(original, null).equals(
expected)); InetAddress addr = mockAddr(hostname);
assertTrue(SecurityUtil.getServerPrincipal(original, "").equals( assertEquals(expected,
expected)); SecurityUtil.getServerPrincipal(original, addr));
assertTrue(SecurityUtil.getServerPrincipal(original, "0.0.0.0").equals( }
expected));
private InetAddress mockAddr(String reverseTo) {
InetAddress mock = Mockito.mock(InetAddress.class);
Mockito.doReturn(reverseTo).when(mock).getCanonicalHostName();
return mock;
} }
@Test @Test
public void testGetServerPrincipal() throws IOException { public void testGetServerPrincipal() throws IOException {
String service = "hdfs/"; String service = "hdfs/";
String realm = "@REALM"; String realm = "@REALM";
String hostname = SecurityUtil.getLocalHostName(); String hostname = "foohost";
String userPrincipal = "foo@FOOREALM";
String shouldReplace = service + SecurityUtil.HOSTNAME_PATTERN + realm; String shouldReplace = service + SecurityUtil.HOSTNAME_PATTERN + realm;
String replaced = service + hostname + realm; String replaced = service + hostname + realm;
verify(shouldReplace, hostname, replaced); verify(shouldReplace, hostname, replaced);
String shouldNotReplace = service + SecurityUtil.HOSTNAME_PATTERN + "NAME" String shouldNotReplace = service + SecurityUtil.HOSTNAME_PATTERN + "NAME"
+ realm; + realm;
verify(shouldNotReplace, hostname, shouldNotReplace); verify(shouldNotReplace, hostname, shouldNotReplace);
verify(shouldNotReplace, shouldNotReplace, shouldNotReplace); verify(userPrincipal, hostname, userPrincipal);
// testing reverse DNS lookup doesn't happen
InetAddress notUsed = Mockito.mock(InetAddress.class);
assertEquals(shouldNotReplace,
SecurityUtil.getServerPrincipal(shouldNotReplace, notUsed));
Mockito.verify(notUsed, Mockito.never()).getCanonicalHostName();
}
@Test
public void testLocalHostNameForNullOrWild() throws Exception {
String local = SecurityUtil.getLocalHostName();
assertEquals("hdfs/" + local + "@REALM",
SecurityUtil.getServerPrincipal("hdfs/_HOST@REALM", (String)null));
assertEquals("hdfs/" + local + "@REALM",
SecurityUtil.getServerPrincipal("hdfs/_HOST@REALM", "0.0.0.0"));
} }
@Test @Test