mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-11-01 11:08:10 +00:00
VAULT-25710: Audit - enforce header formatter requirement in EntryFormatter (#26239)
* Check Enterprise unseal order for audit funcs, enforce header formatter in audit entry formatter node * ApplyConfig return empty headers (but never nil) when nil/empty supplied * Add NoopHeaderFormatter and remove builtin audit testHeaderFormatters
This commit is contained in:
@@ -38,17 +38,14 @@ type timeProvider interface {
|
||||
|
||||
// EntryFormatter should be used to format audit requests and responses.
|
||||
type EntryFormatter struct {
|
||||
config FormatterConfig
|
||||
salter Salter
|
||||
logger hclog.Logger
|
||||
headerFormatter HeaderFormatter
|
||||
name string
|
||||
prefix string
|
||||
config FormatterConfig
|
||||
salter Salter
|
||||
logger hclog.Logger
|
||||
name string
|
||||
}
|
||||
|
||||
// NewEntryFormatter should be used to create an EntryFormatter.
|
||||
// Accepted options: WithHeaderFormatter, WithPrefix.
|
||||
func NewEntryFormatter(name string, config FormatterConfig, salter Salter, logger hclog.Logger, opt ...Option) (*EntryFormatter, error) {
|
||||
func NewEntryFormatter(name string, config FormatterConfig, salter Salter, logger hclog.Logger) (*EntryFormatter, error) {
|
||||
const op = "audit.NewEntryFormatter"
|
||||
|
||||
name = strings.TrimSpace(name)
|
||||
@@ -69,18 +66,11 @@ func NewEntryFormatter(name string, config FormatterConfig, salter Salter, logge
|
||||
return nil, fmt.Errorf("%s: format not valid: %w", op, err)
|
||||
}
|
||||
|
||||
opts, err := getOpts(opt...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: error applying options: %w", op, err)
|
||||
}
|
||||
|
||||
return &EntryFormatter{
|
||||
config: config,
|
||||
salter: salter,
|
||||
logger: logger,
|
||||
headerFormatter: opts.withHeaderFormatter,
|
||||
name: name,
|
||||
prefix: opts.withPrefix,
|
||||
config: config,
|
||||
salter: salter,
|
||||
logger: logger,
|
||||
name: name,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -145,11 +135,14 @@ func (f *EntryFormatter) Process(ctx context.Context, e *eventlogger.Event) (_ *
|
||||
return nil, fmt.Errorf("%s: unable to copy audit event data: %w", op, err)
|
||||
}
|
||||
|
||||
// Ensure that any headers in the request, are formatted as required, and are
|
||||
// only present if they have been configured to appear in the audit log.
|
||||
// e.g. via: /sys/config/auditing/request-headers/:name
|
||||
if f.headerFormatter != nil && data.Request != nil && data.Request.Headers != nil {
|
||||
data.Request.Headers, err = f.headerFormatter.ApplyConfig(ctx, data.Request.Headers, f.salter)
|
||||
// If the request is present in the input data, apply header configuration
|
||||
// regardless. We shouldn't be in a situation where the header formatter isn't
|
||||
// present as it's required.
|
||||
if data.Request != nil {
|
||||
// Ensure that any headers in the request, are formatted as required, and are
|
||||
// only present if they have been configured to appear in the audit log.
|
||||
// e.g. via: /sys/config/auditing/request-headers/:name
|
||||
data.Request.Headers, err = f.config.headerFormatter.ApplyConfig(ctx, data.Request.Headers, f.salter)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: unable to transform headers for auditing: %w", op, err)
|
||||
}
|
||||
@@ -198,8 +191,8 @@ func (f *EntryFormatter) Process(ctx context.Context, e *eventlogger.Event) (_ *
|
||||
// don't support a prefix just sitting there.
|
||||
// However, this would be a breaking change to how Vault currently works to
|
||||
// include the prefix as part of the JSON object or XML document.
|
||||
if f.prefix != "" {
|
||||
result = append([]byte(f.prefix), result...)
|
||||
if f.config.Prefix != "" {
|
||||
result = append([]byte(f.config.Prefix), result...)
|
||||
}
|
||||
|
||||
// Copy some properties from the event (and audit event) and store the
|
||||
@@ -577,19 +570,25 @@ func (f *EntryFormatter) FormatResponse(ctx context.Context, in *logical.LogInpu
|
||||
}
|
||||
|
||||
// NewFormatterConfig should be used to create a FormatterConfig.
|
||||
// Accepted options: WithElision, WithHMACAccessor, WithOmitTime, WithRaw, WithFormat.
|
||||
func NewFormatterConfig(opt ...Option) (FormatterConfig, error) {
|
||||
// Accepted options: WithElision, WithFormat, WithHMACAccessor, WithOmitTime, WithPrefix, WithRaw.
|
||||
func NewFormatterConfig(headerFormatter HeaderFormatter, opt ...Option) (FormatterConfig, error) {
|
||||
const op = "audit.NewFormatterConfig"
|
||||
|
||||
if headerFormatter == nil || reflect.ValueOf(headerFormatter).IsNil() {
|
||||
return FormatterConfig{}, fmt.Errorf("%s: header formatter is required: %w", op, event.ErrInvalidParameter)
|
||||
}
|
||||
|
||||
opts, err := getOpts(opt...)
|
||||
if err != nil {
|
||||
return FormatterConfig{}, fmt.Errorf("%s: error applying options: %w", op, err)
|
||||
}
|
||||
|
||||
return FormatterConfig{
|
||||
headerFormatter: headerFormatter,
|
||||
ElideListResponses: opts.withElision,
|
||||
HMACAccessor: opts.withHMACAccessor,
|
||||
OmitTime: opts.withOmitTime,
|
||||
Prefix: opts.withPrefix,
|
||||
Raw: opts.withRaw,
|
||||
RequiredFormat: opts.withFormat,
|
||||
}, nil
|
||||
@@ -663,10 +662,8 @@ func doElideListResponseData(data map[string]interface{}) {
|
||||
// newTemporaryEntryFormatter creates a cloned EntryFormatter instance with a non-persistent Salter.
|
||||
func newTemporaryEntryFormatter(n *EntryFormatter) *EntryFormatter {
|
||||
return &EntryFormatter{
|
||||
salter: &nonPersistentSalt{},
|
||||
headerFormatter: n.headerFormatter,
|
||||
config: n.config,
|
||||
prefix: n.prefix,
|
||||
salter: &nonPersistentSalt{},
|
||||
config: n.config,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -59,6 +59,24 @@ const testFormatJSONReqBasicStrFmt = `
|
||||
}
|
||||
`
|
||||
|
||||
// testHeaderFormatter is a stub to prevent the need to import the vault package
|
||||
// to bring in vault.AuditedHeadersConfig for testing.
|
||||
type testHeaderFormatter struct {
|
||||
shouldReturnEmpty bool
|
||||
}
|
||||
|
||||
// ApplyConfig satisfies the HeaderFormatter interface for testing.
|
||||
// It will either return the headers it was supplied or empty headers depending
|
||||
// on how it is configured.
|
||||
// ignore-nil-nil-function-check.
|
||||
func (f *testHeaderFormatter) ApplyConfig(_ context.Context, headers map[string][]string, salter Salter) (result map[string][]string, retErr error) {
|
||||
if f.shouldReturnEmpty {
|
||||
return make(map[string][]string), nil
|
||||
}
|
||||
|
||||
return headers, nil
|
||||
}
|
||||
|
||||
// testTimeProvider is just a test struct used to imitate an AuditEvent's ability
|
||||
// to provide a formatted time.
|
||||
type testTimeProvider struct{}
|
||||
@@ -178,9 +196,9 @@ func TestNewEntryFormatter(t *testing.T) {
|
||||
ss = newStaticSalt(t)
|
||||
}
|
||||
|
||||
cfg, err := NewFormatterConfig(tc.Options...)
|
||||
cfg, err := NewFormatterConfig(&testHeaderFormatter{}, tc.Options...)
|
||||
require.NoError(t, err)
|
||||
f, err := NewEntryFormatter(tc.Name, cfg, ss, tc.Logger, tc.Options...)
|
||||
f, err := NewEntryFormatter(tc.Name, cfg, ss, tc.Logger)
|
||||
|
||||
switch {
|
||||
case tc.IsErrorExpected:
|
||||
@@ -191,7 +209,7 @@ func TestNewEntryFormatter(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, f)
|
||||
require.Equal(t, tc.ExpectedFormat, f.config.RequiredFormat)
|
||||
require.Equal(t, tc.ExpectedPrefix, f.prefix)
|
||||
require.Equal(t, tc.ExpectedPrefix, f.config.Prefix)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -202,7 +220,7 @@ func TestEntryFormatter_Reopen(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ss := newStaticSalt(t)
|
||||
cfg, err := NewFormatterConfig()
|
||||
cfg, err := NewFormatterConfig(&testHeaderFormatter{})
|
||||
require.NoError(t, err)
|
||||
|
||||
f, err := NewEntryFormatter("juan", cfg, ss, hclog.NewNullLogger())
|
||||
@@ -216,7 +234,7 @@ func TestEntryFormatter_Type(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ss := newStaticSalt(t)
|
||||
cfg, err := NewFormatterConfig()
|
||||
cfg, err := NewFormatterConfig(&testHeaderFormatter{})
|
||||
require.NoError(t, err)
|
||||
|
||||
f, err := NewEntryFormatter("juan", cfg, ss, hclog.NewNullLogger())
|
||||
@@ -361,7 +379,7 @@ func TestEntryFormatter_Process(t *testing.T) {
|
||||
require.NotNil(t, e)
|
||||
|
||||
ss := newStaticSalt(t)
|
||||
cfg, err := NewFormatterConfig(WithFormat(tc.RequiredFormat.String()))
|
||||
cfg, err := NewFormatterConfig(&testHeaderFormatter{}, WithFormat(tc.RequiredFormat.String()))
|
||||
require.NoError(t, err)
|
||||
|
||||
f, err := NewEntryFormatter("juan", cfg, ss, hclog.NewNullLogger())
|
||||
@@ -426,7 +444,7 @@ func BenchmarkAuditFileSink_Process(b *testing.B) {
|
||||
ctx := namespace.RootContext(context.Background())
|
||||
|
||||
// Create the formatter node.
|
||||
cfg, err := NewFormatterConfig()
|
||||
cfg, err := NewFormatterConfig(&testHeaderFormatter{})
|
||||
require.NoError(b, err)
|
||||
ss := newStaticSalt(b)
|
||||
formatter, err := NewEntryFormatter("juan", cfg, ss, hclog.NewNullLogger())
|
||||
@@ -504,7 +522,7 @@ func TestEntryFormatter_FormatRequest(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ss := newStaticSalt(t)
|
||||
cfg, err := NewFormatterConfig(WithOmitTime(tc.ShouldOmitTime))
|
||||
cfg, err := NewFormatterConfig(&testHeaderFormatter{}, WithOmitTime(tc.ShouldOmitTime))
|
||||
require.NoError(t, err)
|
||||
f, err := NewEntryFormatter("juan", cfg, ss, hclog.NewNullLogger())
|
||||
require.NoError(t, err)
|
||||
@@ -586,7 +604,7 @@ func TestEntryFormatter_FormatResponse(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ss := newStaticSalt(t)
|
||||
cfg, err := NewFormatterConfig(WithOmitTime(tc.ShouldOmitTime))
|
||||
cfg, err := NewFormatterConfig(&testHeaderFormatter{}, WithOmitTime(tc.ShouldOmitTime))
|
||||
require.NoError(t, err)
|
||||
f, err := NewEntryFormatter("juan", cfg, ss, hclog.NewNullLogger())
|
||||
require.NoError(t, err)
|
||||
@@ -702,9 +720,9 @@ func TestEntryFormatter_Process_JSON(t *testing.T) {
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
cfg, err := NewFormatterConfig(WithHMACAccessor(false))
|
||||
cfg, err := NewFormatterConfig(&testHeaderFormatter{}, WithHMACAccessor(false), WithPrefix(tc.Prefix))
|
||||
require.NoError(t, err)
|
||||
formatter, err := NewEntryFormatter("juan", cfg, ss, hclog.NewNullLogger(), WithPrefix(tc.Prefix))
|
||||
formatter, err := NewEntryFormatter("juan", cfg, ss, hclog.NewNullLogger())
|
||||
require.NoError(t, err)
|
||||
|
||||
in := &logical.LogInput{
|
||||
@@ -860,12 +878,14 @@ func TestEntryFormatter_Process_JSONx(t *testing.T) {
|
||||
|
||||
for name, tc := range cases {
|
||||
cfg, err := NewFormatterConfig(
|
||||
&testHeaderFormatter{},
|
||||
WithOmitTime(true),
|
||||
WithHMACAccessor(false),
|
||||
WithFormat(JSONxFormat.String()),
|
||||
WithPrefix(tc.Prefix),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
formatter, err := NewEntryFormatter("juan", cfg, tempStaticSalt, hclog.NewNullLogger(), WithPrefix(tc.Prefix))
|
||||
formatter, err := NewEntryFormatter("juan", cfg, tempStaticSalt, hclog.NewNullLogger())
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, formatter)
|
||||
|
||||
@@ -997,7 +1017,7 @@ func TestEntryFormatter_FormatResponse_ElideListResponses(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Run("Default case", func(t *testing.T) {
|
||||
config, err := NewFormatterConfig(WithElision(true))
|
||||
config, err := NewFormatterConfig(&testHeaderFormatter{}, WithElision(true))
|
||||
require.NoError(t, err)
|
||||
for name, tc := range tests {
|
||||
name := name
|
||||
@@ -1010,7 +1030,7 @@ func TestEntryFormatter_FormatResponse_ElideListResponses(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("When Operation is not list, eliding does not happen", func(t *testing.T) {
|
||||
config, err := NewFormatterConfig(WithElision(true))
|
||||
config, err := NewFormatterConfig(&testHeaderFormatter{}, WithElision(true))
|
||||
require.NoError(t, err)
|
||||
tc := oneInterestingTestCase
|
||||
entry := format(t, config, logical.ReadOperation, tc.inputData)
|
||||
@@ -1018,7 +1038,7 @@ func TestEntryFormatter_FormatResponse_ElideListResponses(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("When ElideListResponses is false, eliding does not happen", func(t *testing.T) {
|
||||
config, err := NewFormatterConfig(WithElision(false), WithFormat(JSONFormat.String()))
|
||||
config, err := NewFormatterConfig(&testHeaderFormatter{}, WithElision(false), WithFormat(JSONFormat.String()))
|
||||
require.NoError(t, err)
|
||||
tc := oneInterestingTestCase
|
||||
entry := format(t, config, logical.ListOperation, tc.inputData)
|
||||
@@ -1026,7 +1046,7 @@ func TestEntryFormatter_FormatResponse_ElideListResponses(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("When Raw is true, eliding still happens", func(t *testing.T) {
|
||||
config, err := NewFormatterConfig(WithElision(true), WithRaw(true), WithFormat(JSONFormat.String()))
|
||||
config, err := NewFormatterConfig(&testHeaderFormatter{}, WithElision(true), WithRaw(true), WithFormat(JSONFormat.String()))
|
||||
require.NoError(t, err)
|
||||
tc := oneInterestingTestCase
|
||||
entry := format(t, config, logical.ListOperation, tc.inputData)
|
||||
@@ -1040,7 +1060,7 @@ func TestEntryFormatter_Process_NoMutation(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create the formatter node.
|
||||
cfg, err := NewFormatterConfig()
|
||||
cfg, err := NewFormatterConfig(&testHeaderFormatter{})
|
||||
require.NoError(t, err)
|
||||
ss := newStaticSalt(t)
|
||||
formatter, err := NewEntryFormatter("juan", cfg, ss, hclog.NewNullLogger())
|
||||
@@ -1100,7 +1120,7 @@ func TestEntryFormatter_Process_Panic(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create the formatter node.
|
||||
cfg, err := NewFormatterConfig()
|
||||
cfg, err := NewFormatterConfig(&testHeaderFormatter{})
|
||||
require.NoError(t, err)
|
||||
ss := newStaticSalt(t)
|
||||
formatter, err := NewEntryFormatter("juan", cfg, ss, hclog.NewNullLogger())
|
||||
@@ -1153,6 +1173,53 @@ func TestEntryFormatter_Process_Panic(t *testing.T) {
|
||||
require.Nil(t, e2)
|
||||
}
|
||||
|
||||
// TestEntryFormatter_NewFormatterConfig_NilHeaderFormatter ensures we cannot
|
||||
// create a FormatterConfig using NewFormatterConfig if we supply a nil formatter.
|
||||
func TestEntryFormatter_NewFormatterConfig_NilHeaderFormatter(t *testing.T) {
|
||||
_, err := NewFormatterConfig(nil)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
// TestEntryFormatter_Process_NeverLeaksHeaders ensures that if we never accidentally
|
||||
// leak headers if applying them means we don't have any. This is more like a sense
|
||||
// check to ensure the returned event doesn't somehow end up with the headers 'back'.
|
||||
func TestEntryFormatter_Process_NeverLeaksHeaders(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create the formatter node.
|
||||
cfg, err := NewFormatterConfig(&testHeaderFormatter{shouldReturnEmpty: true})
|
||||
require.NoError(t, err)
|
||||
ss := newStaticSalt(t)
|
||||
formatter, err := NewEntryFormatter("juan", cfg, ss, hclog.NewNullLogger())
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, formatter)
|
||||
|
||||
// Set up the input and verify we have a single foo:bar header.
|
||||
var input *logical.LogInput
|
||||
err = json.Unmarshal([]byte(testFormatJSONReqBasicStrFmt), &input)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, input)
|
||||
require.ElementsMatch(t, input.Request.Headers["foo"], []string{"bar"})
|
||||
|
||||
e := fakeEvent(t, RequestType, input)
|
||||
|
||||
// Process the node.
|
||||
ctx := namespace.RootContext(context.Background())
|
||||
e2, err := formatter.Process(ctx, e)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, e2)
|
||||
|
||||
// Now check we can retrieve the formatted JSON.
|
||||
jsonFormatted, b2 := e2.Format(JSONFormat.String())
|
||||
require.True(t, b2)
|
||||
require.NotNil(t, jsonFormatted)
|
||||
var input2 *logical.LogInput
|
||||
err = json.Unmarshal(jsonFormatted, &input2)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, input2)
|
||||
require.Len(t, input2.Request.Headers, 0)
|
||||
}
|
||||
|
||||
// hashExpectedValueForComparison replicates enough of the audit HMAC process on a piece of expected data in a test,
|
||||
// so that we can use assert.Equal to compare the expected and output values.
|
||||
func (f *EntryFormatter) hashExpectedValueForComparison(input map[string]any) map[string]any {
|
||||
|
||||
@@ -5,7 +5,6 @@ package audit
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@@ -15,16 +14,15 @@ 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
|
||||
withID string
|
||||
withNow time.Time
|
||||
withSubtype subtype
|
||||
withFormat format
|
||||
withPrefix string
|
||||
withRaw bool
|
||||
withElision bool
|
||||
withOmitTime bool
|
||||
withHMACAccessor bool
|
||||
}
|
||||
|
||||
// getDefaultOptions returns options with their default values.
|
||||
@@ -163,15 +161,3 @@ func WithHMACAccessor(h bool) Option {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithHeaderFormatter provides an Option to supply a HeaderFormatter.
|
||||
// If the HeaderFormatter interface supplied is nil (type or value), the option will not be applied.
|
||||
func WithHeaderFormatter(f HeaderFormatter) Option {
|
||||
return func(o *options) error {
|
||||
if f != nil && !reflect.ValueOf(f).IsNil() {
|
||||
o.withHeaderFormatter = f
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
package audit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -380,47 +379,6 @@ 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
|
||||
ShouldLeaveUninitialized bool
|
||||
}{
|
||||
"nil": {
|
||||
Value: nil,
|
||||
ExpectedValue: nil,
|
||||
},
|
||||
"unassigned-interface": {
|
||||
ShouldLeaveUninitialized: true,
|
||||
},
|
||||
"happy-path": {
|
||||
Value: &testHeaderFormatter{},
|
||||
ExpectedValue: &testHeaderFormatter{},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range tests {
|
||||
name := name
|
||||
tc := tc
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
opts := &options{}
|
||||
var f HeaderFormatter
|
||||
if !tc.ShouldLeaveUninitialized {
|
||||
f = tc.Value
|
||||
}
|
||||
applyOption := WithHeaderFormatter(f)
|
||||
err := applyOption(opts)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.ExpectedValue, opts.withHeaderFormatter)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestOptions_Default exercises getDefaultOptions to assert the default values.
|
||||
func TestOptions_Default(t *testing.T) {
|
||||
t.Parallel()
|
||||
@@ -549,12 +507,3 @@ func TestOptions_Opts(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// testHeaderFormatter is a stub to prevent the need to import the vault package
|
||||
// to bring in vault.AuditedHeadersConfig for testing.
|
||||
type testHeaderFormatter struct{}
|
||||
|
||||
// ApplyConfig satisfied the HeaderFormatter interface for testing.
|
||||
func (f *testHeaderFormatter) ApplyConfig(ctx context.Context, headers map[string][]string, salter Salter) (result map[string][]string, retErr error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -99,6 +99,12 @@ type FormatterConfig struct {
|
||||
|
||||
// The required/target format for the event (supported: JSONFormat and JSONxFormat).
|
||||
RequiredFormat format
|
||||
|
||||
// headerFormatter specifies the formatter used for headers that existing in any incoming audit request.
|
||||
headerFormatter HeaderFormatter
|
||||
|
||||
// Prefix specifies a Prefix that should be prepended to any formatted request or response before serialization.
|
||||
Prefix string
|
||||
}
|
||||
|
||||
// RequestEntry is the structure of a request audit log entry.
|
||||
|
||||
@@ -112,17 +112,12 @@ func Factory(_ context.Context, conf *audit.BackendConfig, headersConfig audit.H
|
||||
return nil, fmt.Errorf("%s: error configuring filter node: %w", op, err)
|
||||
}
|
||||
|
||||
cfg, err := formatterConfig(conf.Config)
|
||||
cfg, err := newFormatterConfig(headersConfig, conf.Config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: failed to create formatter config: %w", op, err)
|
||||
}
|
||||
|
||||
formatterOpts := []audit.Option{
|
||||
audit.WithHeaderFormatter(headersConfig),
|
||||
audit.WithPrefix(conf.Config["prefix"]),
|
||||
}
|
||||
|
||||
err = b.configureFormatterNode(conf.MountPath, cfg, conf.Logger, formatterOpts...)
|
||||
err = b.configureFormatterNode(conf.MountPath, cfg, conf.Logger)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: error configuring formatter node: %w", op, err)
|
||||
}
|
||||
@@ -183,10 +178,10 @@ func (b *Backend) Invalidate(_ context.Context) {
|
||||
b.salt.Store((*salt.Salt)(nil))
|
||||
}
|
||||
|
||||
// formatterConfig creates the configuration required by a formatter node using
|
||||
// newFormatterConfig creates the configuration required by a formatter node using
|
||||
// the config map supplied to the factory.
|
||||
func formatterConfig(config map[string]string) (audit.FormatterConfig, error) {
|
||||
const op = "file.formatterConfig"
|
||||
func newFormatterConfig(headerFormatter audit.HeaderFormatter, config map[string]string) (audit.FormatterConfig, error) {
|
||||
const op = "file.newFormatterConfig"
|
||||
|
||||
var opts []audit.Option
|
||||
|
||||
@@ -220,11 +215,15 @@ func formatterConfig(config map[string]string) (audit.FormatterConfig, error) {
|
||||
opts = append(opts, audit.WithElision(v))
|
||||
}
|
||||
|
||||
return audit.NewFormatterConfig(opts...)
|
||||
if prefix, ok := config["prefix"]; ok {
|
||||
opts = append(opts, audit.WithPrefix(prefix))
|
||||
}
|
||||
|
||||
return audit.NewFormatterConfig(headerFormatter, opts...)
|
||||
}
|
||||
|
||||
// configureFormatterNode is used to configure a formatter node and associated ID on the Backend.
|
||||
func (b *Backend) configureFormatterNode(name string, formatConfig audit.FormatterConfig, logger hclog.Logger, opts ...audit.Option) error {
|
||||
func (b *Backend) configureFormatterNode(name string, formatConfig audit.FormatterConfig, logger hclog.Logger) error {
|
||||
const op = "file.(Backend).configureFormatterNode"
|
||||
|
||||
formatterNodeID, err := event.GenerateNodeID()
|
||||
@@ -232,7 +231,7 @@ func (b *Backend) configureFormatterNode(name string, formatConfig audit.Formatt
|
||||
return fmt.Errorf("%s: error generating random NodeID for formatter node: %w", op, err)
|
||||
}
|
||||
|
||||
formatterNode, err := audit.NewEntryFormatter(name, formatConfig, b, logger, opts...)
|
||||
formatterNode, err := audit.NewEntryFormatter(name, formatConfig, b, logger)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: error creating formatter: %w", op, err)
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/hashicorp/eventlogger"
|
||||
"github.com/hashicorp/go-hclog"
|
||||
"github.com/hashicorp/vault/audit"
|
||||
"github.com/hashicorp/vault/helper/testhelpers/corehelpers"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -74,7 +75,7 @@ func TestBackend_configureFilterFormatterSink(t *testing.T) {
|
||||
nodeMap: map[eventlogger.NodeID]eventlogger.Node{},
|
||||
}
|
||||
|
||||
formatConfig, err := audit.NewFormatterConfig()
|
||||
formatConfig, err := audit.NewFormatterConfig(&corehelpers.NoopHeaderFormatter{})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = b.configureFilterNode("path == bar")
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/hashicorp/eventlogger"
|
||||
"github.com/hashicorp/go-hclog"
|
||||
"github.com/hashicorp/vault/audit"
|
||||
"github.com/hashicorp/vault/helper/testhelpers/corehelpers"
|
||||
"github.com/hashicorp/vault/internal/observability/event"
|
||||
"github.com/hashicorp/vault/sdk/helper/salt"
|
||||
"github.com/hashicorp/vault/sdk/logical"
|
||||
@@ -40,7 +41,7 @@ func TestAuditFile_fileModeNew(t *testing.T) {
|
||||
SaltView: &logical.InmemStorage{},
|
||||
Logger: hclog.NewNullLogger(),
|
||||
}
|
||||
_, err = Factory(context.Background(), backendConfig, nil)
|
||||
_, err = Factory(context.Background(), backendConfig, &corehelpers.NoopHeaderFormatter{})
|
||||
require.NoError(t, err)
|
||||
|
||||
info, err := os.Stat(file)
|
||||
@@ -73,7 +74,7 @@ func TestAuditFile_fileModeExisting(t *testing.T) {
|
||||
Logger: hclog.NewNullLogger(),
|
||||
}
|
||||
|
||||
_, err = Factory(context.Background(), backendConfig, nil)
|
||||
_, err = Factory(context.Background(), backendConfig, &corehelpers.NoopHeaderFormatter{})
|
||||
require.NoError(t, err)
|
||||
|
||||
info, err := os.Stat(f.Name())
|
||||
@@ -107,7 +108,7 @@ func TestAuditFile_fileMode0000(t *testing.T) {
|
||||
Logger: hclog.NewNullLogger(),
|
||||
}
|
||||
|
||||
_, err = Factory(context.Background(), backendConfig, nil)
|
||||
_, err = Factory(context.Background(), backendConfig, &corehelpers.NoopHeaderFormatter{})
|
||||
require.NoError(t, err)
|
||||
|
||||
info, err := os.Stat(f.Name())
|
||||
@@ -136,7 +137,7 @@ func TestAuditFile_EventLogger_fileModeNew(t *testing.T) {
|
||||
Logger: hclog.NewNullLogger(),
|
||||
}
|
||||
|
||||
_, err = Factory(context.Background(), backendConfig, nil)
|
||||
_, err = Factory(context.Background(), backendConfig, &corehelpers.NoopHeaderFormatter{})
|
||||
require.NoError(t, err)
|
||||
|
||||
info, err := os.Stat(file)
|
||||
@@ -144,8 +145,8 @@ func TestAuditFile_EventLogger_fileModeNew(t *testing.T) {
|
||||
require.Equalf(t, os.FileMode(mode), info.Mode(), "File mode does not match.")
|
||||
}
|
||||
|
||||
// TestBackend_formatterConfig ensures that all the configuration values are parsed correctly.
|
||||
func TestBackend_formatterConfig(t *testing.T) {
|
||||
// TestBackend_newFormatterConfig ensures that all the configuration values are parsed correctly.
|
||||
func TestBackend_newFormatterConfig(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := map[string]struct {
|
||||
@@ -201,7 +202,7 @@ func TestBackend_formatterConfig(t *testing.T) {
|
||||
},
|
||||
want: audit.FormatterConfig{},
|
||||
wantErr: true,
|
||||
expectedMessage: "file.formatterConfig: unable to parse 'hmac_accessor': strconv.ParseBool: parsing \"maybe\": invalid syntax",
|
||||
expectedMessage: "file.newFormatterConfig: unable to parse 'hmac_accessor': strconv.ParseBool: parsing \"maybe\": invalid syntax",
|
||||
},
|
||||
"invalid-log-raw": {
|
||||
config: map[string]string{
|
||||
@@ -211,7 +212,7 @@ func TestBackend_formatterConfig(t *testing.T) {
|
||||
},
|
||||
want: audit.FormatterConfig{},
|
||||
wantErr: true,
|
||||
expectedMessage: "file.formatterConfig: unable to parse 'log_raw': strconv.ParseBool: parsing \"maybe\": invalid syntax",
|
||||
expectedMessage: "file.newFormatterConfig: unable to parse 'log_raw': strconv.ParseBool: parsing \"maybe\": invalid syntax",
|
||||
},
|
||||
"invalid-elide-bool": {
|
||||
config: map[string]string{
|
||||
@@ -222,7 +223,18 @@ func TestBackend_formatterConfig(t *testing.T) {
|
||||
},
|
||||
want: audit.FormatterConfig{},
|
||||
wantErr: true,
|
||||
expectedMessage: "file.formatterConfig: unable to parse 'elide_list_responses': strconv.ParseBool: parsing \"maybe\": invalid syntax",
|
||||
expectedMessage: "file.newFormatterConfig: unable to parse 'elide_list_responses': strconv.ParseBool: parsing \"maybe\": invalid syntax",
|
||||
},
|
||||
"prefix": {
|
||||
config: map[string]string{
|
||||
"format": audit.JSONFormat.String(),
|
||||
"prefix": "foo",
|
||||
},
|
||||
want: audit.FormatterConfig{
|
||||
RequiredFormat: audit.JSONFormat,
|
||||
Prefix: "foo",
|
||||
HMACAccessor: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
for name, tc := range tests {
|
||||
@@ -231,14 +243,19 @@ func TestBackend_formatterConfig(t *testing.T) {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
got, err := formatterConfig(tc.config)
|
||||
got, err := newFormatterConfig(&corehelpers.NoopHeaderFormatter{}, tc.config)
|
||||
if tc.wantErr {
|
||||
require.Error(t, err)
|
||||
require.EqualError(t, err, tc.expectedMessage)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
require.Equal(t, tc.want, got)
|
||||
require.Equal(t, tc.want.RequiredFormat, got.RequiredFormat)
|
||||
require.Equal(t, tc.want.Raw, got.Raw)
|
||||
require.Equal(t, tc.want.ElideListResponses, got.ElideListResponses)
|
||||
require.Equal(t, tc.want.HMACAccessor, got.HMACAccessor)
|
||||
require.Equal(t, tc.want.OmitTime, got.OmitTime)
|
||||
require.Equal(t, tc.want.Prefix, got.Prefix)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -253,7 +270,7 @@ func TestBackend_configureFormatterNode(t *testing.T) {
|
||||
nodeMap: map[eventlogger.NodeID]eventlogger.Node{},
|
||||
}
|
||||
|
||||
formatConfig, err := audit.NewFormatterConfig()
|
||||
formatConfig, err := audit.NewFormatterConfig(&corehelpers.NoopHeaderFormatter{})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = b.configureFormatterNode("juan", formatConfig, hclog.NewNullLogger())
|
||||
@@ -476,7 +493,7 @@ func TestBackend_Factory_Conf(t *testing.T) {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
be, err := Factory(ctx, tc.backendConfig, nil)
|
||||
be, err := Factory(ctx, tc.backendConfig, &corehelpers.NoopHeaderFormatter{})
|
||||
|
||||
switch {
|
||||
case tc.isErrorExpected:
|
||||
@@ -535,7 +552,7 @@ func TestBackend_IsFallback(t *testing.T) {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
be, err := Factory(ctx, tc.backendConfig, nil)
|
||||
be, err := Factory(ctx, tc.backendConfig, &corehelpers.NoopHeaderFormatter{})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, be)
|
||||
require.Equal(t, tc.isFallbackExpected, be.IsFallback())
|
||||
|
||||
@@ -93,17 +93,12 @@ func Factory(_ context.Context, conf *audit.BackendConfig, headersConfig audit.H
|
||||
return nil, fmt.Errorf("%s: error configuring filter node: %w", op, err)
|
||||
}
|
||||
|
||||
cfg, err := formatterConfig(conf.Config)
|
||||
cfg, err := newFormatterConfig(headersConfig, conf.Config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: failed to create formatter config: %w", op, err)
|
||||
}
|
||||
|
||||
opts := []audit.Option{
|
||||
audit.WithHeaderFormatter(headersConfig),
|
||||
audit.WithPrefix(conf.Config["prefix"]),
|
||||
}
|
||||
|
||||
err = b.configureFormatterNode(conf.MountPath, cfg, conf.Logger, opts...)
|
||||
err = b.configureFormatterNode(conf.MountPath, cfg, conf.Logger)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: error configuring formatter node: %w", op, err)
|
||||
}
|
||||
@@ -165,15 +160,15 @@ func (b *Backend) Invalidate(_ context.Context) {
|
||||
b.salt = nil
|
||||
}
|
||||
|
||||
// formatterConfig creates the configuration required by a formatter node using
|
||||
// newFormatterConfig creates the configuration required by a formatter node using
|
||||
// the config map supplied to the factory.
|
||||
func formatterConfig(config map[string]string) (audit.FormatterConfig, error) {
|
||||
const op = "socket.formatterConfig"
|
||||
func newFormatterConfig(headerFormatter audit.HeaderFormatter, config map[string]string) (audit.FormatterConfig, error) {
|
||||
const op = "socket.newFormatterConfig"
|
||||
|
||||
var cfgOpts []audit.Option
|
||||
var opts []audit.Option
|
||||
|
||||
if format, ok := config["format"]; ok {
|
||||
cfgOpts = append(cfgOpts, audit.WithFormat(format))
|
||||
opts = append(opts, audit.WithFormat(format))
|
||||
}
|
||||
|
||||
// Check if hashing of accessor is disabled
|
||||
@@ -182,7 +177,7 @@ func formatterConfig(config map[string]string) (audit.FormatterConfig, error) {
|
||||
if err != nil {
|
||||
return audit.FormatterConfig{}, fmt.Errorf("%s: unable to parse 'hmac_accessor': %w", op, err)
|
||||
}
|
||||
cfgOpts = append(cfgOpts, audit.WithHMACAccessor(v))
|
||||
opts = append(opts, audit.WithHMACAccessor(v))
|
||||
}
|
||||
|
||||
// Check if raw logging is enabled
|
||||
@@ -191,7 +186,7 @@ func formatterConfig(config map[string]string) (audit.FormatterConfig, error) {
|
||||
if err != nil {
|
||||
return audit.FormatterConfig{}, fmt.Errorf("%s: unable to parse 'log_raw': %w", op, err)
|
||||
}
|
||||
cfgOpts = append(cfgOpts, audit.WithRaw(v))
|
||||
opts = append(opts, audit.WithRaw(v))
|
||||
}
|
||||
|
||||
if elideListResponsesRaw, ok := config["elide_list_responses"]; ok {
|
||||
@@ -199,14 +194,18 @@ func formatterConfig(config map[string]string) (audit.FormatterConfig, error) {
|
||||
if err != nil {
|
||||
return audit.FormatterConfig{}, fmt.Errorf("%s: unable to parse 'elide_list_responses': %w", op, err)
|
||||
}
|
||||
cfgOpts = append(cfgOpts, audit.WithElision(v))
|
||||
opts = append(opts, audit.WithElision(v))
|
||||
}
|
||||
|
||||
return audit.NewFormatterConfig(cfgOpts...)
|
||||
if prefix, ok := config["prefix"]; ok {
|
||||
opts = append(opts, audit.WithPrefix(prefix))
|
||||
}
|
||||
|
||||
return audit.NewFormatterConfig(headerFormatter, opts...)
|
||||
}
|
||||
|
||||
// configureFormatterNode is used to configure a formatter node and associated ID on the Backend.
|
||||
func (b *Backend) configureFormatterNode(name string, formatConfig audit.FormatterConfig, logger hclog.Logger, opts ...audit.Option) error {
|
||||
func (b *Backend) configureFormatterNode(name string, formatConfig audit.FormatterConfig, logger hclog.Logger) error {
|
||||
const op = "socket.(Backend).configureFormatterNode"
|
||||
|
||||
formatterNodeID, err := event.GenerateNodeID()
|
||||
@@ -214,7 +213,7 @@ func (b *Backend) configureFormatterNode(name string, formatConfig audit.Formatt
|
||||
return fmt.Errorf("%s: error generating random NodeID for formatter node: %w", op, err)
|
||||
}
|
||||
|
||||
formatterNode, err := audit.NewEntryFormatter(name, formatConfig, b, logger, opts...)
|
||||
formatterNode, err := audit.NewEntryFormatter(name, formatConfig, b, logger)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: error creating formatter: %w", op, err)
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/hashicorp/eventlogger"
|
||||
"github.com/hashicorp/go-hclog"
|
||||
"github.com/hashicorp/vault/audit"
|
||||
"github.com/hashicorp/vault/helper/testhelpers/corehelpers"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -74,7 +75,7 @@ func TestBackend_configureFilterFormatterSink(t *testing.T) {
|
||||
nodeMap: map[eventlogger.NodeID]eventlogger.Node{},
|
||||
}
|
||||
|
||||
formatConfig, err := audit.NewFormatterConfig()
|
||||
formatConfig, err := audit.NewFormatterConfig(&corehelpers.NoopHeaderFormatter{})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = b.configureFilterNode("path == bar")
|
||||
|
||||
@@ -10,14 +10,15 @@ import (
|
||||
"github.com/hashicorp/eventlogger"
|
||||
"github.com/hashicorp/go-hclog"
|
||||
"github.com/hashicorp/vault/audit"
|
||||
"github.com/hashicorp/vault/helper/testhelpers/corehelpers"
|
||||
"github.com/hashicorp/vault/internal/observability/event"
|
||||
"github.com/hashicorp/vault/sdk/helper/salt"
|
||||
"github.com/hashicorp/vault/sdk/logical"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestBackend_formatterConfig ensures that all the configuration values are parsed correctly.
|
||||
func TestBackend_formatterConfig(t *testing.T) {
|
||||
// TestBackend_newFormatterConfig ensures that all the configuration values are parsed correctly.
|
||||
func TestBackend_newFormatterConfig(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := map[string]struct {
|
||||
@@ -73,7 +74,7 @@ func TestBackend_formatterConfig(t *testing.T) {
|
||||
},
|
||||
want: audit.FormatterConfig{},
|
||||
wantErr: true,
|
||||
expectedErrMsg: "socket.formatterConfig: unable to parse 'hmac_accessor': strconv.ParseBool: parsing \"maybe\": invalid syntax",
|
||||
expectedErrMsg: "socket.newFormatterConfig: unable to parse 'hmac_accessor': strconv.ParseBool: parsing \"maybe\": invalid syntax",
|
||||
},
|
||||
"invalid-log-raw": {
|
||||
config: map[string]string{
|
||||
@@ -83,7 +84,7 @@ func TestBackend_formatterConfig(t *testing.T) {
|
||||
},
|
||||
want: audit.FormatterConfig{},
|
||||
wantErr: true,
|
||||
expectedErrMsg: "socket.formatterConfig: unable to parse 'log_raw': strconv.ParseBool: parsing \"maybe\": invalid syntax",
|
||||
expectedErrMsg: "socket.newFormatterConfig: unable to parse 'log_raw': strconv.ParseBool: parsing \"maybe\": invalid syntax",
|
||||
},
|
||||
"invalid-elide-bool": {
|
||||
config: map[string]string{
|
||||
@@ -94,7 +95,18 @@ func TestBackend_formatterConfig(t *testing.T) {
|
||||
},
|
||||
want: audit.FormatterConfig{},
|
||||
wantErr: true,
|
||||
expectedErrMsg: "socket.formatterConfig: unable to parse 'elide_list_responses': strconv.ParseBool: parsing \"maybe\": invalid syntax",
|
||||
expectedErrMsg: "socket.newFormatterConfig: unable to parse 'elide_list_responses': strconv.ParseBool: parsing \"maybe\": invalid syntax",
|
||||
},
|
||||
"prefix": {
|
||||
config: map[string]string{
|
||||
"format": audit.JSONFormat.String(),
|
||||
"prefix": "foo",
|
||||
},
|
||||
want: audit.FormatterConfig{
|
||||
RequiredFormat: audit.JSONFormat,
|
||||
Prefix: "foo",
|
||||
HMACAccessor: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
for name, tc := range tests {
|
||||
@@ -103,14 +115,19 @@ func TestBackend_formatterConfig(t *testing.T) {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
got, err := formatterConfig(tc.config)
|
||||
got, err := newFormatterConfig(&corehelpers.NoopHeaderFormatter{}, tc.config)
|
||||
if tc.wantErr {
|
||||
require.Error(t, err)
|
||||
require.EqualError(t, err, tc.expectedErrMsg)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
require.Equal(t, tc.want, got)
|
||||
require.Equal(t, tc.want.RequiredFormat, got.RequiredFormat)
|
||||
require.Equal(t, tc.want.Raw, got.Raw)
|
||||
require.Equal(t, tc.want.ElideListResponses, got.ElideListResponses)
|
||||
require.Equal(t, tc.want.HMACAccessor, got.HMACAccessor)
|
||||
require.Equal(t, tc.want.OmitTime, got.OmitTime)
|
||||
require.Equal(t, tc.want.Prefix, got.Prefix)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -125,7 +142,7 @@ func TestBackend_configureFormatterNode(t *testing.T) {
|
||||
nodeMap: map[eventlogger.NodeID]eventlogger.Node{},
|
||||
}
|
||||
|
||||
formatConfig, err := audit.NewFormatterConfig()
|
||||
formatConfig, err := audit.NewFormatterConfig(&corehelpers.NoopHeaderFormatter{})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = b.configureFormatterNode("juan", formatConfig, hclog.NewNullLogger())
|
||||
@@ -370,7 +387,7 @@ func TestBackend_Factory_Conf(t *testing.T) {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
be, err := Factory(ctx, tc.backendConfig, nil)
|
||||
be, err := Factory(ctx, tc.backendConfig, &corehelpers.NoopHeaderFormatter{})
|
||||
|
||||
switch {
|
||||
case tc.isErrorExpected:
|
||||
@@ -431,7 +448,7 @@ func TestBackend_IsFallback(t *testing.T) {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
be, err := Factory(ctx, tc.backendConfig, nil)
|
||||
be, err := Factory(ctx, tc.backendConfig, &corehelpers.NoopHeaderFormatter{})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, be)
|
||||
require.Equal(t, tc.isFallbackExpected, be.IsFallback())
|
||||
|
||||
@@ -90,17 +90,12 @@ func Factory(_ context.Context, conf *audit.BackendConfig, headersConfig audit.H
|
||||
return nil, fmt.Errorf("%s: error configuring filter node: %w", op, err)
|
||||
}
|
||||
|
||||
cfg, err := formatterConfig(conf.Config)
|
||||
cfg, err := newFormatterConfig(headersConfig, conf.Config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: failed to create formatter config: %w", op, err)
|
||||
}
|
||||
|
||||
formatterOpts := []audit.Option{
|
||||
audit.WithHeaderFormatter(headersConfig),
|
||||
audit.WithPrefix(conf.Config["prefix"]),
|
||||
}
|
||||
|
||||
err = b.configureFormatterNode(conf.MountPath, cfg, conf.Logger, formatterOpts...)
|
||||
err = b.configureFormatterNode(conf.MountPath, cfg, conf.Logger)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: error configuring formatter node: %w", op, err)
|
||||
}
|
||||
@@ -156,10 +151,10 @@ func (b *Backend) Invalidate(_ context.Context) {
|
||||
b.salt = nil
|
||||
}
|
||||
|
||||
// formatterConfig creates the configuration required by a formatter node using
|
||||
// newFormatterConfig creates the configuration required by a formatter node using
|
||||
// the config map supplied to the factory.
|
||||
func formatterConfig(config map[string]string) (audit.FormatterConfig, error) {
|
||||
const op = "syslog.formatterConfig"
|
||||
func newFormatterConfig(headerFormatter audit.HeaderFormatter, config map[string]string) (audit.FormatterConfig, error) {
|
||||
const op = "syslog.newFormatterConfig"
|
||||
|
||||
var opts []audit.Option
|
||||
|
||||
@@ -193,11 +188,15 @@ func formatterConfig(config map[string]string) (audit.FormatterConfig, error) {
|
||||
opts = append(opts, audit.WithElision(v))
|
||||
}
|
||||
|
||||
return audit.NewFormatterConfig(opts...)
|
||||
if prefix, ok := config["prefix"]; ok {
|
||||
opts = append(opts, audit.WithPrefix(prefix))
|
||||
}
|
||||
|
||||
return audit.NewFormatterConfig(headerFormatter, opts...)
|
||||
}
|
||||
|
||||
// configureFormatterNode is used to configure a formatter node and associated ID on the Backend.
|
||||
func (b *Backend) configureFormatterNode(name string, formatConfig audit.FormatterConfig, logger hclog.Logger, opts ...audit.Option) error {
|
||||
func (b *Backend) configureFormatterNode(name string, formatConfig audit.FormatterConfig, logger hclog.Logger) error {
|
||||
const op = "syslog.(Backend).configureFormatterNode"
|
||||
|
||||
formatterNodeID, err := event.GenerateNodeID()
|
||||
@@ -205,7 +204,7 @@ func (b *Backend) configureFormatterNode(name string, formatConfig audit.Formatt
|
||||
return fmt.Errorf("%s: error generating random NodeID for formatter node: %w", op, err)
|
||||
}
|
||||
|
||||
formatterNode, err := audit.NewEntryFormatter(name, formatConfig, b, logger, opts...)
|
||||
formatterNode, err := audit.NewEntryFormatter(name, formatConfig, b, logger)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: error creating formatter: %w", op, err)
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/hashicorp/eventlogger"
|
||||
"github.com/hashicorp/go-hclog"
|
||||
"github.com/hashicorp/vault/audit"
|
||||
"github.com/hashicorp/vault/helper/testhelpers/corehelpers"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -74,7 +75,7 @@ func TestBackend_configureFilterFormatterSink(t *testing.T) {
|
||||
nodeMap: map[eventlogger.NodeID]eventlogger.Node{},
|
||||
}
|
||||
|
||||
formatConfig, err := audit.NewFormatterConfig()
|
||||
formatConfig, err := audit.NewFormatterConfig(&corehelpers.NoopHeaderFormatter{})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = b.configureFilterNode("path == bar")
|
||||
|
||||
@@ -10,14 +10,15 @@ import (
|
||||
"github.com/hashicorp/eventlogger"
|
||||
"github.com/hashicorp/go-hclog"
|
||||
"github.com/hashicorp/vault/audit"
|
||||
"github.com/hashicorp/vault/helper/testhelpers/corehelpers"
|
||||
"github.com/hashicorp/vault/internal/observability/event"
|
||||
"github.com/hashicorp/vault/sdk/helper/salt"
|
||||
"github.com/hashicorp/vault/sdk/logical"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestBackend_formatterConfig ensures that all the configuration values are parsed correctly.
|
||||
func TestBackend_formatterConfig(t *testing.T) {
|
||||
// TestBackend_newFormatterConfig ensures that all the configuration values are parsed correctly.
|
||||
func TestBackend_newFormatterConfig(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := map[string]struct {
|
||||
@@ -73,7 +74,7 @@ func TestBackend_formatterConfig(t *testing.T) {
|
||||
},
|
||||
want: audit.FormatterConfig{},
|
||||
wantErr: true,
|
||||
expectedErrMsg: "syslog.formatterConfig: unable to parse 'hmac_accessor': strconv.ParseBool: parsing \"maybe\": invalid syntax",
|
||||
expectedErrMsg: "syslog.newFormatterConfig: unable to parse 'hmac_accessor': strconv.ParseBool: parsing \"maybe\": invalid syntax",
|
||||
},
|
||||
"invalid-log-raw": {
|
||||
config: map[string]string{
|
||||
@@ -83,7 +84,7 @@ func TestBackend_formatterConfig(t *testing.T) {
|
||||
},
|
||||
want: audit.FormatterConfig{},
|
||||
wantErr: true,
|
||||
expectedErrMsg: "syslog.formatterConfig: unable to parse 'log_raw': strconv.ParseBool: parsing \"maybe\": invalid syntax",
|
||||
expectedErrMsg: "syslog.newFormatterConfig: unable to parse 'log_raw': strconv.ParseBool: parsing \"maybe\": invalid syntax",
|
||||
},
|
||||
"invalid-elide-bool": {
|
||||
config: map[string]string{
|
||||
@@ -94,7 +95,18 @@ func TestBackend_formatterConfig(t *testing.T) {
|
||||
},
|
||||
want: audit.FormatterConfig{},
|
||||
wantErr: true,
|
||||
expectedErrMsg: "syslog.formatterConfig: unable to parse 'elide_list_responses': strconv.ParseBool: parsing \"maybe\": invalid syntax",
|
||||
expectedErrMsg: "syslog.newFormatterConfig: unable to parse 'elide_list_responses': strconv.ParseBool: parsing \"maybe\": invalid syntax",
|
||||
},
|
||||
"prefix": {
|
||||
config: map[string]string{
|
||||
"format": audit.JSONFormat.String(),
|
||||
"prefix": "foo",
|
||||
},
|
||||
want: audit.FormatterConfig{
|
||||
RequiredFormat: audit.JSONFormat,
|
||||
Prefix: "foo",
|
||||
HMACAccessor: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
for name, tc := range tests {
|
||||
@@ -103,14 +115,19 @@ func TestBackend_formatterConfig(t *testing.T) {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
got, err := formatterConfig(tc.config)
|
||||
got, err := newFormatterConfig(&corehelpers.NoopHeaderFormatter{}, tc.config)
|
||||
if tc.wantErr {
|
||||
require.Error(t, err)
|
||||
require.EqualError(t, err, tc.expectedErrMsg)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
require.Equal(t, tc.want, got)
|
||||
require.Equal(t, tc.want.RequiredFormat, got.RequiredFormat)
|
||||
require.Equal(t, tc.want.Raw, got.Raw)
|
||||
require.Equal(t, tc.want.ElideListResponses, got.ElideListResponses)
|
||||
require.Equal(t, tc.want.HMACAccessor, got.HMACAccessor)
|
||||
require.Equal(t, tc.want.OmitTime, got.OmitTime)
|
||||
require.Equal(t, tc.want.Prefix, got.Prefix)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -125,7 +142,7 @@ func TestBackend_configureFormatterNode(t *testing.T) {
|
||||
nodeMap: map[eventlogger.NodeID]eventlogger.Node{},
|
||||
}
|
||||
|
||||
formatConfig, err := audit.NewFormatterConfig()
|
||||
formatConfig, err := audit.NewFormatterConfig(&corehelpers.NoopHeaderFormatter{})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = b.configureFormatterNode("juan", formatConfig, hclog.NewNullLogger())
|
||||
@@ -274,7 +291,7 @@ func TestBackend_Factory_Conf(t *testing.T) {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
be, err := Factory(ctx, tc.backendConfig, nil)
|
||||
be, err := Factory(ctx, tc.backendConfig, &corehelpers.NoopHeaderFormatter{})
|
||||
|
||||
switch {
|
||||
case tc.isErrorExpected:
|
||||
@@ -331,7 +348,7 @@ func TestBackend_IsFallback(t *testing.T) {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
be, err := Factory(ctx, tc.backendConfig, nil)
|
||||
be, err := Factory(ctx, tc.backendConfig, &corehelpers.NoopHeaderFormatter{})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, be)
|
||||
require.Equal(t, tc.isFallbackExpected, be.IsFallback())
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -216,23 +217,44 @@ func (m *mockBuiltinRegistry) DeprecationStatus(name string, pluginType consts.P
|
||||
return consts.Unknown, false
|
||||
}
|
||||
|
||||
func TestNoopAudit(t testing.T, path string, config map[string]string, opts ...audit.Option) *NoopAudit {
|
||||
func TestNoopAudit(t testing.T, path string, config map[string]string) *NoopAudit {
|
||||
cfg := &audit.BackendConfig{
|
||||
Config: config,
|
||||
MountPath: path,
|
||||
Logger: NewTestLogger(t),
|
||||
}
|
||||
n, err := NewNoopAudit(cfg, opts...)
|
||||
n, err := NewNoopAudit(cfg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// NoopHeaderFormatter can be used within no-op audit devices to do nothing when
|
||||
// it comes to only allow configured headers to appear in the result.
|
||||
// Whatever is passed in will be returned (nil becomes an empty map) in lowercase.
|
||||
type NoopHeaderFormatter struct{}
|
||||
|
||||
// ApplyConfig implements the relevant interface to make NoopHeaderFormatter an audit.HeaderFormatter.
|
||||
func (f *NoopHeaderFormatter) ApplyConfig(_ context.Context, headers map[string][]string, _ audit.Salter) (result map[string][]string, retErr error) {
|
||||
if len(headers) < 1 {
|
||||
return map[string][]string{}, nil
|
||||
}
|
||||
|
||||
// Make a copy of the incoming headers with everything lower so we can
|
||||
// case-insensitively compare
|
||||
lowerHeaders := make(map[string][]string, len(headers))
|
||||
for k, v := range headers {
|
||||
lowerHeaders[strings.ToLower(k)] = v
|
||||
}
|
||||
|
||||
return lowerHeaders, nil
|
||||
}
|
||||
|
||||
// NewNoopAudit should be used to create a NoopAudit as it handles creation of a
|
||||
// predictable salt and wraps eventlogger nodes so information can be retrieved on
|
||||
// what they've seen or formatted.
|
||||
func NewNoopAudit(config *audit.BackendConfig, opts ...audit.Option) (*NoopAudit, error) {
|
||||
func NewNoopAudit(config *audit.BackendConfig) (*NoopAudit, error) {
|
||||
view := &logical.InmemStorage{}
|
||||
|
||||
// Create the salt with a known key for predictable hmac values.
|
||||
@@ -259,7 +281,7 @@ func NewNoopAudit(config *audit.BackendConfig, opts ...audit.Option) (*NoopAudit
|
||||
nodeMap: make(map[eventlogger.NodeID]eventlogger.Node, 2),
|
||||
}
|
||||
|
||||
cfg, err := audit.NewFormatterConfig()
|
||||
cfg, err := audit.NewFormatterConfig(&NoopHeaderFormatter{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -269,7 +291,7 @@ func NewNoopAudit(config *audit.BackendConfig, opts ...audit.Option) (*NoopAudit
|
||||
return nil, fmt.Errorf("error generating random NodeID for formatter node: %w", err)
|
||||
}
|
||||
|
||||
formatterNode, err := audit.NewEntryFormatter(config.MountPath, cfg, noopBackend, config.Logger, opts...)
|
||||
formatterNode, err := audit.NewEntryFormatter(config.MountPath, cfg, noopBackend, config.Logger)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating formatter: %w", err)
|
||||
}
|
||||
@@ -296,8 +318,8 @@ func NewNoopAudit(config *audit.BackendConfig, opts ...audit.Option) (*NoopAudit
|
||||
// have been formatted by the pipeline during audit requests.
|
||||
// The records parameter will be repointed to the one used within the pipeline.
|
||||
func NoopAuditFactory(records **[][]byte) audit.Factory {
|
||||
return func(_ context.Context, config *audit.BackendConfig, headerFormatter audit.HeaderFormatter) (audit.Backend, error) {
|
||||
n, err := NewNoopAudit(config, audit.WithHeaderFormatter(headerFormatter))
|
||||
return func(_ context.Context, config *audit.BackendConfig, _ audit.HeaderFormatter) (audit.Backend, error) {
|
||||
n, err := NewNoopAudit(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -345,6 +345,7 @@ func TestAuditBroker_LogRequest(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
a1 := corehelpers.TestNoopAudit(t, "foo", nil)
|
||||
a2 := corehelpers.TestNoopAudit(t, "bar", nil)
|
||||
err = b.Register("foo", a1, false)
|
||||
@@ -433,6 +434,7 @@ func TestAuditBroker_LogResponse(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
a1 := corehelpers.TestNoopAudit(t, "foo", nil)
|
||||
a2 := corehelpers.TestNoopAudit(t, "bar", nil)
|
||||
err = b.Register("foo", a1, false)
|
||||
@@ -537,19 +539,9 @@ func TestAuditBroker_AuditHeaders(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, barrier, _ := mockBarrier(t)
|
||||
view := NewBarrierView(barrier, "headers/")
|
||||
|
||||
headersConf := &AuditedHeadersConfig{
|
||||
view: view,
|
||||
}
|
||||
err = headersConf.add(context.Background(), "X-Test-Header", false)
|
||||
require.NoError(t, err)
|
||||
err = headersConf.add(context.Background(), "X-Vault-Header", false)
|
||||
require.NoError(t, err)
|
||||
|
||||
a1 := corehelpers.TestNoopAudit(t, "foo", nil, audit.WithHeaderFormatter(headersConf))
|
||||
a2 := corehelpers.TestNoopAudit(t, "bar", nil, audit.WithHeaderFormatter(headersConf))
|
||||
a1 := corehelpers.TestNoopAudit(t, "foo", nil)
|
||||
a2 := corehelpers.TestNoopAudit(t, "bar", nil)
|
||||
|
||||
err = b.Register("foo", a1, false)
|
||||
require.NoError(t, err)
|
||||
@@ -570,7 +562,6 @@ func TestAuditBroker_AuditHeaders(t *testing.T) {
|
||||
Headers: map[string][]string{
|
||||
"X-Test-Header": {"foo"},
|
||||
"X-Vault-Header": {"bar"},
|
||||
"Content-Type": {"baz"},
|
||||
},
|
||||
}
|
||||
respErr := fmt.Errorf("permission denied")
|
||||
|
||||
@@ -182,8 +182,14 @@ func (a *AuditedHeadersConfig) invalidate(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ApplyConfig returns a map of approved headers and their values, either hmac'ed or plaintext.
|
||||
// ApplyConfig returns a map of approved headers and their values, either HMAC'd or plaintext.
|
||||
// If the supplied headers are empty or nil, an empty set of headers will be returned.
|
||||
func (a *AuditedHeadersConfig) ApplyConfig(ctx context.Context, headers map[string][]string, salter audit.Salter) (result map[string][]string, retErr error) {
|
||||
// Return early if we don't have headers.
|
||||
if len(headers) < 1 {
|
||||
return map[string][]string{}, nil
|
||||
}
|
||||
|
||||
// Grab a read lock
|
||||
a.RLock()
|
||||
defer a.RUnlock()
|
||||
|
||||
@@ -273,21 +273,22 @@ func TestAuditedHeadersConfig_ApplyConfig(t *testing.T) {
|
||||
func TestAuditedHeadersConfig_ApplyConfig_NoRequestHeaders(t *testing.T) {
|
||||
conf := mockAuditedHeadersConfig(t)
|
||||
|
||||
conf.add(context.Background(), "X-TesT-Header", false)
|
||||
conf.add(context.Background(), "X-Vault-HeAdEr", true)
|
||||
|
||||
reqHeaders := map[string][]string{}
|
||||
err := conf.add(context.Background(), "X-TesT-Header", false)
|
||||
require.NoError(t, err)
|
||||
err = conf.add(context.Background(), "X-Vault-HeAdEr", true)
|
||||
require.NoError(t, err)
|
||||
|
||||
salter := &TestSalter{}
|
||||
|
||||
result, err := conf.ApplyConfig(context.Background(), reqHeaders, salter)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Test sending in nil headers first.
|
||||
result, err := conf.ApplyConfig(context.Background(), nil, salter)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, result)
|
||||
|
||||
if len(result) != 0 {
|
||||
t.Fatalf("Expected no headers but actually got: %d\n", len(result))
|
||||
}
|
||||
result, err = conf.ApplyConfig(context.Background(), map[string][]string{}, salter)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, result)
|
||||
require.Len(t, result, 0)
|
||||
}
|
||||
|
||||
func TestAuditedHeadersConfig_ApplyConfig_NoConfiguredHeaders(t *testing.T) {
|
||||
|
||||
@@ -1545,9 +1545,9 @@ func TestCore_HandleRequest_AuditTrail(t *testing.T) {
|
||||
// Create a noop audit backend
|
||||
var noop *corehelpers.NoopAudit
|
||||
c, _, root := TestCoreUnsealed(t)
|
||||
c.auditBackends["noop"] = func(ctx context.Context, config *audit.BackendConfig, headerFormatter audit.HeaderFormatter) (audit.Backend, error) {
|
||||
c.auditBackends["noop"] = func(_ context.Context, config *audit.BackendConfig, _ audit.HeaderFormatter) (audit.Backend, error) {
|
||||
var err error
|
||||
noop, err = corehelpers.NewNoopAudit(config, audit.WithHeaderFormatter(headerFormatter))
|
||||
noop, err = corehelpers.NewNoopAudit(config)
|
||||
return noop, err
|
||||
}
|
||||
|
||||
@@ -1608,9 +1608,9 @@ func TestCore_HandleRequest_AuditTrail_noHMACKeys(t *testing.T) {
|
||||
// Create a noop audit backend
|
||||
var noop *corehelpers.NoopAudit
|
||||
c, _, root := TestCoreUnsealed(t)
|
||||
c.auditBackends["noop"] = func(ctx context.Context, config *audit.BackendConfig, headerFormatter audit.HeaderFormatter) (audit.Backend, error) {
|
||||
c.auditBackends["noop"] = func(_ context.Context, config *audit.BackendConfig, _ audit.HeaderFormatter) (audit.Backend, error) {
|
||||
var err error
|
||||
noop, err = corehelpers.NewNoopAudit(config, audit.WithHeaderFormatter(headerFormatter))
|
||||
noop, err = corehelpers.NewNoopAudit(config)
|
||||
return noop, err
|
||||
}
|
||||
|
||||
@@ -1729,9 +1729,9 @@ func TestCore_HandleLogin_AuditTrail(t *testing.T) {
|
||||
c.credentialBackends["noop"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
|
||||
return noopBack, nil
|
||||
}
|
||||
c.auditBackends["noop"] = func(ctx context.Context, config *audit.BackendConfig, headerFormatter audit.HeaderFormatter) (audit.Backend, error) {
|
||||
c.auditBackends["noop"] = func(_ context.Context, config *audit.BackendConfig, _ audit.HeaderFormatter) (audit.Backend, error) {
|
||||
var err error
|
||||
noop, err = corehelpers.NewNoopAudit(config, audit.WithHeaderFormatter(headerFormatter))
|
||||
noop, err = corehelpers.NewNoopAudit(config)
|
||||
return noop, err
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user