LENS-1509 : Lens Server SPNEGO authentication
diff --git a/contrib/clients/python/lens/client/session.py b/contrib/clients/python/lens/client/session.py
index a1ccc4b..e63da1e 100644
--- a/contrib/clients/python/lens/client/session.py
+++ b/contrib/clients/python/lens/client/session.py
@@ -17,6 +17,7 @@
import requests
+from .auth import SpnegoAuth
from .models import WrappedJson
from .utils import conf_to_xml
@@ -24,6 +25,8 @@
class LensSessionClient(object):
def __init__(self, base_url, username, password, database, conf):
self.base_url = base_url + "session/"
+ self.keytab = conf.get('lens.client.authentication.kerberos.keytab')
+ self.principal = username or conf.get('lens.client.authentication.kerberos.principal')
self.open(username, password, database, conf)
def __getitem__(self, key):
@@ -41,10 +44,12 @@
payload = [('username', username), ('password', password), ('sessionconf', conf_to_xml(conf))]
if database:
payload.append(('database', database))
- r = requests.post(self.base_url, files=payload, headers={'accept': 'application/xml'})
+ r = requests.post(self.base_url, files=payload, headers={'accept': 'application/xml'},
+ auth=SpnegoAuth(self.keytab, self.principal))
r.raise_for_status()
self._sessionid = r.text
def close(self):
- requests.delete(self.base_url, params={'sessionid': self._sessionid})
+ requests.delete(self.base_url, params={'sessionid': self._sessionid},
+ auth=SpnegoAuth(self.keytab, self.principal))
diff --git a/contrib/clients/python/setup.py b/contrib/clients/python/setup.py
index de59d32..dc40bfa 100644
--- a/contrib/clients/python/setup.py
+++ b/contrib/clients/python/setup.py
@@ -57,7 +57,7 @@
license='Apache Software License',
author='Apache',
tests_require=['tox'],
- install_requires=['requests>=2.9.1', 'six>=1.10.0'],
+ install_requires=['requests>=2.9.1', 'six>=1.10.0', 'pykerberos=1.2.1'],
cmdclass={'test': Tox},
author_email='dev@lens.apache.org',
description='Python Lens Client',
diff --git a/lens-client/src/main/java/org/apache/lens/client/LensClientConfig.java b/lens-client/src/main/java/org/apache/lens/client/LensClientConfig.java
index b703e13..eb12ee3 100644
--- a/lens-client/src/main/java/org/apache/lens/client/LensClientConfig.java
+++ b/lens-client/src/main/java/org/apache/lens/client/LensClientConfig.java
@@ -90,6 +90,12 @@
public static final int DEFAULT_CONNECTION_TIMEOUT_MILLIS = 60000; //60 secs
+ public static final String KERBEROS_PRINCIPAL = CLIENT_PFX + "authentication.kerberos.principal";
+
+ public static final String KERBEROS_REALM = CLIENT_PFX + "authentication.kerberos.realm";
+
+ public static final String KERBEROS_KEYTAB = CLIENT_PFX + "authentication.kerberos.keytab";
+
/**
* Get the username from config
*
@@ -167,4 +173,5 @@
public static String getWSFilterImplConfKey(String filterName) {
return CLIENT_PFX + filterName + WS_FILTER_IMPL_SFX;
}
+
}
diff --git a/lens-client/src/main/java/org/apache/lens/client/LensConnection.java b/lens-client/src/main/java/org/apache/lens/client/LensConnection.java
index bb15b23..ab49831 100644
--- a/lens-client/src/main/java/org/apache/lens/client/LensConnection.java
+++ b/lens-client/src/main/java/org/apache/lens/client/LensConnection.java
@@ -185,6 +185,7 @@
if (e.getCause() != null && e.getCause() instanceof ConnectException) {
throw new LensClientServerConnectionException(e.getCause().getMessage(), e);
}
+ throw e;
}
log.debug("Successfully switched to database {}", params.getDbName());
diff --git a/lens-client/src/main/java/org/apache/lens/client/LensConnectionParams.java b/lens-client/src/main/java/org/apache/lens/client/LensConnectionParams.java
index 3a5dcdb..90c70c8 100644
--- a/lens-client/src/main/java/org/apache/lens/client/LensConnectionParams.java
+++ b/lens-client/src/main/java/org/apache/lens/client/LensConnectionParams.java
@@ -53,6 +53,7 @@
private Set<Class<?>> requestFilters = new HashSet<Class<?>>();
private void setupRequestFilters() {
+ requestFilters.add(SpnegoClientFilter.class); // add default filter
if (this.conf.get(LensClientConfig.SESSION_FILTER_NAMES) != null) {
String[] filterNames = this.conf.getStrings(LensClientConfig.SESSION_FILTER_NAMES);
for (String filterName : filterNames) {
diff --git a/lens-server-api/src/main/java/org/apache/lens/server/api/LensConfConstants.java b/lens-server-api/src/main/java/org/apache/lens/server/api/LensConfConstants.java
index f14ae44..bda995d 100644
--- a/lens-server-api/src/main/java/org/apache/lens/server/api/LensConfConstants.java
+++ b/lens-server-api/src/main/java/org/apache/lens/server/api/LensConfConstants.java
@@ -277,6 +277,11 @@
*/
public static final String SESSION_LOGGEDIN_USER = SESSION_PFX + "loggedin.user";
+ /**
+ * constant for session proxy user
+ */
+ public static final String SESSION_PROXY_USER = SESSION_PFX + "proxy.user";
+
// ldap user to cluster/hdfs accessing user resolver related configs
/**
* The Constant USER_RESOLVER_TYPE.
@@ -1297,4 +1302,16 @@
public static final double DEFAULT_DRIVER_QUERY_COST = 0.0;
public static final String DRIVER_COST_QUERY_DECIDER = DRIVER_PFX + "cost.query.decider.class";
+
+ public static final String AUTH_SCHEME = SERVER_PFX + "authentication.scheme";
+
+ public static final String KERBEROS_PRINCIPAL = SERVER_PFX + "authentication.kerberos.principal";
+
+ public static final String KERBEROS_REALM = SERVER_PFX + "authentication.kerberos.realm";
+
+ public static final String KERBEROS_KEYTAB = SERVER_PFX + "authentication.kerberos.keytab";
+
+ public static final String ALLOWED_PROXY_USERS = SERVER_PFX + "authentication.allowed.proxy.users";
+
+
}
diff --git a/lens-server/src/main/java/org/apache/lens/server/BaseLensService.java b/lens-server/src/main/java/org/apache/lens/server/BaseLensService.java
index b5248f3..006ee88 100644
--- a/lens-server/src/main/java/org/apache/lens/server/BaseLensService.java
+++ b/lens-server/src/main/java/org/apache/lens/server/BaseLensService.java
@@ -26,6 +26,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import javax.ws.rs.BadRequestException;
@@ -36,6 +37,7 @@
import org.apache.lens.api.LensConf;
import org.apache.lens.api.LensSessionHandle;
+import org.apache.lens.api.auth.AuthScheme;
import org.apache.lens.api.session.UserSessionInfo;
import org.apache.lens.api.util.PathValidator;
import org.apache.lens.server.api.LensConfConstants;
@@ -73,6 +75,9 @@
@Slf4j
public abstract class BaseLensService extends CompositeService implements Externalizable, LensService,
SessionValidator {
+ public static final Configuration CONF = LensServerConf.getHiveConf();
+ public static final Optional<AuthScheme> AUTH_SCHEME =
+ AuthScheme.getFromString(CONF.get(LensConfConstants.AUTH_SCHEME));
/** The cli service. */
private final CLIService cliService;
@@ -161,7 +166,7 @@
*/
public LensSessionHandle openSession(String username, String password, Map<String, String> configuration)
throws LensException {
- return openSession(username, password, configuration, true);
+ return openSession(username, password, configuration, !AUTH_SCHEME.isPresent());
}
public LensSessionHandle openSession(String username, String password, Map<String, String> configuration,
@@ -343,9 +348,9 @@
} else {
SESSIONS_PER_USER.put(userName, --sessionCount);
}
- }else {
+ } else {
log.info("Trying to decrement session count for non existing session {} for user {}: ",
- sessionHandle, userName);
+ sessionHandle, userName);
}
}
}
diff --git a/lens-server/src/main/java/org/apache/lens/server/LensApplication.java b/lens-server/src/main/java/org/apache/lens/server/LensApplication.java
index c3f9952..1e26a9e 100644
--- a/lens-server/src/main/java/org/apache/lens/server/LensApplication.java
+++ b/lens-server/src/main/java/org/apache/lens/server/LensApplication.java
@@ -26,6 +26,7 @@
import org.apache.lens.server.api.LensConfConstants;
import org.apache.lens.server.error.GenericExceptionMapper;
+import org.apache.lens.server.error.NotAuthorizedExceptionMapper;
import org.apache.hadoop.conf.Configuration;
@@ -67,6 +68,7 @@
classes.add(wsListenerClass);
log.info("Added listener {}", wsListenerClass);
}
+
for (String filterName : filterNames) {
Class wsFilterClass = CONF.getClass(LensConfConstants.getWSFilterImplConfKey(filterName), null);
classes.add(wsFilterClass);
@@ -74,6 +76,7 @@
}
classes.add(GenericExceptionMapper.class);
+ classes.add(NotAuthorizedExceptionMapper.class);
return classes;
}
diff --git a/lens-server/src/main/java/org/apache/lens/server/LogResource.java b/lens-server/src/main/java/org/apache/lens/server/LogResource.java
index d6d3348..38e33ce 100644
--- a/lens-server/src/main/java/org/apache/lens/server/LogResource.java
+++ b/lens-server/src/main/java/org/apache/lens/server/LogResource.java
@@ -32,11 +32,13 @@
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.StreamingOutput;
+import org.apache.lens.server.auth.Authenticate;
import org.apache.lens.server.util.UtilityMethods;
/**
* The logs resource
*/
+@Authenticate
@Path("/logs")
public class LogResource {
diff --git a/lens-server/src/main/java/org/apache/lens/server/metastore/MetastoreResource.java b/lens-server/src/main/java/org/apache/lens/server/metastore/MetastoreResource.java
index 8818844..f7b3427 100644
--- a/lens-server/src/main/java/org/apache/lens/server/metastore/MetastoreResource.java
+++ b/lens-server/src/main/java/org/apache/lens/server/metastore/MetastoreResource.java
@@ -42,12 +42,14 @@
import org.apache.hadoop.hive.ql.metadata.HiveException;
import lombok.extern.slf4j.Slf4j;
+import org.apache.lens.server.auth.Authenticate;
-/**
+ /**
* metastore resource api
* <p> </p>
* This provides api for all things metastore.
*/
+@Authenticate
@Path("metastore")
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
@Slf4j
diff --git a/lens-server/src/main/java/org/apache/lens/server/query/QueryServiceResource.java b/lens-server/src/main/java/org/apache/lens/server/query/QueryServiceResource.java
index d53fbbf..d00087a 100644
--- a/lens-server/src/main/java/org/apache/lens/server/query/QueryServiceResource.java
+++ b/lens-server/src/main/java/org/apache/lens/server/query/QueryServiceResource.java
@@ -39,6 +39,7 @@
import org.apache.lens.server.api.error.LensException;
import org.apache.lens.server.api.query.QueryExecutionService;
import org.apache.lens.server.api.query.cost.QueryCostTOBuilder;
+import org.apache.lens.server.auth.Authenticate;
import org.apache.lens.server.error.UnSupportedOpException;
import org.apache.lens.server.model.LogSegregationContext;
import org.apache.lens.server.util.UtilityMethods;
@@ -54,6 +55,7 @@
* <p></p>
* This provides api for all things query.
*/
+@Authenticate
@Slf4j
@Path("/queryapi")
public class QueryServiceResource {
diff --git a/lens-server/src/main/java/org/apache/lens/server/query/save/SavedQueryResource.java b/lens-server/src/main/java/org/apache/lens/server/query/save/SavedQueryResource.java
index 72748cd..961884b 100644
--- a/lens-server/src/main/java/org/apache/lens/server/query/save/SavedQueryResource.java
+++ b/lens-server/src/main/java/org/apache/lens/server/query/save/SavedQueryResource.java
@@ -45,6 +45,7 @@
import org.apache.lens.server.api.query.save.*;
import org.apache.lens.server.api.query.save.param.ParameterParser;
import org.apache.lens.server.api.query.save.param.ParameterResolver;
+import org.apache.lens.server.auth.Authenticate;
import org.apache.lens.server.model.LogSegregationContext;
import org.apache.hadoop.hive.conf.HiveConf;
@@ -53,6 +54,7 @@
import org.glassfish.jersey.media.multipart.FormDataParam;
+@Authenticate
@Path("/queryapi")
/**
* Saved query resource
diff --git a/lens-server/src/main/java/org/apache/lens/server/quota/QuotaResource.java b/lens-server/src/main/java/org/apache/lens/server/quota/QuotaResource.java
index 2130191..18d2b20 100644
--- a/lens-server/src/main/java/org/apache/lens/server/quota/QuotaResource.java
+++ b/lens-server/src/main/java/org/apache/lens/server/quota/QuotaResource.java
@@ -18,6 +18,8 @@
*/
package org.apache.lens.server.quota;
+import org.apache.lens.server.auth.Authenticate;
+
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
@@ -26,6 +28,7 @@
/**
* The Class QuotaResource.
*/
+@Authenticate
@Path("/quota")
public class QuotaResource {
diff --git a/lens-server/src/main/java/org/apache/lens/server/scheduler/ScheduleResource.java b/lens-server/src/main/java/org/apache/lens/server/scheduler/ScheduleResource.java
index 6c8b8ad..1d5959b 100644
--- a/lens-server/src/main/java/org/apache/lens/server/scheduler/ScheduleResource.java
+++ b/lens-server/src/main/java/org/apache/lens/server/scheduler/ScheduleResource.java
@@ -30,6 +30,7 @@
import org.apache.lens.server.LensServices;
import org.apache.lens.server.api.error.LensException;
import org.apache.lens.server.api.scheduler.SchedulerService;
+import org.apache.lens.server.auth.Authenticate;
import org.apache.lens.server.error.UnSupportedOpException;
import org.apache.lens.server.model.LogSegregationContext;
import org.apache.lens.server.util.UtilityMethods;
@@ -37,6 +38,7 @@
/**
* REST end point for all scheduler operations.
*/
+@Authenticate
@Path("scheduler")
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public class ScheduleResource {
diff --git a/lens-server/src/main/java/org/apache/lens/server/session/LensSessionImpl.java b/lens-server/src/main/java/org/apache/lens/server/session/LensSessionImpl.java
index 08a5cff..7d81375 100644
--- a/lens-server/src/main/java/org/apache/lens/server/session/LensSessionImpl.java
+++ b/lens-server/src/main/java/org/apache/lens/server/session/LensSessionImpl.java
@@ -115,6 +115,7 @@
persistInfo.setPassword(getPassword());
persistInfo.setLastAccessTime(System.currentTimeMillis());
persistInfo.setSessionConf(sessionConf);
+ persistInfo.setProxyUser(sessionConf.get(LensConfConstants.SESSION_PROXY_USER));
if (sessionConf != null) {
for (Map.Entry<String, String> entry : sessionConf.entrySet()) {
conf.set(entry.getKey(), entry.getValue());
@@ -637,6 +638,9 @@
/** Whether it's marked for close */
private boolean markedForClose;
+ /** The proxy user which is initiating the request. This could be null */
+ private String proxyUser;
+
public void setSessionConf(Map<String, String> sessionConf) {
UtilityMethods.mergeMaps(config, sessionConf, true);
}
@@ -666,6 +670,7 @@
}
out.writeLong(lastAccessTime);
out.writeBoolean(markedForClose);
+ out.writeUTF(proxyUser == null ? "" : proxyUser);
}
/*
@@ -697,6 +702,7 @@
}
lastAccessTime = in.readLong();
markedForClose = in.readBoolean();
+ proxyUser = in.readUTF();
}
}
diff --git a/lens-server/src/main/java/org/apache/lens/server/session/SessionResource.java b/lens-server/src/main/java/org/apache/lens/server/session/SessionResource.java
index 63eea63..dfecba5 100644
--- a/lens-server/src/main/java/org/apache/lens/server/session/SessionResource.java
+++ b/lens-server/src/main/java/org/apache/lens/server/session/SessionResource.java
@@ -18,24 +18,47 @@
*/
package org.apache.lens.server.session;
+import java.security.Principal;
+import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
-import javax.ws.rs.*;
+
+import javax.ws.rs.BadRequestException;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.SecurityContext;
import org.apache.lens.api.APIResult;
import org.apache.lens.api.APIResult.Status;
import org.apache.lens.api.LensConf;
import org.apache.lens.api.LensSessionHandle;
import org.apache.lens.api.StringList;
+import org.apache.lens.api.auth.AuthScheme;
import org.apache.lens.api.session.UserSessionInfo;
+import org.apache.lens.server.LensServerConf;
import org.apache.lens.server.LensServices;
+import org.apache.lens.server.api.LensConfConstants;
import org.apache.lens.server.api.error.LensException;
import org.apache.lens.server.api.session.SessionService;
+import org.apache.lens.server.auth.Authenticate;
import org.apache.lens.server.util.ScannedPaths;
+import org.apache.commons.lang.StringUtils;
+import org.apache.hadoop.conf.Configuration;
+
import org.glassfish.jersey.media.multipart.FormDataParam;
import lombok.extern.slf4j.Slf4j;
@@ -45,13 +68,20 @@
* <p></p>
* This provides api for all things in session.
*/
+@Authenticate
@Path("session")
@Slf4j
public class SessionResource {
+ public static final Configuration CONF = LensServerConf.getHiveConf();
+ public static final Optional<AuthScheme> AUTH_SCHEME =
+ AuthScheme.getFromString(CONF.get(LensConfConstants.AUTH_SCHEME));
/** The session service. */
private SessionService sessionService;
+ @Context
+ private SecurityContext securityContext;
+
/**
* API to know if session service is up and running
*
@@ -94,7 +124,24 @@
} else {
conf = new HashMap();
}
- return sessionService.openSession(username, password, database, conf);
+
+ if (AUTH_SCHEME.isPresent()) {
+ Principal userPrincipal = securityContext.getUserPrincipal();
+ String userPrincipalName = userPrincipal.getName();
+ Collection<String> allowedProxyUsers = CONF.getTrimmedStringCollection(LensConfConstants.ALLOWED_PROXY_USERS);
+ if (allowedProxyUsers.contains(userPrincipalName)) {
+ String loggedInUser = conf.get(LensConfConstants.SESSION_LOGGEDIN_USER);
+ if (StringUtils.isBlank(loggedInUser)) {
+ throw new BadRequestException(LensConfConstants.SESSION_LOGGEDIN_USER + " is required in sessionconf");
+ }
+ username = loggedInUser;
+ conf.put(LensConfConstants.SESSION_PROXY_USER, userPrincipalName);
+ } else {
+ username = userPrincipalName;
+ }
+ password = "";
+ }
+ return sessionService.openSession(username, password, database, conf);
}
/**