// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package syslog import ( "bytes" "context" "fmt" "strconv" "sync" gsyslog "github.com/hashicorp/go-syslog" "github.com/hashicorp/vault/audit" "github.com/hashicorp/vault/sdk/helper/salt" "github.com/hashicorp/vault/sdk/logical" ) func Factory(ctx context.Context, conf *audit.BackendConfig) (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") } // Get facility or default to AUTH facility, ok := conf.Config["facility"] if !ok { facility = "AUTH" } // Get tag or default to 'vault' tag, ok := conf.Config["tag"] if !ok { tag = "vault" } format, ok := conf.Config["format"] if !ok { format = "json" } switch format { case "json", "jsonx": 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 } // Get the logger logger, err := gsyslog.NewLogger(gsyslog.LOG_INFO, facility, tag) if err != nil { return nil, err } b := &Backend{ logger: logger, saltConfig: conf.SaltConfig, saltView: conf.SaltView, formatConfig: audit.FormatterConfig{ Raw: logRaw, HMACAccessor: hmacAccessor, ElideListResponses: elideListResponses, }, } switch format { case "json": b.formatter.AuditFormatWriter = &audit.JSONFormatWriter{ Prefix: conf.Config["prefix"], SaltFunc: b.Salt, } case "jsonx": b.formatter.AuditFormatWriter = &audit.JSONxFormatWriter{ Prefix: conf.Config["prefix"], SaltFunc: b.Salt, } } return b, nil } // Backend is the audit backend for the syslog-based audit store. type Backend struct { logger gsyslog.Syslogger formatter audit.AuditFormatter formatConfig audit.FormatterConfig saltMutex sync.RWMutex salt *salt.Salt saltConfig *salt.Config saltView logical.Storage } 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.FormatRequest(ctx, &buf, b.formatConfig, in); err != nil { return err } // Write out to syslog _, err := b.logger.Write(buf.Bytes()) return err } func (b *Backend) LogResponse(ctx context.Context, in *logical.LogInput) error { var buf bytes.Buffer if err := b.formatter.FormatResponse(ctx, &buf, b.formatConfig, in); err != nil { return err } // Write out to syslog _, err := b.logger.Write(buf.Bytes()) return err } func (b *Backend) LogTestMessage(ctx context.Context, in *logical.LogInput, config map[string]string) error { var buf bytes.Buffer temporaryFormatter := audit.NewTemporaryFormatter(config["format"], config["prefix"]) if err := temporaryFormatter.FormatRequest(ctx, &buf, b.formatConfig, in); err != nil { return err } // Send to syslog _, err := b.logger.Write(buf.Bytes()) return err } func (b *Backend) Reload(_ context.Context) error { return nil } 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 }