Skip to content

Commit

Permalink
Add documentation and enhance middleware structures (#6)
Browse files Browse the repository at this point in the history
This adds documentation on the event writer and middleware structures,
and adapts the middleware factory functions to make them more flexible
for future implementations.

Signed-off-by: Juan Antonio Osorio <juan.osoriorobles@eu.equinix.com>
  • Loading branch information
JAORMX authored Apr 12, 2022
1 parent 2a9efaa commit b950661
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 7 deletions.
72 changes: 71 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,74 @@ if err != nil {
panic(err)
}
e.WithData(jsonData)
```
```

# Writing audit logs

The base package comes with a utility structure called `auditevent.EventWriter`. The `EventWriter`'s
purpose is to encode an audit event to whatever representation is needed. This could writing directly
to a file, a UNIX socket, or even an HTTP server. The requirement is that the writer that's passed
to the `EventWriter` structure **must** implement the `io.Writer` interface.

Audit events also need to be encoded somehow, so an encoder must be passed to the `EventWriter`. An
encoder **must** implement the `EventEncoder` interface that's made available in this package.

The creation of an event writer would look as follows:

```golang
aew := auditevent.NewAuditEventWriter(writer, encoder)
```

Since JSON encoding is common and expected, there is a default implementation that assumes
JSON encoding. It's may be used as follows:

```golang
aew := auditevent.NewDefaultAuditEventWriter(writer)
```

To write events to the `EventWriter` one can do so as follows:

```golang
err := aew.Write(eventToWrite)
```

# Gin Middleware

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:

```golang
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:

```golang
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:

```golang
fd, err := ginaudit.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 `ginaudit.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.
20 changes: 15 additions & 5 deletions ginaudit/mdw.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ const (
retryInterval = 100 * time.Millisecond
)

// OpenAuditLogFileUntilSuccess attempts to open a file for writing audit events until
// it succeeds.
// It assumes that audit events are less than 4096 bytes to ensure atomicity.
// it takes a writer for the audit log.
func OpenAuditLogFileUntilSuccess(path string) (*os.File, error) {
for {
// This is opened with the O_APPEND option to ensure
Expand All @@ -43,16 +47,22 @@ func OpenAuditLogFileUntilSuccess(path string) (*os.File, error) {
}
}

// NewMiddleware returns a new middleware instance with a default writer
// It assumes that audit events are less than 4096 bytes to ensure atomicity.
// it takes a writer for the audit log.
func NewMiddleware(component string, w io.Writer) *Middleware {
// NewMiddleware returns a new instance of audit Middleware.
func NewMiddleware(component string, aew *auditevent.EventWriter) *Middleware {
return &Middleware{
component: component,
aew: auditevent.NewDefaultAuditEventWriter(w),
aew: aew,
}
}

// NewJSONMiddleware returns a new middleware instance with a default JSON writer.
func NewJSONMiddleware(component string, w io.Writer) *Middleware {
return NewMiddleware(
component,
auditevent.NewDefaultAuditEventWriter(w),
)
}

func (m *Middleware) RegisterEventType(eventType, httpMethod, path string) {
m.eventTypeMap.Store(keyFromHTTPMethodAndPath(httpMethod, path), eventType)
}
Expand Down
2 changes: 1 addition & 1 deletion ginaudit/mdw_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ func getNamedPipe(t *testing.T) string {
func setFixtures(t *testing.T, w io.Writer) *gin.Engine {
t.Helper()

mdw := ginaudit.NewMiddleware(comp, w)
mdw := ginaudit.NewJSONMiddleware(comp, w)

r := gin.New()
r.Use(mdw.Audit())
Expand Down
2 changes: 2 additions & 0 deletions writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
)

// EventEncoder allows for encoding audit events.
// The parameter to the `Encode` method is the audit event to encode
// and it must accept pointer to an AuditEvent struct.
type EventEncoder interface {
Encode(any) error
}
Expand Down

0 comments on commit b950661

Please sign in to comment.