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:
Peter Wilson
2024-04-03 10:53:34 +01:00
committed by GitHub
parent 39499e6fba
commit e0a905e8f8
19 changed files with 319 additions and 243 deletions

View File

@@ -41,14 +41,11 @@ type EntryFormatter struct {
config FormatterConfig config FormatterConfig
salter Salter salter Salter
logger hclog.Logger logger hclog.Logger
headerFormatter HeaderFormatter
name string name string
prefix string
} }
// NewEntryFormatter should be used to create an EntryFormatter. // NewEntryFormatter should be used to create an EntryFormatter.
// Accepted options: WithHeaderFormatter, WithPrefix. func NewEntryFormatter(name string, config FormatterConfig, salter Salter, logger hclog.Logger) (*EntryFormatter, error) {
func NewEntryFormatter(name string, config FormatterConfig, salter Salter, logger hclog.Logger, opt ...Option) (*EntryFormatter, error) {
const op = "audit.NewEntryFormatter" const op = "audit.NewEntryFormatter"
name = strings.TrimSpace(name) 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) 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{ return &EntryFormatter{
config: config, config: config,
salter: salter, salter: salter,
logger: logger, logger: logger,
headerFormatter: opts.withHeaderFormatter,
name: name, name: name,
prefix: opts.withPrefix,
}, nil }, 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) return nil, fmt.Errorf("%s: unable to copy audit event data: %w", op, err)
} }
// 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 // 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. // only present if they have been configured to appear in the audit log.
// e.g. via: /sys/config/auditing/request-headers/:name // 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.config.headerFormatter.ApplyConfig(ctx, data.Request.Headers, f.salter)
data.Request.Headers, err = f.headerFormatter.ApplyConfig(ctx, data.Request.Headers, f.salter)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s: unable to transform headers for auditing: %w", op, err) 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. // don't support a prefix just sitting there.
// However, this would be a breaking change to how Vault currently works to // 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. // include the prefix as part of the JSON object or XML document.
if f.prefix != "" { if f.config.Prefix != "" {
result = append([]byte(f.prefix), result...) result = append([]byte(f.config.Prefix), result...)
} }
// Copy some properties from the event (and audit event) and store the // 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. // NewFormatterConfig should be used to create a FormatterConfig.
// Accepted options: WithElision, WithHMACAccessor, WithOmitTime, WithRaw, WithFormat. // Accepted options: WithElision, WithFormat, WithHMACAccessor, WithOmitTime, WithPrefix, WithRaw.
func NewFormatterConfig(opt ...Option) (FormatterConfig, error) { func NewFormatterConfig(headerFormatter HeaderFormatter, opt ...Option) (FormatterConfig, error) {
const op = "audit.NewFormatterConfig" 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...) opts, err := getOpts(opt...)
if err != nil { if err != nil {
return FormatterConfig{}, fmt.Errorf("%s: error applying options: %w", op, err) return FormatterConfig{}, fmt.Errorf("%s: error applying options: %w", op, err)
} }
return FormatterConfig{ return FormatterConfig{
headerFormatter: headerFormatter,
ElideListResponses: opts.withElision, ElideListResponses: opts.withElision,
HMACAccessor: opts.withHMACAccessor, HMACAccessor: opts.withHMACAccessor,
OmitTime: opts.withOmitTime, OmitTime: opts.withOmitTime,
Prefix: opts.withPrefix,
Raw: opts.withRaw, Raw: opts.withRaw,
RequiredFormat: opts.withFormat, RequiredFormat: opts.withFormat,
}, nil }, nil
@@ -664,9 +663,7 @@ func doElideListResponseData(data map[string]interface{}) {
func newTemporaryEntryFormatter(n *EntryFormatter) *EntryFormatter { func newTemporaryEntryFormatter(n *EntryFormatter) *EntryFormatter {
return &EntryFormatter{ return &EntryFormatter{
salter: &nonPersistentSalt{}, salter: &nonPersistentSalt{},
headerFormatter: n.headerFormatter,
config: n.config, config: n.config,
prefix: n.prefix,
} }
} }

View File

@@ -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 // testTimeProvider is just a test struct used to imitate an AuditEvent's ability
// to provide a formatted time. // to provide a formatted time.
type testTimeProvider struct{} type testTimeProvider struct{}
@@ -178,9 +196,9 @@ func TestNewEntryFormatter(t *testing.T) {
ss = newStaticSalt(t) ss = newStaticSalt(t)
} }
cfg, err := NewFormatterConfig(tc.Options...) cfg, err := NewFormatterConfig(&testHeaderFormatter{}, tc.Options...)
require.NoError(t, err) 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 { switch {
case tc.IsErrorExpected: case tc.IsErrorExpected:
@@ -191,7 +209,7 @@ func TestNewEntryFormatter(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, f) require.NotNil(t, f)
require.Equal(t, tc.ExpectedFormat, f.config.RequiredFormat) 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() t.Parallel()
ss := newStaticSalt(t) ss := newStaticSalt(t)
cfg, err := NewFormatterConfig() cfg, err := NewFormatterConfig(&testHeaderFormatter{})
require.NoError(t, err) require.NoError(t, err)
f, err := NewEntryFormatter("juan", cfg, ss, hclog.NewNullLogger()) f, err := NewEntryFormatter("juan", cfg, ss, hclog.NewNullLogger())
@@ -216,7 +234,7 @@ func TestEntryFormatter_Type(t *testing.T) {
t.Parallel() t.Parallel()
ss := newStaticSalt(t) ss := newStaticSalt(t)
cfg, err := NewFormatterConfig() cfg, err := NewFormatterConfig(&testHeaderFormatter{})
require.NoError(t, err) require.NoError(t, err)
f, err := NewEntryFormatter("juan", cfg, ss, hclog.NewNullLogger()) f, err := NewEntryFormatter("juan", cfg, ss, hclog.NewNullLogger())
@@ -361,7 +379,7 @@ func TestEntryFormatter_Process(t *testing.T) {
require.NotNil(t, e) require.NotNil(t, e)
ss := newStaticSalt(t) ss := newStaticSalt(t)
cfg, err := NewFormatterConfig(WithFormat(tc.RequiredFormat.String())) cfg, err := NewFormatterConfig(&testHeaderFormatter{}, WithFormat(tc.RequiredFormat.String()))
require.NoError(t, err) require.NoError(t, err)
f, err := NewEntryFormatter("juan", cfg, ss, hclog.NewNullLogger()) f, err := NewEntryFormatter("juan", cfg, ss, hclog.NewNullLogger())
@@ -426,7 +444,7 @@ func BenchmarkAuditFileSink_Process(b *testing.B) {
ctx := namespace.RootContext(context.Background()) ctx := namespace.RootContext(context.Background())
// Create the formatter node. // Create the formatter node.
cfg, err := NewFormatterConfig() cfg, err := NewFormatterConfig(&testHeaderFormatter{})
require.NoError(b, err) require.NoError(b, err)
ss := newStaticSalt(b) ss := newStaticSalt(b)
formatter, err := NewEntryFormatter("juan", cfg, ss, hclog.NewNullLogger()) formatter, err := NewEntryFormatter("juan", cfg, ss, hclog.NewNullLogger())
@@ -504,7 +522,7 @@ func TestEntryFormatter_FormatRequest(t *testing.T) {
t.Parallel() t.Parallel()
ss := newStaticSalt(t) ss := newStaticSalt(t)
cfg, err := NewFormatterConfig(WithOmitTime(tc.ShouldOmitTime)) cfg, err := NewFormatterConfig(&testHeaderFormatter{}, WithOmitTime(tc.ShouldOmitTime))
require.NoError(t, err) require.NoError(t, err)
f, err := NewEntryFormatter("juan", cfg, ss, hclog.NewNullLogger()) f, err := NewEntryFormatter("juan", cfg, ss, hclog.NewNullLogger())
require.NoError(t, err) require.NoError(t, err)
@@ -586,7 +604,7 @@ func TestEntryFormatter_FormatResponse(t *testing.T) {
t.Parallel() t.Parallel()
ss := newStaticSalt(t) ss := newStaticSalt(t)
cfg, err := NewFormatterConfig(WithOmitTime(tc.ShouldOmitTime)) cfg, err := NewFormatterConfig(&testHeaderFormatter{}, WithOmitTime(tc.ShouldOmitTime))
require.NoError(t, err) require.NoError(t, err)
f, err := NewEntryFormatter("juan", cfg, ss, hclog.NewNullLogger()) f, err := NewEntryFormatter("juan", cfg, ss, hclog.NewNullLogger())
require.NoError(t, err) require.NoError(t, err)
@@ -702,9 +720,9 @@ func TestEntryFormatter_Process_JSON(t *testing.T) {
} }
for name, tc := range cases { for name, tc := range cases {
cfg, err := NewFormatterConfig(WithHMACAccessor(false)) cfg, err := NewFormatterConfig(&testHeaderFormatter{}, WithHMACAccessor(false), WithPrefix(tc.Prefix))
require.NoError(t, err) 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) require.NoError(t, err)
in := &logical.LogInput{ in := &logical.LogInput{
@@ -860,12 +878,14 @@ func TestEntryFormatter_Process_JSONx(t *testing.T) {
for name, tc := range cases { for name, tc := range cases {
cfg, err := NewFormatterConfig( cfg, err := NewFormatterConfig(
&testHeaderFormatter{},
WithOmitTime(true), WithOmitTime(true),
WithHMACAccessor(false), WithHMACAccessor(false),
WithFormat(JSONxFormat.String()), WithFormat(JSONxFormat.String()),
WithPrefix(tc.Prefix),
) )
require.NoError(t, err) 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.NoError(t, err)
require.NotNil(t, formatter) require.NotNil(t, formatter)
@@ -997,7 +1017,7 @@ func TestEntryFormatter_FormatResponse_ElideListResponses(t *testing.T) {
} }
t.Run("Default case", func(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) require.NoError(t, err)
for name, tc := range tests { for name, tc := range tests {
name := name 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) { 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) require.NoError(t, err)
tc := oneInterestingTestCase tc := oneInterestingTestCase
entry := format(t, config, logical.ReadOperation, tc.inputData) 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) { 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) require.NoError(t, err)
tc := oneInterestingTestCase tc := oneInterestingTestCase
entry := format(t, config, logical.ListOperation, tc.inputData) 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) { 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) require.NoError(t, err)
tc := oneInterestingTestCase tc := oneInterestingTestCase
entry := format(t, config, logical.ListOperation, tc.inputData) entry := format(t, config, logical.ListOperation, tc.inputData)
@@ -1040,7 +1060,7 @@ func TestEntryFormatter_Process_NoMutation(t *testing.T) {
t.Parallel() t.Parallel()
// Create the formatter node. // Create the formatter node.
cfg, err := NewFormatterConfig() cfg, err := NewFormatterConfig(&testHeaderFormatter{})
require.NoError(t, err) require.NoError(t, err)
ss := newStaticSalt(t) ss := newStaticSalt(t)
formatter, err := NewEntryFormatter("juan", cfg, ss, hclog.NewNullLogger()) formatter, err := NewEntryFormatter("juan", cfg, ss, hclog.NewNullLogger())
@@ -1100,7 +1120,7 @@ func TestEntryFormatter_Process_Panic(t *testing.T) {
t.Parallel() t.Parallel()
// Create the formatter node. // Create the formatter node.
cfg, err := NewFormatterConfig() cfg, err := NewFormatterConfig(&testHeaderFormatter{})
require.NoError(t, err) require.NoError(t, err)
ss := newStaticSalt(t) ss := newStaticSalt(t)
formatter, err := NewEntryFormatter("juan", cfg, ss, hclog.NewNullLogger()) formatter, err := NewEntryFormatter("juan", cfg, ss, hclog.NewNullLogger())
@@ -1153,6 +1173,53 @@ func TestEntryFormatter_Process_Panic(t *testing.T) {
require.Nil(t, e2) 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, // 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. // 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 { func (f *EntryFormatter) hashExpectedValueForComparison(input map[string]any) map[string]any {

View File

@@ -5,7 +5,6 @@ package audit
import ( import (
"errors" "errors"
"reflect"
"strings" "strings"
"time" "time"
) )
@@ -24,7 +23,6 @@ type options struct {
withElision bool withElision bool
withOmitTime bool withOmitTime bool
withHMACAccessor bool withHMACAccessor bool
withHeaderFormatter HeaderFormatter
} }
// getDefaultOptions returns options with their default values. // getDefaultOptions returns options with their default values.
@@ -163,15 +161,3 @@ func WithHMACAccessor(h bool) Option {
return nil 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
}
}

View File

@@ -4,7 +4,6 @@
package audit package audit
import ( import (
"context"
"testing" "testing"
"time" "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. // TestOptions_Default exercises getDefaultOptions to assert the default values.
func TestOptions_Default(t *testing.T) { func TestOptions_Default(t *testing.T) {
t.Parallel() 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
}

View File

@@ -99,6 +99,12 @@ type FormatterConfig struct {
// The required/target format for the event (supported: JSONFormat and JSONxFormat). // The required/target format for the event (supported: JSONFormat and JSONxFormat).
RequiredFormat format 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. // RequestEntry is the structure of a request audit log entry.

View File

@@ -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) 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 { if err != nil {
return nil, fmt.Errorf("%s: failed to create formatter config: %w", op, err) return nil, fmt.Errorf("%s: failed to create formatter config: %w", op, err)
} }
formatterOpts := []audit.Option{ err = b.configureFormatterNode(conf.MountPath, cfg, conf.Logger)
audit.WithHeaderFormatter(headersConfig),
audit.WithPrefix(conf.Config["prefix"]),
}
err = b.configureFormatterNode(conf.MountPath, cfg, conf.Logger, formatterOpts...)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s: error configuring formatter node: %w", op, err) 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)) 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. // the config map supplied to the factory.
func formatterConfig(config map[string]string) (audit.FormatterConfig, error) { func newFormatterConfig(headerFormatter audit.HeaderFormatter, config map[string]string) (audit.FormatterConfig, error) {
const op = "file.formatterConfig" const op = "file.newFormatterConfig"
var opts []audit.Option var opts []audit.Option
@@ -220,11 +215,15 @@ func formatterConfig(config map[string]string) (audit.FormatterConfig, error) {
opts = append(opts, audit.WithElision(v)) 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. // 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" const op = "file.(Backend).configureFormatterNode"
formatterNodeID, err := event.GenerateNodeID() 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) 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 { if err != nil {
return fmt.Errorf("%s: error creating formatter: %w", op, err) return fmt.Errorf("%s: error creating formatter: %w", op, err)
} }

View File

@@ -11,6 +11,7 @@ import (
"github.com/hashicorp/eventlogger" "github.com/hashicorp/eventlogger"
"github.com/hashicorp/go-hclog" "github.com/hashicorp/go-hclog"
"github.com/hashicorp/vault/audit" "github.com/hashicorp/vault/audit"
"github.com/hashicorp/vault/helper/testhelpers/corehelpers"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@@ -74,7 +75,7 @@ func TestBackend_configureFilterFormatterSink(t *testing.T) {
nodeMap: map[eventlogger.NodeID]eventlogger.Node{}, nodeMap: map[eventlogger.NodeID]eventlogger.Node{},
} }
formatConfig, err := audit.NewFormatterConfig() formatConfig, err := audit.NewFormatterConfig(&corehelpers.NoopHeaderFormatter{})
require.NoError(t, err) require.NoError(t, err)
err = b.configureFilterNode("path == bar") err = b.configureFilterNode("path == bar")

View File

@@ -13,6 +13,7 @@ import (
"github.com/hashicorp/eventlogger" "github.com/hashicorp/eventlogger"
"github.com/hashicorp/go-hclog" "github.com/hashicorp/go-hclog"
"github.com/hashicorp/vault/audit" "github.com/hashicorp/vault/audit"
"github.com/hashicorp/vault/helper/testhelpers/corehelpers"
"github.com/hashicorp/vault/internal/observability/event" "github.com/hashicorp/vault/internal/observability/event"
"github.com/hashicorp/vault/sdk/helper/salt" "github.com/hashicorp/vault/sdk/helper/salt"
"github.com/hashicorp/vault/sdk/logical" "github.com/hashicorp/vault/sdk/logical"
@@ -40,7 +41,7 @@ func TestAuditFile_fileModeNew(t *testing.T) {
SaltView: &logical.InmemStorage{}, SaltView: &logical.InmemStorage{},
Logger: hclog.NewNullLogger(), Logger: hclog.NewNullLogger(),
} }
_, err = Factory(context.Background(), backendConfig, nil) _, err = Factory(context.Background(), backendConfig, &corehelpers.NoopHeaderFormatter{})
require.NoError(t, err) require.NoError(t, err)
info, err := os.Stat(file) info, err := os.Stat(file)
@@ -73,7 +74,7 @@ func TestAuditFile_fileModeExisting(t *testing.T) {
Logger: hclog.NewNullLogger(), Logger: hclog.NewNullLogger(),
} }
_, err = Factory(context.Background(), backendConfig, nil) _, err = Factory(context.Background(), backendConfig, &corehelpers.NoopHeaderFormatter{})
require.NoError(t, err) require.NoError(t, err)
info, err := os.Stat(f.Name()) info, err := os.Stat(f.Name())
@@ -107,7 +108,7 @@ func TestAuditFile_fileMode0000(t *testing.T) {
Logger: hclog.NewNullLogger(), Logger: hclog.NewNullLogger(),
} }
_, err = Factory(context.Background(), backendConfig, nil) _, err = Factory(context.Background(), backendConfig, &corehelpers.NoopHeaderFormatter{})
require.NoError(t, err) require.NoError(t, err)
info, err := os.Stat(f.Name()) info, err := os.Stat(f.Name())
@@ -136,7 +137,7 @@ func TestAuditFile_EventLogger_fileModeNew(t *testing.T) {
Logger: hclog.NewNullLogger(), Logger: hclog.NewNullLogger(),
} }
_, err = Factory(context.Background(), backendConfig, nil) _, err = Factory(context.Background(), backendConfig, &corehelpers.NoopHeaderFormatter{})
require.NoError(t, err) require.NoError(t, err)
info, err := os.Stat(file) 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.") require.Equalf(t, os.FileMode(mode), info.Mode(), "File mode does not match.")
} }
// TestBackend_formatterConfig ensures that all the configuration values are parsed correctly. // TestBackend_newFormatterConfig ensures that all the configuration values are parsed correctly.
func TestBackend_formatterConfig(t *testing.T) { func TestBackend_newFormatterConfig(t *testing.T) {
t.Parallel() t.Parallel()
tests := map[string]struct { tests := map[string]struct {
@@ -201,7 +202,7 @@ func TestBackend_formatterConfig(t *testing.T) {
}, },
want: audit.FormatterConfig{}, want: audit.FormatterConfig{},
wantErr: true, 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": { "invalid-log-raw": {
config: map[string]string{ config: map[string]string{
@@ -211,7 +212,7 @@ func TestBackend_formatterConfig(t *testing.T) {
}, },
want: audit.FormatterConfig{}, want: audit.FormatterConfig{},
wantErr: true, 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": { "invalid-elide-bool": {
config: map[string]string{ config: map[string]string{
@@ -222,7 +223,18 @@ func TestBackend_formatterConfig(t *testing.T) {
}, },
want: audit.FormatterConfig{}, want: audit.FormatterConfig{},
wantErr: true, 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 { for name, tc := range tests {
@@ -231,14 +243,19 @@ func TestBackend_formatterConfig(t *testing.T) {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
t.Parallel() t.Parallel()
got, err := formatterConfig(tc.config) got, err := newFormatterConfig(&corehelpers.NoopHeaderFormatter{}, tc.config)
if tc.wantErr { if tc.wantErr {
require.Error(t, err) require.Error(t, err)
require.EqualError(t, err, tc.expectedMessage) require.EqualError(t, err, tc.expectedMessage)
} else { } else {
require.NoError(t, err) 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{}, nodeMap: map[eventlogger.NodeID]eventlogger.Node{},
} }
formatConfig, err := audit.NewFormatterConfig() formatConfig, err := audit.NewFormatterConfig(&corehelpers.NoopHeaderFormatter{})
require.NoError(t, err) require.NoError(t, err)
err = b.configureFormatterNode("juan", formatConfig, hclog.NewNullLogger()) 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.Run(name, func(t *testing.T) {
t.Parallel() t.Parallel()
be, err := Factory(ctx, tc.backendConfig, nil) be, err := Factory(ctx, tc.backendConfig, &corehelpers.NoopHeaderFormatter{})
switch { switch {
case tc.isErrorExpected: case tc.isErrorExpected:
@@ -535,7 +552,7 @@ func TestBackend_IsFallback(t *testing.T) {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
t.Parallel() t.Parallel()
be, err := Factory(ctx, tc.backendConfig, nil) be, err := Factory(ctx, tc.backendConfig, &corehelpers.NoopHeaderFormatter{})
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, be) require.NotNil(t, be)
require.Equal(t, tc.isFallbackExpected, be.IsFallback()) require.Equal(t, tc.isFallbackExpected, be.IsFallback())

View File

@@ -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) 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 { if err != nil {
return nil, fmt.Errorf("%s: failed to create formatter config: %w", op, err) return nil, fmt.Errorf("%s: failed to create formatter config: %w", op, err)
} }
opts := []audit.Option{ err = b.configureFormatterNode(conf.MountPath, cfg, conf.Logger)
audit.WithHeaderFormatter(headersConfig),
audit.WithPrefix(conf.Config["prefix"]),
}
err = b.configureFormatterNode(conf.MountPath, cfg, conf.Logger, opts...)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s: error configuring formatter node: %w", op, err) 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 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. // the config map supplied to the factory.
func formatterConfig(config map[string]string) (audit.FormatterConfig, error) { func newFormatterConfig(headerFormatter audit.HeaderFormatter, config map[string]string) (audit.FormatterConfig, error) {
const op = "socket.formatterConfig" const op = "socket.newFormatterConfig"
var cfgOpts []audit.Option var opts []audit.Option
if format, ok := config["format"]; ok { 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 // Check if hashing of accessor is disabled
@@ -182,7 +177,7 @@ func formatterConfig(config map[string]string) (audit.FormatterConfig, error) {
if err != nil { if err != nil {
return audit.FormatterConfig{}, fmt.Errorf("%s: unable to parse 'hmac_accessor': %w", op, err) 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 // Check if raw logging is enabled
@@ -191,7 +186,7 @@ func formatterConfig(config map[string]string) (audit.FormatterConfig, error) {
if err != nil { if err != nil {
return audit.FormatterConfig{}, fmt.Errorf("%s: unable to parse 'log_raw': %w", op, err) 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 { if elideListResponsesRaw, ok := config["elide_list_responses"]; ok {
@@ -199,14 +194,18 @@ func formatterConfig(config map[string]string) (audit.FormatterConfig, error) {
if err != nil { if err != nil {
return audit.FormatterConfig{}, fmt.Errorf("%s: unable to parse 'elide_list_responses': %w", op, err) 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. // 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" const op = "socket.(Backend).configureFormatterNode"
formatterNodeID, err := event.GenerateNodeID() 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) 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 { if err != nil {
return fmt.Errorf("%s: error creating formatter: %w", op, err) return fmt.Errorf("%s: error creating formatter: %w", op, err)
} }

View File

@@ -11,6 +11,7 @@ import (
"github.com/hashicorp/eventlogger" "github.com/hashicorp/eventlogger"
"github.com/hashicorp/go-hclog" "github.com/hashicorp/go-hclog"
"github.com/hashicorp/vault/audit" "github.com/hashicorp/vault/audit"
"github.com/hashicorp/vault/helper/testhelpers/corehelpers"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@@ -74,7 +75,7 @@ func TestBackend_configureFilterFormatterSink(t *testing.T) {
nodeMap: map[eventlogger.NodeID]eventlogger.Node{}, nodeMap: map[eventlogger.NodeID]eventlogger.Node{},
} }
formatConfig, err := audit.NewFormatterConfig() formatConfig, err := audit.NewFormatterConfig(&corehelpers.NoopHeaderFormatter{})
require.NoError(t, err) require.NoError(t, err)
err = b.configureFilterNode("path == bar") err = b.configureFilterNode("path == bar")

View File

@@ -10,14 +10,15 @@ import (
"github.com/hashicorp/eventlogger" "github.com/hashicorp/eventlogger"
"github.com/hashicorp/go-hclog" "github.com/hashicorp/go-hclog"
"github.com/hashicorp/vault/audit" "github.com/hashicorp/vault/audit"
"github.com/hashicorp/vault/helper/testhelpers/corehelpers"
"github.com/hashicorp/vault/internal/observability/event" "github.com/hashicorp/vault/internal/observability/event"
"github.com/hashicorp/vault/sdk/helper/salt" "github.com/hashicorp/vault/sdk/helper/salt"
"github.com/hashicorp/vault/sdk/logical" "github.com/hashicorp/vault/sdk/logical"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
// TestBackend_formatterConfig ensures that all the configuration values are parsed correctly. // TestBackend_newFormatterConfig ensures that all the configuration values are parsed correctly.
func TestBackend_formatterConfig(t *testing.T) { func TestBackend_newFormatterConfig(t *testing.T) {
t.Parallel() t.Parallel()
tests := map[string]struct { tests := map[string]struct {
@@ -73,7 +74,7 @@ func TestBackend_formatterConfig(t *testing.T) {
}, },
want: audit.FormatterConfig{}, want: audit.FormatterConfig{},
wantErr: true, 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": { "invalid-log-raw": {
config: map[string]string{ config: map[string]string{
@@ -83,7 +84,7 @@ func TestBackend_formatterConfig(t *testing.T) {
}, },
want: audit.FormatterConfig{}, want: audit.FormatterConfig{},
wantErr: true, 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": { "invalid-elide-bool": {
config: map[string]string{ config: map[string]string{
@@ -94,7 +95,18 @@ func TestBackend_formatterConfig(t *testing.T) {
}, },
want: audit.FormatterConfig{}, want: audit.FormatterConfig{},
wantErr: true, 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 { for name, tc := range tests {
@@ -103,14 +115,19 @@ func TestBackend_formatterConfig(t *testing.T) {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
t.Parallel() t.Parallel()
got, err := formatterConfig(tc.config) got, err := newFormatterConfig(&corehelpers.NoopHeaderFormatter{}, tc.config)
if tc.wantErr { if tc.wantErr {
require.Error(t, err) require.Error(t, err)
require.EqualError(t, err, tc.expectedErrMsg) require.EqualError(t, err, tc.expectedErrMsg)
} else { } else {
require.NoError(t, err) 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{}, nodeMap: map[eventlogger.NodeID]eventlogger.Node{},
} }
formatConfig, err := audit.NewFormatterConfig() formatConfig, err := audit.NewFormatterConfig(&corehelpers.NoopHeaderFormatter{})
require.NoError(t, err) require.NoError(t, err)
err = b.configureFormatterNode("juan", formatConfig, hclog.NewNullLogger()) 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.Run(name, func(t *testing.T) {
t.Parallel() t.Parallel()
be, err := Factory(ctx, tc.backendConfig, nil) be, err := Factory(ctx, tc.backendConfig, &corehelpers.NoopHeaderFormatter{})
switch { switch {
case tc.isErrorExpected: case tc.isErrorExpected:
@@ -431,7 +448,7 @@ func TestBackend_IsFallback(t *testing.T) {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
t.Parallel() t.Parallel()
be, err := Factory(ctx, tc.backendConfig, nil) be, err := Factory(ctx, tc.backendConfig, &corehelpers.NoopHeaderFormatter{})
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, be) require.NotNil(t, be)
require.Equal(t, tc.isFallbackExpected, be.IsFallback()) require.Equal(t, tc.isFallbackExpected, be.IsFallback())

View File

@@ -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) 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 { if err != nil {
return nil, fmt.Errorf("%s: failed to create formatter config: %w", op, err) return nil, fmt.Errorf("%s: failed to create formatter config: %w", op, err)
} }
formatterOpts := []audit.Option{ err = b.configureFormatterNode(conf.MountPath, cfg, conf.Logger)
audit.WithHeaderFormatter(headersConfig),
audit.WithPrefix(conf.Config["prefix"]),
}
err = b.configureFormatterNode(conf.MountPath, cfg, conf.Logger, formatterOpts...)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s: error configuring formatter node: %w", op, err) 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 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. // the config map supplied to the factory.
func formatterConfig(config map[string]string) (audit.FormatterConfig, error) { func newFormatterConfig(headerFormatter audit.HeaderFormatter, config map[string]string) (audit.FormatterConfig, error) {
const op = "syslog.formatterConfig" const op = "syslog.newFormatterConfig"
var opts []audit.Option var opts []audit.Option
@@ -193,11 +188,15 @@ func formatterConfig(config map[string]string) (audit.FormatterConfig, error) {
opts = append(opts, audit.WithElision(v)) 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. // 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" const op = "syslog.(Backend).configureFormatterNode"
formatterNodeID, err := event.GenerateNodeID() 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) 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 { if err != nil {
return fmt.Errorf("%s: error creating formatter: %w", op, err) return fmt.Errorf("%s: error creating formatter: %w", op, err)
} }

View File

@@ -11,6 +11,7 @@ import (
"github.com/hashicorp/eventlogger" "github.com/hashicorp/eventlogger"
"github.com/hashicorp/go-hclog" "github.com/hashicorp/go-hclog"
"github.com/hashicorp/vault/audit" "github.com/hashicorp/vault/audit"
"github.com/hashicorp/vault/helper/testhelpers/corehelpers"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@@ -74,7 +75,7 @@ func TestBackend_configureFilterFormatterSink(t *testing.T) {
nodeMap: map[eventlogger.NodeID]eventlogger.Node{}, nodeMap: map[eventlogger.NodeID]eventlogger.Node{},
} }
formatConfig, err := audit.NewFormatterConfig() formatConfig, err := audit.NewFormatterConfig(&corehelpers.NoopHeaderFormatter{})
require.NoError(t, err) require.NoError(t, err)
err = b.configureFilterNode("path == bar") err = b.configureFilterNode("path == bar")

View File

@@ -10,14 +10,15 @@ import (
"github.com/hashicorp/eventlogger" "github.com/hashicorp/eventlogger"
"github.com/hashicorp/go-hclog" "github.com/hashicorp/go-hclog"
"github.com/hashicorp/vault/audit" "github.com/hashicorp/vault/audit"
"github.com/hashicorp/vault/helper/testhelpers/corehelpers"
"github.com/hashicorp/vault/internal/observability/event" "github.com/hashicorp/vault/internal/observability/event"
"github.com/hashicorp/vault/sdk/helper/salt" "github.com/hashicorp/vault/sdk/helper/salt"
"github.com/hashicorp/vault/sdk/logical" "github.com/hashicorp/vault/sdk/logical"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
// TestBackend_formatterConfig ensures that all the configuration values are parsed correctly. // TestBackend_newFormatterConfig ensures that all the configuration values are parsed correctly.
func TestBackend_formatterConfig(t *testing.T) { func TestBackend_newFormatterConfig(t *testing.T) {
t.Parallel() t.Parallel()
tests := map[string]struct { tests := map[string]struct {
@@ -73,7 +74,7 @@ func TestBackend_formatterConfig(t *testing.T) {
}, },
want: audit.FormatterConfig{}, want: audit.FormatterConfig{},
wantErr: true, 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": { "invalid-log-raw": {
config: map[string]string{ config: map[string]string{
@@ -83,7 +84,7 @@ func TestBackend_formatterConfig(t *testing.T) {
}, },
want: audit.FormatterConfig{}, want: audit.FormatterConfig{},
wantErr: true, 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": { "invalid-elide-bool": {
config: map[string]string{ config: map[string]string{
@@ -94,7 +95,18 @@ func TestBackend_formatterConfig(t *testing.T) {
}, },
want: audit.FormatterConfig{}, want: audit.FormatterConfig{},
wantErr: true, 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 { for name, tc := range tests {
@@ -103,14 +115,19 @@ func TestBackend_formatterConfig(t *testing.T) {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
t.Parallel() t.Parallel()
got, err := formatterConfig(tc.config) got, err := newFormatterConfig(&corehelpers.NoopHeaderFormatter{}, tc.config)
if tc.wantErr { if tc.wantErr {
require.Error(t, err) require.Error(t, err)
require.EqualError(t, err, tc.expectedErrMsg) require.EqualError(t, err, tc.expectedErrMsg)
} else { } else {
require.NoError(t, err) 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{}, nodeMap: map[eventlogger.NodeID]eventlogger.Node{},
} }
formatConfig, err := audit.NewFormatterConfig() formatConfig, err := audit.NewFormatterConfig(&corehelpers.NoopHeaderFormatter{})
require.NoError(t, err) require.NoError(t, err)
err = b.configureFormatterNode("juan", formatConfig, hclog.NewNullLogger()) 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.Run(name, func(t *testing.T) {
t.Parallel() t.Parallel()
be, err := Factory(ctx, tc.backendConfig, nil) be, err := Factory(ctx, tc.backendConfig, &corehelpers.NoopHeaderFormatter{})
switch { switch {
case tc.isErrorExpected: case tc.isErrorExpected:
@@ -331,7 +348,7 @@ func TestBackend_IsFallback(t *testing.T) {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
t.Parallel() t.Parallel()
be, err := Factory(ctx, tc.backendConfig, nil) be, err := Factory(ctx, tc.backendConfig, &corehelpers.NoopHeaderFormatter{})
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, be) require.NotNil(t, be)
require.Equal(t, tc.isFallbackExpected, be.IsFallback()) require.Equal(t, tc.isFallbackExpected, be.IsFallback())

View File

@@ -14,6 +14,7 @@ import (
"io" "io"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"sync" "sync"
"time" "time"
@@ -216,23 +217,44 @@ func (m *mockBuiltinRegistry) DeprecationStatus(name string, pluginType consts.P
return consts.Unknown, false 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{ cfg := &audit.BackendConfig{
Config: config, Config: config,
MountPath: path, MountPath: path,
Logger: NewTestLogger(t), Logger: NewTestLogger(t),
} }
n, err := NewNoopAudit(cfg, opts...) n, err := NewNoopAudit(cfg)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
return n 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 // 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 // predictable salt and wraps eventlogger nodes so information can be retrieved on
// what they've seen or formatted. // 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{} view := &logical.InmemStorage{}
// Create the salt with a known key for predictable hmac values. // 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), nodeMap: make(map[eventlogger.NodeID]eventlogger.Node, 2),
} }
cfg, err := audit.NewFormatterConfig() cfg, err := audit.NewFormatterConfig(&NoopHeaderFormatter{})
if err != nil { if err != nil {
return nil, err 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) 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 { if err != nil {
return nil, fmt.Errorf("error creating formatter: %w", err) 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. // have been formatted by the pipeline during audit requests.
// The records parameter will be repointed to the one used within the pipeline. // The records parameter will be repointed to the one used within the pipeline.
func NoopAuditFactory(records **[][]byte) audit.Factory { func NoopAuditFactory(records **[][]byte) audit.Factory {
return func(_ context.Context, config *audit.BackendConfig, headerFormatter audit.HeaderFormatter) (audit.Backend, error) { return func(_ context.Context, config *audit.BackendConfig, _ audit.HeaderFormatter) (audit.Backend, error) {
n, err := NewNoopAudit(config, audit.WithHeaderFormatter(headerFormatter)) n, err := NewNoopAudit(config)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -345,6 +345,7 @@ func TestAuditBroker_LogRequest(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
a1 := corehelpers.TestNoopAudit(t, "foo", nil) a1 := corehelpers.TestNoopAudit(t, "foo", nil)
a2 := corehelpers.TestNoopAudit(t, "bar", nil) a2 := corehelpers.TestNoopAudit(t, "bar", nil)
err = b.Register("foo", a1, false) err = b.Register("foo", a1, false)
@@ -433,6 +434,7 @@ func TestAuditBroker_LogResponse(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
a1 := corehelpers.TestNoopAudit(t, "foo", nil) a1 := corehelpers.TestNoopAudit(t, "foo", nil)
a2 := corehelpers.TestNoopAudit(t, "bar", nil) a2 := corehelpers.TestNoopAudit(t, "bar", nil)
err = b.Register("foo", a1, false) err = b.Register("foo", a1, false)
@@ -537,19 +539,9 @@ func TestAuditBroker_AuditHeaders(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
_, barrier, _ := mockBarrier(t)
view := NewBarrierView(barrier, "headers/")
headersConf := &AuditedHeadersConfig{ a1 := corehelpers.TestNoopAudit(t, "foo", nil)
view: view, a2 := corehelpers.TestNoopAudit(t, "bar", nil)
}
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))
err = b.Register("foo", a1, false) err = b.Register("foo", a1, false)
require.NoError(t, err) require.NoError(t, err)
@@ -570,7 +562,6 @@ func TestAuditBroker_AuditHeaders(t *testing.T) {
Headers: map[string][]string{ Headers: map[string][]string{
"X-Test-Header": {"foo"}, "X-Test-Header": {"foo"},
"X-Vault-Header": {"bar"}, "X-Vault-Header": {"bar"},
"Content-Type": {"baz"},
}, },
} }
respErr := fmt.Errorf("permission denied") respErr := fmt.Errorf("permission denied")

View File

@@ -182,8 +182,14 @@ func (a *AuditedHeadersConfig) invalidate(ctx context.Context) error {
return nil 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) { 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 // Grab a read lock
a.RLock() a.RLock()
defer a.RUnlock() defer a.RUnlock()

View File

@@ -273,21 +273,22 @@ func TestAuditedHeadersConfig_ApplyConfig(t *testing.T) {
func TestAuditedHeadersConfig_ApplyConfig_NoRequestHeaders(t *testing.T) { func TestAuditedHeadersConfig_ApplyConfig_NoRequestHeaders(t *testing.T) {
conf := mockAuditedHeadersConfig(t) conf := mockAuditedHeadersConfig(t)
conf.add(context.Background(), "X-TesT-Header", false) err := conf.add(context.Background(), "X-TesT-Header", false)
conf.add(context.Background(), "X-Vault-HeAdEr", true) require.NoError(t, err)
err = conf.add(context.Background(), "X-Vault-HeAdEr", true)
reqHeaders := map[string][]string{} require.NoError(t, err)
salter := &TestSalter{} salter := &TestSalter{}
result, err := conf.ApplyConfig(context.Background(), reqHeaders, salter) // Test sending in nil headers first.
if err != nil { result, err := conf.ApplyConfig(context.Background(), nil, salter)
t.Fatal(err) require.NoError(t, err)
} require.NotNil(t, result)
if len(result) != 0 { result, err = conf.ApplyConfig(context.Background(), map[string][]string{}, salter)
t.Fatalf("Expected no headers but actually got: %d\n", len(result)) require.NoError(t, err)
} require.NotNil(t, result)
require.Len(t, result, 0)
} }
func TestAuditedHeadersConfig_ApplyConfig_NoConfiguredHeaders(t *testing.T) { func TestAuditedHeadersConfig_ApplyConfig_NoConfiguredHeaders(t *testing.T) {

View File

@@ -1545,9 +1545,9 @@ func TestCore_HandleRequest_AuditTrail(t *testing.T) {
// Create a noop audit backend // Create a noop audit backend
var noop *corehelpers.NoopAudit var noop *corehelpers.NoopAudit
c, _, root := TestCoreUnsealed(t) 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 var err error
noop, err = corehelpers.NewNoopAudit(config, audit.WithHeaderFormatter(headerFormatter)) noop, err = corehelpers.NewNoopAudit(config)
return noop, err return noop, err
} }
@@ -1608,9 +1608,9 @@ func TestCore_HandleRequest_AuditTrail_noHMACKeys(t *testing.T) {
// Create a noop audit backend // Create a noop audit backend
var noop *corehelpers.NoopAudit var noop *corehelpers.NoopAudit
c, _, root := TestCoreUnsealed(t) 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 var err error
noop, err = corehelpers.NewNoopAudit(config, audit.WithHeaderFormatter(headerFormatter)) noop, err = corehelpers.NewNoopAudit(config)
return noop, err 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) { c.credentialBackends["noop"] = func(context.Context, *logical.BackendConfig) (logical.Backend, error) {
return noopBack, nil 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 var err error
noop, err = corehelpers.NewNoopAudit(config, audit.WithHeaderFormatter(headerFormatter)) noop, err = corehelpers.NewNoopAudit(config)
return noop, err return noop, err
} }