From c770a2e7b0e75c4f18b954d8ada36469f00c8355 Mon Sep 17 00:00:00 2001
From: mapr
Date: Mon, 11 Sep 2017 16:56:22 -0700
Subject: [PATCH 1/3] Added comments
---
.../org/apache/drill/exec/ExecConstants.java | 4 +
.../server/rest/LogInLogOutResources.java | 38 +++-
.../server/rest/ViewableWithPermissions.java | 30 ++--
.../drill/exec/server/rest/WebServer.java | 12 +-
.../auth/DrillConstraintSecurityHandler.java | 38 ++++
.../server/rest/auth/DrillErrorHandler.java | 52 ++++++
.../rest/auth/DrillSecurityHandler.java | 164 ++++++++++++++++++
.../rest/auth/DrillSpnegoAuthenticator.java | 141 +++++++++++++++
.../rest/auth/DrillSpnegoLoginService.java | 154 ++++++++++++++++
.../exec/server/rest/auth/SpnegoUtil.java | 86 +++++++++
.../src/main/resources/rest/generic.ftl | 2 +-
.../src/main/resources/rest/mainlogin.ftl | 26 +++
12 files changed, 727 insertions(+), 20 deletions(-)
create mode 100644 exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/DrillConstraintSecurityHandler.java
create mode 100644 exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/DrillErrorHandler.java
create mode 100644 exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/DrillSecurityHandler.java
create mode 100644 exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/DrillSpnegoAuthenticator.java
create mode 100644 exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/DrillSpnegoLoginService.java
create mode 100644 exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/SpnegoUtil.java
create mode 100644 exec/java-exec/src/main/resources/rest/mainlogin.ftl
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/ExecConstants.java b/exec/java-exec/src/main/java/org/apache/drill/exec/ExecConstants.java
index 95ee00e715c..dd1e3f7edac 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/ExecConstants.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/ExecConstants.java
@@ -122,6 +122,10 @@ public interface ExecConstants {
String HTTP_SESSION_MEMORY_RESERVATION = "drill.exec.http.session.memory.reservation";
String HTTP_SESSION_MEMORY_MAXIMUM = "drill.exec.http.session.memory.maximum";
String HTTP_SESSION_MAX_IDLE_SECS = "drill.exec.http.session_max_idle_secs";
+ String HTTP_AUTHENTICATION_MECHANISMS = "drill.exec.http.auth.mechanisms";
+ String HTTP_SPNEGO_PRINCIPAL_= "drill.exec.http.spnego.auth.principal";
+ String HTTP_SPNEGO_KEYTAB_= "drill.exec.http.spnego.auth.keytab";
+ String HTTP_SPNEGO_REALM_= "drill.exec.http.spnego.auth.realm";
String HTTP_KEYSTORE_PATH = "drill.exec.ssl.keyStorePath";
String HTTP_KEYSTORE_PASSWORD = "drill.exec.ssl.keyStorePassword";
String HTTP_TRUSTSTORE_PATH = "drill.exec.ssl.trustStorePath";
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/LogInLogOutResources.java b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/LogInLogOutResources.java
index 20cd6da1d8e..f30e62fe4bd 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/LogInLogOutResources.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/LogInLogOutResources.java
@@ -44,14 +44,14 @@
@PermitAll
public class LogInLogOutResources {
public static final String REDIRECT_QUERY_PARM = "redirect";
- public static final String LOGIN_RESOURCE = "login";
+ public static final String LOGIN_RESOURCE = "mainlogin";
@GET
@Path("/login")
@Produces(MediaType.TEXT_HTML)
public Viewable getLoginPage(@Context HttpServletRequest request, @Context HttpServletResponse response,
- @Context SecurityContext sc, @Context UriInfo uriInfo, @QueryParam(REDIRECT_QUERY_PARM) String redirect)
- throws Exception {
+ @Context SecurityContext sc, @Context UriInfo uriInfo, @QueryParam(REDIRECT_QUERY_PARM) String redirect)
+ throws Exception {
if (AuthDynamicFeature.isUserLoggedIn(sc)) {
// if the user is already login, forward the request to homepage.
request.getRequestDispatcher("/").forward(request, response);
@@ -69,6 +69,20 @@ public Viewable getLoginPage(@Context HttpServletRequest request, @Context HttpS
return ViewableWithPermissions.createLoginPage(null);
}
+ @GET
+ @Path("/sn")
+ @Produces(MediaType.TEXT_HTML)
+ public Viewable getspnegologin(@Context HttpServletRequest request, @Context HttpServletResponse response,
+ @Context SecurityContext sc, @Context UriInfo uriInfo, @QueryParam(REDIRECT_QUERY_PARM) String redirect)
+ throws Exception {
+ if (AuthDynamicFeature.isUserLoggedIn(sc)) {
+ request.getRequestDispatcher("/").forward(request, response);
+ return null;
+ }
+
+ return ViewableWithPermissions.createmainLoginPage("Invalid SPNEGO credentials.");
+ }
+
// Request type is POST because POST request which contains the login credentials are invalid and the request is
// dispatched here directly.
@POST
@@ -88,4 +102,22 @@ public void logout(@Context HttpServletRequest req, @Context HttpServletResponse
req.getRequestDispatcher("/").forward(req, resp);
}
+ @GET
+ @Path("/mainlogin")
+ @Produces(MediaType.TEXT_HTML)
+ public Viewable getMainLoginPage(@Context HttpServletRequest request, @Context HttpServletResponse response,
+ @Context SecurityContext sc, @Context UriInfo uriInfo, @QueryParam(REDIRECT_QUERY_PARM) String redirect)
+ throws Exception {
+ //req.getRequestDispatcher("/").forward(req, resp);
+ if (!StringUtils.isEmpty(redirect)) {
+ // If the URL has redirect in it, set the redirect URI in session, so that after the login is successful, request
+ // is forwarded to the redirect page.
+ final HttpSession session = request.getSession(true);
+ final URI destURI = UriBuilder.fromUri(URLDecoder.decode(redirect, "UTF-8")).build();
+ session.setAttribute(FormAuthenticator.__J_URI, destURI.toString());
+ }
+ return ViewableWithPermissions.createmainLoginPage(null);
+
+ }
+
}
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/ViewableWithPermissions.java b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/ViewableWithPermissions.java
index 73019aa11a3..fb56cfd0ab0 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/ViewableWithPermissions.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/ViewableWithPermissions.java
@@ -48,7 +48,7 @@ public static Viewable create(final boolean authEnabled, final String templateNa
* @return
*/
public static Viewable create(final boolean authEnabled, final String templateName, final SecurityContext sc,
- final Object model) {
+ final Object model) {
return new ViewableWithPermissions(authEnabled, templateName, sc, true, model);
}
@@ -61,29 +61,33 @@ public static Viewable createLoginPage(final String errorMsg) {
return new ViewableWithPermissions(true, "/rest/login.ftl", null, false, errorMsg);
}
+ public static Viewable createmainLoginPage(final String errorMsg) {
+ return new ViewableWithPermissions(true, "/rest/mainlogin.ftl", null, false, errorMsg);
+ }
+
private ViewableWithPermissions(final boolean authEnabled, final String templateName, final SecurityContext sc,
- final boolean showControls, final Object model) throws IllegalArgumentException {
+ final boolean showControls, final Object model) throws IllegalArgumentException {
super(templateName, createModel(authEnabled, sc, showControls, model));
}
private static Map createModel(final boolean authEnabled, final SecurityContext sc,
- final boolean showControls, final Object pageModel) {
+ final boolean showControls, final Object pageModel) {
final boolean isAdmin = !authEnabled /* when auth is disabled every user is an admin user */
- || (showControls && sc.isUserInRole(DrillUserPrincipal.ADMIN_ROLE));
+ || (showControls && sc.isUserInRole(DrillUserPrincipal.ADMIN_ROLE));
final boolean isUserLoggedIn = AuthDynamicFeature.isUserLoggedIn(sc);
final ImmutableMap.Builder mapBuilder = ImmutableMap.builder()
- .put("showStorage", isAdmin)
- .put("showOptions", isAdmin)
- .put("showThreads", isAdmin)
- .put("showLogs", isAdmin)
- .put("showLogin", authEnabled && showControls && !isUserLoggedIn)
- .put("showLogout", authEnabled && showControls && isUserLoggedIn)
- .put("loggedInUserName", authEnabled && showControls &&
- isUserLoggedIn ? sc.getUserPrincipal().getName() : DrillUserPrincipal.ANONYMOUS_USER)
- .put("showControls", showControls);
+ .put("showStorage", isAdmin)
+ .put("showOptions", isAdmin)
+ .put("showThreads", isAdmin)
+ .put("showLogs", isAdmin)
+ .put("showLogin", authEnabled && showControls && !isUserLoggedIn)
+ .put("showLogout", authEnabled && showControls && isUserLoggedIn)
+ .put("loggedInUserName", authEnabled && showControls &&
+ isUserLoggedIn ? sc.getUserPrincipal().getName() : DrillUserPrincipal.ANONYMOUS_USER)
+ .put("showControls", showControls);
if (pageModel != null) {
mapBuilder.put("model", pageModel);
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/WebServer.java b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/WebServer.java
index 3fc95cd8338..afe4ba843ce 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/WebServer.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/WebServer.java
@@ -21,6 +21,7 @@
import com.codahale.metrics.servlets.MetricsServlet;
import com.codahale.metrics.servlets.ThreadDumpServlet;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.drill.common.exceptions.DrillException;
@@ -31,6 +32,7 @@
import org.apache.drill.exec.rpc.security.plain.PlainFactory;
import org.apache.drill.exec.server.BootStrapContext;
import org.apache.drill.exec.server.rest.auth.DrillRestLoginService;
+import org.apache.drill.exec.server.rest.auth.DrillSecurityHandler;
import org.apache.drill.exec.work.WorkManager;
import org.bouncycastle.asn1.x500.X500NameBuilder;
import org.bouncycastle.asn1.x500.style.BCStyle;
@@ -80,6 +82,7 @@
import java.util.Collections;
import java.util.Date;
import java.util.EnumSet;
+import java.util.List;
import java.util.Set;
import static org.apache.drill.exec.server.rest.auth.DrillUserPrincipal.ADMIN_ROLE;
@@ -101,6 +104,8 @@ public class WebServer implements AutoCloseable {
private final BootStrapContext context;
+ private final String SpnegoAuth = "SPNEGO";
+
/**
* Create Jetty based web server.
*
@@ -142,13 +147,12 @@ public void start() throws Exception {
if (embeddedJetty == null) {
return;
}
- final boolean authEnabled = config.getBoolean(ExecConstants.USER_AUTHENTICATION_ENABLED);
+ boolean authEnabled = config.getBoolean(ExecConstants.USER_AUTHENTICATION_ENABLED);
if (authEnabled && !context.getAuthProvider().containsFactory(PlainFactory.SIMPLE_NAME)) {
logger.warn("Not starting web server. Currently Drill supports web authentication only through " +
"username/password. But PLAIN mechanism is not configured.");
return;
}
-
final ServerConnector serverConnector;
if (config.getBoolean(ExecConstants.HTTP_ENABLE_SSL)) {
try {
@@ -190,7 +194,9 @@ public void start() throws Exception {
servletContextHandler.addServlet(staticHolder, "/static/*");
if (authEnabled) {
- servletContextHandler.setSecurityHandler(createSecurityHandler());
+ //DrillSecurityHandler is used to support SPNEGO and FORM authentication together
+ DrillSecurityHandler securityHandler = new DrillSecurityHandler(config, workManager);
+ servletContextHandler.setSecurityHandler(securityHandler);
servletContextHandler.setSessionHandler(createSessionHandler(servletContextHandler.getSecurityHandler()));
}
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/DrillConstraintSecurityHandler.java b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/DrillConstraintSecurityHandler.java
new file mode 100644
index 00000000000..c0cff32d4ba
--- /dev/null
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/DrillConstraintSecurityHandler.java
@@ -0,0 +1,38 @@
+/**
+ * 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.auth;
+
+import org.eclipse.jetty.security.ConstraintSecurityHandler;
+
+
+public class DrillConstraintSecurityHandler extends ConstraintSecurityHandler {
+
+ public DrillConstraintSecurityHandler() {
+
+ }
+
+ @Override
+ public void doStart() throws Exception {
+ super.doStart();
+ }
+
+ @Override
+ public void doStop() throws Exception {
+ super.doStop();
+ }
+}
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/DrillErrorHandler.java b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/DrillErrorHandler.java
new file mode 100644
index 00000000000..da9e42aa141
--- /dev/null
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/DrillErrorHandler.java
@@ -0,0 +1,52 @@
+/*
+ * 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.auth;
+
+import org.eclipse.jetty.server.Authentication;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.handler.ErrorHandler;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import java.io.IOException;
+import java.io.Writer;
+
+
+public class DrillErrorHandler extends ErrorHandler {
+
+
+
+ @Override
+ protected void writeErrorPageMessage(HttpServletRequest request, Writer writer, int code, String message, String uri) throws IOException {
+
+ super.writeErrorPageMessage(request,writer,code,message,uri);
+ //writer.write("
Spnego login failed
");
+ String path = request.getContextPath();
+ String ref = path+"/login";
+ writer.write(" root ");
+ }
+ }
+}
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/DrillSecurityHandler.java b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/DrillSecurityHandler.java
new file mode 100644
index 00000000000..4bcd71756b2
--- /dev/null
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/DrillSecurityHandler.java
@@ -0,0 +1,164 @@
+/*
+ * 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.auth;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+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.exception.DrillbitStartupException;
+import org.apache.drill.exec.work.WorkManager;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.security.ConstraintMapping;
+import org.eclipse.jetty.security.ConstraintSecurityHandler;
+import org.eclipse.jetty.security.DefaultIdentityService;
+import org.eclipse.jetty.security.IdentityService;
+import org.eclipse.jetty.security.SpnegoLoginService;
+import org.eclipse.jetty.security.authentication.FormAuthenticator;
+import org.eclipse.jetty.security.authentication.SessionAuthentication;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Request;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import static org.apache.drill.exec.server.rest.auth.DrillUserPrincipal.ADMIN_ROLE;
+import static org.apache.drill.exec.server.rest.auth.DrillUserPrincipal.AUTHENTICATED_ROLE;
+
+
+public class DrillSecurityHandler extends ConstraintSecurityHandler {
+
+ private DrillConfig DrillConfig;
+ private final WorkManager workManager;
+ private DrillConstraintSecurityHandler basicSecurityHandler;
+ private DrillConstraintSecurityHandler spnegoSecurityHandler;
+ private final String HTTP_FORM ="FORM";
+ private final String HTTP_SPNEGO ="SPNEGO";
+ private List configuredMechanisms = Lists.newArrayList();
+ private Set knownRoles = ImmutableSet.of(AUTHENTICATED_ROLE, ADMIN_ROLE);
+ private SpnegoUtil spnUtil;
+
+ public DrillSecurityHandler(DrillConfig config,WorkManager work) throws DrillbitStartupException,IOException {
+ DrillConfig = config;
+ spnUtil = new SpnegoUtil(config);
+ this.workManager = work;
+ try {
+ if (config.hasPath(ExecConstants.HTTP_AUTHENTICATION_MECHANISMS)) {
+ configuredMechanisms = config.getStringList(ExecConstants.HTTP_AUTHENTICATION_MECHANISMS);
+ if (configuredMechanisms.contains(HTTP_FORM)) {
+ initializeFormAuthentication();
+
+ }
+ if (configuredMechanisms.contains(HTTP_SPNEGO)) {
+ initializeSpnegoAuthentication();
+
+ }
+ }
+ }
+ catch(DrillException e){
+ throw new DrillbitStartupException(e.getMessage(), e);
+ }
+ //Backward compatability for Form authentication
+ if(!config.hasPath(ExecConstants.HTTP_AUTHENTICATION_MECHANISMS)){
+ initializeFormAuthentication();
+ }
+ }
+
+ public void initializeFormAuthentication(){
+ basicSecurityHandler = new DrillConstraintSecurityHandler();
+ basicSecurityHandler.setConstraintMappings(Collections.emptyList(), knownRoles);
+ basicSecurityHandler.setAuthenticator(new FormAuthenticator("/login", "/login", true));
+ basicSecurityHandler.setLoginService(new DrillRestLoginService(workManager.getContext()));
+
+
+ }
+
+ public void initializeSpnegoAuthentication() throws DrillException {
+ spnegoSecurityHandler = new DrillConstraintSecurityHandler();
+ spnegoSecurityHandler.setAuthenticator(new DrillSpnegoAuthenticator());
+ final SpnegoLoginService loginService = new DrillSpnegoLoginService(spnUtil.getSpnegoRealm(),spnUtil.getSpnegoPrincipal(),workManager.getContext(),spnUtil.getUgi());
+ final IdentityService identityService = new DefaultIdentityService();
+ loginService.setIdentityService(identityService);
+ spnegoSecurityHandler.setLoginService(loginService);
+ spnegoSecurityHandler.setConstraintMappings(Collections.emptyList(),knownRoles);
+
+ }
+
+ @Override
+ public void doStart() throws Exception {
+ super.doStart();
+ if(!configuredMechanisms.isEmpty() && configuredMechanisms.contains(HTTP_SPNEGO)) {
+ spnegoSecurityHandler.doStart();
+ }
+ if(configuredMechanisms.isEmpty() || configuredMechanisms.contains(HTTP_FORM) ) {
+ basicSecurityHandler.doStart();
+ }
+
+ }
+
+
+ @Override
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
+ HttpServletRequest req = (HttpServletRequest)baseRequest;
+ String header = req.getHeader(HttpHeader.AUTHORIZATION.asString());
+ HttpSession session = req.getSession(true);
+ SessionAuthentication authentication = (SessionAuthentication) session.getAttribute("org.eclipse.jetty.security.UserIdentity");
+ String uri = req.getRequestURI();
+ //Before authentication, all requests go through the Formauthenticator except for Spnegologin request
+ //If this authentication is null, user hasn't logged in yet
+ if(authentication == null){
+ if( uri.equals("/sn") ){
+ spnegoSecurityHandler.handle(target, baseRequest, request, response);
+ }
+ else {
+ basicSecurityHandler.handle(target, baseRequest, request, response);
+ }
+
+ }
+ //If user has logged in, use the corresponding handler to handle the request
+ else{
+ if(authentication.getAuthMethod() == "FORM"){
+ basicSecurityHandler.handle(target, baseRequest, request, response);
+ }
+ else if(authentication.getAuthMethod() == "SPNEGO"){
+ spnegoSecurityHandler.handle(target, baseRequest, request, response);
+ }
+ }
+ }
+
+ @Override
+ public void setHandler(Handler handler) {
+ super.setHandler(handler);
+ if(!configuredMechanisms.isEmpty() && configuredMechanisms.contains(HTTP_SPNEGO)) {
+ spnegoSecurityHandler.setHandler(handler);
+ }
+ if(configuredMechanisms.isEmpty() || configuredMechanisms.contains(HTTP_SPNEGO)) {
+ basicSecurityHandler.setHandler(handler);
+ }
+ }
+ }
\ No newline at end of file
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/DrillSpnegoAuthenticator.java b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/DrillSpnegoAuthenticator.java
new file mode 100644
index 00000000000..3ecfa92f438
--- /dev/null
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/DrillSpnegoAuthenticator.java
@@ -0,0 +1,141 @@
+/*
+ * 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.auth;
+
+
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.security.ServerAuthException;
+import org.eclipse.jetty.security.SpnegoLoginService;
+import org.eclipse.jetty.security.UserAuthentication;
+import org.eclipse.jetty.security.authentication.DeferredAuthentication;
+import org.eclipse.jetty.security.authentication.FormAuthenticator;
+import org.eclipse.jetty.security.authentication.SessionAuthentication;
+import org.eclipse.jetty.security.authentication.SpnegoAuthenticator;
+import org.eclipse.jetty.server.Authentication;
+import org.eclipse.jetty.server.HttpChannel;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.server.Request;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+import javax.servlet.http.HttpSession;
+import java.io.IOException;
+
+public class DrillSpnegoAuthenticator extends SpnegoAuthenticator {
+ private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(DrillSpnegoAuthenticator.class);
+
+ public DrillSpnegoAuthenticator() {
+
+ }
+
+
+ @Override
+ public Authentication validateRequest(ServletRequest request, ServletResponse response, boolean mandatory) throws ServerAuthException {
+
+ HttpServletRequest req = (HttpServletRequest) request;
+ HttpServletResponse res = (HttpServletResponse) response;
+ HttpSession session = req.getSession(true);
+ Authentication authentication = (Authentication) session.getAttribute("org.eclipse.jetty.security.UserIdentity");
+ String uri = req.getRequestURI();
+ if( uri.equals("/sn")){
+ mandatory = true;
+ }
+ if (authentication != null && uri.equals("/logout")) {
+ session.removeAttribute("org.eclipse.jetty.security.UserIdentity");
+ Authentication auth = (Authentication) session.getAttribute("org.eclipse.jetty.security.UserIdentity");
+ return auth;
+ }
+ else if (authentication != null) {
+ return authentication;
+ }
+ else {
+ String header = req.getHeader(HttpHeader.AUTHORIZATION.asString());
+ if (!mandatory) {
+ return new DeferredAuthentication(this);
+ } else if (header == null) {
+ try {
+ if (DeferredAuthentication.isDeferred(res)) {
+ return Authentication.UNAUTHENTICATED;
+ } else {
+ res.setHeader(HttpHeader.WWW_AUTHENTICATE.asString(), HttpHeader.NEGOTIATE.asString());
+ res.sendError(401);
+ return Authentication.SEND_CONTINUE;
+ }
+ } catch (IOException var9) {
+ throw new ServerAuthException(var9);
+ }
+ } else {
+ if (header != null && header.startsWith(HttpHeader.NEGOTIATE.asString())) {
+ String spnegoToken = header.substring(10);
+ UserIdentity user = this.login((String) null, spnegoToken, request);
+ //redirect the request to the desired page after successful login
+ if (user != null) {
+ String nuri;
+ synchronized(session) {
+ nuri = (String)session.getAttribute("org.eclipse.jetty.security.form_URI");
+ if(nuri == null || nuri.length() == 0) {
+ nuri = req.getContextPath();
+ if(nuri.length() == 0) {
+ nuri = "/";
+ }
+ }
+ }
+ response.setContentLength(0);
+ Response base_response = HttpChannel.getCurrentHttpChannel().getResponse();
+ Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
+ int redirectCode = base_request.getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion()?302:303;
+ try {
+ base_response.sendRedirect(redirectCode, res.encodeRedirectURL(nuri));
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return new UserAuthentication(this.getAuthMethod(), user);
+ }
+
+ }
+
+ return Authentication.UNAUTHENTICATED;
+ }
+ }
+
+ }
+
+
+ public UserIdentity login(String username, Object password, ServletRequest request) {
+ UserIdentity user = super.login(username, password, request);
+ if (user != null) {
+ HttpSession session = ((HttpServletRequest) request).getSession(true);
+ Authentication cached = new SessionAuthentication(this.getAuthMethod(), user, password);
+ session.setAttribute("org.eclipse.jetty.security.UserIdentity", cached);
+ }
+
+ return user;
+ }
+
+
+}
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/DrillSpnegoLoginService.java b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/DrillSpnegoLoginService.java
new file mode 100644
index 00000000000..0e6e8108412
--- /dev/null
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/DrillSpnegoLoginService.java
@@ -0,0 +1,154 @@
+/*
+ * 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.auth;
+
+
+import org.apache.drill.common.config.DrillConfig;
+import org.apache.drill.exec.ExecConstants;
+import org.apache.drill.exec.expr.fn.impl.ContextFunctions;
+import org.apache.drill.exec.server.DrillbitContext;
+import org.apache.drill.exec.server.options.SystemOptionManager;
+import org.apache.drill.exec.util.ImpersonationUtil;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.CommonConfigurationKeys;
+import org.apache.hadoop.security.UserGroupInformation;
+import org.eclipse.jetty.security.SpnegoLoginService;
+import org.eclipse.jetty.security.SpnegoUserPrincipal;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.B64Code;
+import org.ietf.jgss.GSSContext;
+import org.ietf.jgss.GSSCredential;
+import org.ietf.jgss.GSSException;
+import org.ietf.jgss.GSSManager;
+import org.ietf.jgss.GSSName;
+import org.ietf.jgss.Oid;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.security.auth.Subject;
+import java.lang.reflect.Field;
+import java.security.PrivilegedExceptionAction;
+import java.util.Objects;
+
+public class DrillSpnegoLoginService extends SpnegoLoginService {
+ private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(DrillSpnegoLoginService.class);
+
+ private static final String TARGET_NAME_FIELD_NAME = "_targetName";
+ private final String serverPrincipal;
+ private final DrillbitContext drillContext;
+ private UserIdentity identity;
+ private final UserGroupInformation ugi;
+
+
+
+ public DrillSpnegoLoginService(String realm, String serverPrincipal, DrillbitContext drillbitContext, UserGroupInformation ugSpnego) {
+ super(realm);
+ this.serverPrincipal = Objects.requireNonNull(serverPrincipal);
+ drillContext = drillbitContext;
+ ugi = ugSpnego;
+ }
+
+ @Override
+ protected void doStart() throws Exception {
+ // Override the parent implementation, setting _targetName to be the serverPrincipal
+ // without the need for a one-line file to do the same thing.
+ //
+ // AbstractLifeCycle's doStart() method does nothing, so we aren't missing any extra logic.
+ final Field targetNameField = SpnegoLoginService.class.getDeclaredField(TARGET_NAME_FIELD_NAME);
+ targetNameField.setAccessible(true);
+ targetNameField.set(this, serverPrincipal);
+ }
+
+ @Override
+ public UserIdentity login(final String username, final Object credentials) {
+ try {
+ identity = ugi.doAs(new PrivilegedExceptionAction() {
+ @Override
+ public UserIdentity run() {
+ return spnegologin(username, credentials);
+ }
+ });
+ } catch (Exception e) {
+ logger.error("Failed to login using SPNEGO");
+ }
+ return identity;
+
+ }
+
+ public UserIdentity spnegologin(String username, Object credentials) {
+
+ String encodedAuthToken = (String) credentials;
+ byte[] authToken = B64Code.decode(encodedAuthToken);
+
+ GSSManager manager = GSSManager.getInstance();
+ try {
+
+ Oid spnegoOid = new Oid("1.3.6.1.5.5.2");
+ Oid krb5Oid = new Oid("1.2.840.113554.1.2.2");
+ GSSName gssName = manager.createName(serverPrincipal, null);
+ GSSCredential serverCreds = manager.createCredential(gssName,
+ GSSCredential.INDEFINITE_LIFETIME, new Oid[] {krb5Oid, spnegoOid},
+ GSSCredential.ACCEPT_ONLY);
+ GSSContext gContext = manager.createContext(serverCreds);
+ if (gContext == null) {
+ logger.debug("SpnegoUserRealm: failed to establish GSSContext");
+ } else {
+ while (!gContext.isEstablished()) {
+ authToken = gContext.acceptSecContext(authToken, 0, authToken.length);
+ }
+ if (gContext.isEstablished()) {
+ String clientName = gContext.getSrcName().toString();
+ String role = clientName.substring(0, clientName.indexOf(64));
+ // LOG.debug("SpnegoUserRealm: established a security context", new Object[0]);
+ //LOG.debug("Client Principal is: " + gContext.getSrcName(), new Object[0]);
+ // LOG.debug("Server Principal is: " + gContext.getTargName(), new Object[0]);
+ // LOG.debug("Client Default Role: " + role, new Object[0]);
+ final SystemOptionManager sysOptions = drillContext.getOptionManager();
+
+ final boolean isAdmin = ImpersonationUtil.hasAdminPrivileges(role,
+ sysOptions.getOption(ExecConstants.ADMIN_USERS_KEY).string_val,
+ sysOptions.getOption(ExecConstants.ADMIN_USER_GROUPS_KEY).string_val);
+ DrillUserPrincipal user = new DrillUserPrincipal(clientName, isAdmin);
+ Subject subject = new Subject();
+ subject.getPrincipals().add(user);
+ if (isAdmin) {
+ return this._identityService.newUserIdentity(subject, user, DrillUserPrincipal.ADMIN_USER_ROLES);
+ } else {
+ return this._identityService.newUserIdentity(subject, user, DrillUserPrincipal.NON_ADMIN_USER_ROLES);
+ }
+ }
+ }
+ } catch (GSSException gsse) {
+ logger.warn("Caught GSSException trying to authenticate the client", gsse);
+ }
+
+ return null;
+
+ }
+
+ @Override
+ public boolean validate(UserIdentity user) {
+ return true;
+ }
+
+
+
+}
+
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/SpnegoUtil.java b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/SpnegoUtil.java
new file mode 100644
index 00000000000..a7103c30b30
--- /dev/null
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/SpnegoUtil.java
@@ -0,0 +1,86 @@
+/*
+ * 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.auth;
+
+
+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.exception.DrillbitStartupException;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.CommonConfigurationKeys;
+import org.apache.hadoop.security.UserGroupInformation;
+
+import java.io.IOException;
+
+public class SpnegoUtil {
+
+ private final DrillConfig config;
+ private UserGroupInformation ugi;
+ private String realm;
+ private String principal;
+ private String keytab;
+ public SpnegoUtil(DrillConfig configuration){
+ this.config = configuration;
+ }
+
+ public String getSpnegoRealm() throws DrillException {
+ realm = config.getString(ExecConstants.HTTP_SPNEGO_REALM_).trim();
+ if(realm.isEmpty()){
+ throw new DrillException(" drill.exec.http.spnego.auth.realm in the configuration file can't be empty");
+ }
+ return realm;
+ }
+
+
+ public String getSpnegoPrincipal() throws DrillException {
+ principal = config.getString(ExecConstants.HTTP_SPNEGO_PRINCIPAL_).trim();
+ if(principal.isEmpty()){
+ throw new DrillException(" drill.exec.http.spnego.auth.principal in the configuration file can't be empty");
+ }
+ return principal;
+ }
+
+ public String getSpnegoKeytab() throws DrillException {
+ keytab = config.getString(ExecConstants.HTTP_SPNEGO_KEYTAB_).trim();
+ if(keytab.isEmpty()){
+ throw new DrillException(" drill.exec.http.spnego.auth.keytab in the configuration file can't be empty");
+ }
+ return keytab;
+ }
+
+ public UserGroupInformation getUgi() throws DrillException {
+ if (!UserGroupInformation.isSecurityEnabled()) {
+ final Configuration Config = new Configuration();
+ Config.set(CommonConfigurationKeys.HADOOP_SECURITY_AUTHENTICATION,
+ UserGroupInformation.AuthenticationMethod.KERBEROS.toString());
+ UserGroupInformation.setConfiguration(Config);
+ }
+ try{
+ ugi = UserGroupInformation.loginUserFromKeytabAndReturnUGI(principal,this.getSpnegoKeytab());
+
+ }
+ catch (Exception e){
+ throw new DrillException("Login failed for"+principal+"with given keytab");
+ }
+
+ return ugi;
+ }
+}
diff --git a/exec/java-exec/src/main/resources/rest/generic.ftl b/exec/java-exec/src/main/resources/rest/generic.ftl
index 9025adb1f80..a7cee1d7034 100644
--- a/exec/java-exec/src/main/resources/rest/generic.ftl
+++ b/exec/java-exec/src/main/resources/rest/generic.ftl
@@ -77,7 +77,7 @@
#if>
Documentation
<#if showLogin == true >
- Log In
+ Log In
#if>
<#if showLogout == true >
Log Out (${loggedInUserName})
diff --git a/exec/java-exec/src/main/resources/rest/mainlogin.ftl b/exec/java-exec/src/main/resources/rest/mainlogin.ftl
new file mode 100644
index 00000000000..b406d450fff
--- /dev/null
+++ b/exec/java-exec/src/main/resources/rest/mainlogin.ftl
@@ -0,0 +1,26 @@
+<#-- 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. -->
+<#include "*/generic.ftl">
+<#macro page_head>
+#macro>
+
+<#macro page_body>
+
+#macro>
+<@page_html/>
\ No newline at end of file
From 6015e98f08c1961bbbc9911946522f9a076e87e0 Mon Sep 17 00:00:00 2001
From: mapr
Date: Wed, 13 Sep 2017 12:48:28 -0700
Subject: [PATCH 2/3] Added Test Cases
---
.../rest/auth/DrillSecurityHandler.java | 20 +--
.../rest/auth/DrillSpnegoAuthenticator.java | 16 +-
.../exec/server/rest/auth/SpnegoUtil.java | 26 +--
.../src/main/resources/rest/mainlogin.ftl | 16 +-
.../drill/exec/server/SpnegoTestUtils.java | 154 ++++++++++++++++++
.../exec/server/TestSpnegoAuthentication.java | 127 +++++++++++++++
6 files changed, 320 insertions(+), 39 deletions(-)
create mode 100644 exec/java-exec/src/test/java/org/apache/drill/exec/server/SpnegoTestUtils.java
create mode 100644 exec/java-exec/src/test/java/org/apache/drill/exec/server/TestSpnegoAuthentication.java
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/DrillSecurityHandler.java b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/DrillSecurityHandler.java
index 4bcd71756b2..5ce36a84b17 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/DrillSecurityHandler.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/DrillSecurityHandler.java
@@ -42,7 +42,6 @@
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
-import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
@@ -53,7 +52,6 @@
public class DrillSecurityHandler extends ConstraintSecurityHandler {
- private DrillConfig DrillConfig;
private final WorkManager workManager;
private DrillConstraintSecurityHandler basicSecurityHandler;
private DrillConstraintSecurityHandler spnegoSecurityHandler;
@@ -64,7 +62,6 @@ public class DrillSecurityHandler extends ConstraintSecurityHandler {
private SpnegoUtil spnUtil;
public DrillSecurityHandler(DrillConfig config,WorkManager work) throws DrillbitStartupException,IOException {
- DrillConfig = config;
spnUtil = new SpnegoUtil(config);
this.workManager = work;
try {
@@ -94,8 +91,6 @@ public void initializeFormAuthentication(){
basicSecurityHandler.setConstraintMappings(Collections.emptyList(), knownRoles);
basicSecurityHandler.setAuthenticator(new FormAuthenticator("/login", "/login", true));
basicSecurityHandler.setLoginService(new DrillRestLoginService(workManager.getContext()));
-
-
}
public void initializeSpnegoAuthentication() throws DrillException {
@@ -106,7 +101,6 @@ public void initializeSpnegoAuthentication() throws DrillException {
loginService.setIdentityService(identityService);
spnegoSecurityHandler.setLoginService(loginService);
spnegoSecurityHandler.setConstraintMappings(Collections.emptyList(),knownRoles);
-
}
@Override
@@ -118,24 +112,22 @@ public void doStart() throws Exception {
if(configuredMechanisms.isEmpty() || configuredMechanisms.contains(HTTP_FORM) ) {
basicSecurityHandler.doStart();
}
-
}
-
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
- HttpServletRequest req = (HttpServletRequest)baseRequest;
- String header = req.getHeader(HttpHeader.AUTHORIZATION.asString());
- HttpSession session = req.getSession(true);
+
+ String header = request.getHeader(HttpHeader.AUTHORIZATION.asString());
+ HttpSession session = request.getSession(true);
SessionAuthentication authentication = (SessionAuthentication) session.getAttribute("org.eclipse.jetty.security.UserIdentity");
- String uri = req.getRequestURI();
+ String uri = request.getRequestURI();
//Before authentication, all requests go through the Formauthenticator except for Spnegologin request
//If this authentication is null, user hasn't logged in yet
if(authentication == null){
- if( uri.equals("/sn") ){
+ if(configuredMechanisms.contains(HTTP_SPNEGO) && configuredMechanisms.contains(HTTP_FORM) && uri.equals("/sn") || configuredMechanisms.contains(HTTP_SPNEGO) && !configuredMechanisms.contains(HTTP_FORM)){
spnegoSecurityHandler.handle(target, baseRequest, request, response);
}
- else {
+ else if (configuredMechanisms.contains(HTTP_FORM) || configuredMechanisms.isEmpty()){
basicSecurityHandler.handle(target, baseRequest, request, response);
}
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/DrillSpnegoAuthenticator.java b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/DrillSpnegoAuthenticator.java
index 3ecfa92f438..6db99805e71 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/DrillSpnegoAuthenticator.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/DrillSpnegoAuthenticator.java
@@ -62,9 +62,11 @@ public Authentication validateRequest(ServletRequest request, ServletResponse re
HttpSession session = req.getSession(true);
Authentication authentication = (Authentication) session.getAttribute("org.eclipse.jetty.security.UserIdentity");
String uri = req.getRequestURI();
+ //If the Request URI is for Spnego login then the request is sent to login module using this flag
if( uri.equals("/sn")){
mandatory = true;
}
+ //For logout remove the attribute from the session that holds useridentity
if (authentication != null && uri.equals("/logout")) {
session.removeAttribute("org.eclipse.jetty.security.UserIdentity");
Authentication auth = (Authentication) session.getAttribute("org.eclipse.jetty.security.UserIdentity");
@@ -95,13 +97,13 @@ else if (authentication != null) {
UserIdentity user = this.login((String) null, spnegoToken, request);
//redirect the request to the desired page after successful login
if (user != null) {
- String nuri;
+ String newUri;
synchronized(session) {
- nuri = (String)session.getAttribute("org.eclipse.jetty.security.form_URI");
- if(nuri == null || nuri.length() == 0) {
- nuri = req.getContextPath();
- if(nuri.length() == 0) {
- nuri = "/";
+ newUri = (String)session.getAttribute("org.eclipse.jetty.security.form_URI");
+ if(newUri == null || newUri.length() == 0) {
+ newUri = req.getContextPath();
+ if(newUri.length() == 0) {
+ newUri = "/";
}
}
}
@@ -110,7 +112,7 @@ else if (authentication != null) {
Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
int redirectCode = base_request.getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion()?302:303;
try {
- base_response.sendRedirect(redirectCode, res.encodeRedirectURL(nuri));
+ base_response.sendRedirect(redirectCode, res.encodeRedirectURL(newUri));
} catch (IOException e) {
e.printStackTrace();
}
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/SpnegoUtil.java b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/SpnegoUtil.java
index a7103c30b30..75fbcee4685 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/SpnegoUtil.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/SpnegoUtil.java
@@ -41,6 +41,7 @@ public SpnegoUtil(DrillConfig configuration){
this.config = configuration;
}
+ //Reads the SPNEGO realm from the config file
public String getSpnegoRealm() throws DrillException {
realm = config.getString(ExecConstants.HTTP_SPNEGO_REALM_).trim();
if(realm.isEmpty()){
@@ -49,7 +50,7 @@ public String getSpnegoRealm() throws DrillException {
return realm;
}
-
+ //Reads the SPNEGO principal from the config file
public String getSpnegoPrincipal() throws DrillException {
principal = config.getString(ExecConstants.HTTP_SPNEGO_PRINCIPAL_).trim();
if(principal.isEmpty()){
@@ -58,6 +59,7 @@ public String getSpnegoPrincipal() throws DrillException {
return principal;
}
+ //Reads the SPNEGO keytab from the config file
public String getSpnegoKeytab() throws DrillException {
keytab = config.getString(ExecConstants.HTTP_SPNEGO_KEYTAB_).trim();
if(keytab.isEmpty()){
@@ -66,21 +68,23 @@ public String getSpnegoKeytab() throws DrillException {
return keytab;
}
+ //Performs the Server login to KDC for SPNEGO
public UserGroupInformation getUgi() throws DrillException {
+ try {
if (!UserGroupInformation.isSecurityEnabled()) {
- final Configuration Config = new Configuration();
- Config.set(CommonConfigurationKeys.HADOOP_SECURITY_AUTHENTICATION,
+ final Configuration newConfig = new Configuration();
+ newConfig.set(CommonConfigurationKeys.HADOOP_SECURITY_AUTHENTICATION,
UserGroupInformation.AuthenticationMethod.KERBEROS.toString());
- UserGroupInformation.setConfiguration(Config);
- }
- try{
- ugi = UserGroupInformation.loginUserFromKeytabAndReturnUGI(principal,this.getSpnegoKeytab());
-
+ UserGroupInformation.setConfiguration(newConfig);
+ ugi = UserGroupInformation.loginUserFromKeytabAndReturnUGI(principal, this.getSpnegoKeytab());
+ final Configuration oldConfig = new Configuration();
+ UserGroupInformation.setConfiguration(oldConfig);
+ } else {
+ ugi = UserGroupInformation.loginUserFromKeytabAndReturnUGI(principal, this.getSpnegoKeytab());
}
- catch (Exception e){
- throw new DrillException("Login failed for"+principal+"with given keytab");
+ }catch (Exception e) {
+ throw new DrillException("Login failed for" + principal + "with given keytab");
}
-
return ugi;
}
}
diff --git a/exec/java-exec/src/main/resources/rest/mainlogin.ftl b/exec/java-exec/src/main/resources/rest/mainlogin.ftl
index b406d450fff..031cd61dbe5 100644
--- a/exec/java-exec/src/main/resources/rest/mainlogin.ftl
+++ b/exec/java-exec/src/main/resources/rest/mainlogin.ftl
@@ -14,13 +14,15 @@
<#macro page_body>
+
-
Login
-
SSO
- <#if model??>
-
${model}
- #if>
-
-
+ Login
+ SSO
+ <#if model??>
+ ${model}
+ #if>
+
+
#macro>
<@page_html/>
\ No newline at end of file
diff --git a/exec/java-exec/src/test/java/org/apache/drill/exec/server/SpnegoTestUtils.java b/exec/java-exec/src/test/java/org/apache/drill/exec/server/SpnegoTestUtils.java
new file mode 100644
index 00000000000..d3437ff4bf2
--- /dev/null
+++ b/exec/java-exec/src/test/java/org/apache/drill/exec/server/SpnegoTestUtils.java
@@ -0,0 +1,154 @@
+/*
+ * 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;
+
+
+import org.apache.drill.exec.rpc.security.KerberosHelper;
+import org.apache.kerby.kerberos.kerb.KrbException;
+import org.apache.kerby.kerberos.kerb.server.SimpleKdcServer;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.nio.file.Files;
+
+import static org.apache.drill.exec.ExecTest.getTempDir;
+
+public class SpnegoTestUtils {
+ private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(KerberosHelper.class);
+
+ public File workspace;
+
+ private File kdcDir;
+ private SimpleKdcServer kdc;
+ private int kdcPort;
+
+ private final String HOSTNAME = "localhost";
+
+ public final String CLIENT_SHORT_NAME = "testUser";
+ public final String CLIENT_PRINCIPAL;
+
+ public String SERVER_PRINCIPAL;
+ private final String testName;
+
+ private File keytabDir;
+ public File clientKeytab;
+ public File serverKeytab;
+
+ private boolean kdcStarted;
+
+ public SpnegoTestUtils(final String testName) {
+ final String realm = "EXAMPLE.COM";
+ CLIENT_PRINCIPAL = CLIENT_SHORT_NAME + "@" + realm;
+
+ SERVER_PRINCIPAL = "HTTP" + "/" + HOSTNAME + "@" + realm;
+ this.testName = testName;
+ }
+
+ public void setupKdc() throws Exception {
+ kdc = new SimpleKdcServer();
+ workspace = new File(getTempDir("spnego_target"));
+
+ kdcDir = new File(workspace, testName);
+ if(!kdcDir.mkdirs()) {
+ throw new Exception(String.format("Failed to create the kdc directory %s", kdcDir.getName()));
+ }
+ kdc.setWorkDir(kdcDir);
+
+ kdc.setKdcHost(HOSTNAME);
+ kdcPort = getFreePort();
+ kdc.setAllowTcp(true);
+ kdc.setAllowUdp(false);
+ kdc.setKdcTcpPort(kdcPort);
+
+ logger.debug("Starting KDC server at {}:{}", HOSTNAME, kdcPort);
+
+ kdc.init();
+ kdc.start();
+ kdcStarted = true;
+
+
+ keytabDir = new File(workspace, testName + "_keytabs");
+ if(!keytabDir.mkdirs()) {
+ throw new Exception(String.format("Failed to create the keytab directory %s", keytabDir.getName()));
+ }
+ setupUsers(keytabDir);
+
+ // Kerby sets "java.security.krb5.conf" for us!
+ System.clearProperty("java.security.auth.login.config");
+ System.setProperty("javax.security.auth.useSubjectCredsOnly", "false");
+ // Uncomment the following lines for debugging.
+ // System.setProperty("sun.security.spnego.debug", "true");
+ // System.setProperty("sun.security.krb5.debug", "true");
+ }
+
+ private int getFreePort() throws IOException {
+ ServerSocket s = null;
+ try {
+ s = new ServerSocket(0);
+ s.setReuseAddress(true);
+ return s.getLocalPort();
+ } finally {
+ if (s != null) {
+ s.close();
+ }
+ }
+ }
+
+ private void setupUsers(File keytabDir) throws KrbException {
+ // Create the client user
+ String clientPrincipal = CLIENT_PRINCIPAL.substring(0, CLIENT_PRINCIPAL.indexOf('@'));
+ clientKeytab = new File(keytabDir, clientPrincipal.replace('/', '_') + ".keytab");
+ logger.debug("Creating {} with keytab {}", clientPrincipal, clientKeytab);
+ setupUser(kdc, clientKeytab, clientPrincipal);
+
+ // Create the server user
+ String serverPrincipal = SERVER_PRINCIPAL.substring(0, SERVER_PRINCIPAL.indexOf('@'));
+ serverKeytab = new File(keytabDir, serverPrincipal.replace('/', '_') + ".keytab");
+ logger.debug("Creating {} with keytab {}", SERVER_PRINCIPAL, serverKeytab);
+ setupUser(kdc, serverKeytab, SERVER_PRINCIPAL);
+ }
+
+ private void setupUser(SimpleKdcServer kdc, File keytab, String principal)
+ throws KrbException {
+ kdc.createPrincipal(principal);
+ kdc.exportPrincipal(principal, keytab);
+ }
+
+ public void stopKdc() throws Exception {
+ if (kdcStarted) {
+ logger.info("Stopping KDC on {}", kdcPort);
+ kdc.stop();
+ }
+
+ deleteIfExists(clientKeytab);
+ deleteIfExists(serverKeytab);
+ deleteIfExists(keytabDir);
+ deleteIfExists(kdcDir);
+ deleteIfExists(workspace);
+ }
+
+ private void deleteIfExists(File file) throws IOException {
+ if (file != null) {
+ Files.deleteIfExists(file.toPath());
+ }
+ }
+}
diff --git a/exec/java-exec/src/test/java/org/apache/drill/exec/server/TestSpnegoAuthentication.java b/exec/java-exec/src/test/java/org/apache/drill/exec/server/TestSpnegoAuthentication.java
new file mode 100644
index 00000000000..ec193e9c818
--- /dev/null
+++ b/exec/java-exec/src/test/java/org/apache/drill/exec/server/TestSpnegoAuthentication.java
@@ -0,0 +1,127 @@
+/*
+ * 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;
+
+
+import com.google.common.collect.Lists;
+import com.typesafe.config.ConfigValueFactory;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.drill.BaseTestQuery;
+import org.apache.drill.common.config.DrillConfig;
+import org.apache.drill.common.config.DrillProperties;
+import org.apache.drill.exec.ExecConstants;
+import org.apache.drill.exec.rpc.security.KerberosHelper;
+import org.apache.drill.exec.rpc.user.security.TestUserBitKerberos;
+import org.apache.drill.exec.rpc.user.security.testing.UserAuthenticatorTestImpl;
+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.ietf.jgss.GSSContext;
+import org.ietf.jgss.GSSManager;
+import org.ietf.jgss.GSSName;
+import org.ietf.jgss.Oid;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import javax.security.auth.Subject;
+import java.lang.reflect.Field;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.security.PrivilegedExceptionAction;
+import java.util.Properties;
+import java.util.concurrent.Callable;
+
+
+public class TestSpnegoAuthentication extends BaseTestQuery {
+
+ private static SpnegoTestUtils spnegoHelper;
+
+ @BeforeClass
+ public static void setupTest() throws Exception {
+ spnegoHelper = new SpnegoTestUtils(TestSpnegoAuthentication.class.getSimpleName());
+ spnegoHelper.setupKdc();
+
+ final DrillConfig newConfig = new DrillConfig(DrillConfig.create(cloneDefaultTestConfigProperties())
+ .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 Properties connectionProps = new Properties();
+ connectionProps.setProperty(DrillProperties.USER, "anonymous");
+ connectionProps.setProperty(DrillProperties.PASSWORD, "anything works!");
+ sun.security.krb5.Config.refresh();
+
+ final Field defaultRealm = KerberosName.class.getDeclaredField("defaultRealm");
+ defaultRealm.setAccessible(true);
+ defaultRealm.set(null, KerberosUtil.getDefaultRealm());
+
+ updateTestCluster(1, newConfig, connectionProps);
+
+
+ }
+
+ @Test
+ public void testRequestWithAuthorization() throws Exception {
+
+ final Properties connectionProps = new Properties();
+ connectionProps.setProperty(DrillProperties.SERVICE_PRINCIPAL, spnegoHelper.SERVER_PRINCIPAL);
+ connectionProps.setProperty(DrillProperties.KERBEROS_FROM_SUBJECT, "true");
+ final Subject clientSubject = JaasKrbUtil.loginUsingKeytab(spnegoHelper.CLIENT_PRINCIPAL,
+ spnegoHelper.clientKeytab.getAbsoluteFile());
+
+ String token = Subject.doAs(clientSubject, new PrivilegedExceptionAction() {
+ @Override
+ public String run() throws Exception {
+
+ GSSManager gssManager = GSSManager.getInstance();
+ GSSContext gssContext = null;
+ try {
+ String servicePrincipal = spnegoHelper.SERVER_PRINCIPAL;
+ Oid oid = KerberosUtil.getOidInstance("NT_GSS_KRB5_PRINCIPAL");
+ GSSName serviceName = gssManager.createName(servicePrincipal,
+ oid);
+ oid = KerberosUtil.getOidInstance("GSS_KRB5_MECH_OID");
+ gssContext = gssManager.createContext(serviceName, oid, null,
+ GSSContext.DEFAULT_LIFETIME);
+ gssContext.requestCredDeleg(true);
+ gssContext.requestMutualAuth(true);
+
+ byte[] inToken = new byte[0];
+ byte[] outToken = gssContext.initSecContext(inToken, 0, inToken.length);
+ Base64 base64 = new Base64(0);
+ return base64.encodeToString(outToken);
+
+ } finally {
+ if (gssContext != null) {
+ gssContext.dispose();
+ }
+ }
+ }
+ });
+
+
+
+ }
+
+
+}
From 5ff12ba1fe38fd728b0c33a686c665547dd21c50 Mon Sep 17 00:00:00 2001
From: Sindhuri
Date: Fri, 15 Sep 2017 15:21:53 -0700
Subject: [PATCH 3/3] Formatted code
---
.../org/apache/drill/exec/ExecConstants.java | 5 +-
.../server/rest/LogInLogOutResources.java | 15 +++---
.../server/rest/ViewableWithPermissions.java | 4 +-
.../drill/exec/server/rest/WebServer.java | 12 ++---
.../auth/DrillConstraintSecurityHandler.java | 8 ++-
.../server/rest/auth/DrillErrorHandler.java | 21 ++------
.../rest/auth/DrillSecurityHandler.java | 52 ++++++++++---------
.../rest/auth/DrillSpnegoAuthenticator.java | 33 ++++--------
.../rest/auth/DrillSpnegoLoginService.java | 29 +++--------
.../exec/server/rest/auth/SpnegoUtil.java | 8 ---
.../src/main/resources/rest/generic.ftl | 2 +-
.../rest/{mainlogin.ftl => mainLogin.ftl} | 4 +-
.../exec/server/TestSpnegoAuthentication.java | 32 ++++++++++++
13 files changed, 102 insertions(+), 123 deletions(-)
rename exec/java-exec/src/main/resources/rest/{mainlogin.ftl => mainLogin.ftl} (87%)
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/ExecConstants.java b/exec/java-exec/src/main/java/org/apache/drill/exec/ExecConstants.java
index dd1e3f7edac..48140eea291 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/ExecConstants.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/ExecConstants.java
@@ -123,9 +123,8 @@ public interface ExecConstants {
String HTTP_SESSION_MEMORY_MAXIMUM = "drill.exec.http.session.memory.maximum";
String HTTP_SESSION_MAX_IDLE_SECS = "drill.exec.http.session_max_idle_secs";
String HTTP_AUTHENTICATION_MECHANISMS = "drill.exec.http.auth.mechanisms";
- String HTTP_SPNEGO_PRINCIPAL_= "drill.exec.http.spnego.auth.principal";
- String HTTP_SPNEGO_KEYTAB_= "drill.exec.http.spnego.auth.keytab";
- String HTTP_SPNEGO_REALM_= "drill.exec.http.spnego.auth.realm";
+ String HTTP_SPNEGO_PRINCIPAL_= "drill.exec.http.auth.spnego.principal";
+ String HTTP_SPNEGO_KEYTAB_= "drill.exec.http.auth.spnego.keytab";
String HTTP_KEYSTORE_PATH = "drill.exec.ssl.keyStorePath";
String HTTP_KEYSTORE_PASSWORD = "drill.exec.ssl.keyStorePassword";
String HTTP_TRUSTSTORE_PATH = "drill.exec.ssl.trustStorePath";
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/LogInLogOutResources.java b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/LogInLogOutResources.java
index f30e62fe4bd..af765d888d9 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/LogInLogOutResources.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/LogInLogOutResources.java
@@ -44,7 +44,7 @@
@PermitAll
public class LogInLogOutResources {
public static final String REDIRECT_QUERY_PARM = "redirect";
- public static final String LOGIN_RESOURCE = "mainlogin";
+ public static final String LOGIN_RESOURCE = "mainLogin";
@GET
@Path("/login")
@@ -70,17 +70,16 @@ public Viewable getLoginPage(@Context HttpServletRequest request, @Context HttpS
}
@GET
- @Path("/sn")
+ @Path("/spnegoLogin")
@Produces(MediaType.TEXT_HTML)
- public Viewable getspnegologin(@Context HttpServletRequest request, @Context HttpServletResponse response,
+ public Viewable getSpnegologin(@Context HttpServletRequest request, @Context HttpServletResponse response,
@Context SecurityContext sc, @Context UriInfo uriInfo, @QueryParam(REDIRECT_QUERY_PARM) String redirect)
throws Exception {
if (AuthDynamicFeature.isUserLoggedIn(sc)) {
request.getRequestDispatcher("/").forward(request, response);
return null;
}
-
- return ViewableWithPermissions.createmainLoginPage("Invalid SPNEGO credentials.");
+ return ViewableWithPermissions.createMainLoginPage("Invalid SPNEGO credentials or SPNEGO is not configured");
}
// Request type is POST because POST request which contains the login credentials are invalid and the request is
@@ -103,12 +102,11 @@ public void logout(@Context HttpServletRequest req, @Context HttpServletResponse
req.getRequestDispatcher("/").forward(req, resp);
}
@GET
- @Path("/mainlogin")
+ @Path("/mainLogin")
@Produces(MediaType.TEXT_HTML)
public Viewable getMainLoginPage(@Context HttpServletRequest request, @Context HttpServletResponse response,
@Context SecurityContext sc, @Context UriInfo uriInfo, @QueryParam(REDIRECT_QUERY_PARM) String redirect)
throws Exception {
- //req.getRequestDispatcher("/").forward(req, resp);
if (!StringUtils.isEmpty(redirect)) {
// If the URL has redirect in it, set the redirect URI in session, so that after the login is successful, request
// is forwarded to the redirect page.
@@ -116,8 +114,7 @@ public Viewable getMainLoginPage(@Context HttpServletRequest request, @Context H
final URI destURI = UriBuilder.fromUri(URLDecoder.decode(redirect, "UTF-8")).build();
session.setAttribute(FormAuthenticator.__J_URI, destURI.toString());
}
- return ViewableWithPermissions.createmainLoginPage(null);
+ return ViewableWithPermissions.createMainLoginPage(null);
}
-
}
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/ViewableWithPermissions.java b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/ViewableWithPermissions.java
index fb56cfd0ab0..00a057defae 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/ViewableWithPermissions.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/ViewableWithPermissions.java
@@ -61,8 +61,8 @@ public static Viewable createLoginPage(final String errorMsg) {
return new ViewableWithPermissions(true, "/rest/login.ftl", null, false, errorMsg);
}
- public static Viewable createmainLoginPage(final String errorMsg) {
- return new ViewableWithPermissions(true, "/rest/mainlogin.ftl", null, false, errorMsg);
+ public static Viewable createMainLoginPage(final String errorMsg) {
+ return new ViewableWithPermissions(true, "/rest/mainLogin.ftl", null, false, errorMsg);
}
private ViewableWithPermissions(final boolean authEnabled, final String templateName, final SecurityContext sc,
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/WebServer.java b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/WebServer.java
index afe4ba843ce..f87f6af2e5a 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/WebServer.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/WebServer.java
@@ -31,6 +31,7 @@
import org.apache.drill.exec.exception.DrillbitStartupException;
import org.apache.drill.exec.rpc.security.plain.PlainFactory;
import org.apache.drill.exec.server.BootStrapContext;
+import org.apache.drill.exec.server.rest.auth.DrillErrorHandler;
import org.apache.drill.exec.server.rest.auth.DrillRestLoginService;
import org.apache.drill.exec.server.rest.auth.DrillSecurityHandler;
import org.apache.drill.exec.work.WorkManager;
@@ -104,8 +105,6 @@ public class WebServer implements AutoCloseable {
private final BootStrapContext context;
- private final String SpnegoAuth = "SPNEGO";
-
/**
* Create Jetty based web server.
*
@@ -147,8 +146,8 @@ public void start() throws Exception {
if (embeddedJetty == null) {
return;
}
- boolean authEnabled = config.getBoolean(ExecConstants.USER_AUTHENTICATION_ENABLED);
- if (authEnabled && !context.getAuthProvider().containsFactory(PlainFactory.SIMPLE_NAME)) {
+ boolean authEnabled = (config.getBoolean(ExecConstants.USER_AUTHENTICATION_ENABLED));
+ if (authEnabled && !context.getAuthProvider().containsFactory(PlainFactory.SIMPLE_NAME)) {
logger.warn("Not starting web server. Currently Drill supports web authentication only through " +
"username/password. But PLAIN mechanism is not configured.");
return;
@@ -167,7 +166,7 @@ public void start() throws Exception {
embeddedJetty.addConnector(serverConnector);
// Add resources
- final ErrorHandler errorHandler = new ErrorHandler();
+ final DrillErrorHandler errorHandler = new DrillErrorHandler();
errorHandler.setShowStacks(true);
errorHandler.setShowMessageInTitle(true);
@@ -195,8 +194,7 @@ public void start() throws Exception {
if (authEnabled) {
//DrillSecurityHandler is used to support SPNEGO and FORM authentication together
- DrillSecurityHandler securityHandler = new DrillSecurityHandler(config, workManager);
- servletContextHandler.setSecurityHandler(securityHandler);
+ servletContextHandler.setSecurityHandler(new DrillSecurityHandler(config, workManager.getContext()));
servletContextHandler.setSessionHandler(createSessionHandler(servletContextHandler.getSecurityHandler()));
}
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/DrillConstraintSecurityHandler.java b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/DrillConstraintSecurityHandler.java
index c0cff32d4ba..5edb7676a76 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/DrillConstraintSecurityHandler.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/DrillConstraintSecurityHandler.java
@@ -19,12 +19,9 @@
import org.eclipse.jetty.security.ConstraintSecurityHandler;
-
+/** Class that extends the ConstraintSecurityHandler that helps to initiate two both the Form and SecurityHandlers**/
public class DrillConstraintSecurityHandler extends ConstraintSecurityHandler {
- public DrillConstraintSecurityHandler() {
-
- }
@Override
public void doStart() throws Exception {
@@ -35,4 +32,5 @@ public void doStart() throws Exception {
public void doStop() throws Exception {
super.doStop();
}
-}
+
+}
\ No newline at end of file
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/DrillErrorHandler.java b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/DrillErrorHandler.java
index da9e42aa141..e5634001f98 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/DrillErrorHandler.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/DrillErrorHandler.java
@@ -15,38 +15,25 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-
-
-
package org.apache.drill.exec.server.rest.auth;
-import org.eclipse.jetty.server.Authentication;
-import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.ErrorHandler;
-
import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.Writer;
public class DrillErrorHandler extends ErrorHandler {
-
-
@Override
protected void writeErrorPageMessage(HttpServletRequest request, Writer writer, int code, String message, String uri) throws IOException {
super.writeErrorPageMessage(request,writer,code,message,uri);
- //writer.write("Spnego login failed
");
- String path = request.getContextPath();
- String ref = path+"/login";
- writer.write(" root ");
+ writer.write("Check the requirements or Use this link to do Form Authentication
");
+ writer.write(" login ");
}
}
-}
+}
\ No newline at end of file
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/DrillSecurityHandler.java b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/DrillSecurityHandler.java
index 5ce36a84b17..d85a9164924 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/DrillSecurityHandler.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/DrillSecurityHandler.java
@@ -25,6 +25,8 @@
import org.apache.drill.common.exceptions.DrillException;
import org.apache.drill.exec.ExecConstants;
import org.apache.drill.exec.exception.DrillbitStartupException;
+import org.apache.drill.exec.rpc.security.AuthStringUtil;
+import org.apache.drill.exec.server.DrillbitContext;
import org.apache.drill.exec.work.WorkManager;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.security.ConstraintMapping;
@@ -52,28 +54,28 @@
public class DrillSecurityHandler extends ConstraintSecurityHandler {
- private final WorkManager workManager;
private DrillConstraintSecurityHandler basicSecurityHandler;
private DrillConstraintSecurityHandler spnegoSecurityHandler;
+ private Set knownRoles = ImmutableSet.of(AUTHENTICATED_ROLE, ADMIN_ROLE);
private final String HTTP_FORM ="FORM";
private final String HTTP_SPNEGO ="SPNEGO";
- private List configuredMechanisms = Lists.newArrayList();
- private Set knownRoles = ImmutableSet.of(AUTHENTICATED_ROLE, ADMIN_ROLE);
+ private List configuredMechanisms = Lists.newArrayList();
private SpnegoUtil spnUtil;
+ private boolean spnegoEnabled = false;
+ private boolean formEnabled = false;
- public DrillSecurityHandler(DrillConfig config,WorkManager work) throws DrillbitStartupException,IOException {
+ public DrillSecurityHandler(DrillConfig config,DrillbitContext drillContext) throws DrillbitStartupException,IOException {
spnUtil = new SpnegoUtil(config);
- this.workManager = work;
try {
if (config.hasPath(ExecConstants.HTTP_AUTHENTICATION_MECHANISMS)) {
configuredMechanisms = config.getStringList(ExecConstants.HTTP_AUTHENTICATION_MECHANISMS);
- if (configuredMechanisms.contains(HTTP_FORM)) {
- initializeFormAuthentication();
-
+ if (AuthStringUtil.listContains(configuredMechanisms,HTTP_FORM)) {
+ formEnabled = true;
+ initializeFormAuthentication(drillContext);
}
- if (configuredMechanisms.contains(HTTP_SPNEGO)) {
- initializeSpnegoAuthentication();
-
+ if (AuthStringUtil.listContains(configuredMechanisms,HTTP_SPNEGO)) {
+ spnegoEnabled = true ;
+ initializeSpnegoAuthentication(drillContext);
}
}
}
@@ -82,21 +84,22 @@ public DrillSecurityHandler(DrillConfig config,WorkManager work) throws Drillbit
}
//Backward compatability for Form authentication
if(!config.hasPath(ExecConstants.HTTP_AUTHENTICATION_MECHANISMS)){
- initializeFormAuthentication();
+ formEnabled = true;
+ initializeFormAuthentication(drillContext);
}
}
- public void initializeFormAuthentication(){
+ public void initializeFormAuthentication(DrillbitContext drillContext){
basicSecurityHandler = new DrillConstraintSecurityHandler();
basicSecurityHandler.setConstraintMappings(Collections.emptyList(), knownRoles);
basicSecurityHandler.setAuthenticator(new FormAuthenticator("/login", "/login", true));
- basicSecurityHandler.setLoginService(new DrillRestLoginService(workManager.getContext()));
+ basicSecurityHandler.setLoginService(new DrillRestLoginService(drillContext));
}
- public void initializeSpnegoAuthentication() throws DrillException {
+ public void initializeSpnegoAuthentication(DrillbitContext drillContext) throws DrillException {
spnegoSecurityHandler = new DrillConstraintSecurityHandler();
spnegoSecurityHandler.setAuthenticator(new DrillSpnegoAuthenticator());
- final SpnegoLoginService loginService = new DrillSpnegoLoginService(spnUtil.getSpnegoRealm(),spnUtil.getSpnegoPrincipal(),workManager.getContext(),spnUtil.getUgi());
+ final SpnegoLoginService loginService = new DrillSpnegoLoginService(spnUtil,drillContext);
final IdentityService identityService = new DefaultIdentityService();
loginService.setIdentityService(identityService);
spnegoSecurityHandler.setLoginService(loginService);
@@ -106,10 +109,10 @@ public void initializeSpnegoAuthentication() throws DrillException {
@Override
public void doStart() throws Exception {
super.doStart();
- if(!configuredMechanisms.isEmpty() && configuredMechanisms.contains(HTTP_SPNEGO)) {
+ if(!configuredMechanisms.isEmpty() && spnegoEnabled) {
spnegoSecurityHandler.doStart();
}
- if(configuredMechanisms.isEmpty() || configuredMechanisms.contains(HTTP_FORM) ) {
+ if(formEnabled) {
basicSecurityHandler.doStart();
}
}
@@ -124,20 +127,19 @@ public void handle(String target, Request baseRequest, HttpServletRequest reques
//Before authentication, all requests go through the Formauthenticator except for Spnegologin request
//If this authentication is null, user hasn't logged in yet
if(authentication == null){
- if(configuredMechanisms.contains(HTTP_SPNEGO) && configuredMechanisms.contains(HTTP_FORM) && uri.equals("/sn") || configuredMechanisms.contains(HTTP_SPNEGO) && !configuredMechanisms.contains(HTTP_FORM)){
+ if(spnegoEnabled && uri.equals("/sn") || !formEnabled){
spnegoSecurityHandler.handle(target, baseRequest, request, response);
}
- else if (configuredMechanisms.contains(HTTP_FORM) || configuredMechanisms.isEmpty()){
+ else if (formEnabled){
basicSecurityHandler.handle(target, baseRequest, request, response);
}
-
}
//If user has logged in, use the corresponding handler to handle the request
else{
- if(authentication.getAuthMethod() == "FORM"){
+ if(authentication.getAuthMethod() == HTTP_FORM){
basicSecurityHandler.handle(target, baseRequest, request, response);
}
- else if(authentication.getAuthMethod() == "SPNEGO"){
+ else if(authentication.getAuthMethod() == HTTP_SPNEGO){
spnegoSecurityHandler.handle(target, baseRequest, request, response);
}
}
@@ -146,10 +148,10 @@ else if(authentication.getAuthMethod() == "SPNEGO"){
@Override
public void setHandler(Handler handler) {
super.setHandler(handler);
- if(!configuredMechanisms.isEmpty() && configuredMechanisms.contains(HTTP_SPNEGO)) {
+ if(!configuredMechanisms.isEmpty() && spnegoEnabled) {
spnegoSecurityHandler.setHandler(handler);
}
- if(configuredMechanisms.isEmpty() || configuredMechanisms.contains(HTTP_SPNEGO)) {
+ if(formEnabled) {
basicSecurityHandler.setHandler(handler);
}
}
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/DrillSpnegoAuthenticator.java b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/DrillSpnegoAuthenticator.java
index 6db99805e71..9aef671de72 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/DrillSpnegoAuthenticator.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/DrillSpnegoAuthenticator.java
@@ -47,12 +47,8 @@
import java.io.IOException;
public class DrillSpnegoAuthenticator extends SpnegoAuthenticator {
- private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(DrillSpnegoAuthenticator.class);
-
- public DrillSpnegoAuthenticator() {
-
- }
+ private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(DrillSpnegoAuthenticator.class);
@Override
public Authentication validateRequest(ServletRequest request, ServletResponse response, boolean mandatory) throws ServerAuthException {
@@ -62,8 +58,8 @@ public Authentication validateRequest(ServletRequest request, ServletResponse re
HttpSession session = req.getSession(true);
Authentication authentication = (Authentication) session.getAttribute("org.eclipse.jetty.security.UserIdentity");
String uri = req.getRequestURI();
- //If the Request URI is for Spnego login then the request is sent to login module using this flag
- if( uri.equals("/sn")){
+ //If the Request URI is for Spnego login then the request is sent to the login module my modifying this flag
+ if (uri.equals("/spnegoLogin")) {
mandatory = true;
}
//For logout remove the attribute from the session that holds useridentity
@@ -71,11 +67,9 @@ public Authentication validateRequest(ServletRequest request, ServletResponse re
session.removeAttribute("org.eclipse.jetty.security.UserIdentity");
Authentication auth = (Authentication) session.getAttribute("org.eclipse.jetty.security.UserIdentity");
return auth;
- }
- else if (authentication != null) {
+ } else if (authentication != null) {
return authentication;
- }
- else {
+ } else {
String header = req.getHeader(HttpHeader.AUTHORIZATION.asString());
if (!mandatory) {
return new DeferredAuthentication(this);
@@ -98,11 +92,11 @@ else if (authentication != null) {
//redirect the request to the desired page after successful login
if (user != null) {
String newUri;
- synchronized(session) {
- newUri = (String)session.getAttribute("org.eclipse.jetty.security.form_URI");
- if(newUri == null || newUri.length() == 0) {
+ synchronized (session) {
+ newUri = (String) session.getAttribute("org.eclipse.jetty.security.form_URI");
+ if (newUri == null || newUri.length() == 0) {
newUri = req.getContextPath();
- if(newUri.length() == 0) {
+ if (newUri.length() == 0) {
newUri = "/";
}
}
@@ -110,7 +104,7 @@ else if (authentication != null) {
response.setContentLength(0);
Response base_response = HttpChannel.getCurrentHttpChannel().getResponse();
Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
- int redirectCode = base_request.getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion()?302:303;
+ int redirectCode = base_request.getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion() ? 302 : 303;
try {
base_response.sendRedirect(redirectCode, res.encodeRedirectURL(newUri));
} catch (IOException e) {
@@ -118,13 +112,10 @@ else if (authentication != null) {
}
return new UserAuthentication(this.getAuthMethod(), user);
}
-
}
-
return Authentication.UNAUTHENTICATED;
}
}
-
}
@@ -138,6 +129,4 @@ public UserIdentity login(String username, Object password, ServletRequest reque
return user;
}
-
-
-}
+}
\ No newline at end of file
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/DrillSpnegoLoginService.java b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/DrillSpnegoLoginService.java
index 0e6e8108412..e945ba9fb64 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/DrillSpnegoLoginService.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/DrillSpnegoLoginService.java
@@ -21,6 +21,7 @@
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.expr.fn.impl.ContextFunctions;
import org.apache.drill.exec.server.DrillbitContext;
@@ -58,19 +59,17 @@ public class DrillSpnegoLoginService extends SpnegoLoginService {
- public DrillSpnegoLoginService(String realm, String serverPrincipal, DrillbitContext drillbitContext, UserGroupInformation ugSpnego) {
- super(realm);
- this.serverPrincipal = Objects.requireNonNull(serverPrincipal);
- drillContext = drillbitContext;
- ugi = ugSpnego;
+ public DrillSpnegoLoginService(SpnegoUtil spnegoUtil, DrillbitContext drillBitContext) throws DrillException {
+ super(DrillSpnegoLoginService.class.getName());
+ this.serverPrincipal = spnegoUtil.getSpnegoPrincipal();
+ drillContext = drillBitContext;
+ ugi = spnegoUtil.getUgi();
}
@Override
protected void doStart() throws Exception {
// Override the parent implementation, setting _targetName to be the serverPrincipal
// without the need for a one-line file to do the same thing.
- //
- // AbstractLifeCycle's doStart() method does nothing, so we aren't missing any extra logic.
final Field targetNameField = SpnegoLoginService.class.getDeclaredField(TARGET_NAME_FIELD_NAME);
targetNameField.setAccessible(true);
targetNameField.set(this, serverPrincipal);
@@ -99,7 +98,7 @@ public UserIdentity spnegologin(String username, Object credentials) {
GSSManager manager = GSSManager.getInstance();
try {
-
+ //Providing both OID's is required here. If we provide only one, we're requiring that clients provide us the SPNEGO OID to authentica via Kerberos.
Oid spnegoOid = new Oid("1.3.6.1.5.5.2");
Oid krb5Oid = new Oid("1.2.840.113554.1.2.2");
GSSName gssName = manager.createName(serverPrincipal, null);
@@ -116,10 +115,6 @@ public UserIdentity spnegologin(String username, Object credentials) {
if (gContext.isEstablished()) {
String clientName = gContext.getSrcName().toString();
String role = clientName.substring(0, clientName.indexOf(64));
- // LOG.debug("SpnegoUserRealm: established a security context", new Object[0]);
- //LOG.debug("Client Principal is: " + gContext.getSrcName(), new Object[0]);
- // LOG.debug("Server Principal is: " + gContext.getTargName(), new Object[0]);
- // LOG.debug("Client Default Role: " + role, new Object[0]);
final SystemOptionManager sysOptions = drillContext.getOptionManager();
final boolean isAdmin = ImpersonationUtil.hasAdminPrivileges(role,
@@ -138,17 +133,7 @@ public UserIdentity spnegologin(String username, Object credentials) {
} catch (GSSException gsse) {
logger.warn("Caught GSSException trying to authenticate the client", gsse);
}
-
return null;
-
}
-
- @Override
- public boolean validate(UserIdentity user) {
- return true;
- }
-
-
-
}
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/SpnegoUtil.java b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/SpnegoUtil.java
index 75fbcee4685..7d22d17a56f 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/SpnegoUtil.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/auth/SpnegoUtil.java
@@ -41,14 +41,6 @@ public SpnegoUtil(DrillConfig configuration){
this.config = configuration;
}
- //Reads the SPNEGO realm from the config file
- public String getSpnegoRealm() throws DrillException {
- realm = config.getString(ExecConstants.HTTP_SPNEGO_REALM_).trim();
- if(realm.isEmpty()){
- throw new DrillException(" drill.exec.http.spnego.auth.realm in the configuration file can't be empty");
- }
- return realm;
- }
//Reads the SPNEGO principal from the config file
public String getSpnegoPrincipal() throws DrillException {
diff --git a/exec/java-exec/src/main/resources/rest/generic.ftl b/exec/java-exec/src/main/resources/rest/generic.ftl
index a7cee1d7034..d04414fec22 100644
--- a/exec/java-exec/src/main/resources/rest/generic.ftl
+++ b/exec/java-exec/src/main/resources/rest/generic.ftl
@@ -77,7 +77,7 @@
#if>
Documentation
<#if showLogin == true >
- Log In
+ Log In
#if>
<#if showLogout == true >
Log Out (${loggedInUserName})
diff --git a/exec/java-exec/src/main/resources/rest/mainlogin.ftl b/exec/java-exec/src/main/resources/rest/mainLogin.ftl
similarity index 87%
rename from exec/java-exec/src/main/resources/rest/mainlogin.ftl
rename to exec/java-exec/src/main/resources/rest/mainLogin.ftl
index 031cd61dbe5..de14ae84cf4 100644
--- a/exec/java-exec/src/main/resources/rest/mainlogin.ftl
+++ b/exec/java-exec/src/main/resources/rest/mainLogin.ftl
@@ -17,8 +17,8 @@
-
Login
-
SSO
+
Login using FORM AUTHENTICATION
+
Login using SPNEGO
<#if model??>
${model}
#if>
diff --git a/exec/java-exec/src/test/java/org/apache/drill/exec/server/TestSpnegoAuthentication.java b/exec/java-exec/src/test/java/org/apache/drill/exec/server/TestSpnegoAuthentication.java
index ec193e9c818..dc0f9e310f5 100644
--- a/exec/java-exec/src/test/java/org/apache/drill/exec/server/TestSpnegoAuthentication.java
+++ b/exec/java-exec/src/test/java/org/apache/drill/exec/server/TestSpnegoAuthentication.java
@@ -28,17 +28,35 @@
import org.apache.drill.exec.rpc.security.KerberosHelper;
import org.apache.drill.exec.rpc.user.security.TestUserBitKerberos;
import org.apache.drill.exec.rpc.user.security.testing.UserAuthenticatorTestImpl;
+import org.apache.drill.exec.server.rest.auth.DrillSecurityHandler;
+import org.apache.drill.exec.server.rest.auth.DrillSpnegoAuthenticator;
+import org.apache.drill.exec.server.rest.auth.DrillSpnegoLoginService;
+import org.apache.hadoop.security.authentication.client.KerberosAuthenticator;
+import org.apache.hadoop.security.authentication.server.AuthenticationToken;
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.security.ConstraintSecurityHandler;
+import org.eclipse.jetty.security.RoleInfo;
+import org.eclipse.jetty.security.SecurityHandler;
+import org.eclipse.jetty.server.Authentication;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+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.BeforeClass;
import org.junit.Test;
+import org.mockito.Mockito;
import javax.security.auth.Subject;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
import java.lang.reflect.Field;
import java.net.HttpURLConnection;
import java.net.URL;
@@ -119,6 +137,20 @@ public String run() throws Exception {
}
});
+ HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
+ HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
+
+ Mockito.when(request.getHeader(KerberosAuthenticator.AUTHORIZATION))
+ .thenReturn(KerberosAuthenticator.NEGOTIATE + " " + token);
+ Mockito.when(request.getServerName()).thenReturn("localhost");
+
+
+
+ DrillSpnegoAuthenticator authenticator = new DrillSpnegoAuthenticator();
+ ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler();
+ securityHandler.setAuthenticator(authenticator);
+ Authentication authToken = authenticator.validateRequest((ServletRequest)request,(ServletResponse) response,true);
+
}