diff --git a/audit/entry_filter.go b/audit/entry_filter.go index d11a51a3e3..4b72615356 100644 --- a/audit/entry_filter.go +++ b/audit/entry_filter.go @@ -17,6 +17,13 @@ import ( var _ eventlogger.Node = (*EntryFilter)(nil) +// EntryFilter should be used to filter audit requests and responses which should +// make it to a sink. +type EntryFilter struct { + // the evaluator for the bexpr expression that should be applied by the node. + evaluator *bexpr.Evaluator +} + // NewEntryFilter should be used to create an EntryFilter node. // The filter supplied should be in bexpr format and reference fields from logical.LogInputBexpr. func NewEntryFilter(filter string) (*EntryFilter, error) { diff --git a/audit/entry_formatter.go b/audit/entry_formatter.go index 7e349513e5..328c1901ce 100644 --- a/audit/entry_formatter.go +++ b/audit/entry_formatter.go @@ -26,6 +26,14 @@ var ( _ eventlogger.Node = (*EntryFormatter)(nil) ) +// EntryFormatter should be used to format audit requests and responses. +type EntryFormatter struct { + salter Salter + headerFormatter HeaderFormatter + config FormatterConfig + prefix string +} + // NewEntryFormatter should be used to create an EntryFormatter. // Accepted options: WithHeaderFormatter, WithPrefix. func NewEntryFormatter(config FormatterConfig, salter Salter, opt ...Option) (*EntryFormatter, error) { diff --git a/audit/entry_formatter_test.go b/audit/entry_formatter_test.go index 6e2583da5c..a101213eef 100644 --- a/audit/entry_formatter_test.go +++ b/audit/entry_formatter_test.go @@ -59,6 +59,8 @@ const testFormatJSONReqBasicStrFmt = ` // TestNewEntryFormatter ensures we can create new EntryFormatter structs. func TestNewEntryFormatter(t *testing.T) { + t.Parallel() + tests := map[string]struct { UseStaticSalt bool Options []Option // Only supports WithPrefix @@ -154,6 +156,8 @@ func TestNewEntryFormatter(t *testing.T) { // TestEntryFormatter_Reopen ensures that we do not get an error when calling Reopen. func TestEntryFormatter_Reopen(t *testing.T) { + t.Parallel() + ss := newStaticSalt(t) cfg, err := NewFormatterConfig() require.NoError(t, err) @@ -166,6 +170,8 @@ func TestEntryFormatter_Reopen(t *testing.T) { // TestEntryFormatter_Type ensures that the node is a 'formatter' type. func TestEntryFormatter_Type(t *testing.T) { + t.Parallel() + ss := newStaticSalt(t) cfg, err := NewFormatterConfig() require.NoError(t, err) @@ -179,6 +185,8 @@ func TestEntryFormatter_Type(t *testing.T) { // TestEntryFormatter_Process attempts to run the Process method to convert the // logical.LogInput within an audit event to JSON and JSONx (RequestEntry or ResponseEntry). func TestEntryFormatter_Process(t *testing.T) { + t.Parallel() + tests := map[string]struct { IsErrorExpected bool ExpectedErrorMessage string @@ -409,6 +417,8 @@ func BenchmarkAuditFileSink_Process(b *testing.B) { // TestEntryFormatter_FormatRequest exercises EntryFormatter.FormatRequest with // varying inputs. func TestEntryFormatter_FormatRequest(t *testing.T) { + t.Parallel() + tests := map[string]struct { Input *logical.LogInput IsErrorExpected bool @@ -476,6 +486,8 @@ func TestEntryFormatter_FormatRequest(t *testing.T) { // TestEntryFormatter_FormatResponse exercises EntryFormatter.FormatResponse with // varying inputs. func TestEntryFormatter_FormatResponse(t *testing.T) { + t.Parallel() + tests := map[string]struct { Input *logical.LogInput IsErrorExpected bool @@ -543,6 +555,8 @@ func TestEntryFormatter_FormatResponse(t *testing.T) { // TestEntryFormatter_Process_JSON ensures that the JSON output we get matches what // we expect for the specified LogInput. func TestEntryFormatter_Process_JSON(t *testing.T) { + t.Parallel() + ss := newStaticSalt(t) expectedResultStr := fmt.Sprintf(testFormatJSONReqBasicStrFmt, ss.salt.GetIdentifiedHMAC("foo")) @@ -683,6 +697,8 @@ func TestEntryFormatter_Process_JSON(t *testing.T) { // TestEntryFormatter_Process_JSONx ensures that the JSONx output we get matches what // we expect for the specified LogInput. func TestEntryFormatter_Process_JSONx(t *testing.T) { + t.Parallel() + s, err := salt.NewSalt(context.Background(), nil, nil) require.NoError(t, err) tempStaticSalt := &staticSalt{salt: s} @@ -826,11 +842,7 @@ func TestEntryFormatter_Process_JSONx(t *testing.T) { // TestEntryFormatter_FormatResponse_ElideListResponses ensures that we correctly // elide data in responses to LIST operations. func TestEntryFormatter_FormatResponse_ElideListResponses(t *testing.T) { - type test struct { - name string - inputData map[string]any - expectedData map[string]any - } + t.Parallel() tests := map[string]struct { inputData map[string]any @@ -957,6 +969,8 @@ func TestEntryFormatter_FormatResponse_ElideListResponses(t *testing.T) { // TestEntryFormatter_Process_NoMutation tests that the event returned by an // EntryFormatter.Process method is not the same as the one that it accepted. func TestEntryFormatter_Process_NoMutation(t *testing.T) { + t.Parallel() + // Create the formatter node. cfg, err := NewFormatterConfig() require.NoError(t, err) diff --git a/audit/event.go b/audit/event.go index 3532037214..8802ea4f5c 100644 --- a/audit/event.go +++ b/audit/event.go @@ -5,10 +5,42 @@ package audit import ( "fmt" + "time" "github.com/hashicorp/vault/internal/observability/event" + "github.com/hashicorp/vault/sdk/logical" ) +// version defines the version of audit events. +const version = "v0.1" + +// Audit subtypes. +const ( + RequestType subtype = "AuditRequest" + ResponseType subtype = "AuditResponse" +) + +// Audit formats. +const ( + JSONFormat format = "json" + JSONxFormat format = "jsonx" +) + +// AuditEvent is the audit event. +type AuditEvent struct { + ID string `json:"id"` + Version string `json:"version"` + Subtype subtype `json:"subtype"` // the subtype of the audit event. + Timestamp time.Time `json:"timestamp"` + Data *logical.LogInput `json:"data"` +} + +// format defines types of format audit events support. +type format string + +// subtype defines the type of audit event. +type subtype string + // NewEvent should be used to create an audit event. The subtype field is needed // for audit events. It will generate an ID if no ID is supplied. Supported // options: WithID, WithNow. @@ -99,13 +131,14 @@ func (f format) String() string { } // MetricTag returns a tag corresponding to this subtype to include in metrics. -func (st subtype) MetricTag() string { - switch st { +// If a tag cannot be found the value is returned 'as-is' in string format. +func (t subtype) MetricTag() string { + switch t { case RequestType: return "log_request" case ResponseType: return "log_response" } - return "" + return string(t) } diff --git a/audit/event_test.go b/audit/event_test.go index acd586ec94..66bdf424c7 100644 --- a/audit/event_test.go +++ b/audit/event_test.go @@ -12,6 +12,8 @@ import ( // 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 @@ -107,6 +109,8 @@ func TestAuditEvent_new(t *testing.T) { // 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 @@ -198,6 +202,8 @@ func TestAuditEvent_Validate(t *testing.T) { // 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 @@ -243,6 +249,8 @@ func TestAuditEvent_Validate_Subtype(t *testing.T) { // 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 @@ -285,3 +293,42 @@ func TestAuditEvent_Validate_Format(t *testing.T) { }) } } + +// 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) + }) + } +} diff --git a/audit/nodes_test.go b/audit/nodes_test.go index f2aca0bc25..17d2f78add 100644 --- a/audit/nodes_test.go +++ b/audit/nodes_test.go @@ -40,6 +40,8 @@ func TestProcessManual_NilData(t *testing.T) { // TestProcessManual_BadIDs tests ProcessManual when different bad values are // supplied for the ID parameter. func TestProcessManual_BadIDs(t *testing.T) { + t.Parallel() + tests := map[string]struct { IDs []eventlogger.NodeID ExpectedErrorMessage string diff --git a/audit/options.go b/audit/options.go index 71ae8cf843..109b721e98 100644 --- a/audit/options.go +++ b/audit/options.go @@ -10,6 +10,23 @@ import ( "time" ) +// Option is how options are passed as arguments. +type Option func(*options) error + +// options are used to represent configuration for a audit related nodes. +type options struct { + withID string + withNow time.Time + withSubtype subtype + withFormat format + withPrefix string + withRaw bool + withElision bool + withOmitTime bool + withHMACAccessor bool + withHeaderFormatter HeaderFormatter +} + // getDefaultOptions returns options with their default values. func getDefaultOptions() options { return options{ diff --git a/audit/options_test.go b/audit/options_test.go index edb8e6142f..3bbe05f8e6 100644 --- a/audit/options_test.go +++ b/audit/options_test.go @@ -13,6 +13,8 @@ import ( // TestOptions_WithFormat exercises WithFormat Option to ensure it performs as expected. func TestOptions_WithFormat(t *testing.T) { + t.Parallel() + tests := map[string]struct { Value string IsErrorExpected bool @@ -68,6 +70,8 @@ func TestOptions_WithFormat(t *testing.T) { // TestOptions_WithSubtype exercises WithSubtype Option to ensure it performs as expected. func TestOptions_WithSubtype(t *testing.T) { + t.Parallel() + tests := map[string]struct { Value string IsErrorExpected bool @@ -113,6 +117,8 @@ func TestOptions_WithSubtype(t *testing.T) { // TestOptions_WithNow exercises WithNow Option to ensure it performs as expected. func TestOptions_WithNow(t *testing.T) { + t.Parallel() + tests := map[string]struct { Value time.Time IsErrorExpected bool @@ -154,6 +160,8 @@ func TestOptions_WithNow(t *testing.T) { // TestOptions_WithID exercises WithID Option to ensure it performs as expected. func TestOptions_WithID(t *testing.T) { + t.Parallel() + tests := map[string]struct { Value string IsErrorExpected bool @@ -199,6 +207,8 @@ func TestOptions_WithID(t *testing.T) { // TestOptions_WithPrefix exercises WithPrefix Option to ensure it performs as expected. func TestOptions_WithPrefix(t *testing.T) { + t.Parallel() + tests := map[string]struct { Value string IsErrorExpected bool @@ -244,6 +254,8 @@ func TestOptions_WithPrefix(t *testing.T) { // TestOptions_WithRaw exercises WithRaw Option to ensure it performs as expected. func TestOptions_WithRaw(t *testing.T) { + t.Parallel() + tests := map[string]struct { Value bool ExpectedValue bool @@ -274,6 +286,8 @@ func TestOptions_WithRaw(t *testing.T) { // TestOptions_WithElision exercises WithElision Option to ensure it performs as expected. func TestOptions_WithElision(t *testing.T) { + t.Parallel() + tests := map[string]struct { Value bool ExpectedValue bool @@ -304,6 +318,8 @@ func TestOptions_WithElision(t *testing.T) { // TestOptions_WithHMACAccessor exercises WithHMACAccessor Option to ensure it performs as expected. func TestOptions_WithHMACAccessor(t *testing.T) { + t.Parallel() + tests := map[string]struct { Value bool ExpectedValue bool @@ -334,6 +350,8 @@ func TestOptions_WithHMACAccessor(t *testing.T) { // TestOptions_WithOmitTime exercises WithOmitTime Option to ensure it performs as expected. func TestOptions_WithOmitTime(t *testing.T) { + t.Parallel() + tests := map[string]struct { Value bool ExpectedValue bool @@ -365,6 +383,8 @@ func TestOptions_WithOmitTime(t *testing.T) { // TestOptions_WithHeaderFormatter exercises the WithHeaderFormatter Option to // ensure it applies the option as expected under various circumstances. func TestOptions_WithHeaderFormatter(t *testing.T) { + t.Parallel() + tests := map[string]struct { Value HeaderFormatter ExpectedValue HeaderFormatter @@ -403,6 +423,8 @@ func TestOptions_WithHeaderFormatter(t *testing.T) { // TestOptions_Default exercises getDefaultOptions to assert the default values. func TestOptions_Default(t *testing.T) { + t.Parallel() + opts := getDefaultOptions() require.NotNil(t, opts) require.True(t, time.Now().After(opts.withNow)) @@ -411,6 +433,8 @@ func TestOptions_Default(t *testing.T) { // TestOptions_Opts exercises GetOpts with various Option values. func TestOptions_Opts(t *testing.T) { + t.Parallel() + tests := map[string]struct { opts []Option IsErrorExpected bool diff --git a/audit/types.go b/audit/types.go index b42296d0f4..1a91c0e705 100644 --- a/audit/types.go +++ b/audit/types.go @@ -6,61 +6,12 @@ package audit import ( "context" "io" - "time" - "github.com/hashicorp/go-bexpr" "github.com/hashicorp/vault/internal/observability/event" "github.com/hashicorp/vault/sdk/helper/salt" "github.com/hashicorp/vault/sdk/logical" ) -// Audit subtypes. -const ( - RequestType subtype = "AuditRequest" - ResponseType subtype = "AuditResponse" -) - -// Audit formats. -const ( - JSONFormat format = "json" - JSONxFormat format = "jsonx" -) - -// version defines the version of audit events. -const version = "v0.1" - -// subtype defines the type of audit event. -type subtype string - -// format defines types of format audit events support. -type format string - -// AuditEvent is the audit event. -type AuditEvent struct { - ID string `json:"id"` - Version string `json:"version"` - Subtype subtype `json:"subtype"` // the subtype of the audit event. - Timestamp time.Time `json:"timestamp"` - Data *logical.LogInput `json:"data"` -} - -// Option is how options are passed as arguments. -type Option func(*options) error - -// options are used to represent configuration for a audit related nodes. -type options struct { - withID string - withNow time.Time - withSubtype subtype - withFormat format - withPrefix string - withRaw bool - withElision bool - withOmitTime bool - withHMACAccessor bool - withHeaderFormatter HeaderFormatter -} - // Salter is an interface that provides a way to obtain a Salt for hashing. type Salter interface { // Salt returns a non-nil salt or an error. @@ -94,14 +45,6 @@ type HeaderFormatter interface { ApplyConfig(context.Context, map[string][]string, Salter) (map[string][]string, error) } -// EntryFormatter should be used to format audit requests and responses. -type EntryFormatter struct { - salter Salter - headerFormatter HeaderFormatter - config FormatterConfig - prefix string -} - // EntryFormatterWriter should be used to format and write out audit requests and responses. type EntryFormatterWriter struct { Formatter @@ -144,13 +87,6 @@ type FormatterConfig struct { RequiredFormat format } -// EntryFilter should be used to filter audit requests and responses which should -// make it to a sink. -type EntryFilter struct { - // the evaluator for the bexpr expression that should be applied by the node. - evaluator *bexpr.Evaluator -} - // RequestEntry is the structure of a request audit log entry. type RequestEntry struct { Time string `json:"time,omitempty"`