vault/audit/nodes_test.go

334 lines
8.9 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package audit
import (
"context"
"testing"
"github.com/hashicorp/eventlogger"
"github.com/hashicorp/go-uuid"
nshelper "github.com/hashicorp/vault/helper/namespace"
"github.com/hashicorp/vault/internal/observability/event"
"github.com/hashicorp/vault/sdk/logical"
"github.com/stretchr/testify/require"
)
// TestProcessManual_NilData tests processManual when nil data is supplied.
func TestProcessManual_NilData(t *testing.T) {
t.Parallel()
var ids []eventlogger.NodeID
nodes := make(map[eventlogger.NodeID]eventlogger.Node)
// Formatter node
formatterId, formatterNode := newFormatterNode(t)
ids = append(ids, formatterId)
nodes[formatterId] = formatterNode
// Sink node
sinkId, sinkNode := newSinkNode(t)
ids = append(ids, sinkId)
nodes[sinkId] = sinkNode
err := processManual(nshelper.RootContext(context.Background()), nil, ids, nodes)
require.Error(t, err)
require.EqualError(t, err, "data cannot be nil")
}
// 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
}{
"nil": {
IDs: nil,
ExpectedErrorMessage: "minimum of 2 ids are required",
},
"one": {
IDs: []eventlogger.NodeID{"1"},
ExpectedErrorMessage: "minimum of 2 ids are required",
},
}
for name, tc := range tests {
name := name
tc := tc
t.Run(name, func(t *testing.T) {
t.Parallel()
nodes := make(map[eventlogger.NodeID]eventlogger.Node)
// Formatter node
formatterId, formatterNode := newFormatterNode(t)
nodes[formatterId] = formatterNode
// Sink node
sinkId, sinkNode := newSinkNode(t)
nodes[sinkId] = sinkNode
// Data
requestId, err := uuid.GenerateUUID()
require.NoError(t, err)
data := newData(requestId)
err = processManual(nshelper.RootContext(context.Background()), data, tc.IDs, nodes)
require.Error(t, err)
require.EqualError(t, err, tc.ExpectedErrorMessage)
})
}
}
// TestProcessManual_NoNodes tests processManual when no nodes are supplied.
func TestProcessManual_NoNodes(t *testing.T) {
t.Parallel()
var ids []eventlogger.NodeID
nodes := make(map[eventlogger.NodeID]eventlogger.Node)
// Formatter node
formatterId, _ := newFormatterNode(t)
ids = append(ids, formatterId)
// Sink node
sinkId, _ := newSinkNode(t)
ids = append(ids, sinkId)
// Data
requestId, err := uuid.GenerateUUID()
require.NoError(t, err)
data := newData(requestId)
err = processManual(nshelper.RootContext(context.Background()), data, ids, nodes)
require.Error(t, err)
require.EqualError(t, err, "nodes are required")
}
// TestProcessManual_IdNodeMismatch tests processManual when IDs don't match with
// the nodes in the supplied map.
func TestProcessManual_IdNodeMismatch(t *testing.T) {
t.Parallel()
var ids []eventlogger.NodeID
nodes := make(map[eventlogger.NodeID]eventlogger.Node)
// Formatter node
formatterId, formatterNode := newFormatterNode(t)
ids = append(ids, formatterId)
nodes[formatterId] = formatterNode
// Sink node
sinkId, _ := newSinkNode(t)
ids = append(ids, sinkId)
// Data
requestId, err := uuid.GenerateUUID()
require.NoError(t, err)
data := newData(requestId)
err = processManual(nshelper.RootContext(context.Background()), data, ids, nodes)
require.Error(t, err)
require.ErrorContains(t, err, "node not found: ")
}
// TestProcessManual_NotEnoughNodes tests processManual when there is only one
// node provided.
func TestProcessManual_NotEnoughNodes(t *testing.T) {
t.Parallel()
var ids []eventlogger.NodeID
nodes := make(map[eventlogger.NodeID]eventlogger.Node)
// Formatter node
formatterId, formatterNode := newFormatterNode(t)
ids = append(ids, formatterId)
nodes[formatterId] = formatterNode
// Data
requestId, err := uuid.GenerateUUID()
require.NoError(t, err)
data := newData(requestId)
err = processManual(nshelper.RootContext(context.Background()), data, ids, nodes)
require.Error(t, err)
require.EqualError(t, err, "minimum of 2 ids are required")
}
// TestProcessManual_LastNodeNotSink tests processManual when the last node is
// not a Sink node.
func TestProcessManual_LastNodeNotSink(t *testing.T) {
t.Parallel()
var ids []eventlogger.NodeID
nodes := make(map[eventlogger.NodeID]eventlogger.Node)
// Formatter node
formatterId, formatterNode := newFormatterNode(t)
ids = append(ids, formatterId)
nodes[formatterId] = formatterNode
// Another Formatter node
formatterId, formatterNode = newFormatterNode(t)
ids = append(ids, formatterId)
nodes[formatterId] = formatterNode
// Data
requestId, err := uuid.GenerateUUID()
require.NoError(t, err)
data := newData(requestId)
err = processManual(nshelper.RootContext(context.Background()), data, ids, nodes)
require.Error(t, err)
require.EqualError(t, err, "last node must be a filter or sink")
}
// TestProcessManualEndWithSink ensures that the manual processing of a test
// message works as expected with proper inputs, which mean processing ends with
// sink node.
func TestProcessManualEndWithSink(t *testing.T) {
t.Parallel()
var ids []eventlogger.NodeID
nodes := make(map[eventlogger.NodeID]eventlogger.Node)
// Formatter node
formatterId, formatterNode := newFormatterNode(t)
ids = append(ids, formatterId)
nodes[formatterId] = formatterNode
// Sink node
sinkId, sinkNode := newSinkNode(t)
ids = append(ids, sinkId)
nodes[sinkId] = sinkNode
// Data
requestId, err := uuid.GenerateUUID()
require.NoError(t, err)
data := newData(requestId)
err = processManual(nshelper.RootContext(context.Background()), data, ids, nodes)
require.NoError(t, err)
}
// TestProcessManual_EndWithFilter ensures that the manual processing of a test
// message works as expected with proper inputs, which mean processing ends with
// sink node.
func TestProcessManual_EndWithFilter(t *testing.T) {
t.Parallel()
var ids []eventlogger.NodeID
nodes := make(map[eventlogger.NodeID]eventlogger.Node)
// Filter node
filterId, filterNode := newFilterNode(t)
ids = append(ids, filterId)
nodes[filterId] = filterNode
// Formatter node
formatterId, formatterNode := newFormatterNode(t)
ids = append(ids, formatterId)
nodes[formatterId] = formatterNode
// Sink node
sinkId, sinkNode := newSinkNode(t)
ids = append(ids, sinkId)
nodes[sinkId] = sinkNode
// Data
requestId, err := uuid.GenerateUUID()
require.NoError(t, err)
data := newData(requestId)
err = processManual(nshelper.RootContext(context.Background()), data, ids, nodes)
require.NoError(t, err)
}
// newSinkNode creates a new UUID and NoopSink (sink node).
func newSinkNode(t *testing.T) (eventlogger.NodeID, *event.NoopSink) {
t.Helper()
sinkId, err := event.GenerateNodeID()
require.NoError(t, err)
sinkNode := event.NewNoopSink()
return sinkId, sinkNode
}
// TestFilter is a trivial implementation of eventlogger.Node used as a placeholder
// for Filter nodes in tests.
type TestFilter struct{}
// Process trivially filters the event preventing it from being processed by subsequent nodes.
func (f *TestFilter) Process(_ context.Context, e *eventlogger.Event) (*eventlogger.Event, error) {
return nil, nil
}
// Reopen does nothing.
func (f *TestFilter) Reopen() error {
return nil
}
// Type returns the eventlogger.NodeTypeFormatter type.
func (f *TestFilter) Type() eventlogger.NodeType {
return eventlogger.NodeTypeFilter
}
// TestFormatter is a trivial implementation of the eventlogger.Node interface
// used as a place-holder for Formatter nodes in tests.
type TestFormatter struct{}
// Process trivially formats the event by storing "test" as a byte slice under
// the test format type.
func (f *TestFormatter) Process(_ context.Context, e *eventlogger.Event) (*eventlogger.Event, error) {
e.FormattedAs("test", []byte("test"))
return e, nil
}
// Reopen does nothing.
func (f *TestFormatter) Reopen() error {
return nil
}
// Type returns the eventlogger.NodeTypeFormatter type.
func (f *TestFormatter) Type() eventlogger.NodeType {
return eventlogger.NodeTypeFormatter
}
// newFilterNode creates a new TestFormatter (filter node).
func newFilterNode(t *testing.T) (eventlogger.NodeID, *TestFilter) {
nodeId, err := event.GenerateNodeID()
require.NoError(t, err)
node := &TestFilter{}
return nodeId, node
}
// newFormatterNode creates a new TestFormatter (formatter node).
func newFormatterNode(t *testing.T) (eventlogger.NodeID, *TestFormatter) {
nodeId, err := event.GenerateNodeID()
require.NoError(t, err)
node := &TestFormatter{}
return nodeId, node
}
// newData creates a sample logical.LogInput to be used as data for tests.
func newData(id string) *logical.LogInput {
return &logical.LogInput{
Type: "request",
Auth: nil,
Request: &logical.Request{
ID: id,
Operation: "update",
Path: "sys/audit/test",
},
Response: nil,
OuterErr: nil,
}
}