org.apache.solr.security.KerberosPlugin.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.solr.security.KerberosPlugin.java

Source

/*
 * 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.io.PrintWriter;
import java.lang.invoke.MethodHandles;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

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.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;

import com.google.common.annotations.VisibleForTesting;
import org.apache.commons.collections.iterators.IteratorEnumeration;
import org.apache.solr.client.solrj.impl.Krb5HttpClientBuilder;
import org.apache.solr.client.solrj.impl.SolrHttpClientBuilder;
import org.apache.solr.cloud.ZkController;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrException.ErrorCode;
import org.apache.solr.common.cloud.SecurityAwareZkACLProvider;
import org.apache.solr.common.util.SuppressForbidden;
import org.apache.solr.core.CoreContainer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class KerberosPlugin extends AuthenticationPlugin implements HttpClientBuilderPlugin {
    private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

    Krb5HttpClientBuilder kerberosBuilder = new Krb5HttpClientBuilder();
    private Filter kerberosFilter;

    public static final String NAME_RULES_PARAM = "solr.kerberos.name.rules";
    public static final String COOKIE_DOMAIN_PARAM = "solr.kerberos.cookie.domain";
    public static final String COOKIE_PATH_PARAM = "solr.kerberos.cookie.path";
    public static final String PRINCIPAL_PARAM = "solr.kerberos.principal";
    public static final String KEYTAB_PARAM = "solr.kerberos.keytab";
    public static final String TOKEN_VALID_PARAM = "solr.kerberos.token.valid";
    public static final String COOKIE_PORT_AWARE_PARAM = "solr.kerberos.cookie.portaware";
    public static final String IMPERSONATOR_PREFIX = "solr.kerberos.impersonator.user.";
    public static final String DELEGATION_TOKEN_ENABLED = "solr.kerberos.delegation.token.enabled";
    public static final String DELEGATION_TOKEN_KIND = "solr.kerberos.delegation.token.kind";
    public static final String DELEGATION_TOKEN_VALIDITY = "solr.kerberos.delegation.token.validity";
    public static final String DELEGATION_TOKEN_SECRET_PROVIDER = "solr.kerberos.delegation.token.signer.secret.provider";
    public static final String DELEGATION_TOKEN_SECRET_PROVIDER_ZK_PATH = "solr.kerberos.delegation.token.signer.secret.provider.zookeper.path";
    public static final String DELEGATION_TOKEN_SECRET_MANAGER_ZNODE_WORKING_PATH = "solr.kerberos.delegation.token.secret.manager.znode.working.path";

    public static final String DELEGATION_TOKEN_TYPE_DEFAULT = "solr-dt";
    public static final String IMPERSONATOR_DO_AS_HTTP_PARAM = "doAs";
    public static final String IMPERSONATOR_USER_NAME = "solr.impersonator.user.name";

    static final String DELEGATION_TOKEN_ZK_CLIENT = "solr.kerberos.delegation.token.zk.client";

    private final CoreContainer coreContainer;

    public KerberosPlugin(CoreContainer coreContainer) {
        this.coreContainer = coreContainer;
    }

    @Override
    public void init(Map<String, Object> pluginConfig) {
        try {
            FilterConfig conf = getInitFilterConfig(pluginConfig, false);
            kerberosFilter.init(conf);
        } catch (ServletException e) {
            throw new SolrException(ErrorCode.SERVER_ERROR,
                    "Error initializing kerberos authentication plugin: " + e);
        }
    }

    @VisibleForTesting
    protected FilterConfig getInitFilterConfig(Map<String, Object> pluginConfig, boolean skipKerberosChecking) {
        Map<String, String> params = new HashMap();
        params.put("type", "kerberos");
        putParam(params, "kerberos.name.rules", NAME_RULES_PARAM, "DEFAULT");
        putParam(params, "token.valid", TOKEN_VALID_PARAM, "30");
        putParam(params, "cookie.path", COOKIE_PATH_PARAM, "/");
        if (!skipKerberosChecking) {
            putParam(params, "kerberos.principal", PRINCIPAL_PARAM, null);
            putParam(params, "kerberos.keytab", KEYTAB_PARAM, null);
        } else {
            putParamOptional(params, "kerberos.principal", PRINCIPAL_PARAM);
            putParamOptional(params, "kerberos.keytab", KEYTAB_PARAM);
        }

        String delegationTokenStr = System.getProperty(DELEGATION_TOKEN_ENABLED, null);
        boolean delegationTokenEnabled = (delegationTokenStr == null) ? false
                : Boolean.parseBoolean(delegationTokenStr);
        ZkController controller = coreContainer.getZkController();

        if (delegationTokenEnabled) {
            putParam(params, "delegation-token.token-kind", DELEGATION_TOKEN_KIND, DELEGATION_TOKEN_TYPE_DEFAULT);
            if (coreContainer.isZooKeeperAware()) {
                putParam(params, "signer.secret.provider", DELEGATION_TOKEN_SECRET_PROVIDER, "zookeeper");
                if ("zookeeper".equals(params.get("signer.secret.provider"))) {
                    String zkHost = controller.getZkServerAddress();
                    putParam(params, "token.validity", DELEGATION_TOKEN_VALIDITY, "36000");
                    params.put("zk-dt-secret-manager.enable", "true");

                    String chrootPath = zkHost.contains("/") ? zkHost.substring(zkHost.indexOf("/")) : "";
                    String znodeWorkingPath = chrootPath + SecurityAwareZkACLProvider.SECURITY_ZNODE_PATH
                            + "/zkdtsm";
                    // Note - Curator complains if the znodeWorkingPath starts with /
                    znodeWorkingPath = znodeWorkingPath.startsWith("/") ? znodeWorkingPath.substring(1)
                            : znodeWorkingPath;
                    putParam(params, "zk-dt-secret-manager.znodeWorkingPath",
                            DELEGATION_TOKEN_SECRET_MANAGER_ZNODE_WORKING_PATH, znodeWorkingPath);
                    putParam(params, "signer.secret.provider.zookeeper.path",
                            DELEGATION_TOKEN_SECRET_PROVIDER_ZK_PATH, "/token");
                    // ensure krb5 is setup properly before running curator
                    getHttpClientBuilder(SolrHttpClientBuilder.create());
                }
            } else {
                log.info("CoreContainer is not ZooKeeperAware, not setting ZK-related delegation token properties");
            }
        }

        // Special handling for the "cookie.domain" based on whether port should be
        // appended to the domain. Useful for situations where multiple solr nodes are
        // on the same host.
        String usePortStr = System.getProperty(COOKIE_PORT_AWARE_PARAM, null);
        boolean needPortAwareCookies = (usePortStr == null) ? false : Boolean.parseBoolean(usePortStr);

        if (!needPortAwareCookies || !coreContainer.isZooKeeperAware()) {
            putParam(params, "cookie.domain", COOKIE_DOMAIN_PARAM, null);
        } else { // we need port aware cookies and we are in SolrCloud mode.
            String host = System.getProperty(COOKIE_DOMAIN_PARAM, null);
            if (host == null) {
                throw new SolrException(ErrorCode.SERVER_ERROR,
                        "Missing required parameter '" + COOKIE_DOMAIN_PARAM + "'.");
            }
            int port = controller.getHostPort();
            params.put("cookie.domain", host + ":" + port);
        }

        // check impersonator config
        for (Enumeration e = System.getProperties().propertyNames(); e.hasMoreElements();) {
            String key = e.nextElement().toString();
            if (key.startsWith(IMPERSONATOR_PREFIX)) {
                if (!delegationTokenEnabled) {
                    throw new SolrException(ErrorCode.SERVER_ERROR,
                            "Impersonator configuration requires delegation tokens to be enabled: " + key);
                }
                params.put(key, System.getProperty(key));
            }
        }
        final ServletContext servletContext = new AttributeOnlyServletContext();
        if (controller != null) {
            servletContext.setAttribute(DELEGATION_TOKEN_ZK_CLIENT, controller.getZkClient());
        }
        if (delegationTokenEnabled) {
            kerberosFilter = new DelegationTokenKerberosFilter();
            // pass an attribute-enabled context in order to pass the zkClient
            // and because the filter may pass a curator instance.
        } else {
            kerberosFilter = new KerberosFilter();
        }
        log.info("Params: " + params);

        FilterConfig conf = new FilterConfig() {
            @Override
            public ServletContext getServletContext() {
                return servletContext;
            }

            @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";
            }
        };

        return conf;
    }

    private void putParam(Map<String, String> params, String internalParamName, String externalParamName,
            String defaultValue) {
        String value = System.getProperty(externalParamName, defaultValue);
        if (value == null) {
            throw new SolrException(ErrorCode.SERVER_ERROR,
                    "Missing required parameter '" + externalParamName + "'.");
        }
        params.put(internalParamName, value);
    }

    private void putParamOptional(Map<String, String> params, String internalParamName, String externalParamName) {
        String value = System.getProperty(externalParamName);
        if (value != null) {
            params.put(internalParamName, value);
        }
    }

    @Override
    public boolean doAuthenticate(ServletRequest req, ServletResponse rsp, FilterChain chain) throws Exception {
        log.debug("Request to authenticate using kerberos: " + req);

        final HttpServletResponse frsp = (HttpServletResponse) rsp;

        // kerberosFilter may close the stream and write to closed streams,
        // see HADOOP-13346.  To work around, pass a PrintWriter that ignores
        // closes
        HttpServletResponse rspCloseShield = new HttpServletResponseWrapper(frsp) {
            @SuppressForbidden(reason = "Hadoop DelegationTokenAuthenticationFilter uses response writer, this"
                    + "is providing a CloseShield on top of that")
            @Override
            public PrintWriter getWriter() throws IOException {
                final PrintWriter pw = new PrintWriterWrapper(frsp.getWriter()) {
                    @Override
                    public void close() {
                    };
                };
                return pw;
            }
        };
        kerberosFilter.doFilter(req, rspCloseShield, chain);
        String requestContinuesAttr = (String) req
                .getAttribute(RequestContinuesRecorderAuthenticationHandler.REQUEST_CONTINUES_ATTR);
        if (requestContinuesAttr == null) {
            log.warn("Could not find " + RequestContinuesRecorderAuthenticationHandler.REQUEST_CONTINUES_ATTR);
            return false;
        } else {
            return Boolean.parseBoolean(requestContinuesAttr);
        }
    }

    @Override
    public SolrHttpClientBuilder getHttpClientBuilder(SolrHttpClientBuilder builder) {
        return kerberosBuilder.getBuilder(builder);
    }

    @Override
    public void close() {
        kerberosFilter.destroy();
        kerberosBuilder.close();
    }

    protected Filter getKerberosFilter() {
        return kerberosFilter;
    }

    protected void setKerberosFilter(Filter kerberosFilter) {
        this.kerberosFilter = kerberosFilter;
    }
}