mirror of
https://github.com/hashicorp/vault.git
synced 2025-08-14 18:47:01 +02:00
* Adding explicit MPL license for sub-package. This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Adding explicit MPL license for sub-package. This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Updating the license from MPL to Business Source License. Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at https://hashi.co/bsl-blog, FAQ at www.hashicorp.com/licensing-faq, and details of the license at www.hashicorp.com/bsl. * add missing license headers * Update copyright file headers to BUS-1.1 * Fix test that expected exact offset on hcl file --------- Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com> Co-authored-by: Sarah Thompson <sthompson@hashicorp.com> Co-authored-by: Brian Kassouf <bkassouf@hashicorp.com>
302 lines
7.9 KiB
Go
302 lines
7.9 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package event
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/hashicorp/vault/helper/namespace"
|
|
|
|
"github.com/hashicorp/eventlogger"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// TestFileSink_Type ensures that the node is a 'sink' type.
|
|
func TestFileSink_Type(t *testing.T) {
|
|
f, err := NewFileSink(filepath.Join(t.TempDir(), "vault.log"), "json")
|
|
require.NoError(t, err)
|
|
require.NotNil(t, f)
|
|
require.Equal(t, eventlogger.NodeTypeSink, f.Type())
|
|
}
|
|
|
|
// TestNewFileSink tests creation of an AuditFileSink.
|
|
func TestNewFileSink(t *testing.T) {
|
|
tests := map[string]struct {
|
|
ShouldUseAbsolutePath bool // Path should contain the filename if temp dir is true
|
|
Path string
|
|
Format string
|
|
Options []Option
|
|
IsErrorExpected bool
|
|
ExpectedErrorMessage string
|
|
// Expected values of AuditFileSink
|
|
ExpectedFileMode os.FileMode
|
|
ExpectedFormat string
|
|
ExpectedPath string
|
|
ExpectedPrefix string
|
|
}{
|
|
"default-values": {
|
|
ShouldUseAbsolutePath: true,
|
|
IsErrorExpected: true,
|
|
ExpectedErrorMessage: "event.NewFileSink: path is required",
|
|
},
|
|
"spacey-path": {
|
|
ShouldUseAbsolutePath: true,
|
|
Path: " ",
|
|
Format: "json",
|
|
IsErrorExpected: true,
|
|
ExpectedErrorMessage: "event.NewFileSink: path is required",
|
|
},
|
|
"valid-path-and-format": {
|
|
Path: "vault.log",
|
|
Format: "json",
|
|
IsErrorExpected: false,
|
|
ExpectedFileMode: defaultFileMode,
|
|
ExpectedFormat: "json",
|
|
ExpectedPrefix: "",
|
|
},
|
|
"file-mode-not-default-or-zero": {
|
|
Path: "vault.log",
|
|
Format: "json",
|
|
Options: []Option{WithFileMode("0007")},
|
|
IsErrorExpected: false,
|
|
ExpectedFormat: "json",
|
|
ExpectedPrefix: "",
|
|
ExpectedFileMode: 0o007,
|
|
},
|
|
"prefix": {
|
|
Path: "vault.log",
|
|
Format: "json",
|
|
Options: []Option{WithFileMode("0007")},
|
|
IsErrorExpected: false,
|
|
ExpectedPrefix: "bleep",
|
|
ExpectedFormat: "json",
|
|
ExpectedFileMode: 0o007,
|
|
},
|
|
}
|
|
|
|
for name, tc := range tests {
|
|
name := name
|
|
tc := tc
|
|
t.Run(name, func(t *testing.T) {
|
|
// t.Parallel()
|
|
|
|
// If we need a real directory as a path we can use a temp dir.
|
|
// but we should keep track of it for comparison in the new sink.
|
|
var tempDir string
|
|
tempPath := tc.Path
|
|
if !tc.ShouldUseAbsolutePath {
|
|
tempDir = t.TempDir()
|
|
tempPath = filepath.Join(tempDir, tempPath)
|
|
}
|
|
|
|
sink, err := NewFileSink(tempPath, tc.Format, tc.Options...)
|
|
|
|
switch {
|
|
case tc.IsErrorExpected:
|
|
require.Error(t, err)
|
|
require.EqualError(t, err, tc.ExpectedErrorMessage)
|
|
require.Nil(t, sink)
|
|
default:
|
|
require.NoError(t, err)
|
|
require.NotNil(t, sink)
|
|
|
|
// Assert properties are correct.
|
|
require.Equal(t, tc.ExpectedFormat, sink.requiredFormat)
|
|
require.Equal(t, tc.ExpectedFileMode, sink.fileMode)
|
|
|
|
switch {
|
|
case tc.ShouldUseAbsolutePath:
|
|
require.Equal(t, tc.ExpectedPath, sink.path)
|
|
default:
|
|
require.Equal(t, tempPath, sink.path)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestFileSink_Reopen tests that the sink reopens files as expected when requested to.
|
|
// stdout and discard paths are ignored.
|
|
// see: https://developer.hashicorp.com/vault/docs/audit/file#file_path
|
|
func TestFileSink_Reopen(t *testing.T) {
|
|
tests := map[string]struct {
|
|
Path string
|
|
ShouldUseAbsolutePath bool
|
|
ShouldCreateFile bool
|
|
ShouldIgnoreFileMode bool
|
|
Options []Option
|
|
IsErrorExpected bool
|
|
ExpectedErrorMessage string
|
|
ExpectedFileMode os.FileMode
|
|
}{
|
|
// Should be ignored by Reopen
|
|
"devnull": {
|
|
Path: "/dev/null",
|
|
ShouldUseAbsolutePath: true,
|
|
ShouldIgnoreFileMode: true,
|
|
},
|
|
"happy": {
|
|
Path: "vault.log",
|
|
ExpectedFileMode: os.FileMode(defaultFileMode),
|
|
},
|
|
"filemode-existing": {
|
|
Path: "vault.log",
|
|
ShouldCreateFile: true,
|
|
Options: []Option{WithFileMode("0000")},
|
|
ExpectedFileMode: os.FileMode(defaultFileMode),
|
|
},
|
|
}
|
|
|
|
for name, tc := range tests {
|
|
name := name
|
|
tc := tc
|
|
t.Run(name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// If we need a real directory as a path we can use a temp dir.
|
|
// but we should keep track of it for comparison in the new sink.
|
|
var tempDir string
|
|
tempPath := tc.Path
|
|
if !tc.ShouldUseAbsolutePath {
|
|
tempDir = t.TempDir()
|
|
tempPath = filepath.Join(tempDir, tc.Path)
|
|
}
|
|
|
|
// If the file mode is 0 then we will need a pre-created file to stat.
|
|
// Only do this for paths that are not 'special keywords'
|
|
if tc.ShouldCreateFile && tc.Path != devnull {
|
|
f, err := os.OpenFile(tempPath, os.O_CREATE, defaultFileMode)
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
err = os.Remove(f.Name())
|
|
require.NoError(t, err)
|
|
}()
|
|
}
|
|
|
|
sink, err := NewFileSink(tempPath, "json", tc.Options...)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, sink)
|
|
|
|
err = sink.Reopen()
|
|
|
|
switch {
|
|
case tc.IsErrorExpected:
|
|
require.Error(t, err)
|
|
require.EqualError(t, err, tc.ExpectedErrorMessage)
|
|
default:
|
|
require.NoError(t, err)
|
|
info, err := os.Stat(tempPath)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, info)
|
|
if !tc.ShouldIgnoreFileMode {
|
|
require.Equal(t, tc.ExpectedFileMode, info.Mode())
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestFileSink_Process ensures that Process behaves as expected.
|
|
func TestFileSink_Process(t *testing.T) {
|
|
tests := map[string]struct {
|
|
ShouldUseAbsolutePath bool
|
|
Path string
|
|
ShouldCreateFile bool
|
|
Format string
|
|
ShouldIgnoreFormat bool
|
|
Data string
|
|
ShouldUseNilEvent bool
|
|
IsErrorExpected bool
|
|
ExpectedErrorMessage string
|
|
}{
|
|
"devnull": {
|
|
ShouldUseAbsolutePath: true,
|
|
Path: devnull,
|
|
Format: "json",
|
|
Data: "foo",
|
|
IsErrorExpected: false,
|
|
},
|
|
"no-formatted-data": {
|
|
ShouldCreateFile: true,
|
|
Path: "juan.log",
|
|
Format: "json",
|
|
Data: "foo",
|
|
ShouldIgnoreFormat: true,
|
|
IsErrorExpected: true,
|
|
ExpectedErrorMessage: "event.(FileSink).Process: unable to retrieve event formatted as \"json\"",
|
|
},
|
|
"nil": {
|
|
Path: "foo.log",
|
|
Format: "json",
|
|
Data: "foo",
|
|
ShouldUseNilEvent: true,
|
|
IsErrorExpected: true,
|
|
ExpectedErrorMessage: "event.(FileSink).Process: event is nil: invalid parameter",
|
|
},
|
|
}
|
|
|
|
for name, tc := range tests {
|
|
name := name
|
|
tc := tc
|
|
t.Run(name, func(t *testing.T) {
|
|
// Temp dir for most testing unless we're trying to test an error
|
|
var tempDir string
|
|
tempPath := tc.Path
|
|
if !tc.ShouldUseAbsolutePath {
|
|
tempDir = t.TempDir()
|
|
tempPath = filepath.Join(tempDir, tc.Path)
|
|
}
|
|
|
|
// Create a file if we will need it there before Process kicks off.
|
|
if tc.ShouldCreateFile && tc.Path != devnull {
|
|
f, err := os.OpenFile(tempPath, os.O_CREATE, defaultFileMode)
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
err = os.Remove(f.Name())
|
|
require.NoError(t, err)
|
|
}()
|
|
}
|
|
|
|
// Set up a sink
|
|
sink, err := NewFileSink(tempPath, tc.Format)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, sink)
|
|
|
|
// Generate a fake event
|
|
ctx := namespace.RootContext(nil)
|
|
|
|
event := &eventlogger.Event{
|
|
Type: "audit",
|
|
CreatedAt: time.Now(),
|
|
Formatted: make(map[string][]byte),
|
|
Payload: struct{ ID string }{ID: "123"},
|
|
}
|
|
|
|
if !tc.ShouldIgnoreFormat {
|
|
event.FormattedAs(tc.Format, []byte(tc.Data))
|
|
}
|
|
|
|
if tc.ShouldUseNilEvent {
|
|
event = nil
|
|
}
|
|
|
|
// The actual exercising of the sink.
|
|
event, err = sink.Process(ctx, event)
|
|
switch {
|
|
case tc.IsErrorExpected:
|
|
require.Error(t, err)
|
|
require.EqualError(t, err, tc.ExpectedErrorMessage)
|
|
require.Nil(t, event)
|
|
default:
|
|
require.NoError(t, err)
|
|
require.Nil(t, event)
|
|
}
|
|
})
|
|
}
|
|
}
|