1

In KeyCLoak 15.0 (that is WildFly 23.0), I’m trying to configure access-log to also include username (or any ID of the user) when a user is logged in. In keycloak/standalone/configuration/standalone.xml, I configured XML:/server/profile/subsystem[@xmlns="urn:jboss:domain:undertow:12.0"]/server/host/access-log/@pattern pattern="%h %l %u %t "%r" %s/%S %b %T %I "%{i,User-Agent}""

The log gets correctly printed in the file I configured. However, the value of %u or %{REMOTE_USER} is always empty (that is -).

The only way to log some user ID I found was logging session cookie value with %{c,KEYCLOAK_SESSION} (it contains realm/user-ID/secret). Which isn’t a good idea to do in production.

Any idea on how to log username or userID in access-log?

Is it a KeyCLoak bug that %u or %{REMOTE_USER} is empty even when there is an active user session in KeyCloak? Or is it possible in KeyCLoak to configure which user attribute value goes in REMOTE_USER?

Alternativelly, how to put the userID in some header to use one of the following?

  • %{i,xxx} for incoming headers
  • %{o,xxx} for outgoing response headers
  • %{c,xxx} for a specific cookie
  • %{r,xxx} where xxx is an attribute in the ServletRequest
  • %{s,xxx} where xxx is an attribute in the HttpSession

Among others, I tried these. None of them was populated.

%{s,user} 
%{s,userId} 
%{s,client_id} 
%{s,USER_ID} 
%{s,USER} 
%{s,org.keycloak.adapters.spi.KeycloakAccount} 
%{s,KeycloakAccount} 
%{s,org.keycloak.adapters.tomcat.CatalinaSessionTokenStore.SerializableKeycloakAccount} 
%{s,SerializableKeycloakAccount} 
%{s,org.keycloak.adapters.saml.SamlSession} 
%{s,SamlSession} 
%{s,org.keycloak.adapters.undertow.KeycloakUndertowAccount} 
%{s,KeycloakUndertowAccount} 
%{s,org.keycloak.KeycloakSecurityContext} 
%{s,KeycloakSecurityContext} 
%{s,io.undertow.servlet.util.SavedRequest} 
%{s,SavedRequest}

%{r,tokenInfo} 
%{r,KeycloakSecurityContext} 
%{r,ElytronHttpFacade} 
%{r,AdapterDeploymentContext} 
%{r,TOKEN_STORE_NOTE}
McLayn
  • 61
  • 6

1 Answers1

2

I come over similar issue (my client asked me to log client id) and ended up looking for solution. From looking at the source code and how access log is populated I can tell you that there is pretty big gap between where log is formed and where actual work is being done.

If you have a look on Keycloak it is based on Wildfly which uses Undertow to host http server functionality. While access log entry is emitted once request was served there are few gaps and abstractions which complicate things.

From software perspective there is undertow handler, then servlet, then resteasy servlet, then keycloak application and specific resources. When you use Keycloak user or admin console then in most of places it is a "thin" client which is rendered by a web browser. And this browser calls rest resource.

If you wish to get user related information quite often it will not be found in the session, cause most of work being done by Kecloak is issuing tokens on behalf of users. Formally client who ships request acts on behalf of user which means that it is not a explicit information available for each incoming request. Additionally, most of rest resources by definition is stateless which means that they do operate somehow with user but do not populate session much. Only one part for which you can count on access for user information is when user actually logs in and does something within user account console. Beside that it might be lost fight since keycloak resources issuing tokens in most of cases will handle client or client related sessions.

To the point - I come to point where I located place which does the access log format parsing. It is based on Undertow ExchangeAttribute idea which allows to bring your own macro for log. This macro can be utilized to either traverse memory structures in pursue of the necessary information. For me it was a client_id which was doing the work. For that I ended up with implementation of a FormAttribute. I still need to find a way to wire it in, but from unit test perspective it already "clicked", see how basic code is:

package org.code_house.wildfly.stuff.undertow.attributes;
// remember to create META-INF/services/io.undertow.attribute.ExchangeAttributeBuilder
// with line containing class name, ie.
// org.code_house.wildfly.stuff.undertow.attributes.FormAttribute$Builder

/**
 * Expose form parameters within exchange attributes which can be logged in access log.
 * Use %{F,*} to dump all params or %{F,client_id} to render selected from field.
 *
 * @author Łukasz Dywicki @ code-house.org
 **/
public class FormAttribute implements ExchangeAttribute {

  private final String paramName;

  public FormAttribute(String paramName) {
    this.paramName = paramName;
  }

  @Override
  public String readAttribute(HttpServerExchange exchange) {
    FormData formData = exchange.getAttachment(FormDataParser.FORM_DATA);
    if ("*".equals(paramName)) {
      return "" + formData;
    }
    return formData == null ? "" : "" + formData.get(paramName);
  }

  @Override
  public void writeAttribute(HttpServerExchange exchange, String newValue) throws ReadOnlyAttributeException {
    throw new ReadOnlyAttributeException("Form", newValue);
  }

  public static final class Builder implements ExchangeAttributeBuilder {
    @Override
    public String name() {
      return "form";
    }

    @Override
    public ExchangeAttribute build(final String token) {
      if (token.startsWith("%{F,") && token.endsWith("}")) {
        final String paramName = token.substring(4, token.length() - 1);
        return new FormAttribute(paramName);
      }
      return null;
    }

    @Override
    public int priority() {
      return 0;
    }
  }
}

Main point - by using form attribute to log value entered in "username" field of login form to get "who", then you can combine that with session cookie which will be retained by browser. By basic merger of above two aces you can achieve necessary result. Using above blueprint you can implement your own thing and keep track of tokens and other things which will let you build your application.

I may update the answer when I find a glue logic to properly inject additional attribute into log format into undertow. EDIT: Only one way I found so far to inject extra attributes is by copying them into $JBOSS_HOME/modules/system/layers/base/org/wildfly/extension/undertow/main/ and updating module.xml within this directory:

<module name="org.wildfly.extension.undertow" xmlns="urn:jboss:module:1.5">
...
    <resources>
        <resource-root path="wildfly-undertow-20.0.1.Final.jar"/>
        <!-- put here name of jar you made -->
        <resource-root path="undertow-client-request-filter-1.0.0-SNAPSHOT.jar"/>
    </resources>
...
</module>
splatch
  • 121
  • 5