Audit: some refactoring out of types.go (#25511)

* some refactoring out of types.go

* tests for metrics tag, parallelize other tests
This commit is contained in:
Peter Wilson
2024-02-20 09:56:35 +00:00
committed by GitHub
parent 633dae1a6a
commit 808cdfc2d2
9 changed files with 160 additions and 72 deletions

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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)

View File

@@ -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)
}

View File

@@ -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)
})
}
}

View File

@@ -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

View File

@@ -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{

View File

@@ -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

View File

@@ -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"`