diff --git a/docs/middleware.md b/docs/middleware.md index c55cd6e3..b35c8f8d 100644 --- a/docs/middleware.md +++ b/docs/middleware.md @@ -72,6 +72,20 @@ When using the gin middleware, the audit ID will be added to the context and ava 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. + +```golang +// 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. diff --git a/ginaudit/mdw.go b/ginaudit/mdw.go index b64d853f..45b52a96 100644 --- a/ginaudit/mdw.go +++ b/ginaudit/mdw.go @@ -16,6 +16,7 @@ limitations under the License. package ginaudit import ( + "encoding/json" "fmt" "io" "sync" @@ -28,6 +29,8 @@ import ( ) const ( + // AuditDataContextKey is the gin context key for additional audit data. + AuditDataContextKey = "audit.data" // AuditIDContextKey is the gin context key for the audit ID. AuditIDContextKey = "audit.id" ) @@ -125,6 +128,14 @@ func (m *Middleware) AuditWithType(t string) gin.HandlerFunc { "path": path, }) + data, ok := c.Get(AuditDataContextKey) + if ok { + ed, ok := data.(*json.RawMessage) + if ok { + event.WithData(ed) + } + } + // persist event m.write(event) } diff --git a/ginaudit/mdw_test.go b/ginaudit/mdw_test.go index a32fe2d8..6a50fc90 100644 --- a/ginaudit/mdw_test.go +++ b/ginaudit/mdw_test.go @@ -50,6 +50,8 @@ const ( comp = "test" ) +var testData = json.RawMessage(`{"foo":"bar"}`) + type testCase struct { name string expectedEvent *auditevent.AuditEvent @@ -141,6 +143,66 @@ func getTestCases() []testCase { "X-User-Id": "user-ozz-from-header", }, }, + { + "user request succeeds, enriched by context data", + auditevent.NewAuditEvent( + "GET:/changes", + auditevent.EventSource{ + Type: "IP", + Value: "127.0.0.1", + }, + auditevent.OutcomeSucceeded, + map[string]string{ + "user": "user-ozz", + "sub": "sub-ozz", + }, + comp, + ).WithTarget(map[string]string{ + "path": "/changes", + }).WithData(&testData), + http.MethodGet, + nil, + }, + { + "user request denied, encriched by context data", + auditevent.NewAuditEvent( + "GET:/changes/denied", + auditevent.EventSource{ + Type: "IP", + Value: "127.0.0.1", + }, + auditevent.OutcomeDenied, + map[string]string{ + "user": "Unknown", + "sub": "Unknown", + }, + comp, + ).WithTarget(map[string]string{ + "path": "/changes/denied", + }).WithData(&testData), + http.MethodGet, + nil, + }, + { + "user request succeeds, context data added as wrong type", + auditevent.NewAuditEvent( + "GET:/nodata", + auditevent.EventSource{ + Type: "IP", + Value: "127.0.0.1", + }, + auditevent.OutcomeSucceeded, + map[string]string{ + "user": "user-ozz", + "sub": "sub-ozz", + }, + comp, + ).WithTarget(map[string]string{ + "path": "/nodata", + }), + http.MethodGet, + nil, + }, } } @@ -185,6 +247,28 @@ func setFixtures(t *testing.T, w io.Writer, pr prometheus.Registerer) (*gin.Engi c.JSON(http.StatusForbidden, "denied") }) + // allowed with user, enriched by context data + r.GET("/changes", func(c *gin.Context) { + c.Set("jwt.user", "user-ozz") + c.Set("jwt.subject", "sub-ozz") + c.Set(ginaudit.AuditDataContextKey, &testData) + c.JSON(http.StatusOK, "ok") + }) + + // denied with no user, enriched by context data + r.GET("/changes/denied", func(c *gin.Context) { + c.Set(ginaudit.AuditDataContextKey, &testData) + c.JSON(http.StatusForbidden, "denied") + }) + + // context data of wrong type + r.GET("/nodata", func(c *gin.Context) { + c.Set("jwt.user", "user-ozz") + c.Set("jwt.subject", "sub-ozz") + c.Set(ginaudit.AuditDataContextKey, "some random string") + c.JSON(http.StatusOK, "ok") + }) + return r, mdw } diff --git a/go.sum b/go.sum index a9b73ed1..0ecc608a 100644 --- a/go.sum +++ b/go.sum @@ -357,8 +357,6 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7 h1:ZrnxWX62AgTKOSagEqxvb3ffipvEDX2pl7E1TdqLqIc= -golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0 h1:cu5kTvlzcw1Q5S9f5ip1/cpiB4nXvw1XYzFPGgzLUOY= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=