Skip to content

Latest commit

 

History

History
176 lines (125 loc) · 5.55 KB

middleware.md

File metadata and controls

176 lines (125 loc) · 5.55 KB

Gin Middleware

Setup

As a useful utility to use this audit event structure, a gin-based middleware structure is available: ginaudit.Middleware. This structure allows one to set gin routes to log audit events to a specified io.Writer via the aforementioned auditevent.EventWriter structure.

One would create a ginaudit.Middleware instance as follows:

mdw := ginaudit.NewMiddleware("my-test-component", eventwriter)

Given that JSON is a reasonable default, a utility function that defaults to using a JSON writer was implemented:

mdw := ginaudit.NewJSONMiddleware("my-test-component", writer)

Here, writer is an instance of an structure that implements the io.Writer interface.

It is often the case that one must not start to process events until the audit logging capabilities are set up. For this, the following pattern is suggested:

fd, err := helpers.OpenAuditLogFileUntilSuccess(auditLogPath)
if err != nil {
    panic(err)
}
// The file descriptor shall be closed only if the gin server is shut down
defer fd.Close()

// Set up middleware with the file descriptor
mdw := ginaudit.NewJSONMiddleware("my-test-component", fd)

The function helpers.OpenAuditLogFileUntilSuccess attempts to open the audit log file, and will block until it's available. This file may be created beforehand or it may be created by another process e.g. a sidecar container. It opens the file with O_APPEND which enables atomic writes as long as the audit events are less than 4096 bytes.

Usage

Now that we have a middleware instance available, it's a matter of taking it into use in our gin Router:

// Get router instance
r := gin.New(...)

// Add middleware
r.Use(mdw.Audit())

// ... All paths after the middleware addition will issue
// audit events
r.GET("/", myGetHandler)

Since it's standard gin middleware, it's also possible to set it up per handler:

r.GET("/foo", mdw.Audit() myGetFooHandler)

Context

When using the gin middleware, the audit ID will be added to the context and available for use in subsequent handlers in the chain.

auditID := c.GetString(mdw.AuditIDContextKey)

Addtional Data

Additional audit data can be passed down to the audit middleware via the context key AuditDataContextKey. This can be leveraged to enrich the audit events with diff information or other data for forensic analysis. The value is expected to be a *json.RawMessage and you are responsible for ensuring proper JSON structure.

// add additional data to context to be logged in the
// audit event by the middleware
mydata := json.RawMessage(`{"foo":"bar"}`)
c.Set(mdw.AuditDataContextKey, &mydata)

Audit event types

Audit event types identify the action that happened on a given request. By default, the event type will take the following form: <HTTP Method>:<Path>.

It is often a best practice to have human readable names, and to have an exhaustive list of event types that your application may produce. So, in order to register a type and tie it to a handler, the RegisterEventType function is available. It may be used as follows:

// Add middleware
r.Use(mdw.Audit())

// ... All paths after the middleware addition will issue
// audit events

mdw.RegisterEventType("ListFoos", http.MethodGet, "/foo")
r.GET("/foo", myGetHandler)

mdw.RegisterEventType("CreateFoo", http.MethodPost, "/foo")
r.POST("/foo", myGetHandler)

It's also possible to both set the audit middleware for a specific path and set a specific audit event type for the path:

router.GET("/user/:name", mdw.AuditWithType("GetUserInfo"), userInfoHandler)

NOTE: It is not recommended to assign a default or shared event type to all events as audit events need to be uniquely identifiable actions.

Audit event outcomes

By default, the audit events generated by this middleware will be created with the following heuristics:

  • For HTTP statuses 500 and above: failed

  • For HTTP statuses 400 and above, but below 500: denied

  • Anything else: succeeded

If this pattern isn't appropriate for your server logic, it's possible to overwrite the default outcome handler as follows:

// Create middleware instance
mdw := ginaudit.NewMiddleware("my-test-component", eventwriter)

// Overwrite handler. This one always succeeds
mdw.WithOutcomeHandler(func(c *gin.Context) string {
    return auditevent.OutcomeSucceeded
})

NOTE: It's recommended to not add random strings as an outcome, as one normally wants predictable strings here. If a custom outcome is desired, make sure to document it well.

Audit event subjects

By default, the audit events generated by this middleware will be created with subjects parsed by the ginjwt middleware.

  • jwt.subject will be assigned to the sub key if it exists, otherwise Unknown will be set.
  • jwt.user will be assigned to the user key if it exists, otherwise the middleware will look for the X-User-Id header. If neither is found, Unknown will be set.

If this pattern isn't appropriate for your application, it's possible to override the subject handler as follows:

// Create middleware instance
mdw := ginaudit.NewMiddleware("my-test-component", eventwriter)

// Overwrite handler with a dummy handler
mdw.WithSubjectHandler(func(c *gin.Context) map[string]string {
    return map[string]string{"foo": "bar"}
})

Audit event metrics

ginaudit.Middleware instances may generate metrics for events and errors. For more information, see the metrics documentation.