blob: 30badf1e47c4415456d82210152b08b40e593c5a [file] [log] [blame]
/*
* 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.Optional;
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.Http2SolrClient;
import org.apache.solr.client.solrj.impl.HttpClientUtil;
import org.apache.solr.client.solrj.impl.HttpListenerFactory;
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;
import org.eclipse.jetty.client.api.Request;
/**
* 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 {
getPrincipal().ifPresent(usr -> 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);
}
}
private Optional<String> getPrincipal() {
SolrRequestInfo reqInfo = SolrRequestInfo.getRequestInfo();
String usr;
if (reqInfo != null) {
Principal principal = reqInfo.getUserPrincipal();
if (principal == null) {
//this had a request but not authenticated
//so we don't not need to set a principal
return Optional.empty();
} 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 Optional.empty();
}
//this request seems to be originated from Solr itself
usr = "$"; //special name to denote the user is the node itself
}
return Optional.of(usr);
}
@Override
public void setup(Http2SolrClient client) {
final HttpListenerFactory.RequestResponseListener listener = new HttpListenerFactory.RequestResponseListener() {
@Override
public void onQueued(Request request) {
getPrincipal().ifPresent(usr -> request.header(INTERNAL_REQUEST_HEADER, usr));
}
};
client.addListenerFactory(() -> listener);
}
@Override
public SolrHttpClientBuilder getHttpClientBuilder(SolrHttpClientBuilder builder) {
HttpClientUtil.addRequestInterceptor(interceptor);
builder = super.getHttpClientBuilder(builder);
return 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 (@SuppressWarnings({"rawtypes"})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);
}
}
}