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}