mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-30 10:12:35 +00:00
* begin refactoring of event package into audit package * audit options additions * rename option structs * Trying to remove 'audit' from the start of names. * typo * typo * typo * newEvent required params * typo * comments on noop sink * more refactoring - merge json/jsonx formatters * fix file backend and tests * Moved unexported funcs to formatter, fixed file tests * typos, comments, moved func * fix corehelpers * fix backends (syslog, socket) * Moved some sinks back to generic event package. * return of the file sink * remove unneeded sink params/return vars * Implement Register and Deregister Audit Devices for EventLogger Framework (#21940) * add function to create StdoutSinkNode * add boolean argument to audit Factory function * create eventlogger nodes in backend factory functions * simplify NewNoopSink function and remove DiscardSinkNode * make the sanity test in the file backend mutually exclusive based on useEventLogger value * remove test cases that no longer made sense and were failing * NewFileSink attempts to open file for sanity check * fix FileSink tests and update FileSink to remove discard, stdout but add /dev/null * Moved WithPrefix from FileSink to EventFormatter * move prefix in backend * NewFormatterConfig and Options (tests fixed) * Little tidy up * add test where audit file is created with useEventLogger set to true * only create eventlogger.Node instances when useEventLogger is true fix failing test due to invalid string conversion of FileMode value * moved variable definition to more appropriate scope --------- Co-authored-by: Marc Boudreau <marc.boudreau@hashicorp.com>
355 lines
7.8 KiB
Go
355 lines
7.8 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package socket
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"strconv"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/hashicorp/eventlogger"
|
|
"github.com/hashicorp/go-multierror"
|
|
"github.com/hashicorp/go-secure-stdlib/parseutil"
|
|
"github.com/hashicorp/vault/audit"
|
|
"github.com/hashicorp/vault/internal/observability/event"
|
|
"github.com/hashicorp/vault/sdk/helper/salt"
|
|
"github.com/hashicorp/vault/sdk/logical"
|
|
)
|
|
|
|
func Factory(ctx context.Context, conf *audit.BackendConfig, useEventLogger bool) (audit.Backend, error) {
|
|
if conf.SaltConfig == nil {
|
|
return nil, fmt.Errorf("nil salt config")
|
|
}
|
|
if conf.SaltView == nil {
|
|
return nil, fmt.Errorf("nil salt view")
|
|
}
|
|
|
|
address, ok := conf.Config["address"]
|
|
if !ok {
|
|
return nil, fmt.Errorf("address is required")
|
|
}
|
|
|
|
socketType, ok := conf.Config["socket_type"]
|
|
if !ok {
|
|
socketType = "tcp"
|
|
}
|
|
|
|
writeDeadline, ok := conf.Config["write_timeout"]
|
|
if !ok {
|
|
writeDeadline = "2s"
|
|
}
|
|
writeDuration, err := parseutil.ParseDurationSecond(writeDeadline)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
format, ok := conf.Config["format"]
|
|
if !ok {
|
|
format = audit.JSONFormat.String()
|
|
}
|
|
switch format {
|
|
case audit.JSONFormat.String(), audit.JSONxFormat.String():
|
|
default:
|
|
return nil, fmt.Errorf("unknown format type %q", format)
|
|
}
|
|
|
|
// Check if hashing of accessor is disabled
|
|
hmacAccessor := true
|
|
if hmacAccessorRaw, ok := conf.Config["hmac_accessor"]; ok {
|
|
value, err := strconv.ParseBool(hmacAccessorRaw)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
hmacAccessor = value
|
|
}
|
|
|
|
// Check if raw logging is enabled
|
|
logRaw := false
|
|
if raw, ok := conf.Config["log_raw"]; ok {
|
|
b, err := strconv.ParseBool(raw)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
logRaw = b
|
|
}
|
|
|
|
elideListResponses := false
|
|
if elideListResponsesRaw, ok := conf.Config["elide_list_responses"]; ok {
|
|
value, err := strconv.ParseBool(elideListResponsesRaw)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
elideListResponses = value
|
|
}
|
|
|
|
cfg, err := audit.NewFormatterConfig(
|
|
audit.WithElision(elideListResponses),
|
|
audit.WithFormat(format),
|
|
audit.WithHMACAccessor(hmacAccessor),
|
|
audit.WithRaw(logRaw),
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
b := &Backend{
|
|
saltConfig: conf.SaltConfig,
|
|
saltView: conf.SaltView,
|
|
formatConfig: cfg,
|
|
|
|
writeDuration: writeDuration,
|
|
address: address,
|
|
socketType: socketType,
|
|
}
|
|
|
|
// Configure the formatter for either case.
|
|
f, err := audit.NewEntryFormatter(b.formatConfig, b)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error creating formatter: %w", err)
|
|
}
|
|
var w audit.Writer
|
|
switch format {
|
|
case audit.JSONFormat.String():
|
|
w = &audit.JSONWriter{Prefix: conf.Config["prefix"]}
|
|
case audit.JSONxFormat.String():
|
|
w = &audit.JSONxWriter{Prefix: conf.Config["prefix"]}
|
|
}
|
|
|
|
fw, err := audit.NewEntryFormatterWriter(b.formatConfig, f, w)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error creating formatter writer: %w", err)
|
|
}
|
|
|
|
b.formatter = fw
|
|
|
|
if useEventLogger {
|
|
b.nodeIDList = make([]eventlogger.NodeID, 2)
|
|
b.nodeMap = make(map[eventlogger.NodeID]eventlogger.Node)
|
|
|
|
formatterNodeID, err := event.GenerateNodeID()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error generating random NodeID for formatter node: %w", err)
|
|
}
|
|
b.nodeIDList[0] = formatterNodeID
|
|
b.nodeMap[formatterNodeID] = f
|
|
|
|
sinkNode, err := event.NewSocketSink(format, address, event.WithSocketType(socketType), event.WithMaxDuration(writeDuration.String()))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error creating socket sink node: %w", err)
|
|
}
|
|
sinkNodeID, err := event.GenerateNodeID()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error generating random NodeID for sink node: %w", err)
|
|
}
|
|
b.nodeIDList[1] = sinkNodeID
|
|
b.nodeMap[sinkNodeID] = sinkNode
|
|
}
|
|
|
|
return b, nil
|
|
}
|
|
|
|
// Backend is the audit backend for the socket audit transport.
|
|
type Backend struct {
|
|
connection net.Conn
|
|
|
|
formatter *audit.EntryFormatterWriter
|
|
formatConfig audit.FormatterConfig
|
|
|
|
writeDuration time.Duration
|
|
address string
|
|
socketType string
|
|
|
|
sync.Mutex
|
|
|
|
saltMutex sync.RWMutex
|
|
salt *salt.Salt
|
|
saltConfig *salt.Config
|
|
saltView logical.Storage
|
|
|
|
nodeIDList []eventlogger.NodeID
|
|
nodeMap map[eventlogger.NodeID]eventlogger.Node
|
|
}
|
|
|
|
var _ audit.Backend = (*Backend)(nil)
|
|
|
|
func (b *Backend) GetHash(ctx context.Context, data string) (string, error) {
|
|
salt, err := b.Salt(ctx)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return audit.HashString(salt, data), nil
|
|
}
|
|
|
|
func (b *Backend) LogRequest(ctx context.Context, in *logical.LogInput) error {
|
|
var buf bytes.Buffer
|
|
if err := b.formatter.FormatAndWriteRequest(ctx, &buf, in); err != nil {
|
|
return err
|
|
}
|
|
|
|
b.Lock()
|
|
defer b.Unlock()
|
|
|
|
err := b.write(ctx, buf.Bytes())
|
|
if err != nil {
|
|
rErr := b.reconnect(ctx)
|
|
if rErr != nil {
|
|
err = multierror.Append(err, rErr)
|
|
} else {
|
|
// Try once more after reconnecting
|
|
err = b.write(ctx, buf.Bytes())
|
|
}
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func (b *Backend) LogResponse(ctx context.Context, in *logical.LogInput) error {
|
|
var buf bytes.Buffer
|
|
if err := b.formatter.FormatAndWriteResponse(ctx, &buf, in); err != nil {
|
|
return err
|
|
}
|
|
|
|
b.Lock()
|
|
defer b.Unlock()
|
|
|
|
err := b.write(ctx, buf.Bytes())
|
|
if err != nil {
|
|
rErr := b.reconnect(ctx)
|
|
if rErr != nil {
|
|
err = multierror.Append(err, rErr)
|
|
} else {
|
|
// Try once more after reconnecting
|
|
err = b.write(ctx, buf.Bytes())
|
|
}
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func (b *Backend) LogTestMessage(ctx context.Context, in *logical.LogInput, config map[string]string) error {
|
|
var buf bytes.Buffer
|
|
|
|
temporaryFormatter, err := audit.NewTemporaryFormatter(config["format"], config["prefix"])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = temporaryFormatter.FormatAndWriteRequest(ctx, &buf, in); err != nil {
|
|
return err
|
|
}
|
|
|
|
b.Lock()
|
|
defer b.Unlock()
|
|
|
|
err = b.write(ctx, buf.Bytes())
|
|
if err != nil {
|
|
rErr := b.reconnect(ctx)
|
|
if rErr != nil {
|
|
err = multierror.Append(err, rErr)
|
|
} else {
|
|
// Try once more after reconnecting
|
|
err = b.write(ctx, buf.Bytes())
|
|
}
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func (b *Backend) write(ctx context.Context, buf []byte) error {
|
|
if b.connection == nil {
|
|
if err := b.reconnect(ctx); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
err := b.connection.SetWriteDeadline(time.Now().Add(b.writeDuration))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = b.connection.Write(buf)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (b *Backend) reconnect(ctx context.Context) error {
|
|
if b.connection != nil {
|
|
b.connection.Close()
|
|
b.connection = nil
|
|
}
|
|
|
|
timeoutContext, cancel := context.WithTimeout(ctx, b.writeDuration)
|
|
defer cancel()
|
|
|
|
dialer := net.Dialer{}
|
|
conn, err := dialer.DialContext(timeoutContext, b.socketType, b.address)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
b.connection = conn
|
|
|
|
return nil
|
|
}
|
|
|
|
func (b *Backend) Reload(ctx context.Context) error {
|
|
b.Lock()
|
|
defer b.Unlock()
|
|
|
|
err := b.reconnect(ctx)
|
|
|
|
return err
|
|
}
|
|
|
|
func (b *Backend) Salt(ctx context.Context) (*salt.Salt, error) {
|
|
b.saltMutex.RLock()
|
|
if b.salt != nil {
|
|
defer b.saltMutex.RUnlock()
|
|
return b.salt, nil
|
|
}
|
|
b.saltMutex.RUnlock()
|
|
b.saltMutex.Lock()
|
|
defer b.saltMutex.Unlock()
|
|
if b.salt != nil {
|
|
return b.salt, nil
|
|
}
|
|
salt, err := salt.NewSalt(ctx, b.saltView, b.saltConfig)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
b.salt = salt
|
|
return salt, nil
|
|
}
|
|
|
|
func (b *Backend) Invalidate(_ context.Context) {
|
|
b.saltMutex.Lock()
|
|
defer b.saltMutex.Unlock()
|
|
b.salt = nil
|
|
}
|
|
|
|
// RegisterNodesAndPipeline registers the nodes and a pipeline as required by
|
|
// the audit.Backend interface.
|
|
func (b *Backend) RegisterNodesAndPipeline(broker *eventlogger.Broker, name string) error {
|
|
for id, node := range b.nodeMap {
|
|
if err := broker.RegisterNode(id, node); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
pipeline := eventlogger.Pipeline{
|
|
PipelineID: eventlogger.PipelineID(name),
|
|
EventType: eventlogger.EventType("audit"),
|
|
NodeIDs: b.nodeIDList,
|
|
}
|
|
|
|
return broker.RegisterPipeline(pipeline)
|
|
}
|