Enable username logging in Jetty request logs
Description
Environment
Acceptance / Success Criteria
Attachments
- 27 Apr 2017, 04:35 PM
Lucidchart Diagrams
Activity
Jeff Gehlbach April 27, 2017 at 4:35 PM
Source file as attachment (formatting in comment got messed up)
Jeff Gehlbach April 27, 2017 at 4:20 PM
Brilliant. Thanks, @Neiman Alvarez. We have an OCA on file for you already, so I don't expect many barriers to upstreaming this work.
Neiman Alvarez April 27, 2017 at 4:14 PM
To add the filter to the Spring security filter chain, I modified the applicationContext-spring-security.xml as follows:
1. Add the following attribute to each <http> tag: servlet-api-provision="false"
2. Add the following custom: filter inside each <http> tag (near the end)
<!-- Added for access logging -->
<custom-filter position="SERVLET_API_SUPPORT_FILTER" ref="securityContextHolderAwareFilter" />
3. Add the following bean definition:
<!-- Enable access log to show the authenticated user -->
<beans:bean id="securityContextHolderAwareFilter" class="org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilterForJetty"/>
Example:
Change from this:
------------------------------------------------------------------------------
<http pattern="/rest/**" realm="OpenNMS Realm">
<!-- OPTIONS pre-flight requests should always be accepted -->
<intercept-url pattern="/rest/**" method="OPTIONS" access="ROLE_ANONYMOUS,ROLE_REST,ROLE_ADMIN,ROLE_USER,ROLE_MOBILE"/>
...
<http-basic />
<custom-filter position="LAST" ref="authFilterEnabler"/>
</http>
------------------------------------------------------------------------------
to this:
------------------------------------------------------------------------------
<http pattern="/rest/**" realm="OpenNMS Realm" servlet-api-provision="false">
<!-- OPTIONS pre-flight requests should always be accepted -->
<intercept-url pattern="/rest/**" method="OPTIONS" access="ROLE_ANONYMOUS,ROLE_REST,ROLE_ADMIN,ROLE_USER,ROLE_MOBILE"/>
...
<http-basic />
<!-- Added for access logging -->
<custom-filter position="SERVLET_API_SUPPORT_FILTER" ref="securityContextHolderAwareFilter" />
<custom-filter position="LAST" ref="authFilterEnabler"/>
</http>
------------------------------------------------------------------------------
(Make the same change for each <http> tag that requires authentication.)
Neiman Alvarez April 27, 2017 at 4:14 PM
I came up with the following class as a quick and minimally intrusive way to set the authenticated user in the Jetty request log (compiled into a separate jar and copied into opennms/lib directory). The code could definitely use some refinement, as I haven't had time to look into the Jetty documentation to understand all possible edge cases. But it seems to be working fine in our deployments:
------------------------------------------------------------------------------
package org.springframework.security.web.servletapi;
import java.io.IOException;
import java.security.Principal;
import javax.security.auth.Subject;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletRequestWrapper;
import javax.servlet.ServletResponse;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.UserIdentity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
public class SecurityContextHolderAwareRequestFilterForJetty extends SecurityContextHolderAwareRequestFilter {
private static final Logger LOG = LogManager.getLogger();
public static class UserIdentityStub implements UserIdentity {
private final String userName;
public UserIdentityStub(String userName) {
this.userName = userName;
}
public Subject getSubject() {
// Not implemented
LOG.error("Unexpected call to getSubject()");
return new Subject();
}
public Principal getUserPrincipal() {
return new Principal() {
public String getName() {
return userName;
}
};
}
public boolean isUserInRole(String role, Scope scope) {
// Not implemented
LOG.error("Unexpected call to isUserInRole()");
return false;
}
}
public static class AuthenticationUserStub implements org.eclipse.jetty.server.Authentication.User {
private final String userName;
public AuthenticationUserStub(String userName) {
this.userName = userName;
}
public String getAuthMethod() {
// Not implemented
LOG.error("Unexpected call to getAuthMethod()");
return null;
}
public UserIdentity getUserIdentity() {
return new UserIdentityStub(userName);
}
public boolean isUserInRole(UserIdentity.Scope scope, String role) {
// Not implemented
LOG.error("Unexpected call to isUserInRole()");
return false;
}
public void logout() {
// Not implemented
LOG.error("Unexpected call to logout()");
}
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
final ServletRequest jettyRequest = unwrapRequest(req, 20);
if (jettyRequest instanceof Request) {
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null) {
final String userName = authentication.getName();
((Request) jettyRequest).setAuthentication(new AuthenticationUserStub(userName));
}
} else {
LOG.error("Couldn't access Jetty Request object to log username. Request type is {}",
jettyRequest.getClass().getCanonicalName());
}
super.doFilter(req, res, chain);
}
public ServletRequest unwrapRequest(ServletRequest req, int maxDepth) {
while (req instanceof ServletRequestWrapper) {
if (maxDepth-- <= 0) {
LOG.info("**** max depth reached - couldn't access org.eclipse.jetty.server.Request object");
return req;
}
req = ((ServletRequestWrapper) req).getRequest();
}
return req;
}
}
------------------------------------------------------------------------------
Details
Assignee
Jesse WhiteJesse WhiteReporter
Jeff GehlbachJeff GehlbachLabels
Components
Sprint
NoneFix versions
Affects versions
Priority
Major
Details
Details
Assignee
Reporter
Labels
Components
Sprint
Fix versions
Affects versions
Priority
PagerDuty
PagerDuty Incident
PagerDuty
PagerDuty Incident
PagerDuty

It's possible to enable request logging by adding a section like the following to
etc/jetty.xml
:<Ref id="RequestLog"> <Set name="requestLog"> <New id="RequestLogImpl" class="org.eclipse.jetty.server.NCSARequestLog"> <Arg>logs/jetty-requests-yyyy_mm_dd.log</Arg> <Set name="retainDays">90</Set> <Set name="append">true</Set> <Set name="extended">true</Set> <Set name="logTimeZone">US/Central</Set> </New> </Set> </Ref>
And referencing
RequestLog
in theHandlers
array:<New id="Handlers" class="org.eclipse.jetty.server.handler.HandlerCollection"> <Set name="handlers"> <Array type="org.eclipse.jetty.server.Handler"> <!-- Note #2 Uncomment to set X-Frame-Options with #1 above --> <!-- <Item> <Ref id="RewriteHandler"/> </Item> --> <Item> <New id="Contexts" class="org.eclipse.jetty.server.handler.ContextHandlerCollection"/> </Item> <Item> <New id="DefaultHandler" class="org.eclipse.jetty.server.handler.DefaultHandler"/> </Item> <Item> <New id="RequestLog" class="org.eclipse.jetty.server.handler.RequestLogHandler"/> </Item> </Array> </Set> </New>
This results in a request log in extended NCSA format, e.g.:
[jeffg@monkeyfaced opennms-20.0.0-SNAPSHOT]$ tail -f logs/jetty-requests-2017_04_27.log 0:0:0:0:0:0:0:1 - - [27/Apr/2017:14:20:23 -0500] "GET /opennms/login.jsp HTTP/1.1" 200 5123 "-" "Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0" 0:0:0:0:0:0:0:1 - - [27/Apr/2017:14:20:29 -0500] "POST /opennms/j_spring_security_check HTTP/1.1" 302 0 "http://localhost:8980/opennms/login.jsp" "Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0" 0:0:0:0:0:0:0:1 - - [27/Apr/2017:14:20:29 -0500] "GET /opennms/index.jsp HTTP/1.1" 200 25775 "http://localhost:8980/opennms/login.jsp" "Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0" 0:0:0:0:0:0:0:1 - - [27/Apr/2017:14:20:32 -0500] "GET /opennms/api/v2/geolocation/config HTTP/1.1" 200 431 "http://localhost:8980/opennms/index.jsp" "Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0" 0:0:0:0:0:0:0:1 - - [27/Apr/2017:14:20:33 -0500] "POST /opennms/api/v2/geolocation HTTP/1.1" 204 0 "http://localhost:8980/opennms/index.jsp" "Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0"
The second column is always just
-
, no matter which user authenticates and no matter whether via Basic or forms-based mechanisms. Seth thinks: