Recording Custom Audit Events

This section describes how to record a custom audit event to standard output. The example is based on the example in "Validating Access_Tokens Through the Introspection Endpoint", adding an audit event for the custom topic OAuth2AccessTopic.

To record custom audit events to other outputs, adapt the route in the following procedure to use another audit event handler.

For information about how to configure supported audit event handlers, and exclude sensitive data from log files, see Auditing Your Deployment. For more information about audit event handlers, see Audit Framework.

Record Custom Audit Events to Standard Output

Before you start, prepare IG and the sample application as described in Getting Started Guide.

  1. Set up AM as described in "Validating Access_Tokens Through the Introspection Endpoint".

  2. Define the schema of an event topic called OAuth2AccessTopic by adding the following route to IG:

    $HOME/.openig/audit-schemas/OAuth2AccessTopic.json
    %appdata%\OpenIG\audit-schemas/OAuth2AccessTopic.json
    {
      "schema": {
        "$schema": "http://json-schema.org/draft-04/schema#",
        "id": "OAuth2Access",
        "type": "object",
        "properties": {
          "_id": {
            "type": "string"
          },
          "timestamp": {
            "type": "string"
          },
          "transactionId": {
            "type": "string"
          },
          "eventName": {
            "type": "string"
          },
          "accessToken": {
            "type": "object",
            "properties": {
              "scopes": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "expiresAt": "number",
              "sub": "string"
            },
            "required": [ "scopes" ]
          },
          "resource": {
            "type": "object",
            "properties": {
              "path": {
                "type": "string"
              },
              "method": {
                "type": "string"
              }
            }
          }
        }
      },
      "filterPolicies": {
        "field": {
          "includeIf": [
            "/_id",
            "/timestamp",
            "/eventName",
            "/transactionId",
            "/accessToken",
            "/resource"
          ]
        }
      },
      "required": [ "_id", "timestamp", "transactionId", "eventName" ]
    }

    Notice that the schema includes the following fields:

    • Mandatory fields _id, timestamp, transactionId, and eventName.

    • accessToken, to include the access_token scopes, expiry time, and the subject.

    • resource, to include the path and method.

    • filterPolicies, to specify additional event fields to include in the logs.

  3. Define a script to generate audit events on the topic named OAuth2AccessTopic, by adding the following file to the IG configuration as:

    $HOME/.openig/scripts/groovy/OAuth2Access.groovy
    %appdata%\OpenIG\scripts\groovy/OAuth2Access.groovy
    import static org.forgerock.json.resource.Requests.newCreateRequest;
    import static org.forgerock.json.resource.ResourcePath.resourcePath;
    
    // Helper functions
    def String transactionId() {
        return contexts.transactionId.transactionId.value;
    }
    
    def JsonValue auditEvent(String eventName) {
        return json(object(field('eventName', eventName),
                field('transactionId', transactionId()),
                field('timestamp', clock.instant().toEpochMilli())));
    }
    
    def auditEventRequest(String topicName, JsonValue auditEvent) {
        return newCreateRequest(resourcePath("/" + topicName), auditEvent);
    }
    
    def accessTokenInfo() {
        def accessTokenInfo = contexts.oauth2.accessToken;
        return object(field('scopes', accessTokenInfo.scopes as List),
                field('expiresAt', accessTokenInfo.expiresAt),
                field('sub', accessTokenInfo.info.sub));
    }
    
    def resourceEvent() {
        return object(field('path', request.uri.path),
                field('method', request.method));
    }
    
    // --------------------------------------------
    
    // Build the event
    JsonValue auditEvent = auditEvent('OAuth2AccessEvent')
            .add('accessToken', accessTokenInfo())
            .add('resource', resourceEvent());
    
    // Send the event
    auditService.handleCreate(context, auditEventRequest("OAuth2AccessTopic", auditEvent));
    
    // Continue onto the next filter
    return next.handle(context, request)

    The script generates audit events named OAuth2AccessEvent, on a topic named OAuth2AccessTopic. The events conform to the topic schema.

  4. Set an environment variable for the IG agent password, and then restart IG:

    $ export AGENT_SECRET_ID='cGFzc3dvcmQ='

    The password is retrieved by a SystemAndEnvSecretStore, and must be base64-encoded.

  5. Add the following route to IG:

    $HOME/.openig/config/routes/30-custom.json
    %appdata%\OpenIG\config\routes\30-custom.json
    {
      "name": "30-custom",
      "baseURI": "http://app.example.com:8081",
      "condition": "${matches(request.uri.path, '^/rs-introspect-audit')}",
      "heap": [
        {
          "name": "AuditService-1",
          "type": "AuditService",
          "config": {
            "config": {},
            "eventHandlers": [
              {
                "class": "org.forgerock.audit.handlers.json.stdout.JsonStdoutAuditEventHandler",
                "config": {
                  "name": "jsonstdout",
                  "elasticsearchCompatible": false,
                  "topics": [
                    "OAuth2AccessTopic"
                  ]
                }
              }
            ]
          }
        },
        {
          "name": "SystemAndEnvSecretStore-1",
          "type": "SystemAndEnvSecretStore"
        },
        {
          "name": "AmService-1",
          "type": "AmService",
          "config": {
            "agent": {
              "username": "ig_agent",
              "passwordSecretId": "agent.secret.id"
            },
            "secretsProvider": "SystemAndEnvSecretStore-1",
            "url": "http://openam.example.com:8088/openam/",
            "version": "7"
          }
        }
      ],
      "handler": {
        "type": "Chain",
        "config": {
          "filters": [
            {
              "name": "OAuth2ResourceServerFilter-1",
              "type": "OAuth2ResourceServerFilter",
              "config": {
                "scopes": [
                  "mail",
                  "employeenumber"
                ],
                "requireHttps": false,
                "realm": "OpenIG",
                "accessTokenResolver": {
                  "name": "token-resolver-1",
                  "type": "TokenIntrospectionAccessTokenResolver",
                  "config": {
                    "amService": "AmService-1",
                    "providerHandler": {
                      "type": "Chain",
                      "config": {
                        "filters": [
                          {
                            "type": "HttpBasicAuthenticationClientFilter",
                            "config": {
                              "username": "ig_agent",
                              "passwordSecretId": "agent.secret.id",
                              "secretsProvider": "SystemAndEnvSecretStore-1"
                            }
                          }
                        ],
                        "handler": "ForgeRockClientHandler"
                      }
                    }
                  }
                }
              }
            },
            {
              "type": "ScriptableFilter",
              "config": {
                "type": "application/x-groovy",
                "file": "OAuth2Access.groovy",
                "args": {
                  "auditService": "${heap['AuditService-1']}",
                  "clock": "${heap['Clock']}"
                }
              }
            }
          ],
          "handler": {
            "type": "StaticResponseHandler",
            "config": {
              "status": 200,
              "headers": {
                "Content-Type": [ "text/html" ]
              },
              "entity": "<html><body><h2>Decoded access_token: ${contexts.oauth2.accessToken.info}</h2></body></html>"
            }
          }
        }
      }
    }
    

    Notice the following features of the route:

    • The route matches requests to /rs-introspect-audit.

    • The accessTokenResolver uses the token introspection endpoint to validate the access_token.

    • The HttpBasicAuthenticationClientFilter adds the credentials to the outgoing token introspection request.

    • The ScriptableFilter uses the Groovy script OAuth2Access.groovy to generate audit events named OAuth2AccessEvent, with a topic named OAuth2AccessTopic.

    • The audit service publishes the custom audit event to the JsonStdoutAuditEventHandler. A single line per audit event is published to standard output.

Test the Setup
  1. In a terminal window, use a curl command similar to the following to retrieve an access_token:

    $ mytoken=$(curl -s \
    --user "client-application:password" \
    --data "grant_type=password&username=george&password=C0stanza&scope=mail%20employeenumber" \
    http://openam.example.com:8088/openam/oauth2/access_token | jq -r ".access_token")

  2. Access the route, with the access_token returned in the previous step:

    $ curl -v http://openig.example.com:8080/rs-introspect-audit --header "Authorization: Bearer ${mytoken}"

    Information about the decoded access_token is returned.

  3. Search the standard output for an audit message like the following example, that includes an audit event on the topic OAuth2AccessTopic:

    {
      "_id": "fa2...-14",
      "timestamp": 155...541,
      "eventName": "OAuth2AccessEvent",
      "transactionId": "fa2...-13",
      "accessToken": {
        "scopes": ["employeenumber", "mail"],
        "expiresAt": 155...000,
        "sub": "george"
      },
      "resource": {
        "path": "/rs-introspect-audit",
        "method": "GET"
      },
      "source": "audit",
      "topic": "OAuth2AccessTopic",
      "level": "INFO"
    }
Read a different version of :