001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *      http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019package org.apache.oozie.servlet;
020
021import org.apache.hadoop.security.authentication.server.AuthenticationFilter;
022import org.apache.hadoop.conf.Configuration;
023import org.apache.oozie.service.Services;
024import org.apache.hadoop.security.SecurityUtil;
025
026import javax.servlet.FilterChain;
027import javax.servlet.FilterConfig;
028import javax.servlet.ServletException;
029import javax.servlet.ServletRequest;
030import javax.servlet.ServletResponse;
031import javax.servlet.http.HttpServlet;
032import javax.servlet.http.HttpServletRequest;
033import java.io.IOException;
034import java.util.Map;
035import java.util.Properties;
036import java.net.InetAddress;
037import java.net.UnknownHostException;
038import org.apache.oozie.service.JobsConcurrencyService;
039import org.apache.oozie.util.ZKUtils;
040
041/**
042 * Authentication filter that extends Hadoop-auth AuthenticationFilter to override
043 * the configuration loading.
044 */
045public class AuthFilter extends AuthenticationFilter {
046    public static final String OOZIE_PREFIX = "oozie.authentication.";
047    private static final String KERBEROS_PRINCIPAL_CONFIG = "kerberos.principal";
048
049    private HttpServlet optionsServlet;
050    private ZKUtils zkUtils = null;
051
052    /**
053     * Initialize the filter.
054     *
055     * @param filterConfig filter configuration.
056     * @throws ServletException thrown if the filter could not be initialized.
057     */
058    @Override
059    public void init(FilterConfig filterConfig) throws ServletException {
060        // If using HA, we'd like to use our Curator client with ZKSignerSecretProvider, so we have to pass it
061        if (Services.get().get(JobsConcurrencyService.class).isHighlyAvailableMode()) {
062            try {
063                zkUtils = ZKUtils.register(this);
064            } catch(Exception e) {
065                throw new ServletException(e);
066            }
067            filterConfig.getServletContext().setAttribute("signer.secret.provider.zookeeper.curator.client", zkUtils.getClient());
068        }
069        super.init(filterConfig);
070        optionsServlet = new HttpServlet() {};
071        optionsServlet.init();
072    }
073
074    /**
075     * Destroy the filter.
076     */
077    @Override
078    public void destroy() {
079        optionsServlet.destroy();
080        if (zkUtils != null) {
081            zkUtils.unregister(this);
082        }
083        super.destroy();
084    }
085
086    /**
087     * Returns the configuration from Oozie configuration to be used by the authentication filter.
088     * <p>
089     * All properties from Oozie configuration which name starts with {@link #OOZIE_PREFIX} will
090     * be returned. The keys of the returned properties are trimmed from the {@link #OOZIE_PREFIX}
091     * prefix, for example the Oozie configuration property name 'oozie.authentication.type' will
092     * be just 'type'.
093     *
094     * @param configPrefix configuration prefix, this parameter is ignored by this implementation.
095     * @param filterConfig filter configuration, this parameter is ignored by this implementation.
096     * @return all Oozie configuration properties prefixed with {@link #OOZIE_PREFIX}, without the
097     * prefix.
098     */
099    @Override
100    protected Properties getConfiguration(String configPrefix, FilterConfig filterConfig) {
101        Properties props = new Properties();
102        Configuration conf = Services.get().getConf();
103
104        //setting the cookie path to root '/' so it is used for all resources.
105        props.setProperty(AuthenticationFilter.COOKIE_PATH, "/");
106
107        for (Map.Entry<String, String> entry : conf) {
108            String name = entry.getKey();
109            if (name.startsWith(OOZIE_PREFIX)) {
110                String value = conf.get(name);
111                name = name.substring(OOZIE_PREFIX.length());
112                if (name.equals(KERBEROS_PRINCIPAL_CONFIG)) {
113                    String hostName = "localhost";
114                    String principal = value;
115                    try {
116                        hostName = InetAddress.getLocalHost().getCanonicalHostName();
117                        principal = SecurityUtil.getServerPrincipal(value, hostName);
118                    } catch (IOException ioe) {
119                       // ignore.
120                    }
121                    props.setProperty(name, principal);
122                 } else {
123                    props.setProperty(name, value);
124                }
125            }
126        }
127
128        // If using HA, we need to set some extra configs for the ZKSignerSecretProvider.  No need to bother the user with these
129        // details, so we'll set them for the user (unless the user really wants to set them)
130        if (Services.get().get(JobsConcurrencyService.class).isHighlyAvailableMode()) {
131            if (!props.containsKey("signer.secret.provider")) {
132                props.setProperty("signer.secret.provider", "zookeeper");
133            }
134            if (!props.containsKey("signer.secret.provider.zookeeper.path")) {
135                props.setProperty("signer.secret.provider.zookeeper.path",
136                        ZKUtils.ZK_BASE_SERVICES_PATH + "/signersecrets");
137            }
138            props.setProperty("signer.secret.provider.zookeeper.disconnect.on.shutdown", "false");
139        }
140
141        return props;
142    }
143
144    /**
145     * Enforces authentication using Hadoop-auth AuthenticationFilter.
146     * <p>
147     * This method is overriden to respond to HTTP OPTIONS requests for authenticated calls, regardless
148     * of the target servlet supporting OPTIONS or not and to inject the authenticated user name as
149     * request attribute for Oozie to retrieve the user id.
150     *
151     * @param request http request.
152     * @param response http response.
153     * @param filterChain filter chain.
154     * @throws IOException thrown if an IO error occurs.
155     * @throws ServletException thrown if a servlet error occurs.
156     */
157    @Override
158    public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain filterChain)
159            throws IOException, ServletException {
160
161        FilterChain filterChainWrapper = new FilterChain() {
162            @Override
163            public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse)
164                    throws IOException, ServletException {
165                HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
166                if (httpRequest.getMethod().equals("OPTIONS")) {
167                    optionsServlet.service(request, response);
168                }
169                else {
170                  httpRequest.setAttribute(JsonRestServlet.USER_NAME, httpRequest.getRemoteUser());
171                  filterChain.doFilter(servletRequest, servletResponse);
172                }
173            }
174        };
175
176        super.doFilter(request, response, filterChainWrapper);
177    }
178
179}