diff --git a/hadoop-common-project/hadoop-common/CHANGES.txt b/hadoop-common-project/hadoop-common/CHANGES.txt index 4416a152b80..2975033312f 100644 --- a/hadoop-common-project/hadoop-common/CHANGES.txt +++ b/hadoop-common-project/hadoop-common/CHANGES.txt @@ -361,6 +361,9 @@ Release 2.0.3-alpha - Unreleased HADOOP-8860. Split MapReduce and YARN sections in documentation navigation. (tomwhite via tucu) + HADOOP-9021. Enforce configured SASL method on the server (daryn via + bobby) + OPTIMIZATIONS HADOOP-8866. SampleQuantiles#query is O(N^2) instead of O(N). (Andrew Wang diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/ipc/Server.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/ipc/Server.java index 60155e71ce8..0af7d2fd6f0 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/ipc/Server.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/ipc/Server.java @@ -45,6 +45,7 @@ import java.security.PrivilegedExceptionAction; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -87,7 +88,9 @@ import org.apache.hadoop.security.SaslRpcServer.AuthMethod; import org.apache.hadoop.security.SaslRpcServer.SaslDigestCallbackHandler; import org.apache.hadoop.security.SaslRpcServer.SaslGssCallbackHandler; import org.apache.hadoop.security.SaslRpcServer.SaslStatus; +import org.apache.hadoop.security.SecurityUtil; import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod; import org.apache.hadoop.security.authentication.util.KerberosName; import org.apache.hadoop.security.authorize.AuthorizationException; import org.apache.hadoop.security.authorize.PolicyProvider; @@ -113,7 +116,7 @@ import com.google.common.annotations.VisibleForTesting; @InterfaceStability.Evolving public abstract class Server { private final boolean authorize; - private boolean isSecurityEnabled; + private EnumSet enabledAuthMethods; private ExceptionsHandler exceptionsHandler = new ExceptionsHandler(); public void addTerseExceptions(Class... exceptionClass) { @@ -1334,34 +1337,9 @@ public abstract class Server { if (authMethod == null) { throw new IOException("Unable to read authentication method"); } - boolean useSaslServer = isSecurityEnabled; - final boolean clientUsingSasl; - switch (authMethod) { - case SIMPLE: { // no sasl for simple - clientUsingSasl = false; - break; - } - case DIGEST: { // always allow tokens if there's a secret manager - useSaslServer |= (secretManager != null); - clientUsingSasl = true; - break; - } - default: { - clientUsingSasl = true; - break; - } - } - if (useSaslServer) { - saslServer = createSaslServer(authMethod); - } else if (clientUsingSasl) { // security is off - doSaslReply(SaslStatus.SUCCESS, new IntWritable( - SaslRpcServer.SWITCH_TO_SIMPLE_AUTH), null, null); - authMethod = AuthMethod.SIMPLE; - // client has already sent the initial Sasl message and we - // should ignore it. Both client and server should fall back - // to simple auth from now on. - skipInitialSaslHandshake = true; - } + + // this may create a SASL server, or switch us into SIMPLE + authMethod = initializeAuthContext(authMethod); connectionHeaderBuf = null; connectionHeaderRead = true; @@ -1409,10 +1387,24 @@ public abstract class Server { } } - private SaslServer createSaslServer(AuthMethod authMethod) + private AuthMethod initializeAuthContext(AuthMethod authMethod) throws IOException { try { - return createSaslServerInternal(authMethod); + if (enabledAuthMethods.contains(authMethod)) { + saslServer = createSaslServer(authMethod); + } else if (enabledAuthMethods.contains(AuthMethod.SIMPLE)) { + doSaslReply(SaslStatus.SUCCESS, new IntWritable( + SaslRpcServer.SWITCH_TO_SIMPLE_AUTH), null, null); + authMethod = AuthMethod.SIMPLE; + // client has already sent the initial Sasl message and we + // should ignore it. Both client and server should fall back + // to simple auth from now on. + skipInitialSaslHandshake = true; + } else { + throw new AccessControlException( + authMethod + " authentication is not enabled." + + " Available:" + enabledAuthMethods); + } } catch (IOException ioe) { final String ioeClass = ioe.getClass().getName(); final String ioeMessage = ioe.getLocalizedMessage(); @@ -1425,9 +1417,10 @@ public abstract class Server { } throw ioe; } + return authMethod; } - private SaslServer createSaslServerInternal(AuthMethod authMethod) + private SaslServer createSaslServer(AuthMethod authMethod) throws IOException { SaslServer saslServer = null; String hostname = null; @@ -1436,18 +1429,9 @@ public abstract class Server { switch (authMethod) { case SIMPLE: { - throw new AccessControlException("Authorization (" - + CommonConfigurationKeys.HADOOP_SECURITY_AUTHORIZATION - + ") is enabled but authentication (" - + CommonConfigurationKeys.HADOOP_SECURITY_AUTHENTICATION - + ") is configured as simple. Please configure another method " - + "like kerberos or digest."); + return null; // no sasl for simple } case DIGEST: { - if (secretManager == null) { - throw new AccessControlException( - "Server is not configured to do DIGEST authentication."); - } secretManager.checkAvailableForRead(); hostname = SaslRpcServer.SASL_DEFAULT_REALM; saslCallback = new SaslDigestCallbackHandler(secretManager, this); @@ -1469,6 +1453,7 @@ public abstract class Server { break; } default: + // we should never be able to get here throw new AccessControlException( "Server does not support SASL " + authMethod); } @@ -1908,7 +1893,9 @@ public abstract class Server { this.authorize = conf.getBoolean(CommonConfigurationKeys.HADOOP_SECURITY_AUTHORIZATION, false); - this.isSecurityEnabled = UserGroupInformation.isSecurityEnabled(); + + // configure supported authentications + this.enabledAuthMethods = getAuthMethods(secretManager, conf); // Start the listener here and let it bind to the port listener = new Listener(); @@ -1929,6 +1916,31 @@ public abstract class Server { this.exceptionsHandler.addTerseExceptions(StandbyException.class); } + // get the security type from the conf. implicitly include token support + // if a secret manager is provided, or fail if token is the conf value but + // there is no secret manager + private EnumSet getAuthMethods(SecretManager secretManager, + Configuration conf) { + AuthenticationMethod confAuthenticationMethod = + SecurityUtil.getAuthenticationMethod(conf); + EnumSet authMethods = + EnumSet.of(confAuthenticationMethod.getAuthMethod()); + + if (confAuthenticationMethod == AuthenticationMethod.TOKEN) { + if (secretManager == null) { + throw new IllegalArgumentException(AuthenticationMethod.TOKEN + + " authentication requires a secret manager"); + } + } else if (secretManager != null) { + LOG.debug(AuthenticationMethod.TOKEN + + " authentication enabled for secret manager"); + authMethods.add(AuthenticationMethod.TOKEN.getAuthMethod()); + } + + LOG.debug("Server accepts auth methods:" + authMethods); + return authMethods; + } + private void closeConnection(Connection connection) { synchronized (connectionList) { if (connectionList.remove(connection)) @@ -2045,16 +2057,6 @@ public abstract class Server { return conf; } - /** for unit testing only, should be called before server is started */ - void disableSecurity() { - this.isSecurityEnabled = false; - } - - /** for unit testing only, should be called before server is started */ - void enableSecurity() { - this.isSecurityEnabled = true; - } - /** Sets the socket buffer size used for responding to RPCs */ public void setSocketSendBufSize(int size) { this.socketSendBufferSize = size; } diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/UserGroupInformation.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/UserGroupInformation.java index f630d695a90..bc82decd622 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/UserGroupInformation.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/UserGroupInformation.java @@ -240,6 +240,7 @@ public class UserGroupInformation { AuthenticationMethod auth = SecurityUtil.getAuthenticationMethod(conf); switch (auth) { case SIMPLE: + case TOKEN: useKerberos = false; break; case KERBEROS: diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/ipc/TestSaslRPC.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/ipc/TestSaslRPC.java index be3064d20b3..158e48a9376 100644 --- a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/ipc/TestSaslRPC.java +++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/ipc/TestSaslRPC.java @@ -569,10 +569,13 @@ public class TestSaslRPC { private static Pattern KrbFailed = Pattern.compile(".*Failed on local exception:.* " + "Failed to specify server's Kerberos principal name.*"); - private static Pattern Denied = - Pattern.compile(".*Authorization .* is enabled .*"); - private static Pattern NoDigest = - Pattern.compile(".*Server is not configured to do DIGEST auth.*"); + private static Pattern Denied(AuthenticationMethod method) { + return Pattern.compile(".*RemoteException.*AccessControlException.*: " + +method.getAuthMethod() + " authentication is not enabled.*"); + } + private static Pattern NoTokenAuth = + Pattern.compile(".*IllegalArgumentException: " + + "TOKEN authentication requires a secret manager"); /* * simple server @@ -604,13 +607,40 @@ public class TestSaslRPC { assertAuthEquals(SIMPLE, getAuthMethod(KERBEROS, SIMPLE, false)); } + /* + * token server + */ + @Test + public void testTokenOnlyServer() throws Exception { + assertAuthEquals(Denied(SIMPLE), getAuthMethod(SIMPLE, TOKEN)); + assertAuthEquals(KrbFailed, getAuthMethod(KERBEROS, TOKEN)); + } + + @Test + public void testTokenOnlyServerWithTokens() throws Exception { + assertAuthEquals(TOKEN, getAuthMethod(SIMPLE, TOKEN, true)); + assertAuthEquals(TOKEN, getAuthMethod(KERBEROS, TOKEN, true)); + forceSecretManager = false; + assertAuthEquals(NoTokenAuth, getAuthMethod(SIMPLE, TOKEN, true)); + assertAuthEquals(NoTokenAuth, getAuthMethod(KERBEROS, TOKEN, true)); + } + + @Test + public void testTokenOnlyServerWithInvalidTokens() throws Exception { + assertAuthEquals(BadToken, getAuthMethod(SIMPLE, TOKEN, false)); + assertAuthEquals(BadToken, getAuthMethod(KERBEROS, TOKEN, false)); + forceSecretManager = false; + assertAuthEquals(NoTokenAuth, getAuthMethod(SIMPLE, TOKEN, false)); + assertAuthEquals(NoTokenAuth, getAuthMethod(KERBEROS, TOKEN, false)); + } + /* * kerberos server */ @Test public void testKerberosServer() throws Exception { - assertAuthEquals(Denied, getAuthMethod(SIMPLE, KERBEROS)); - assertAuthEquals(KrbFailed, getAuthMethod(KERBEROS, KERBEROS)); + assertAuthEquals(Denied(SIMPLE), getAuthMethod(SIMPLE, KERBEROS)); + assertAuthEquals(KrbFailed, getAuthMethod(KERBEROS, KERBEROS)); } @Test @@ -620,8 +650,8 @@ public class TestSaslRPC { assertAuthEquals(TOKEN, getAuthMethod(KERBEROS, KERBEROS, true)); // can't fallback to simple when using kerberos w/o tokens forceSecretManager = false; - assertAuthEquals(NoDigest, getAuthMethod(SIMPLE, KERBEROS, true)); - assertAuthEquals(NoDigest, getAuthMethod(KERBEROS, KERBEROS, true)); + assertAuthEquals(Denied(TOKEN), getAuthMethod(SIMPLE, KERBEROS, true)); + assertAuthEquals(Denied(TOKEN), getAuthMethod(KERBEROS, KERBEROS, true)); } @Test @@ -629,8 +659,8 @@ public class TestSaslRPC { assertAuthEquals(BadToken, getAuthMethod(SIMPLE, KERBEROS, false)); assertAuthEquals(BadToken, getAuthMethod(KERBEROS, KERBEROS, false)); forceSecretManager = false; - assertAuthEquals(NoDigest, getAuthMethod(SIMPLE, KERBEROS, true)); - assertAuthEquals(NoDigest, getAuthMethod(KERBEROS, KERBEROS, true)); + assertAuthEquals(Denied(TOKEN), getAuthMethod(SIMPLE, KERBEROS, false)); + assertAuthEquals(Denied(TOKEN), getAuthMethod(KERBEROS, KERBEROS, false)); }