/* * 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.ambari.server.security.authentication.kerberos; import java.io.IOException; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.ambari.server.audit.AuditLogger; import org.apache.ambari.server.audit.event.AuditEvent; import org.apache.ambari.server.audit.event.LoginAuditEvent; import org.apache.ambari.server.configuration.Configuration; import org.apache.ambari.server.security.authentication.AmbariAuthenticationFilter; import org.apache.ambari.server.security.authorization.AuthorizationHelper; import org.apache.ambari.server.security.authorization.PermissionHelper; import org.apache.ambari.server.utils.RequestUtils; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.kerberos.web.authentication.SpnegoAuthenticationProcessingFilter; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; /** * AmbariKerberosAuthenticationFilter extends the {@link SpnegoAuthenticationProcessingFilter} class * to perform Kerberos-based authentication for Ambari. * <p> * If configured, auditing is performed using {@link AuditLogger}. */ public class AmbariKerberosAuthenticationFilter extends SpnegoAuthenticationProcessingFilter implements AmbariAuthenticationFilter { /** * Audit logger */ private final AuditLogger auditLogger; /** * A Boolean value indicating whether Kerberos authentication is enabled or not. */ private final boolean kerberosAuthenticationEnabled; /** * Constructor. * <p> * Given supplied data, sets up the the {@link SpnegoAuthenticationProcessingFilter} to perform * authentication and audit logging if configured do to so. * * @param authenticationManager the Spring authentication manager * @param entryPoint the Spring entry point * @param configuration the Ambari configuration data * @param auditLogger an audit logger * @param permissionHelper a permission helper to aid in audit logging */ public AmbariKerberosAuthenticationFilter(AuthenticationManager authenticationManager, final AuthenticationEntryPoint entryPoint, Configuration configuration, final AuditLogger auditLogger, final PermissionHelper permissionHelper) { AmbariKerberosAuthenticationProperties kerberosAuthenticationProperties = (configuration == null) ? null : configuration.getKerberosAuthenticationProperties(); kerberosAuthenticationEnabled = (kerberosAuthenticationProperties != null) && kerberosAuthenticationProperties.isKerberosAuthenticationEnabled(); this.auditLogger = auditLogger; setAuthenticationManager(authenticationManager); setFailureHandler(new AuthenticationFailureHandler() { @Override public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException { if (auditLogger.isEnabled()) { AuditEvent loginFailedAuditEvent = LoginAuditEvent.builder() .withRemoteIp(RequestUtils.getRemoteAddress(httpServletRequest)) .withTimestamp(System.currentTimeMillis()) .withReasonOfFailure(e.getLocalizedMessage()) .build(); auditLogger.log(loginFailedAuditEvent); } entryPoint.commence(httpServletRequest, httpServletResponse, e); } }); setSuccessHandler(new AuthenticationSuccessHandler() { @Override public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException { if (auditLogger.isEnabled()) { AuditEvent loginSucceededAuditEvent = LoginAuditEvent.builder() .withRemoteIp(RequestUtils.getRemoteAddress(httpServletRequest)) .withUserName(authentication.getName()) .withTimestamp(System.currentTimeMillis()) .withRoles(permissionHelper.getPermissionLabels(authentication)) .build(); auditLogger.log(loginSucceededAuditEvent); } } }); } /** * Tests to determine if this authentication filter is applicable given the Ambari configuration * and the user's HTTP request. * <p> * If the Ambari configuration indicates the Kerberos authentication is enabled and the HTTP request * contains the appropriate <code>Authorization</code> header, than this filter may be applied; * otherwise it should be skipped. * * @param httpServletRequest the request * @return true if this filter should be applied; false otherwise */ @Override public boolean shouldApply(HttpServletRequest httpServletRequest) { if (kerberosAuthenticationEnabled) { String header = httpServletRequest.getHeader("Authorization"); return (header != null) && (header.startsWith("Negotiate ") || header.startsWith("Kerberos ")); } else { return false; } } /** * Performs the logic for this filter. * <p> * Checks whether the authentication information is filled. If it is not, then a login failed audit event is logged. * <p> * Then, forwards the workflow to {@link SpnegoAuthenticationProcessingFilter#doFilter(ServletRequest, ServletResponse, FilterChain)} * * @param servletRequest the request * @param servletResponse the response * @param filterChain the Spring filter chain * @throws IOException * @throws ServletException */ @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; if (shouldApply(httpServletRequest)) { if (auditLogger.isEnabled() && (AuthorizationHelper.getAuthenticatedName() == null)) { AuditEvent loginFailedAuditEvent = LoginAuditEvent.builder() .withRemoteIp(RequestUtils.getRemoteAddress(httpServletRequest)) .withTimestamp(System.currentTimeMillis()) .withReasonOfFailure("Authentication required") .withUserName(null) .build(); auditLogger.log(loginFailedAuditEvent); } super.doFilter(servletRequest, servletResponse, filterChain); } else { filterChain.doFilter(servletRequest, servletResponse); } } }