mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-29 09:42:25 +00:00
VAULT-17075: syslog sink node (#21859)
* syslog sink added, options + tests added, tweaks to file sink comments * defaults for syslog options
This commit is contained in:
@@ -34,6 +34,7 @@ type AuditFileSink struct {
|
||||
}
|
||||
|
||||
// NewAuditFileSink should be used to create a new AuditFileSink.
|
||||
// Accepted options: WithFileMode and WithPrefix.
|
||||
func NewAuditFileSink(path string, format auditFormat, opt ...Option) (*AuditFileSink, error) {
|
||||
const op = "event.NewAuditFileSink"
|
||||
|
||||
@@ -110,12 +111,12 @@ func (f *AuditFileSink) Process(ctx context.Context, e *eventlogger.Event) (*eve
|
||||
return nil, fmt.Errorf("%s: unable to retrieve event formatted as %q", op, f.format)
|
||||
}
|
||||
|
||||
buffer := bytes.NewBuffer(formatted)
|
||||
err := f.log(buffer)
|
||||
err := f.log(formatted)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: error writing file for audit sink: %w", op, err)
|
||||
}
|
||||
|
||||
// return nil for the event to indicate the pipeline is complete.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -145,7 +146,7 @@ func (f *AuditFileSink) Reopen() error {
|
||||
return f.open()
|
||||
}
|
||||
|
||||
// Type is used to define which type of node AuditFileSink is.
|
||||
// Type describes the type of this node (sink).
|
||||
func (f *AuditFileSink) Type() eventlogger.NodeType {
|
||||
return eventlogger.NodeTypeSink
|
||||
}
|
||||
@@ -189,13 +190,13 @@ func (f *AuditFileSink) open() error {
|
||||
|
||||
// log writes the buffer to the file.
|
||||
// It acquires a lock on the file to do this.
|
||||
func (f *AuditFileSink) log(buf *bytes.Buffer) error {
|
||||
func (f *AuditFileSink) log(data []byte) error {
|
||||
const op = "event.(AuditFileSink).log"
|
||||
|
||||
f.fileLock.Lock()
|
||||
defer f.fileLock.Unlock()
|
||||
|
||||
reader := bytes.NewReader(buf.Bytes())
|
||||
reader := bytes.NewReader(data)
|
||||
|
||||
var writer io.Writer
|
||||
switch {
|
||||
|
||||
75
internal/observability/event/audit_sink_syslog.go
Normal file
75
internal/observability/event/audit_sink_syslog.go
Normal file
@@ -0,0 +1,75 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package event
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
gsyslog "github.com/hashicorp/go-syslog"
|
||||
|
||||
"github.com/hashicorp/eventlogger"
|
||||
)
|
||||
|
||||
// AuditSyslogSink is a sink node which handles writing audit events to syslog.
|
||||
type AuditSyslogSink struct {
|
||||
format auditFormat
|
||||
logger gsyslog.Syslogger
|
||||
}
|
||||
|
||||
// NewAuditSyslogSink should be used to create a new AuditSyslogSink.
|
||||
// Accepted options: WithFacility and WithTag.
|
||||
func NewAuditSyslogSink(format auditFormat, opt ...Option) (*AuditSyslogSink, error) {
|
||||
const op = "event.NewAuditSyslogSink"
|
||||
|
||||
opts, err := getOpts(opt...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: error applying options: %w", op, err)
|
||||
}
|
||||
|
||||
logger, err := gsyslog.NewLogger(gsyslog.LOG_INFO, opts.withFacility, opts.withTag)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: error creating syslogger: %w", op, err)
|
||||
}
|
||||
|
||||
return &AuditSyslogSink{format: format, logger: logger}, nil
|
||||
}
|
||||
|
||||
// Process handles writing the event to the syslog.
|
||||
func (s *AuditSyslogSink) Process(ctx context.Context, e *eventlogger.Event) (*eventlogger.Event, error) {
|
||||
const op = "event.(AuditSyslogSink).Process"
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
if e == nil {
|
||||
return nil, fmt.Errorf("%s: event is nil: %w", op, ErrInvalidParameter)
|
||||
}
|
||||
|
||||
formatted, found := e.Format(s.format.String())
|
||||
if !found {
|
||||
return nil, fmt.Errorf("%s: unable to retrieve event formatted as %q", op, s.format)
|
||||
}
|
||||
|
||||
_, err := s.logger.Write(formatted)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: error writing to syslog: %w", op, err)
|
||||
}
|
||||
|
||||
// return nil for the event to indicate the pipeline is complete.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Reopen is a no-op for a syslog sink.
|
||||
func (s *AuditSyslogSink) Reopen() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Type describes the type of this node (sink).
|
||||
func (s *AuditSyslogSink) Type() eventlogger.NodeType {
|
||||
return eventlogger.NodeTypeSink
|
||||
}
|
||||
@@ -25,12 +25,16 @@ type options struct {
|
||||
withFormat auditFormat
|
||||
withFileMode *os.FileMode
|
||||
withPrefix string
|
||||
withFacility string
|
||||
withTag string
|
||||
}
|
||||
|
||||
// getDefaultOptions returns options with their default values.
|
||||
func getDefaultOptions() options {
|
||||
return options{
|
||||
withNow: time.Now(),
|
||||
withNow: time.Now(),
|
||||
withFacility: "AUTH",
|
||||
withTag: "vault",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,12 +147,11 @@ func WithFormat(format string) Option {
|
||||
// applied, but it will not return an error in those circumstances.
|
||||
func WithFileMode(mode string) Option {
|
||||
return func(o *options) error {
|
||||
// Clear up whitespace before attempting to parse
|
||||
// If supplied file mode is empty, just return early without setting anything.
|
||||
// We can assume that this option was called by something that didn't
|
||||
// parse the incoming value, perhaps from a config map etc.
|
||||
mode = strings.TrimSpace(mode)
|
||||
if mode == "" {
|
||||
// If supplied file mode is empty, just return early without setting anything.
|
||||
// We can assume that this option was called by something that didn't
|
||||
// parse the incoming value, perhaps from a config map etc.
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -175,3 +178,29 @@ func WithPrefix(prefix string) Option {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithFacility provides an option to represent a 'facility' for a syslog sink.
|
||||
func WithFacility(facility string) Option {
|
||||
return func(o *options) error {
|
||||
facility = strings.TrimSpace(facility)
|
||||
|
||||
if facility != "" {
|
||||
o.withFacility = facility
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithTag provides an option to represent a 'tag' for a syslog sink.
|
||||
func WithTag(tag string) Option {
|
||||
return func(o *options) error {
|
||||
tag = strings.TrimSpace(tag)
|
||||
|
||||
if tag != "" {
|
||||
o.withTag = tag
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,6 +197,82 @@ func TestOptions_WithID(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestOptions_WithFacility exercises WithFacility option to ensure it performs as expected.
|
||||
func TestOptions_WithFacility(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
Value string
|
||||
ExpectedValue string
|
||||
}{
|
||||
"empty": {
|
||||
Value: "",
|
||||
ExpectedValue: "",
|
||||
},
|
||||
"whitespace": {
|
||||
Value: " ",
|
||||
ExpectedValue: "",
|
||||
},
|
||||
"value": {
|
||||
Value: "juan",
|
||||
ExpectedValue: "juan",
|
||||
},
|
||||
"spacey-value": {
|
||||
Value: " juan ",
|
||||
ExpectedValue: "juan",
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range tests {
|
||||
name := name
|
||||
tc := tc
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
options := &options{}
|
||||
applyOption := WithFacility(tc.Value)
|
||||
err := applyOption(options)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.ExpectedValue, options.withFacility)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestOptions_WithTag exercises WithTag option to ensure it performs as expected.
|
||||
func TestOptions_WithTag(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
Value string
|
||||
ExpectedValue string
|
||||
}{
|
||||
"empty": {
|
||||
Value: "",
|
||||
ExpectedValue: "",
|
||||
},
|
||||
"whitespace": {
|
||||
Value: " ",
|
||||
ExpectedValue: "",
|
||||
},
|
||||
"value": {
|
||||
Value: "juan",
|
||||
ExpectedValue: "juan",
|
||||
},
|
||||
"spacey-value": {
|
||||
Value: " juan ",
|
||||
ExpectedValue: "juan",
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range tests {
|
||||
name := name
|
||||
tc := tc
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
options := &options{}
|
||||
applyOption := WithTag(tc.Value)
|
||||
err := applyOption(options)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.ExpectedValue, options.withTag)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestOptions_WithFileMode exercises WithFileMode option to ensure it performs as expected.
|
||||
func TestOptions_WithFileMode(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
@@ -266,6 +342,8 @@ func TestOptions_Default(t *testing.T) {
|
||||
require.NotNil(t, opts)
|
||||
require.True(t, time.Now().After(opts.withNow))
|
||||
require.False(t, opts.withNow.IsZero())
|
||||
require.Equal(t, "AUTH", opts.withFacility)
|
||||
require.Equal(t, "vault", opts.withTag)
|
||||
}
|
||||
|
||||
// TestOptions_Opts exercises getOpts with various Option values.
|
||||
|
||||
Reference in New Issue
Block a user