/*
* 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.ranger.security.web.filter;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.EventListener;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.FilterRegistration;
import javax.servlet.RequestDispatcher;
import javax.servlet.Servlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.SessionCookieConfig;
import javax.servlet.SessionTrackingMode;
import javax.servlet.FilterRegistration.Dynamic;
import javax.servlet.descriptor.JspConfigDescriptor;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.collections.iterators.IteratorEnumeration;
import org.apache.ranger.biz.UserMgr;
import org.apache.ranger.common.PropertiesUtil;
import org.apache.ranger.common.RESTErrorUtil;
import org.apache.ranger.security.handler.RangerAuthenticationProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.apache.commons.lang.StringUtils;
import org.apache.hadoop.security.SecureClientLogin;
import org.apache.hadoop.security.authentication.util.KerberosName;
import org.springframework.security.web.authentication.WebAuthenticationDetails;
public class RangerKRBAuthenticationFilter extends RangerKrbFilter {
private static final Logger LOG = LoggerFactory.getLogger(RangerKRBAuthenticationFilter.class);
@Autowired
UserMgr userMgr;
@Autowired
RESTErrorUtil restErrorUtil;
static final String NAME_RULES = "hadoop.security.auth_to_local";
static final String TOKEN_VALID = "ranger.admin.kerberos.token.valid.seconds";
static final String COOKIE_DOMAIN = "ranger.admin.kerberos.cookie.domain";
static final String COOKIE_PATH = "ranger.admin.kerberos.cookie.path";
static final String PRINCIPAL = "ranger.spnego.kerberos.principal";
static final String KEYTAB = "ranger.spnego.kerberos.keytab";
static final String NAME_RULES_PARAM = "kerberos.name.rules";
static final String TOKEN_VALID_PARAM = "token.validity";
static final String COOKIE_DOMAIN_PARAM = "cookie.domain";
static final String COOKIE_PATH_PARAM = "cookie.path";
static final String PRINCIPAL_PARAM = "kerberos.principal";
static final String KEYTAB_PARAM = "kerberos.keytab";
static final String AUTH_TYPE = "type";
static final String RANGER_AUTH_TYPE = "hadoop.security.authentication";
static final String AUTH_COOKIE_NAME = "hadoop.auth";
static final String HOST_NAME = "ranger.service.host";
private static final String KERBEROS_TYPE = "kerberos";
private static final String S_USER = "suser";
public RangerKRBAuthenticationFilter() {
try {
init(null);
} catch (ServletException e) {
LOG.error("Error while initializing Filter : "+e.getMessage());
}
}
@Override
public void init(FilterConfig conf) throws ServletException {
final FilterConfig globalConf = conf;
final Map<String, String> params = new HashMap<String, String>();
params.put(AUTH_TYPE, PropertiesUtil.getProperty(RANGER_AUTH_TYPE, "simple"));
params.put(NAME_RULES_PARAM, PropertiesUtil.getProperty(NAME_RULES, "DEFAULT"));
params.put(TOKEN_VALID_PARAM, PropertiesUtil.getProperty(TOKEN_VALID,"30"));
params.put(COOKIE_DOMAIN_PARAM, PropertiesUtil.getProperty(COOKIE_DOMAIN, PropertiesUtil.getProperty(HOST_NAME, "localhost")));
params.put(COOKIE_PATH_PARAM, PropertiesUtil.getProperty(COOKIE_PATH, "/"));
try {
params.put(PRINCIPAL_PARAM, SecureClientLogin.getPrincipal(PropertiesUtil.getProperty(PRINCIPAL,""), PropertiesUtil.getProperty(HOST_NAME)));
} catch (IOException ignored) {
// do nothing
}
params.put(KEYTAB_PARAM, PropertiesUtil.getProperty(KEYTAB,""));
FilterConfig myConf = new FilterConfig() {
@Override
public ServletContext getServletContext() {
if (globalConf != null) {
return globalConf.getServletContext();
} else {
return noContext;
}
}
@SuppressWarnings("unchecked")
@Override
public Enumeration<String> getInitParameterNames() {
return new IteratorEnumeration(params.keySet().iterator());
}
@Override
public String getInitParameter(String param) {
return params.get(param);
}
@Override
public String getFilterName() {
return "KerberosFilter";
}
};
super.init(myConf);
}
@Override
protected void doFilter(FilterChain filterChain,
HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
String authType = PropertiesUtil.getProperty(RANGER_AUTH_TYPE);
String userName = null;
boolean checkCookie = response.containsHeader("Set-Cookie");
if(checkCookie){
Collection<String> authUserName = response.getHeaders("Set-Cookie");
if(authUserName != null){
Iterator<String> i = authUserName.iterator();
while(i.hasNext()){
String cookie = i.next();
if(!StringUtils.isEmpty(cookie)){
if(cookie.toLowerCase().startsWith(AUTH_COOKIE_NAME.toLowerCase()) && cookie.contains("u=")){
String[] split = cookie.split(";");
if(split != null){
for(String s : split){
if(!StringUtils.isEmpty(s) && s.toLowerCase().startsWith(AUTH_COOKIE_NAME.toLowerCase())){
int ustr = s.indexOf("u=");
if(ustr != -1){
int andStr = s.indexOf("&", ustr);
if(andStr != -1){
try{
userName = s.substring(ustr+2, andStr);
}catch(Exception e){
userName = null;
}
}
}
}
}
}
}
}
}
}
}
String sessionUserName = request.getParameter(S_USER);
String pathInfo = request.getPathInfo();
if(!StringUtils.isEmpty(sessionUserName) && "keyadmin".equalsIgnoreCase(sessionUserName) && !StringUtils.isEmpty(pathInfo) && pathInfo.contains("public/v2/api/service")){
LOG.info("Session will be created by : "+sessionUserName);
userName = sessionUserName;
}
if((isSpnegoEnable(authType) && (!StringUtils.isEmpty(userName)))){
Authentication existingAuth = SecurityContextHolder.getContext().getAuthentication();
if(existingAuth == null || !existingAuth.isAuthenticated()){
//--------------------------- To Create Ranger Session --------------------------------------
String rangerLdapDefaultRole = PropertiesUtil.getProperty("ranger.ldap.default.role", "ROLE_USER");
//if we get the userName from the token then log into ranger using the same user
final List<GrantedAuthority> grantedAuths = new ArrayList<>();
grantedAuths.add(new SimpleGrantedAuthority(rangerLdapDefaultRole));
final UserDetails principal = new User(userName, "",grantedAuths);
final Authentication finalAuthentication = new UsernamePasswordAuthenticationToken(principal, "", grantedAuths);
WebAuthenticationDetails webDetails = new WebAuthenticationDetails(request);
((AbstractAuthenticationToken) finalAuthentication).setDetails(webDetails);
RangerAuthenticationProvider authenticationProvider = new RangerAuthenticationProvider();
Authentication authentication = authenticationProvider.authenticate(finalAuthentication);
authentication = getGrantedAuthority(authentication);
SecurityContextHolder.getContext().setAuthentication(authentication);
request.setAttribute("spnegoEnabled", true);
LOG.info("Logged into Ranger as = "+userName);
filterChain.doFilter(request, response);
}else{
try{
super.doFilter(filterChain, request, response);
}catch(Exception e){
throw restErrorUtil.createRESTException("RangerKRBAuthenticationFilter Failed : "+e.getMessage());
}
}
}else{
filterChain.doFilter(request, response);
}
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain filterChain) throws IOException, ServletException {
String authtype = PropertiesUtil.getProperty(RANGER_AUTH_TYPE);
HttpServletRequest httpRequest = (HttpServletRequest)request;
if(isSpnegoEnable(authtype)){
KerberosName.setRules(PropertiesUtil.getProperty(NAME_RULES, "DEFAULT"));
Authentication existingAuth = SecurityContextHolder.getContext().getAuthentication();
String userName = null;
Cookie[] cookie = httpRequest.getCookies();
if(cookie != null){
for(Cookie c : cookie){
String cname = c.getName();
if(cname != null && "u".equalsIgnoreCase(cname))
{
int ustr = cname.indexOf("u=");
if(ustr != -1){
int andStr = cname.indexOf("&", ustr);
if(andStr != -1){
userName = cname.substring(ustr+2, andStr);
}
}
}else if(cname != null && AUTH_COOKIE_NAME.equalsIgnoreCase(cname)){
int ustr = cname.indexOf("u=");
if(ustr != -1){
int andStr = cname.indexOf("&", ustr);
if(andStr != -1){
userName = cname.substring(ustr+2, andStr);
}
}
}
}
}
if((existingAuth == null || !existingAuth.isAuthenticated()) && (!StringUtils.isEmpty(userName))){
//--------------------------- To Create Ranger Session --------------------------------------
String rangerLdapDefaultRole = PropertiesUtil.getProperty("ranger.ldap.default.role", "ROLE_USER");
//if we get the userName from the token then log into ranger using the same user
final List<GrantedAuthority> grantedAuths = new ArrayList<>();
grantedAuths.add(new SimpleGrantedAuthority(rangerLdapDefaultRole));
final UserDetails principal = new User(userName, "",grantedAuths);
final Authentication finalAuthentication = new UsernamePasswordAuthenticationToken(principal, "", grantedAuths);
WebAuthenticationDetails webDetails = new WebAuthenticationDetails(httpRequest);
((AbstractAuthenticationToken) finalAuthentication).setDetails(webDetails);
RangerAuthenticationProvider authenticationProvider = new RangerAuthenticationProvider();
Authentication authentication = authenticationProvider.authenticate(finalAuthentication);
authentication = getGrantedAuthority(authentication);
SecurityContextHolder.getContext().setAuthentication(authentication);
request.setAttribute("spnegoEnabled", true);
LOG.info("Logged into Ranger as = "+userName);
}else{
try{
super.doFilter(request, response, filterChain);
}catch(Exception e){
throw restErrorUtil.createRESTException("RangerKRBAuthenticationFilter Failed : "+e.getMessage());
}
}
}else{
filterChain.doFilter(request, response);
}
}
private boolean isSpnegoEnable(String authType){
String principal = PropertiesUtil.getProperty(PRINCIPAL);
String keytabPath = PropertiesUtil.getProperty(KEYTAB);
return ((!StringUtils.isEmpty(authType)) && KERBEROS_TYPE.equalsIgnoreCase(authType) && SecureClientLogin.isKerberosCredentialExists(principal, keytabPath));
}
private Authentication getGrantedAuthority(Authentication authentication) {
UsernamePasswordAuthenticationToken result=null;
if(authentication!=null && authentication.isAuthenticated()){
final List<GrantedAuthority> grantedAuths=getAuthorities(authentication.getName().toString());
final UserDetails userDetails = new User(authentication.getName().toString(), authentication.getCredentials().toString(),grantedAuths);
result = new UsernamePasswordAuthenticationToken(userDetails,authentication.getCredentials(),grantedAuths);
result.setDetails(authentication.getDetails());
return result;
}
return authentication;
}
private List<GrantedAuthority> getAuthorities(String username) {
Collection<String> roleList=userMgr.getRolesByLoginId(username);
final List<GrantedAuthority> grantedAuths = new ArrayList<>();
for(String role:roleList){
grantedAuths.add(new SimpleGrantedAuthority(role));
}
return grantedAuths;
}
protected static ServletContext noContext = new ServletContext() {
@Override
public void setSessionTrackingModes(
Set<SessionTrackingMode> sessionTrackingModes) {
}
@Override
public boolean setInitParameter(String name, String value) {
return false;
}
@Override
public void setAttribute(String name, Object object) {
}
@Override
public void removeAttribute(String name) {
}
@Override
public void log(String message, Throwable throwable) {
}
@Override
public void log(Exception exception, String msg) {
}
@Override
public void log(String msg) {
}
@Override
public String getVirtualServerName() {
return null;
}
@Override
public SessionCookieConfig getSessionCookieConfig() {
return null;
}
@Override
public Enumeration<Servlet> getServlets() {
return null;
}
@Override
public Map<String, ? extends ServletRegistration> getServletRegistrations() {
return null;
}
@Override
public ServletRegistration getServletRegistration(String servletName) {
return null;
}
@Override
public Enumeration<String> getServletNames() {
return null;
}
@Override
public String getServletContextName() {
return null;
}
@Override
public Servlet getServlet(String name) throws ServletException {
return null;
}
@Override
public String getServerInfo() {
return null;
}
@Override
public Set<String> getResourcePaths(String path) {
return null;
}
@Override
public InputStream getResourceAsStream(String path) {
return null;
}
@Override
public URL getResource(String path) throws MalformedURLException {
return null;
}
@Override
public RequestDispatcher getRequestDispatcher(String path) {
return null;
}
@Override
public String getRealPath(String path) {
return null;
}
@Override
public RequestDispatcher getNamedDispatcher(String name) {
return null;
}
@Override
public int getMinorVersion() {
return 0;
}
@Override
public String getMimeType(String file) {
return null;
}
@Override
public int getMajorVersion() {
return 0;
}
@Override
public JspConfigDescriptor getJspConfigDescriptor() {
return null;
}
@Override
public Enumeration<String> getInitParameterNames() {
return null;
}
@Override
public String getInitParameter(String name) {
return null;
}
@Override
public Map<String, ? extends FilterRegistration> getFilterRegistrations() {
return null;
}
@Override
public FilterRegistration getFilterRegistration(String filterName) {
return null;
}
@Override
public Set<SessionTrackingMode> getEffectiveSessionTrackingModes() {
return null;
}
@Override
public int getEffectiveMinorVersion() {
return 0;
}
@Override
public int getEffectiveMajorVersion() {
return 0;
}
@Override
public Set<SessionTrackingMode> getDefaultSessionTrackingModes() {
return null;
}
@Override
public String getContextPath() {
return null;
}
@Override
public ServletContext getContext(String uripath) {
return null;
}
@Override
public ClassLoader getClassLoader() {
return null;
}
@Override
public Enumeration<String> getAttributeNames() {
return null;
}
@Override
public Object getAttribute(String name) {
return null;
}
@Override
public void declareRoles(String... roleNames) {
}
@Override
public <T extends Servlet> T createServlet(Class<T> clazz)
throws ServletException {
return null;
}
@Override
public <T extends EventListener> T createListener(Class<T> clazz)
throws ServletException {
return null;
}
@Override
public <T extends Filter> T createFilter(Class<T> clazz)
throws ServletException {
return null;
}
@Override
public javax.servlet.ServletRegistration.Dynamic addServlet(
String servletName, Class<? extends Servlet> servletClass) {
return null;
}
@Override
public javax.servlet.ServletRegistration.Dynamic addServlet(
String servletName, Servlet servlet) {
return null;
}
@Override
public javax.servlet.ServletRegistration.Dynamic addServlet(
String servletName, String className) {
return null;
}
@Override
public void addListener(Class<? extends EventListener> listenerClass) {
}
@Override
public <T extends EventListener> void addListener(T t) {
}
@Override
public void addListener(String className) {
}
@Override
public Dynamic addFilter(String filterName,
Class<? extends Filter> filterClass) {
return null;
}
@Override
public Dynamic addFilter(String filterName, Filter filter) {
return null;
}
@Override
public Dynamic addFilter(String filterName, String className) {
return null;
}
};
}