Repository: drill Updated Branches: refs/heads/master e25c58f7b -> b4ffa4012
http://git-wip-us.apache.org/repos/asf/drill/blob/adee4614/exec/java-exec/src/test/java/org/apache/drill/exec/server/rest/spnego/TestDrillSpnegoAuthenticator.java ---------------------------------------------------------------------- diff --git a/exec/java-exec/src/test/java/org/apache/drill/exec/server/rest/spnego/TestDrillSpnegoAuthenticator.java b/exec/java-exec/src/test/java/org/apache/drill/exec/server/rest/spnego/TestDrillSpnegoAuthenticator.java new file mode 100644 index 0000000..2b7da56 --- /dev/null +++ b/exec/java-exec/src/test/java/org/apache/drill/exec/server/rest/spnego/TestDrillSpnegoAuthenticator.java @@ -0,0 +1,286 @@ +/* + * 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.drill.exec.server.rest.spnego; + +import com.google.common.collect.Lists; +import com.typesafe.config.ConfigValueFactory; +import org.apache.commons.codec.binary.Base64; +import org.apache.drill.categories.SecurityTest; +import org.apache.drill.common.config.DrillConfig; +import org.apache.drill.exec.ExecConstants; +import org.apache.drill.exec.rpc.security.KerberosHelper; +import org.apache.drill.exec.server.DrillbitContext; +import org.apache.drill.exec.server.options.SystemOptionManager; +import org.apache.drill.exec.server.rest.WebServerConstants; +import org.apache.drill.exec.server.rest.auth.DrillSpnegoAuthenticator; +import org.apache.drill.exec.server.rest.auth.DrillSpnegoLoginService; +import org.apache.drill.test.BaseDirTestWatcher; +import org.apache.hadoop.security.authentication.util.KerberosName; +import org.apache.hadoop.security.authentication.util.KerberosUtil; +import org.apache.kerby.kerberos.kerb.client.JaasKrbUtil; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.security.Authenticator; +import org.eclipse.jetty.security.DefaultIdentityService; +import org.eclipse.jetty.security.UserAuthentication; +import org.eclipse.jetty.security.authentication.SessionAuthentication; +import org.eclipse.jetty.server.Authentication; +import org.ietf.jgss.GSSContext; +import org.ietf.jgss.GSSManager; +import org.ietf.jgss.GSSName; +import org.ietf.jgss.Oid; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.mockito.Mockito; +import sun.security.jgss.GSSUtil; + +import javax.security.auth.Subject; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.lang.reflect.Field; +import java.security.PrivilegedExceptionAction; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +/** + * Test for validating {@link DrillSpnegoAuthenticator} + */ +@Ignore("See DRILL-5387") +@Category(SecurityTest.class) +public class TestDrillSpnegoAuthenticator { + + private static KerberosHelper spnegoHelper; + + private static final String primaryName = "HTTP"; + + private static DrillSpnegoAuthenticator spnegoAuthenticator; + + private static final BaseDirTestWatcher dirTestWatcher = new BaseDirTestWatcher(); + + @BeforeClass + public static void setupTest() throws Exception { + spnegoHelper = new KerberosHelper(TestSpnegoAuthentication.class.getSimpleName(), primaryName); + spnegoHelper.setupKdc(dirTestWatcher.getTmpDir()); + + + sun.security.krb5.Config.refresh(); + + // (2) Reset the default realm. + final Field defaultRealm = KerberosName.class.getDeclaredField("defaultRealm"); + defaultRealm.setAccessible(true); + defaultRealm.set(null, KerberosUtil.getDefaultRealm()); + + // Create a DrillbitContext with service principal and keytab for DrillSpnegoLoginService + final DrillConfig newConfig = new DrillConfig(DrillConfig.create() + .withValue(ExecConstants.HTTP_AUTHENTICATION_MECHANISMS, + ConfigValueFactory.fromIterable(Lists.newArrayList("spnego"))) + .withValue(ExecConstants.HTTP_SPNEGO_PRINCIPAL, + ConfigValueFactory.fromAnyRef(spnegoHelper.SERVER_PRINCIPAL)) + .withValue(ExecConstants.HTTP_SPNEGO_KEYTAB, + ConfigValueFactory.fromAnyRef(spnegoHelper.serverKeytab.toString())), + false); + + // Create mock objects for optionManager and AuthConfiguration + final SystemOptionManager optionManager = Mockito.mock(SystemOptionManager.class); + Mockito.when(optionManager.getOption(ExecConstants.ADMIN_USERS_VALIDATOR)) + .thenReturn(ExecConstants.ADMIN_USERS_VALIDATOR.DEFAULT_ADMIN_USERS); + Mockito.when(optionManager.getOption(ExecConstants.ADMIN_USER_GROUPS_VALIDATOR)) + .thenReturn(ExecConstants.ADMIN_USER_GROUPS_VALIDATOR.DEFAULT_ADMIN_USER_GROUPS); + + final DrillbitContext drillbitContext = Mockito.mock(DrillbitContext.class); + Mockito.when(drillbitContext.getConfig()).thenReturn(newConfig); + Mockito.when(drillbitContext.getOptionManager()).thenReturn(optionManager); + + Authenticator.AuthConfiguration authConfiguration = Mockito.mock(Authenticator.AuthConfiguration.class); + + spnegoAuthenticator = new DrillSpnegoAuthenticator("SPNEGO"); + DrillSpnegoLoginService spnegoLoginService = new DrillSpnegoLoginService(drillbitContext); + + Mockito.when(authConfiguration.getLoginService()).thenReturn(spnegoLoginService); + Mockito.when(authConfiguration.getIdentityService()).thenReturn(new DefaultIdentityService()); + Mockito.when(authConfiguration.isSessionRenewedOnAuthentication()).thenReturn(true); + + // Set the login service and identity service inside SpnegoAuthenticator + spnegoAuthenticator.setConfiguration(authConfiguration); + } + + @AfterClass + public static void cleanTest() throws Exception { + spnegoHelper.stopKdc(); + } + + /** + * Test to verify response when request is sent for {@link WebServerConstants#SPENGO_LOGIN_RESOURCE_PATH} from + * unauthenticated session. Expectation is client will receive response with Negotiate header. + * @throws Exception + */ + @Test + public void testNewSessionReqForSpnegoLogin() throws Exception { + final HttpServletRequest request = Mockito.mock(HttpServletRequest.class); + final HttpServletResponse response = Mockito.mock(HttpServletResponse.class); + final HttpSession session = Mockito.mock(HttpSession.class); + + Mockito.when(request.getSession(true)).thenReturn(session); + Mockito.when(request.getRequestURI()).thenReturn(WebServerConstants.SPENGO_LOGIN_RESOURCE_PATH); + + final Authentication authentication = spnegoAuthenticator.validateRequest(request, response, false); + + assertEquals(authentication, Authentication.SEND_CONTINUE); + verify(response).sendError(401); + verify(response).setHeader(HttpHeader.WWW_AUTHENTICATE.asString(), HttpHeader.NEGOTIATE.asString()); + } + + /** + * Test to verify response when request is sent for {@link WebServerConstants#SPENGO_LOGIN_RESOURCE_PATH} from + * authenticated session. Expectation is server will find the authenticated UserIdentity. + * @throws Exception + */ + @Test + public void testAuthClientRequestForSpnegoLoginResource() throws Exception { + + final HttpServletRequest request = Mockito.mock(HttpServletRequest.class); + final HttpServletResponse response = Mockito.mock(HttpServletResponse.class); + final HttpSession session = Mockito.mock(HttpSession.class); + final Authentication authentication = Mockito.mock(UserAuthentication.class); + + Mockito.when(request.getSession(true)).thenReturn(session); + Mockito.when(request.getRequestURI()).thenReturn(WebServerConstants.SPENGO_LOGIN_RESOURCE_PATH); + Mockito.when(session.getAttribute(SessionAuthentication.__J_AUTHENTICATED)).thenReturn(authentication); + + final UserAuthentication returnedAuthentication = (UserAuthentication) spnegoAuthenticator.validateRequest + (request, response, false); + assertEquals(authentication, returnedAuthentication); + verify(response, never()).sendError(401); + verify(response, never()).setHeader(HttpHeader.WWW_AUTHENTICATE.asString(), HttpHeader.NEGOTIATE.asString()); + } + + /** + * Test to verify response when request is sent for any other resource other than + * {@link WebServerConstants#SPENGO_LOGIN_RESOURCE_PATH} from authenticated session. Expectation is server will + * find the authenticated UserIdentity and will not perform the authentication again for new resource. + * @throws Exception + */ + @Test + public void testAuthClientRequestForOtherPage() throws Exception { + + final HttpServletRequest request = Mockito.mock(HttpServletRequest.class); + final HttpServletResponse response = Mockito.mock(HttpServletResponse.class); + final HttpSession session = Mockito.mock(HttpSession.class); + final Authentication authentication = Mockito.mock(UserAuthentication.class); + + Mockito.when(request.getSession(true)).thenReturn(session); + Mockito.when(request.getRequestURI()).thenReturn(WebServerConstants.WEBSERVER_ROOT_PATH); + Mockito.when(session.getAttribute(SessionAuthentication.__J_AUTHENTICATED)).thenReturn(authentication); + + final UserAuthentication returnedAuthentication = (UserAuthentication) spnegoAuthenticator.validateRequest + (request, response, false); + assertEquals(authentication, returnedAuthentication); + verify(response, never()).sendError(401); + verify(response, never()).setHeader(HttpHeader.WWW_AUTHENTICATE.asString(), HttpHeader.NEGOTIATE.asString()); + } + + /** + * Test to verify that when request is sent for {@link WebServerConstants#LOGOUT_RESOURCE_PATH} then the UserIdentity + * will be removed from the session and returned authentication will be null from + * {@link DrillSpnegoAuthenticator#validateRequest(ServletRequest, ServletResponse, boolean)} + * @throws Exception + */ + @Test + public void testAuthClientRequestForLogOut() throws Exception { + final HttpServletRequest request = Mockito.mock(HttpServletRequest.class); + final HttpServletResponse response = Mockito.mock(HttpServletResponse.class); + final HttpSession session = Mockito.mock(HttpSession.class); + final Authentication authentication = Mockito.mock(UserAuthentication.class); + + Mockito.when(request.getSession(true)).thenReturn(session); + Mockito.when(request.getRequestURI()).thenReturn(WebServerConstants.LOGOUT_RESOURCE_PATH); + Mockito.when(session.getAttribute(SessionAuthentication.__J_AUTHENTICATED)).thenReturn(authentication); + + final UserAuthentication returnedAuthentication = (UserAuthentication) spnegoAuthenticator.validateRequest + (request, response, false); + assertNull(returnedAuthentication); + verify(session).removeAttribute(SessionAuthentication.__J_AUTHENTICATED); + verify(response, never()).sendError(401); + verify(response, never()).setHeader(HttpHeader.WWW_AUTHENTICATE.asString(), HttpHeader.NEGOTIATE.asString()); + } + + /** + * Test to verify authentication fails when client sends invalid SPNEGO token for the + * {@link WebServerConstants#SPENGO_LOGIN_RESOURCE_PATH} resource. + * @throws Exception + */ + @Test + public void testSpnegoLoginInvalidToken() throws Exception { + + final HttpServletRequest request = Mockito.mock(HttpServletRequest.class); + final HttpServletResponse response = Mockito.mock(HttpServletResponse.class); + final HttpSession session = Mockito.mock(HttpSession.class); + + // Create client subject using it's principal and keytab + final Subject clientSubject = JaasKrbUtil.loginUsingKeytab(spnegoHelper.CLIENT_PRINCIPAL, + spnegoHelper.clientKeytab.getAbsoluteFile()); + + // Generate a SPNEGO token for the peer SERVER_PRINCIPAL from this CLIENT_PRINCIPAL + final String token = Subject.doAs(clientSubject, new PrivilegedExceptionAction<String>() { + @Override + public String run() throws Exception { + + final GSSManager gssManager = GSSManager.getInstance(); + GSSContext gssContext = null; + try { + final Oid oid = GSSUtil.GSS_SPNEGO_MECH_OID; + final GSSName serviceName = gssManager.createName(spnegoHelper.SERVER_PRINCIPAL, GSSName.NT_USER_NAME, oid); + + gssContext = gssManager.createContext(serviceName, oid, null, GSSContext.DEFAULT_LIFETIME); + gssContext.requestCredDeleg(true); + gssContext.requestMutualAuth(true); + + byte[] outToken = new byte[0]; + outToken = gssContext.initSecContext(outToken, 0, outToken.length); + return Base64.encodeBase64String(outToken); + + } finally { + if (gssContext != null) { + gssContext.dispose(); + } + } + } + }); + + Mockito.when(request.getSession(true)).thenReturn(session); + + final String httpReqAuthHeader = String.format("%s:%s", HttpHeader.NEGOTIATE.asString(), String.format + ("%s%s","1234", token)); + Mockito.when(request.getHeader(HttpHeader.AUTHORIZATION.asString())).thenReturn(httpReqAuthHeader); + Mockito.when(request.getRequestURI()).thenReturn(WebServerConstants.SPENGO_LOGIN_RESOURCE_PATH); + + assertEquals(spnegoAuthenticator.validateRequest(request, response, false), Authentication.UNAUTHENTICATED); + + verify(session, never()).setAttribute(SessionAuthentication.__J_AUTHENTICATED, null); + verify(response, never()).sendError(401); + verify(response, never()).setHeader(HttpHeader.WWW_AUTHENTICATE.asString(), HttpHeader.NEGOTIATE.asString()); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/drill/blob/adee4614/exec/java-exec/src/test/java/org/apache/drill/exec/server/rest/spnego/TestSpnegoAuthentication.java ---------------------------------------------------------------------- diff --git a/exec/java-exec/src/test/java/org/apache/drill/exec/server/rest/spnego/TestSpnegoAuthentication.java b/exec/java-exec/src/test/java/org/apache/drill/exec/server/rest/spnego/TestSpnegoAuthentication.java new file mode 100644 index 0000000..51171cd --- /dev/null +++ b/exec/java-exec/src/test/java/org/apache/drill/exec/server/rest/spnego/TestSpnegoAuthentication.java @@ -0,0 +1,326 @@ +/* + * 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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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.drill.exec.server.rest.spnego; + + +import com.google.common.collect.Lists; +import com.typesafe.config.ConfigValueFactory; +import org.apache.commons.codec.binary.Base64; +import org.apache.drill.categories.SecurityTest; +import org.apache.drill.common.config.DrillConfig; +import org.apache.drill.common.scanner.ClassPathScanner; +import org.apache.drill.common.scanner.persistence.ScanResult; +import org.apache.drill.exec.ExecConstants; +import org.apache.drill.exec.exception.DrillbitStartupException; +import org.apache.drill.exec.rpc.security.AuthenticatorProviderImpl; +import org.apache.drill.exec.rpc.security.KerberosHelper; +import org.apache.drill.exec.rpc.security.plain.PlainFactory; +import org.apache.drill.exec.server.DrillbitContext; +import org.apache.drill.exec.server.options.SystemOptionManager; +import org.apache.drill.exec.server.rest.auth.DrillHttpSecurityHandlerProvider; +import org.apache.drill.exec.server.rest.auth.DrillSpnegoLoginService; +import org.apache.drill.test.BaseDirTestWatcher; +import org.apache.hadoop.security.authentication.util.KerberosName; +import org.apache.hadoop.security.authentication.util.KerberosUtil; +import org.apache.kerby.kerberos.kerb.client.JaasKrbUtil; +import org.eclipse.jetty.server.UserIdentity; +import org.ietf.jgss.GSSContext; +import org.ietf.jgss.GSSManager; +import org.ietf.jgss.GSSName; +import org.ietf.jgss.Oid; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.mockito.Mockito; +import sun.security.jgss.GSSUtil; + +import javax.security.auth.Subject; +import java.lang.reflect.Field; +import java.security.PrivilegedExceptionAction; + +import static junit.framework.TestCase.fail; +import static org.junit.Assert.assertTrue; + +/** + * Test for validating {@link DrillSpnegoLoginService} + */ +@Ignore("See DRILL-5387") +@Category(SecurityTest.class) +public class TestSpnegoAuthentication { + + private static KerberosHelper spnegoHelper; + + private static final String primaryName = "HTTP"; + + private static final BaseDirTestWatcher dirTestWatcher = new BaseDirTestWatcher(); + + @BeforeClass + public static void setupTest() throws Exception { + spnegoHelper = new KerberosHelper(TestSpnegoAuthentication.class.getSimpleName(), primaryName); + spnegoHelper.setupKdc(dirTestWatcher.getTmpDir()); + + + sun.security.krb5.Config.refresh(); + + // (2) Reset the default realm. + final Field defaultRealm = KerberosName.class.getDeclaredField("defaultRealm"); + defaultRealm.setAccessible(true); + defaultRealm.set(null, KerberosUtil.getDefaultRealm()); + } + + /** + * Both SPNEGO and FORM mechanism is enabled for WebServer in configuration. Test to see if the respective security + * handlers are created successfully or not. + * @throws Exception + */ + @Test + public void testSPNEGOAndFORMEnabled() throws Exception { + + final DrillConfig newConfig = new DrillConfig(DrillConfig.create() + .withValue(ExecConstants.USER_AUTHENTICATION_ENABLED, + ConfigValueFactory.fromAnyRef(true)) + .withValue(ExecConstants.HTTP_AUTHENTICATION_MECHANISMS, + ConfigValueFactory.fromIterable(Lists.newArrayList("form", "spnego"))) + .withValue(ExecConstants.HTTP_SPNEGO_PRINCIPAL, + ConfigValueFactory.fromAnyRef(spnegoHelper.SERVER_PRINCIPAL)) + .withValue(ExecConstants.HTTP_SPNEGO_KEYTAB, + ConfigValueFactory.fromAnyRef(spnegoHelper.serverKeytab.toString())), + false); + + final ScanResult scanResult = ClassPathScanner.fromPrescan(newConfig); + final AuthenticatorProviderImpl authenticatorProvider = Mockito.mock(AuthenticatorProviderImpl.class); + Mockito.when(authenticatorProvider.containsFactory(PlainFactory.SIMPLE_NAME)).thenReturn(true); + + final DrillbitContext context = Mockito.mock(DrillbitContext.class); + Mockito.when(context.getClasspathScan()).thenReturn(scanResult); + Mockito.when(context.getConfig()).thenReturn(newConfig); + Mockito.when(context.getAuthProvider()).thenReturn(authenticatorProvider); + + final DrillHttpSecurityHandlerProvider securityProvider = new DrillHttpSecurityHandlerProvider(newConfig, context); + assertTrue(securityProvider.isFormEnabled()); + assertTrue(securityProvider.isSpnegoEnabled()); + } + + /** + * Validate if FORM security handler is created successfully when only form is configured as auth mechanism + * @throws Exception + */ + @Test + public void testOnlyFORMEnabled() throws Exception { + + final DrillConfig newConfig = new DrillConfig(DrillConfig.create() + .withValue(ExecConstants.HTTP_AUTHENTICATION_MECHANISMS, + ConfigValueFactory.fromIterable(Lists.newArrayList("form"))) + .withValue(ExecConstants.USER_AUTHENTICATION_ENABLED, + ConfigValueFactory.fromAnyRef(true)) + .withValue(ExecConstants.HTTP_SPNEGO_PRINCIPAL, + ConfigValueFactory.fromAnyRef(spnegoHelper.SERVER_PRINCIPAL)) + .withValue(ExecConstants.HTTP_SPNEGO_KEYTAB, + ConfigValueFactory.fromAnyRef(spnegoHelper.serverKeytab.toString())), + false); + + final ScanResult scanResult = ClassPathScanner.fromPrescan(newConfig); + final AuthenticatorProviderImpl authenticatorProvider = Mockito.mock(AuthenticatorProviderImpl.class); + Mockito.when(authenticatorProvider.containsFactory(PlainFactory.SIMPLE_NAME)).thenReturn(true); + + final DrillbitContext context = Mockito.mock(DrillbitContext.class); + Mockito.when(context.getClasspathScan()).thenReturn(scanResult); + Mockito.when(context.getConfig()).thenReturn(newConfig); + Mockito.when(context.getAuthProvider()).thenReturn(authenticatorProvider); + + final DrillHttpSecurityHandlerProvider securityProvider = new DrillHttpSecurityHandlerProvider(newConfig, context); + assertTrue(securityProvider.isFormEnabled()); + assertTrue(!securityProvider.isSpnegoEnabled()); + } + + /** + * Validate failure in creating FORM security handler when PAM authenticator is absent. PAM authenticator is provided + * via {@link PlainFactory#getAuthenticator()} + * @throws Exception + */ + @Test + public void testFORMEnabledWithPlainDisabled() throws Exception { + try { + final DrillConfig newConfig = new DrillConfig(DrillConfig.create() + .withValue(ExecConstants.USER_AUTHENTICATION_ENABLED, + ConfigValueFactory.fromAnyRef(true)) + .withValue(ExecConstants.HTTP_AUTHENTICATION_MECHANISMS, + ConfigValueFactory.fromIterable(Lists.newArrayList("form"))) + .withValue(ExecConstants.HTTP_SPNEGO_PRINCIPAL, + ConfigValueFactory.fromAnyRef(spnegoHelper.SERVER_PRINCIPAL)) + .withValue(ExecConstants.HTTP_SPNEGO_KEYTAB, + ConfigValueFactory.fromAnyRef(spnegoHelper.serverKeytab.toString())), + false); + + final ScanResult scanResult = ClassPathScanner.fromPrescan(newConfig); + final AuthenticatorProviderImpl authenticatorProvider = Mockito.mock(AuthenticatorProviderImpl.class); + Mockito.when(authenticatorProvider.containsFactory(PlainFactory.SIMPLE_NAME)).thenReturn(false); + + final DrillbitContext context = Mockito.mock(DrillbitContext.class); + Mockito.when(context.getClasspathScan()).thenReturn(scanResult); + Mockito.when(context.getConfig()).thenReturn(newConfig); + Mockito.when(context.getAuthProvider()).thenReturn(authenticatorProvider); + + final DrillHttpSecurityHandlerProvider securityProvider = + new DrillHttpSecurityHandlerProvider(newConfig, context); + fail(); + } catch(Exception ex) { + assertTrue(ex instanceof DrillbitStartupException); + } + } + + /** + * Validate only SPNEGO security handler is configured properly when enabled via configuration + * @throws Exception + */ + @Test + public void testOnlySPNEGOEnabled() throws Exception { + + final DrillConfig newConfig = new DrillConfig(DrillConfig.create() + .withValue(ExecConstants.HTTP_AUTHENTICATION_MECHANISMS, + ConfigValueFactory.fromIterable(Lists.newArrayList("spnego"))) + .withValue(ExecConstants.USER_AUTHENTICATION_ENABLED, + ConfigValueFactory.fromAnyRef(true)) + .withValue(ExecConstants.HTTP_SPNEGO_PRINCIPAL, + ConfigValueFactory.fromAnyRef(spnegoHelper.SERVER_PRINCIPAL)) + .withValue(ExecConstants.HTTP_SPNEGO_KEYTAB, + ConfigValueFactory.fromAnyRef(spnegoHelper.serverKeytab.toString())), + false); + + final ScanResult scanResult = ClassPathScanner.fromPrescan(newConfig); + final AuthenticatorProviderImpl authenticatorProvider = Mockito.mock(AuthenticatorProviderImpl.class); + Mockito.when(authenticatorProvider.containsFactory(PlainFactory.SIMPLE_NAME)).thenReturn(false); + + final DrillbitContext context = Mockito.mock(DrillbitContext.class); + Mockito.when(context.getClasspathScan()).thenReturn(scanResult); + Mockito.when(context.getConfig()).thenReturn(newConfig); + Mockito.when(context.getAuthProvider()).thenReturn(authenticatorProvider); + + final DrillHttpSecurityHandlerProvider securityProvider = new DrillHttpSecurityHandlerProvider(newConfig, context); + + assertTrue(!securityProvider.isFormEnabled()); + assertTrue(securityProvider.isSpnegoEnabled()); + } + + /** + * Validate when none of the security mechanism is specified in the + * {@link ExecConstants#HTTP_AUTHENTICATION_MECHANISMS}, FORM security handler is still configured correctly when + * authentication is enabled along with PAM authenticator module. + * @throws Exception + */ + @Test + public void testConfigBackwardCompatibility() throws Exception { + + final DrillConfig newConfig = new DrillConfig(DrillConfig.create() + .withValue(ExecConstants.USER_AUTHENTICATION_ENABLED, + ConfigValueFactory.fromAnyRef(true)), + false); + + final ScanResult scanResult = ClassPathScanner.fromPrescan(newConfig); + final AuthenticatorProviderImpl authenticatorProvider = Mockito.mock(AuthenticatorProviderImpl.class); + Mockito.when(authenticatorProvider.containsFactory(PlainFactory.SIMPLE_NAME)).thenReturn(true); + + final DrillbitContext context = Mockito.mock(DrillbitContext.class); + Mockito.when(context.getClasspathScan()).thenReturn(scanResult); + Mockito.when(context.getConfig()).thenReturn(newConfig); + Mockito.when(context.getAuthProvider()).thenReturn(authenticatorProvider); + + final DrillHttpSecurityHandlerProvider securityProvider = new DrillHttpSecurityHandlerProvider(newConfig, context); + + assertTrue(securityProvider.isFormEnabled()); + assertTrue(!securityProvider.isSpnegoEnabled()); + } + + /** + * Validate successful {@link DrillSpnegoLoginService#login(String, Object)} when provided with client token for a + * configured service principal. + * @throws Exception + */ + @Test + public void testDrillSpnegoLoginService() throws Exception { + + // Create client subject using it's principal and keytab + final Subject clientSubject = JaasKrbUtil.loginUsingKeytab(spnegoHelper.CLIENT_PRINCIPAL, + spnegoHelper.clientKeytab.getAbsoluteFile()); + + // Generate a SPNEGO token for the peer SERVER_PRINCIPAL from this CLIENT_PRINCIPAL + final String token = Subject.doAs(clientSubject, new PrivilegedExceptionAction<String>() { + @Override + public String run() throws Exception { + + final GSSManager gssManager = GSSManager.getInstance(); + GSSContext gssContext = null; + try { + final Oid oid = GSSUtil.GSS_SPNEGO_MECH_OID; + final GSSName serviceName = gssManager.createName(spnegoHelper.SERVER_PRINCIPAL, GSSName.NT_USER_NAME, oid); + + gssContext = gssManager.createContext(serviceName, oid, null, GSSContext.DEFAULT_LIFETIME); + gssContext.requestCredDeleg(true); + gssContext.requestMutualAuth(true); + + byte[] outToken = new byte[0]; + outToken = gssContext.initSecContext(outToken, 0, outToken.length); + return Base64.encodeBase64String(outToken); + + } finally { + if (gssContext != null) { + gssContext.dispose(); + } + } + } + }); + + // Create a DrillbitContext with service principal and keytab for DrillSpnegoLoginService + final DrillConfig newConfig = new DrillConfig(DrillConfig.create() + .withValue(ExecConstants.HTTP_AUTHENTICATION_MECHANISMS, + ConfigValueFactory.fromIterable(Lists.newArrayList("spnego"))) + .withValue(ExecConstants.HTTP_SPNEGO_PRINCIPAL, + ConfigValueFactory.fromAnyRef(spnegoHelper.SERVER_PRINCIPAL)) + .withValue(ExecConstants.HTTP_SPNEGO_KEYTAB, + ConfigValueFactory.fromAnyRef(spnegoHelper.serverKeytab.toString())), + false); + + + final SystemOptionManager optionManager = Mockito.mock(SystemOptionManager.class); + Mockito.when(optionManager.getOption(ExecConstants.ADMIN_USERS_VALIDATOR)) + .thenReturn(ExecConstants.ADMIN_USERS_VALIDATOR.DEFAULT_ADMIN_USERS); + Mockito.when(optionManager.getOption(ExecConstants.ADMIN_USER_GROUPS_VALIDATOR)) + .thenReturn(ExecConstants.ADMIN_USER_GROUPS_VALIDATOR.DEFAULT_ADMIN_USER_GROUPS); + + final DrillbitContext drillbitContext = Mockito.mock(DrillbitContext.class); + Mockito.when(drillbitContext.getConfig()).thenReturn(newConfig); + Mockito.when(drillbitContext.getOptionManager()).thenReturn(optionManager); + + final DrillSpnegoLoginService loginService = new DrillSpnegoLoginService(drillbitContext); + + // Authenticate the client using its SPNEGO token + final UserIdentity user = loginService.login(null, token); + + // Validate the UserIdentity of authenticated client + assertTrue(user != null); + assertTrue(user.getUserPrincipal().getName().equals(spnegoHelper.CLIENT_PRINCIPAL)); + assertTrue(user.isUserInRole("authenticated", null)); + } + + @AfterClass + public static void cleanTest() throws Exception { + spnegoHelper.stopKdc(); + } +} http://git-wip-us.apache.org/repos/asf/drill/blob/adee4614/exec/java-exec/src/test/java/org/apache/drill/exec/server/rest/spnego/TestSpnegoConfig.java ---------------------------------------------------------------------- diff --git a/exec/java-exec/src/test/java/org/apache/drill/exec/server/rest/spnego/TestSpnegoConfig.java b/exec/java-exec/src/test/java/org/apache/drill/exec/server/rest/spnego/TestSpnegoConfig.java new file mode 100644 index 0000000..7803b9a --- /dev/null +++ b/exec/java-exec/src/test/java/org/apache/drill/exec/server/rest/spnego/TestSpnegoConfig.java @@ -0,0 +1,167 @@ +/* + * 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.drill.exec.server.rest.spnego; + +import com.google.common.collect.Lists; +import com.typesafe.config.ConfigValueFactory; +import org.apache.drill.categories.SecurityTest; +import org.apache.drill.common.config.DrillConfig; +import org.apache.drill.common.exceptions.DrillException; +import org.apache.drill.exec.ExecConstants; +import org.apache.drill.exec.rpc.security.KerberosHelper; +import org.apache.drill.exec.rpc.user.security.testing.UserAuthenticatorTestImpl; +import org.apache.drill.exec.server.rest.auth.SpnegoConfig; +import org.apache.drill.test.BaseDirTestWatcher; +import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.security.authentication.util.KerberosName; +import org.apache.hadoop.security.authentication.util.KerberosUtil; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +import java.lang.reflect.Field; + +import static junit.framework.TestCase.fail; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * Test for validating {@link SpnegoConfig} + */ +@Ignore("See DRILL-5387") +@Category(SecurityTest.class) +public class TestSpnegoConfig { + //private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(TestSpnegoConfig.class); + + private static KerberosHelper spnegoHelper; + + private static final String primaryName = "HTTP"; + + private static final BaseDirTestWatcher dirTestWatcher = new BaseDirTestWatcher(); + + @BeforeClass + public static void setupTest() throws Exception { + spnegoHelper = new KerberosHelper(TestSpnegoAuthentication.class.getSimpleName(), primaryName); + spnegoHelper.setupKdc(dirTestWatcher.getTmpDir()); + + + sun.security.krb5.Config.refresh(); + + // (2) Reset the default realm. + final Field defaultRealm = KerberosName.class.getDeclaredField("defaultRealm"); + defaultRealm.setAccessible(true); + defaultRealm.set(null, KerberosUtil.getDefaultRealm()); + } + + @AfterClass + public static void cleanTest() throws Exception { + spnegoHelper.stopKdc(); + } + + /** + * Test invalid {@link SpnegoConfig} with missing keytab and principal + * @throws Exception + */ + @Test + public void testInvalidSpnegoConfig() throws Exception { + // Invalid configuration for SPNEGO + try { + final DrillConfig newConfig = new DrillConfig(DrillConfig.create() + .withValue(ExecConstants.USER_AUTHENTICATION_ENABLED, + ConfigValueFactory.fromAnyRef(true)) + .withValue(ExecConstants.AUTHENTICATION_MECHANISMS, + ConfigValueFactory.fromIterable(Lists.newArrayList("plain"))) + .withValue(ExecConstants.USER_AUTHENTICATOR_IMPL, + ConfigValueFactory.fromAnyRef(UserAuthenticatorTestImpl.TYPE)), + false); + + final SpnegoConfig spnegoConfig = new SpnegoConfig(newConfig); + spnegoConfig.validateSpnegoConfig(); + fail(); + } catch (Exception ex) { + assertTrue(ex instanceof DrillException); + } + } + + /** + * Invalid configuration with keytab only and missing principal + * @throws Exception + */ + @Test + public void testSpnegoConfigOnlyKeytab() throws Exception { + try { + final DrillConfig newConfig = new DrillConfig(DrillConfig.create().withValue(ExecConstants.USER_AUTHENTICATION_ENABLED, ConfigValueFactory.fromAnyRef(true)).withValue(ExecConstants.AUTHENTICATION_MECHANISMS, ConfigValueFactory.fromIterable(Lists.newArrayList("plain"))).withValue(ExecConstants.HTTP_SPNEGO_KEYTAB, ConfigValueFactory.fromAnyRef(spnegoHelper.serverKeytab.toString())).withValue(ExecConstants.USER_AUTHENTICATOR_IMPL, ConfigValueFactory.fromAnyRef(UserAuthenticatorTestImpl.TYPE)), false); + + final SpnegoConfig spnegoConfig = new SpnegoConfig(newConfig); + spnegoConfig.validateSpnegoConfig(); + fail(); + } catch (Exception ex) { + assertTrue(ex instanceof DrillException); + } + } + + /** + * Invalid configuration with principal only and missing keytab + * @throws Exception + */ + @Test + public void testSpnegoConfigOnlyPrincipal() throws Exception { + try { + final DrillConfig newConfig = new DrillConfig(DrillConfig.create().withValue(ExecConstants.USER_AUTHENTICATION_ENABLED, ConfigValueFactory.fromAnyRef(true)).withValue(ExecConstants.AUTHENTICATION_MECHANISMS, ConfigValueFactory.fromIterable(Lists.newArrayList("plain"))).withValue(ExecConstants.HTTP_SPNEGO_PRINCIPAL, ConfigValueFactory.fromAnyRef(spnegoHelper.SERVER_PRINCIPAL)).withValue(ExecConstants.USER_AUTHENTICATOR_IMPL, ConfigValueFactory.fromAnyRef(UserAuthenticatorTestImpl.TYPE)), false); + + final SpnegoConfig spnegoConfig = new SpnegoConfig(newConfig); + spnegoConfig.validateSpnegoConfig(); + fail(); + } catch (Exception ex) { + assertTrue(ex instanceof DrillException); + } + } + + /** + * Valid Configuration with both keytab & principal + * @throws Exception + */ + @Test + public void testValidSpnegoConfig() throws Exception { + + try { + final DrillConfig newConfig = new DrillConfig(DrillConfig.create() + .withValue(ExecConstants.USER_AUTHENTICATION_ENABLED, + ConfigValueFactory.fromAnyRef(true)) + .withValue(ExecConstants.AUTHENTICATION_MECHANISMS, + ConfigValueFactory.fromIterable(Lists.newArrayList("plain"))) + .withValue(ExecConstants.HTTP_SPNEGO_PRINCIPAL, + ConfigValueFactory.fromAnyRef(spnegoHelper.SERVER_PRINCIPAL)) + .withValue(ExecConstants.HTTP_SPNEGO_KEYTAB, + ConfigValueFactory.fromAnyRef(spnegoHelper.serverKeytab.toString())) + .withValue(ExecConstants.USER_AUTHENTICATOR_IMPL, + ConfigValueFactory.fromAnyRef(UserAuthenticatorTestImpl.TYPE)), + false); + + final SpnegoConfig spnegoConfig = new SpnegoConfig(newConfig); + spnegoConfig.validateSpnegoConfig(); + UserGroupInformation ugi = spnegoConfig.getLoggedInUgi(); + assertEquals(primaryName, ugi.getShortUserName()); + assertEquals(spnegoHelper.SERVER_PRINCIPAL, ugi.getUserName()); + } catch (Exception ex) { + fail(); + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/drill/blob/adee4614/exec/rpc/src/main/java/org/apache/drill/exec/rpc/BasicServer.java ---------------------------------------------------------------------- diff --git a/exec/rpc/src/main/java/org/apache/drill/exec/rpc/BasicServer.java b/exec/rpc/src/main/java/org/apache/drill/exec/rpc/BasicServer.java index 67fc89a..888a49b 100644 --- a/exec/rpc/src/main/java/org/apache/drill/exec/rpc/BasicServer.java +++ b/exec/rpc/src/main/java/org/apache/drill/exec/rpc/BasicServer.java @@ -202,6 +202,7 @@ public abstract class BasicServer<T extends EnumLite, SC extends ServerConnectio if (e instanceof BindException && allowPortHunting) { continue; } + final UserException bindException = UserException .resourceError( e )
