vault/audit/event_test.go
Peter Wilson 3dc16db87e
VAULT-24798: audit - improve error messages (#26312)
* audit: remove 'op' from error messages and do some clean up

* Allow early error checking to be concerned with vault/Core vs. audit
2024-04-11 09:09:32 +01:00

447 lines
10 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package audit
import (
"testing"
"time"
"github.com/stretchr/testify/require"
)
// TestAuditEvent_new exercises the newEvent func to create audit events.
func TestAuditEvent_new(t *testing.T) {
t.Parallel()
tests := map[string]struct {
Options []Option
Subtype subtype
Format format
IsErrorExpected bool
ExpectedErrorMessage string
ExpectedID string
ExpectedFormat format
ExpectedSubtype subtype
ExpectedTimestamp time.Time
IsNowExpected bool
}{
"nil": {
Options: nil,
Subtype: subtype(""),
Format: format(""),
IsErrorExpected: true,
ExpectedErrorMessage: "invalid event subtype \"\": invalid internal parameter",
},
"empty-Option": {
Options: []Option{},
Subtype: subtype(""),
Format: format(""),
IsErrorExpected: true,
ExpectedErrorMessage: "invalid event subtype \"\": invalid internal parameter",
},
"bad-id": {
Options: []Option{WithID("")},
Subtype: ResponseType,
Format: JSONFormat,
IsErrorExpected: true,
ExpectedErrorMessage: "id cannot be empty",
},
"good": {
Options: []Option{
WithID("audit_123"),
WithFormat(string(JSONFormat)),
WithSubtype(string(ResponseType)),
WithNow(time.Date(2023, time.July, 4, 12, 3, 0, 0, time.Local)),
},
Subtype: RequestType,
Format: JSONxFormat,
IsErrorExpected: false,
ExpectedID: "audit_123",
ExpectedTimestamp: time.Date(2023, time.July, 4, 12, 3, 0, 0, time.Local),
ExpectedSubtype: RequestType,
ExpectedFormat: JSONxFormat,
},
"good-no-time": {
Options: []Option{
WithID("audit_123"),
WithFormat(string(JSONFormat)),
WithSubtype(string(ResponseType)),
},
Subtype: RequestType,
Format: JSONxFormat,
IsErrorExpected: false,
ExpectedID: "audit_123",
ExpectedSubtype: RequestType,
ExpectedFormat: JSONxFormat,
IsNowExpected: true,
},
}
for name, tc := range tests {
name := name
tc := tc
t.Run(name, func(t *testing.T) {
t.Parallel()
audit, err := NewEvent(tc.Subtype, tc.Options...)
switch {
case tc.IsErrorExpected:
require.Error(t, err)
require.EqualError(t, err, tc.ExpectedErrorMessage)
require.Nil(t, audit)
default:
require.NoError(t, err)
require.NotNil(t, audit)
require.Equal(t, tc.ExpectedID, audit.ID)
require.Equal(t, tc.ExpectedSubtype, audit.Subtype)
switch {
case tc.IsNowExpected:
require.True(t, time.Now().After(audit.Timestamp))
require.False(t, audit.Timestamp.IsZero())
default:
require.Equal(t, tc.ExpectedTimestamp, audit.Timestamp)
}
}
})
}
}
// TestAuditEvent_Validate exercises the validation for an audit event.
func TestAuditEvent_Validate(t *testing.T) {
t.Parallel()
tests := map[string]struct {
Value *AuditEvent
IsErrorExpected bool
ExpectedErrorMessage string
}{
"nil": {
Value: nil,
IsErrorExpected: true,
ExpectedErrorMessage: "event is nil: invalid internal parameter",
},
"default": {
Value: &AuditEvent{},
IsErrorExpected: true,
ExpectedErrorMessage: "missing ID: invalid internal parameter",
},
"id-empty": {
Value: &AuditEvent{
ID: "",
Version: version,
Subtype: RequestType,
Timestamp: time.Now(),
Data: nil,
},
IsErrorExpected: true,
ExpectedErrorMessage: "missing ID: invalid internal parameter",
},
"version-fiddled": {
Value: &AuditEvent{
ID: "audit_123",
Version: "magic-v2",
Subtype: RequestType,
Timestamp: time.Now(),
Data: nil,
},
IsErrorExpected: true,
ExpectedErrorMessage: "event version unsupported: invalid internal parameter",
},
"subtype-fiddled": {
Value: &AuditEvent{
ID: "audit_123",
Version: version,
Subtype: subtype("moon"),
Timestamp: time.Now(),
Data: nil,
},
IsErrorExpected: true,
ExpectedErrorMessage: "invalid event subtype \"moon\": invalid internal parameter",
},
"default-time": {
Value: &AuditEvent{
ID: "audit_123",
Version: version,
Subtype: ResponseType,
Timestamp: time.Time{},
Data: nil,
},
IsErrorExpected: true,
ExpectedErrorMessage: "event timestamp cannot be the zero time instant: invalid internal parameter",
},
"valid": {
Value: &AuditEvent{
ID: "audit_123",
Version: version,
Subtype: ResponseType,
Timestamp: time.Now(),
Data: nil,
},
IsErrorExpected: false,
},
}
for name, tc := range tests {
name := name
tc := tc
t.Run(name, func(t *testing.T) {
t.Parallel()
err := tc.Value.validate()
switch {
case tc.IsErrorExpected:
require.Error(t, err)
require.EqualError(t, err, tc.ExpectedErrorMessage)
default:
require.NoError(t, err)
}
})
}
}
// TestAuditEvent_Validate_Subtype exercises the validation for an audit event's subtype.
func TestAuditEvent_Validate_Subtype(t *testing.T) {
t.Parallel()
tests := map[string]struct {
Value string
IsErrorExpected bool
ExpectedErrorMessage string
}{
"empty": {
Value: "",
IsErrorExpected: true,
ExpectedErrorMessage: "invalid event subtype \"\": invalid internal parameter",
},
"unsupported": {
Value: "foo",
IsErrorExpected: true,
ExpectedErrorMessage: "invalid event subtype \"foo\": invalid internal parameter",
},
"request": {
Value: "AuditRequest",
IsErrorExpected: false,
},
"response": {
Value: "AuditResponse",
IsErrorExpected: false,
},
}
for name, tc := range tests {
name := name
tc := tc
t.Run(name, func(t *testing.T) {
t.Parallel()
err := subtype(tc.Value).validate()
switch {
case tc.IsErrorExpected:
require.Error(t, err)
require.EqualError(t, err, tc.ExpectedErrorMessage)
default:
require.NoError(t, err)
}
})
}
}
// TestAuditEvent_Validate_Format exercises the validation for an audit event's format.
func TestAuditEvent_Validate_Format(t *testing.T) {
t.Parallel()
tests := map[string]struct {
Value string
IsErrorExpected bool
ExpectedErrorMessage string
}{
"empty": {
Value: "",
IsErrorExpected: true,
ExpectedErrorMessage: "invalid format \"\": invalid internal parameter",
},
"unsupported": {
Value: "foo",
IsErrorExpected: true,
ExpectedErrorMessage: "invalid format \"foo\": invalid internal parameter",
},
"json": {
Value: "json",
IsErrorExpected: false,
},
"jsonx": {
Value: "jsonx",
IsErrorExpected: false,
},
}
for name, tc := range tests {
name := name
tc := tc
t.Run(name, func(t *testing.T) {
t.Parallel()
err := format(tc.Value).validate()
switch {
case tc.IsErrorExpected:
require.Error(t, err)
require.EqualError(t, err, tc.ExpectedErrorMessage)
default:
require.NoError(t, err)
}
})
}
}
// TestAuditEvent_Subtype_MetricTag is used to ensure that we get the string value
// we expect for a subtype when we want to use it as a metrics tag.
// In some strange scenario where the subtype was never validated, it is technically
// possible to get a value that isn't related to request/response, but this shouldn't
// really be happening, so we will return it as is.
func TestAuditEvent_Subtype_MetricTag(t *testing.T) {
t.Parallel()
tests := map[string]struct {
input string
expectedOutput string
}{
"request": {
input: "AuditRequest",
expectedOutput: "log_request",
},
"response": {
input: "AuditResponse",
expectedOutput: "log_response",
},
"non-validated": {
input: "juan",
expectedOutput: "juan",
},
}
for name, tc := range tests {
name := name
tc := tc
t.Run(name, func(t *testing.T) {
t.Parallel()
st := subtype(tc.input)
tag := st.MetricTag()
require.Equal(t, tc.expectedOutput, tag)
})
}
}
// TestAuditEvent_Subtype_String is used to ensure that we get the string value
// we expect for a subtype when it is used with the Stringer interface.
// e.g. an AuditRequest subtype is 'request'
func TestAuditEvent_Subtype_String(t *testing.T) {
t.Parallel()
tests := map[string]struct {
input string
expectedOutput string
}{
"request": {
input: "AuditRequest",
expectedOutput: "request",
},
"response": {
input: "AuditResponse",
expectedOutput: "response",
},
"non-validated": {
input: "juan",
expectedOutput: "juan",
},
}
for name, tc := range tests {
name := name
tc := tc
t.Run(name, func(t *testing.T) {
t.Parallel()
st := subtype(tc.input)
require.Equal(t, tc.expectedOutput, st.String())
})
}
}
// TestAuditEvent_formattedTime is used to check the output from the formattedTime
// method returns the correct format.
func TestAuditEvent_formattedTime(t *testing.T) {
theTime := time.Date(2024, time.March, 22, 10, 0o0, 5, 10, time.UTC)
a, err := NewEvent(ResponseType, WithNow(theTime))
require.NoError(t, err)
require.NotNil(t, a)
require.Equal(t, "2024-03-22T10:00:05.00000001Z", a.formattedTime())
}
// TestEvent_IsValidFormat ensures that we can correctly determine valid and
// invalid formats.
func TestEvent_IsValidFormat(t *testing.T) {
t.Parallel()
tests := map[string]struct {
input string
expected bool
}{
"empty": {
input: "",
expected: false,
},
"whitespace": {
input: " ",
expected: false,
},
"invalid-test": {
input: "test",
expected: false,
},
"valid-json": {
input: "json",
expected: true,
},
"upper-json": {
input: "JSON",
expected: true,
},
"mixed-json": {
input: "Json",
expected: true,
},
"spacey-json": {
input: " json ",
expected: true,
},
"valid-jsonx": {
input: "jsonx",
expected: true,
},
"upper-jsonx": {
input: "JSONX",
expected: true,
},
"mixed-jsonx": {
input: "JsonX",
expected: true,
},
"spacey-jsonx": {
input: " jsonx ",
expected: true,
},
}
for name, tc := range tests {
name := name
tc := tc
t.Run(name, func(t *testing.T) {
t.Parallel()
res := IsValidFormat(tc.input)
require.Equal(t, tc.expected, res)
})
}
}