/*
* Copyright (c) 2013 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.security.audit;
import java.io.IOException;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import com.emc.storageos.security.authorization.QueriedObjectCache;
import com.emc.storageos.security.authentication.RequestProcessingUtils;
import javax.ws.rs.core.HttpHeaders;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Generic use http request logging filter.
*/
public class RequestAuditFilter implements Filter {
private static final Logger _log = LoggerFactory.getLogger(RequestAuditFilter.class);
@Autowired
RequestStatTracker _requestTracker;
@Override
public void destroy() {
// nothing to do.
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
beforeRequest();
HttpServletRequest req = (HttpServletRequest) request;
// Request logging
String srcHost = req.getHeader("X-Real-IP");
if (srcHost == null) {
srcHost = req.getRemoteAddr();
}
String rqs = req.getQueryString();
String basicStr = String.format("Request: %s - %s - %s from %s", req.getMethod(),
req.getRequestURL(), (rqs == null ? "empty-query" : rqs), srcHost);
_log.info(stripCookieToken(basicStr));
String detailsStr = String.format("Auth headers: Auth Token: %s - Proxy Token: %s - Basic Auth: %s - Keystone Token:%s",
req.getHeader(RequestProcessingUtils.AUTH_TOKEN_HEADER) == null ? "no" : "yes",
req.getHeader(RequestProcessingUtils.AUTH_PROXY_TOKEN_HEADER) == null ? "no" : "yes",
req.getHeader(HttpHeaders.AUTHORIZATION) == null ? "no" : "yes",
req.getHeader(RequestProcessingUtils.KEYSTONE_AUTH_TOKEN_HEADER) == null ? "no" : "yes");
_log.info(detailsStr);
// Additional fine grained debugging
if (_log.isDebugEnabled()) {
String authT = req.getHeader(RequestProcessingUtils.AUTH_TOKEN_HEADER);
if (authT != null && authT.equals("")) {
_log.debug("Auth token header provided but value was empty. This will most likely cause a 401.");
}
String authPT = req.getHeader(RequestProcessingUtils.AUTH_PROXY_TOKEN_HEADER);
if (authPT != null && authPT.equals("")) {
_log.debug("Proxy token header provided but value was empty. This will most likely cause a 401.");
}
String authKT = req.getHeader(RequestProcessingUtils.KEYSTONE_AUTH_TOKEN_HEADER);
if (authKT != null && authKT.equals("")) {
_log.debug("Keystone token header provided but value was empty. This will most likely cause a 401.");
}
}
// let's look at cookies
Cookie[] cookies = req.getCookies();
if (cookies != null && cookies.length > 0) {
for (Cookie c : cookies) {
if (c.getName().equalsIgnoreCase(RequestProcessingUtils.AUTH_TOKEN_HEADER)) {
if (c.getValue().equals("")) {
_log.debug("Auth token cookie was provided but value was empty. This will most likely cause a 401.");
} else {
_log.info("Auth token provided in cookie");
String cookieStr = String.format("Cookie: %s : %s", c.getName(), c.getValue());
_log.debug(cookieStr);
}
} else {
String cookieStr = (_log.isDebugEnabled()) ? String.format("Cookie: %s : %s", c.getName(), c.getValue()) : String
.format("Cookie: %s", c.getName());
_log.info(cookieStr);
}
}
} else {
_log.debug("No cookies");
}
// Follow the rest of the chain
HttpServletResponseWrapperWithStatus responseS = new HttpServletResponseWrapperWithStatus((HttpServletResponse) response);
try {
filterChain.doFilter(request, responseS);
} catch (RuntimeException ex) {
afterRequest();
throw ex;
}
if (responseS.getStatus() >= 500) {
_requestTracker.flag500Error();
}
afterRequest();
// Response logging
HttpServletResponse resp = (HttpServletResponse) response;
String respHeadersStr = String.format("Response headers: %s", resp.toString());
if (_log.isDebugEnabled()) {
_log.debug(respHeadersStr); // Show the auth cookie in context of the request
} else {
_log.info(stripCookieToken(respHeadersStr));
}
}
// Suppressing: Removeing this hard-coded password since it is just a key name
@SuppressWarnings({ "squid:S2068" })
public static String stripCookieToken(String str) {
Pattern p = Pattern.compile("(?s).*(X-SDS-\\S*:|password=)\\s*([^\\r]*)\\s*\\r?\\n?");
Matcher m = p.matcher(str);
if (m.find()) {
String ss = m.group(2);
return str.replaceAll(ss, " ** masked ** ");
} else {
return str;
}
}
@Override
public void init(FilterConfig config) throws ServletException {
// nothing to do
}
/**
* Servlet response wrapper class to extract the response status code
* before sending it. More recent versions of the servlet api have a getStatus
* built in the servletresponse and don't require this.
*/
private class HttpServletResponseWrapperWithStatus extends HttpServletResponseWrapper {
private int _httpStatus;
public HttpServletResponseWrapperWithStatus(HttpServletResponse response) {
super(response);
}
@Override
public void sendError(int sc) throws IOException {
_httpStatus = sc;
super.sendError(sc);
}
@Override
public void sendError(int sc, String msg) throws IOException {
_httpStatus = sc;
super.sendError(sc, msg);
}
@Override
public void setStatus(int sc) {
_httpStatus = sc;
super.setStatus(sc);
}
public int getStatus() {
return _httpStatus;
}
}
private void beforeRequest() {
// clear the cache before we do anything else since we sit at the top
// of the filter chain.
QueriedObjectCache.clearCache();
_requestTracker.incrementActiveRequests();
_requestTracker.recordStartTime();
}
private void afterRequest() {
_requestTracker.decrementActiveRequests();
_requestTracker.recordEndTime();
}
}