/* * 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.solr.security; import java.io.IOException; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.security.Principal; import java.util.Enumeration; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Properties; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletContext; 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 org.apache.hadoop.security.authentication.client.AuthenticationException; import org.apache.hadoop.security.authentication.server.AuthenticationFilter; import org.apache.hadoop.security.authentication.server.AuthenticationHandler; import org.apache.hadoop.security.authentication.server.AuthenticationToken; import org.apache.hadoop.security.token.delegation.web.DelegationTokenAuthenticationHandler; import org.apache.http.HttpException; import org.apache.http.HttpRequest; import org.apache.http.HttpRequestInterceptor; import org.apache.http.NameValuePair; import org.apache.http.client.utils.URLEncodedUtils; import org.apache.http.protocol.HttpContext; import org.apache.solr.client.solrj.impl.HttpClientUtil; import org.apache.solr.client.solrj.impl.SolrHttpClientBuilder; import org.apache.solr.common.SolrException; import org.apache.solr.common.util.ExecutorUtil; import org.apache.solr.core.CoreContainer; import org.apache.solr.request.SolrRequestInfo; /** * AuthenticationHandler that supports delegation tokens and simple * authentication via the "user" http parameter */ public class HttpParamDelegationTokenPlugin extends KerberosPlugin { public static final String USER_PARAM = "user"; // http parameter for user authentication public static final String REMOTE_HOST_PARAM = "remoteHost"; // http parameter for indicating remote host public static final String REMOTE_ADDRESS_PARAM = "remoteAddress"; // http parameter for indicating remote address public static final String INTERNAL_REQUEST_HEADER = "internalRequest"; // http header for indicating internal request boolean isSolrThread() { return ExecutorUtil.isSolrServerThread(); } private final HttpRequestInterceptor interceptor = new HttpRequestInterceptor() { @Override public void process(HttpRequest httpRequest, HttpContext httpContext) throws HttpException, IOException { SolrRequestInfo reqInfo = SolrRequestInfo.getRequestInfo(); String usr; if (reqInfo != null) { Principal principal = reqInfo.getReq().getUserPrincipal(); if (principal == null) { //this had a request but not authenticated //so we don't not need to set a principal return; } else { usr = principal.getName(); } } else { if (!isSolrThread()) { //if this is not running inside a Solr threadpool (as in testcases) // then no need to add any header return; } //this request seems to be originated from Solr itself usr = "$"; //special name to denote the user is the node itself } httpRequest.setHeader(INTERNAL_REQUEST_HEADER, usr); } }; public HttpParamDelegationTokenPlugin(CoreContainer coreContainer) { super(coreContainer); } @Override public void init(Map<String, Object> pluginConfig) { try { final FilterConfig initConf = getInitFilterConfig(pluginConfig, true); FilterConfig conf = new FilterConfig() { @Override public ServletContext getServletContext() { return initConf.getServletContext(); } @Override public Enumeration<String> getInitParameterNames() { return initConf.getInitParameterNames(); } @Override public String getInitParameter(String param) { if (AuthenticationFilter.AUTH_TYPE.equals(param)) { return HttpParamDelegationTokenAuthenticationHandler.class.getName(); } return initConf.getInitParameter(param); } @Override public String getFilterName() { return "HttpParamFilter"; } }; Filter kerberosFilter = new HttpParamToRequestFilter(); kerberosFilter.init(conf); setKerberosFilter(kerberosFilter); } catch (ServletException e) { throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Error initializing kerberos authentication plugin: "+e); } } @Override public SolrHttpClientBuilder getHttpClientBuilder(SolrHttpClientBuilder builder) { HttpClientUtil.addRequestInterceptor(interceptor); return super.getHttpClientBuilder(builder); } @Override public void close() { HttpClientUtil.removeRequestInterceptor(interceptor); super.close(); } private static String getHttpParam(HttpServletRequest request, String param) { List<NameValuePair> pairs = URLEncodedUtils.parse(request.getQueryString(), Charset.forName("UTF-8")); for (NameValuePair nvp : pairs) { if (param.equals(nvp.getName())) { return nvp.getValue(); } } return null; } public static class HttpParamDelegationTokenAuthenticationHandler extends DelegationTokenAuthenticationHandler { public HttpParamDelegationTokenAuthenticationHandler() { super(new HttpParamAuthenticationHandler()); } @Override public void init(Properties config) throws ServletException { Properties conf = new Properties(); for (Map.Entry entry : config.entrySet()) { conf.setProperty((String) entry.getKey(), (String) entry.getValue()); } conf.setProperty(TOKEN_KIND, KerberosPlugin.DELEGATION_TOKEN_TYPE_DEFAULT); super.init(conf); } private static class HttpParamAuthenticationHandler implements AuthenticationHandler { @Override public String getType() { return "dummy"; } @Override public void init(Properties config) throws ServletException { } @Override public void destroy() { } @Override public boolean managementOperation(AuthenticationToken token, HttpServletRequest request, HttpServletResponse response) throws IOException, AuthenticationException { return false; } @Override public AuthenticationToken authenticate(HttpServletRequest request, HttpServletResponse response) throws IOException, AuthenticationException { AuthenticationToken token = null; String userName = getHttpParam(request, USER_PARAM); if (userName == null) { //check if this is an internal request userName = request.getHeader(INTERNAL_REQUEST_HEADER); } if (userName != null) { return new AuthenticationToken(userName, userName, "test"); } else { response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.setHeader("WWW-Authenticate", "dummy"); } return token; } } } /** * Filter that converts http params to HttpServletRequest params */ private static class HttpParamToRequestFilter extends DelegationTokenKerberosFilter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { final HttpServletRequest httpRequest = (HttpServletRequest) request; final HttpServletRequestWrapper requestWrapper = new HttpServletRequestWrapper(httpRequest) { @Override public String getRemoteHost() { String param = getHttpParam(httpRequest, REMOTE_HOST_PARAM); return param != null ? param : httpRequest.getRemoteHost(); } @Override public String getRemoteAddr() { String param = getHttpParam(httpRequest, REMOTE_ADDRESS_PARAM); return param != null ? param : httpRequest.getRemoteAddr(); } }; super.doFilter(requestWrapper, response, chain); } @Override protected void doFilter(FilterChain filterChain, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { // remove the filter-specific authentication information, so it doesn't get accidentally forwarded. List<NameValuePair> newPairs = new LinkedList<NameValuePair>(); List<NameValuePair> pairs = URLEncodedUtils.parse(request.getQueryString(), Charset.forName("UTF-8")); for (NameValuePair nvp : pairs) { if (!USER_PARAM.equals(nvp.getName())) { newPairs.add(nvp); } else { request.setAttribute(USER_PARAM, nvp.getValue()); } } final String queryStringNoUser = URLEncodedUtils.format(newPairs, StandardCharsets.UTF_8); HttpServletRequest requestWrapper = new HttpServletRequestWrapper(request) { @Override public String getQueryString() { return queryStringNoUser; } }; super.doFilter(filterChain, requestWrapper, response); } } }