vault/internal/observability/event/sink_file_test.go
hashicorp-copywrite[bot] 0b12cdcfd1
[COMPLIANCE] License changes (#22290)
* 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>
2023-08-10 18:14:03 -07:00

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