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:
parent
3d1b9ca1e3
commit
c3fdd289cf
@ -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
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user