mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-11-03 03:58:01 +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.
|
// EntryFormatter should be used to format audit requests and responses.
|
||||||
type EntryFormatter struct {
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that any headers in the request, are formatted as required, and are
|
// If the request is present in the input data, apply header configuration
|
||||||
// only present if they have been configured to appear in the audit log.
|
// regardless. We shouldn't be in a situation where the header formatter isn't
|
||||||
// e.g. via: /sys/config/auditing/request-headers/:name
|
// present as it's required.
|
||||||
if f.headerFormatter != nil && data.Request != nil && data.Request.Headers != nil {
|
if data.Request != nil {
|
||||||
data.Request.Headers, err = f.headerFormatter.ApplyConfig(ctx, data.Request.Headers, f.salter)
|
// 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 {
|
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
|
||||||
@@ -663,10 +662,8 @@ func doElideListResponseData(data map[string]interface{}) {
|
|||||||
// newTemporaryEntryFormatter creates a cloned EntryFormatter instance with a non-persistent Salter.
|
// newTemporaryEntryFormatter creates a cloned EntryFormatter instance with a non-persistent Salter.
|
||||||
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,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ package audit
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"reflect"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -15,16 +14,15 @@ type Option func(*options) error
|
|||||||
|
|
||||||
// options are used to represent configuration for a audit related nodes.
|
// options are used to represent configuration for a audit related nodes.
|
||||||
type options struct {
|
type options struct {
|
||||||
withID string
|
withID string
|
||||||
withNow time.Time
|
withNow time.Time
|
||||||
withSubtype subtype
|
withSubtype subtype
|
||||||
withFormat format
|
withFormat format
|
||||||
withPrefix string
|
withPrefix string
|
||||||
withRaw bool
|
withRaw bool
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user