mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-30 02:02:43 +00:00
Audit: optional logger for sinks will log on errors when context is done (#27859)
* Added optional logger for sink nodes (supplied by backends) will log on errors when context is also done * changelog
This commit is contained in:
@@ -76,12 +76,12 @@ func newFileBackend(conf *BackendConfig, headersConfig HeaderFormatter) (*FileBa
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var opt []event.Option
|
sinkOpts := []event.Option{event.WithLogger(conf.Logger)}
|
||||||
if mode, ok := conf.Config[optionMode]; ok {
|
if mode, ok := conf.Config[optionMode]; ok {
|
||||||
opt = append(opt, event.WithFileMode(mode))
|
sinkOpts = append(sinkOpts, event.WithFileMode(mode))
|
||||||
}
|
}
|
||||||
|
|
||||||
err = b.configureSinkNode(conf.MountPath, filePath, cfg.requiredFormat, opt...)
|
err = b.configureSinkNode(conf.MountPath, filePath, cfg.requiredFormat, sinkOpts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ func newSocketBackend(conf *BackendConfig, headersConfig HeaderFormatter) (*Sock
|
|||||||
sinkOpts := []event.Option{
|
sinkOpts := []event.Option{
|
||||||
event.WithSocketType(socketType),
|
event.WithSocketType(socketType),
|
||||||
event.WithMaxDuration(writeDeadline),
|
event.WithMaxDuration(writeDeadline),
|
||||||
|
event.WithLogger(conf.Logger),
|
||||||
}
|
}
|
||||||
|
|
||||||
err = event.ValidateOptions(sinkOpts...)
|
err = event.ValidateOptions(sinkOpts...)
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ func newSyslogBackend(conf *BackendConfig, headersConfig HeaderFormatter) (*Sysl
|
|||||||
sinkOpts := []event.Option{
|
sinkOpts := []event.Option{
|
||||||
event.WithFacility(facility),
|
event.WithFacility(facility),
|
||||||
event.WithTag(tag),
|
event.WithTag(tag),
|
||||||
|
event.WithLogger(conf.Logger),
|
||||||
}
|
}
|
||||||
|
|
||||||
err = event.ValidateOptions(sinkOpts...)
|
err = event.ValidateOptions(sinkOpts...)
|
||||||
|
|||||||
4
changelog/27859.txt
Normal file
4
changelog/27859.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
```release-note:improvement
|
||||||
|
audit: sinks (file, socket, syslog) will attempt to log errors to the server operational
|
||||||
|
log before returning (if there are errors to log, and the context is done).
|
||||||
|
```
|
||||||
@@ -6,10 +6,12 @@ package event
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/hashicorp/go-hclog"
|
||||||
"github.com/hashicorp/go-secure-stdlib/parseutil"
|
"github.com/hashicorp/go-secure-stdlib/parseutil"
|
||||||
"github.com/hashicorp/go-uuid"
|
"github.com/hashicorp/go-uuid"
|
||||||
)
|
)
|
||||||
@@ -26,6 +28,7 @@ type options struct {
|
|||||||
withSocketType string
|
withSocketType string
|
||||||
withMaxDuration time.Duration
|
withMaxDuration time.Duration
|
||||||
withFileMode *os.FileMode
|
withFileMode *os.FileMode
|
||||||
|
withLogger hclog.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// getDefaultOptions returns Options with their default values.
|
// getDefaultOptions returns Options with their default values.
|
||||||
@@ -201,3 +204,15 @@ func WithFileMode(mode string) Option {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithLogger provides an Option to supply a logger which will be used to write logs.
|
||||||
|
// NOTE: If no logger is supplied then logging may not be possible.
|
||||||
|
func WithLogger(l hclog.Logger) Option {
|
||||||
|
return func(o *options) error {
|
||||||
|
if l != nil && !reflect.ValueOf(l).IsNil() {
|
||||||
|
o.withLogger = l
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/hashicorp/go-hclog"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -423,3 +424,37 @@ func TestOptions_WithFileMode(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestOptions_WithLogger exercises WithLogger Option to ensure it performs as expected.
|
||||||
|
func TestOptions_WithLogger(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := map[string]struct {
|
||||||
|
value hclog.Logger
|
||||||
|
isNilExpected bool
|
||||||
|
}{
|
||||||
|
"nil-pointer": {
|
||||||
|
value: nil,
|
||||||
|
isNilExpected: true,
|
||||||
|
},
|
||||||
|
"logger": {
|
||||||
|
value: hclog.NewNullLogger(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range tests {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
opts := &options{}
|
||||||
|
applyOption := WithLogger(tc.value)
|
||||||
|
err := applyOption(opts)
|
||||||
|
require.NoError(t, err)
|
||||||
|
if tc.isNilExpected {
|
||||||
|
require.Nil(t, opts.withLogger)
|
||||||
|
} else {
|
||||||
|
require.NotNil(t, opts.withLogger)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/hashicorp/eventlogger"
|
"github.com/hashicorp/eventlogger"
|
||||||
|
"github.com/hashicorp/go-hclog"
|
||||||
)
|
)
|
||||||
|
|
||||||
// defaultFileMode is the default file permissions (read/write for everyone).
|
// defaultFileMode is the default file permissions (read/write for everyone).
|
||||||
@@ -31,6 +32,7 @@ type FileSink struct {
|
|||||||
fileMode os.FileMode
|
fileMode os.FileMode
|
||||||
path string
|
path string
|
||||||
requiredFormat string
|
requiredFormat string
|
||||||
|
logger hclog.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFileSink should be used to create a new FileSink.
|
// NewFileSink should be used to create a new FileSink.
|
||||||
@@ -69,6 +71,7 @@ func NewFileSink(path string, format string, opt ...Option) (*FileSink, error) {
|
|||||||
fileMode: mode,
|
fileMode: mode,
|
||||||
requiredFormat: format,
|
requiredFormat: format,
|
||||||
path: p,
|
path: p,
|
||||||
|
logger: opts.withLogger,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that the file can be successfully opened for writing;
|
// Ensure that the file can be successfully opened for writing;
|
||||||
@@ -82,13 +85,22 @@ func NewFileSink(path string, format string, opt ...Option) (*FileSink, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Process handles writing the event to the file sink.
|
// Process handles writing the event to the file sink.
|
||||||
func (s *FileSink) Process(ctx context.Context, e *eventlogger.Event) (*eventlogger.Event, error) {
|
func (s *FileSink) Process(ctx context.Context, e *eventlogger.Event) (_ *eventlogger.Event, retErr error) {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return nil, ctx.Err()
|
return nil, ctx.Err()
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
// If the context is errored (cancelled), and we were planning to return
|
||||||
|
// an error, let's also log (if we have a logger) in case the eventlogger's
|
||||||
|
// status channel and errors propagated.
|
||||||
|
if err := ctx.Err(); err != nil && retErr != nil && s.logger != nil {
|
||||||
|
s.logger.Error("file sink error", "context", err, "error", retErr)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
if e == nil {
|
if e == nil {
|
||||||
return nil, fmt.Errorf("event is nil: %w", ErrInvalidParameter)
|
return nil, fmt.Errorf("event is nil: %w", ErrInvalidParameter)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/hashicorp/eventlogger"
|
"github.com/hashicorp/eventlogger"
|
||||||
|
"github.com/hashicorp/go-hclog"
|
||||||
"github.com/hashicorp/go-multierror"
|
"github.com/hashicorp/go-multierror"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -25,6 +26,7 @@ type SocketSink struct {
|
|||||||
maxDuration time.Duration
|
maxDuration time.Duration
|
||||||
socketLock sync.RWMutex
|
socketLock sync.RWMutex
|
||||||
connection net.Conn
|
connection net.Conn
|
||||||
|
logger hclog.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSocketSink should be used to create a new SocketSink.
|
// NewSocketSink should be used to create a new SocketSink.
|
||||||
@@ -52,21 +54,28 @@ func NewSocketSink(address string, format string, opt ...Option) (*SocketSink, e
|
|||||||
maxDuration: opts.withMaxDuration,
|
maxDuration: opts.withMaxDuration,
|
||||||
socketLock: sync.RWMutex{},
|
socketLock: sync.RWMutex{},
|
||||||
connection: nil,
|
connection: nil,
|
||||||
|
logger: opts.withLogger,
|
||||||
}
|
}
|
||||||
|
|
||||||
return sink, nil
|
return sink, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process handles writing the event to the socket.
|
// Process handles writing the event to the socket.
|
||||||
func (s *SocketSink) Process(ctx context.Context, e *eventlogger.Event) (*eventlogger.Event, error) {
|
func (s *SocketSink) Process(ctx context.Context, e *eventlogger.Event) (_ *eventlogger.Event, retErr error) {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return nil, ctx.Err()
|
return nil, ctx.Err()
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
s.socketLock.Lock()
|
defer func() {
|
||||||
defer s.socketLock.Unlock()
|
// If the context is errored (cancelled), and we were planning to return
|
||||||
|
// an error, let's also log (if we have a logger) in case the eventlogger's
|
||||||
|
// status channel and errors propagated.
|
||||||
|
if err := ctx.Err(); err != nil && retErr != nil && s.logger != nil {
|
||||||
|
s.logger.Error("socket sink error", "context", err, "error", retErr)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
if e == nil {
|
if e == nil {
|
||||||
return nil, fmt.Errorf("event is nil: %w", ErrInvalidParameter)
|
return nil, fmt.Errorf("event is nil: %w", ErrInvalidParameter)
|
||||||
@@ -77,6 +86,9 @@ func (s *SocketSink) Process(ctx context.Context, e *eventlogger.Event) (*eventl
|
|||||||
return nil, fmt.Errorf("unable to retrieve event formatted as %q: %w", s.requiredFormat, ErrInvalidParameter)
|
return nil, fmt.Errorf("unable to retrieve event formatted as %q: %w", s.requiredFormat, ErrInvalidParameter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.socketLock.Lock()
|
||||||
|
defer s.socketLock.Unlock()
|
||||||
|
|
||||||
// Try writing and return early if successful.
|
// Try writing and return early if successful.
|
||||||
err := s.write(ctx, formatted)
|
err := s.write(ctx, formatted)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/eventlogger"
|
"github.com/hashicorp/eventlogger"
|
||||||
|
"github.com/hashicorp/go-hclog"
|
||||||
gsyslog "github.com/hashicorp/go-syslog"
|
gsyslog "github.com/hashicorp/go-syslog"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -17,7 +18,8 @@ var _ eventlogger.Node = (*SyslogSink)(nil)
|
|||||||
// SyslogSink is a sink node which handles writing events to syslog.
|
// SyslogSink is a sink node which handles writing events to syslog.
|
||||||
type SyslogSink struct {
|
type SyslogSink struct {
|
||||||
requiredFormat string
|
requiredFormat string
|
||||||
logger gsyslog.Syslogger
|
syslogger gsyslog.Syslogger
|
||||||
|
logger hclog.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSyslogSink should be used to create a new SyslogSink.
|
// NewSyslogSink should be used to create a new SyslogSink.
|
||||||
@@ -38,17 +40,32 @@ func NewSyslogSink(format string, opt ...Option) (*SyslogSink, error) {
|
|||||||
return nil, fmt.Errorf("error creating syslogger: %w", err)
|
return nil, fmt.Errorf("error creating syslogger: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &SyslogSink{requiredFormat: format, logger: logger}, nil
|
syslog := &SyslogSink{
|
||||||
|
requiredFormat: format,
|
||||||
|
syslogger: logger,
|
||||||
|
logger: opts.withLogger,
|
||||||
|
}
|
||||||
|
|
||||||
|
return syslog, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process handles writing the event to the syslog.
|
// Process handles writing the event to the syslog.
|
||||||
func (s *SyslogSink) Process(ctx context.Context, e *eventlogger.Event) (*eventlogger.Event, error) {
|
func (s *SyslogSink) Process(ctx context.Context, e *eventlogger.Event) (_ *eventlogger.Event, retErr error) {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return nil, ctx.Err()
|
return nil, ctx.Err()
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
// If the context is errored (cancelled), and we were planning to return
|
||||||
|
// an error, let's also log (if we have a logger) in case the eventlogger's
|
||||||
|
// status channel and errors propagated.
|
||||||
|
if err := ctx.Err(); err != nil && retErr != nil && s.logger != nil {
|
||||||
|
s.logger.Error("syslog sink error", "context", err, "error", retErr)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
if e == nil {
|
if e == nil {
|
||||||
return nil, fmt.Errorf("event is nil: %w", ErrInvalidParameter)
|
return nil, fmt.Errorf("event is nil: %w", ErrInvalidParameter)
|
||||||
}
|
}
|
||||||
@@ -58,7 +75,7 @@ func (s *SyslogSink) Process(ctx context.Context, e *eventlogger.Event) (*eventl
|
|||||||
return nil, fmt.Errorf("unable to retrieve event formatted as %q: %w", s.requiredFormat, ErrInvalidParameter)
|
return nil, fmt.Errorf("unable to retrieve event formatted as %q: %w", s.requiredFormat, ErrInvalidParameter)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := s.logger.Write(formatted)
|
_, err := s.syslogger.Write(formatted)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error writing to syslog: %w", err)
|
return nil, fmt.Errorf("error writing to syslog: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -160,7 +160,6 @@ func (c *Core) enableAudit(ctx context.Context, entry *MountEntry, updateStorage
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
c.logger.Error("new audit backend failed test", "path", entry.Path, "type", entry.Type, "error", err)
|
c.logger.Error("new audit backend failed test", "path", entry.Path, "type", entry.Type, "error", err)
|
||||||
return fmt.Errorf("audit backend failed test message: %w", err)
|
return fmt.Errorf("audit backend failed test message: %w", err)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user