open-vault/builtin/audit/syslog/backend.go

197 lines
4.3 KiB
Go
Raw Normal View History

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
2016-12-19 20:35:55 +00:00
package syslog
2015-04-24 18:06:19 +00:00
import (
"bytes"
"context"
"fmt"
"strconv"
"sync"
2015-04-24 18:06:19 +00:00
gsyslog "github.com/hashicorp/go-syslog"
2015-04-24 18:06:19 +00:00
"github.com/hashicorp/vault/audit"
"github.com/hashicorp/vault/sdk/helper/salt"
"github.com/hashicorp/vault/sdk/logical"
2015-04-24 18:06:19 +00:00
)
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")
}
2015-04-24 18:06:19 +00:00
// Get facility or default to AUTH
facility, ok := conf.Config["facility"]
2015-04-24 18:06:19 +00:00
if !ok {
facility = "AUTH"
}
// Get tag or default to 'vault'
tag, ok := conf.Config["tag"]
2015-04-24 18:06:19 +00:00
if !ok {
tag = "vault"
}
2016-09-21 14:29:42 +00:00
format, ok := conf.Config["format"]
if !ok {
format = "json"
}
switch format {
case "json", "jsonx":
default:
return nil, fmt.Errorf("unknown format type %q", format)
2016-09-21 14:29:42 +00:00
}
// Check if hashing of accessor is disabled
2016-03-14 18:52:29 +00:00
hmacAccessor := true
if hmacAccessorRaw, ok := conf.Config["hmac_accessor"]; ok {
value, err := strconv.ParseBool(hmacAccessorRaw)
if err != nil {
return nil, err
}
2016-03-14 18:52:29 +00:00
hmacAccessor = value
}
// Check if raw logging is enabled
2015-04-26 01:26:08 +00:00
logRaw := false
if raw, ok := conf.Config["log_raw"]; ok {
b, err := strconv.ParseBool(raw)
if err != nil {
return nil, err
}
logRaw = b
}
Add option 'elide_list_responses' to audit backends (#18128) This PR relates to a feature request logged through HashiCorp commercial support. Vault lacks pagination in its APIs. As a result, certain list operations can return **very** large responses. The user's chosen audit sinks may experience difficulty consuming audit records that swell to tens of megabytes of JSON. In our case, one of the systems consuming audit log data could not cope, and failed. The responses of list operations are typically not very interesting, as they are mostly lists of keys, or, even when they include a "key_info" field, are not returning confidential information. They become even less interesting once HMAC-ed by the audit system. Some example Vault "list" operations that are prone to becoming very large in an active Vault installation are: auth/token/accessors/ identity/entity/id/ identity/entity-alias/id/ pki/certs/ In response, I've coded a new option that can be applied to audit backends, `elide_list_responses`. When enabled, response data is elided from audit logs, only when the operation type is "list". For added safety, the elision only applies to the "keys" and "key_info" fields within the response data - these are conventionally the only fields present in a list response - see logical.ListResponse, and logical.ListResponseWithInfo. However, other fields are technically possible if a plugin author writes unusual code, and these will be preserved in the audit log even with this option enabled. The elision replaces the values of the "keys" and "key_info" fields with an integer count of the number of entries. This allows even the elided audit logs to still be useful for answering questions like "Was any data returned?" or "How many records were listed?".
2023-01-11 21:15:52 +00:00
elideListResponses := false
if elideListResponsesRaw, ok := conf.Config["elide_list_responses"]; ok {
value, err := strconv.ParseBool(elideListResponsesRaw)
if err != nil {
return nil, err
}
elideListResponses = value
}
2015-04-24 18:06:19 +00:00
// 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,
2016-09-21 14:29:42 +00:00
formatConfig: audit.FormatterConfig{
Add option 'elide_list_responses' to audit backends (#18128) This PR relates to a feature request logged through HashiCorp commercial support. Vault lacks pagination in its APIs. As a result, certain list operations can return **very** large responses. The user's chosen audit sinks may experience difficulty consuming audit records that swell to tens of megabytes of JSON. In our case, one of the systems consuming audit log data could not cope, and failed. The responses of list operations are typically not very interesting, as they are mostly lists of keys, or, even when they include a "key_info" field, are not returning confidential information. They become even less interesting once HMAC-ed by the audit system. Some example Vault "list" operations that are prone to becoming very large in an active Vault installation are: auth/token/accessors/ identity/entity/id/ identity/entity-alias/id/ pki/certs/ In response, I've coded a new option that can be applied to audit backends, `elide_list_responses`. When enabled, response data is elided from audit logs, only when the operation type is "list". For added safety, the elision only applies to the "keys" and "key_info" fields within the response data - these are conventionally the only fields present in a list response - see logical.ListResponse, and logical.ListResponseWithInfo. However, other fields are technically possible if a plugin author writes unusual code, and these will be preserved in the audit log even with this option enabled. The elision replaces the values of the "keys" and "key_info" fields with an integer count of the number of entries. This allows even the elided audit logs to still be useful for answering questions like "Was any data returned?" or "How many records were listed?".
2023-01-11 21:15:52 +00:00
Raw: logRaw,
HMACAccessor: hmacAccessor,
ElideListResponses: elideListResponses,
2016-09-21 14:29:42 +00:00
},
}
switch format {
case "json":
b.formatter.AuditFormatWriter = &audit.JSONFormatWriter{
Prefix: conf.Config["prefix"],
SaltFunc: b.Salt,
}
2016-09-21 14:29:42 +00:00
case "jsonx":
b.formatter.AuditFormatWriter = &audit.JSONxFormatWriter{
Prefix: conf.Config["prefix"],
SaltFunc: b.Salt,
}
2015-04-24 18:06:19 +00:00
}
2016-09-21 14:29:42 +00:00
2015-04-24 18:06:19 +00:00
return b, nil
}
// Backend is the audit backend for the syslog-based audit store.
type Backend struct {
2016-09-21 14:29:42 +00:00
logger gsyslog.Syslogger
formatter audit.AuditFormatter
formatConfig audit.FormatterConfig
saltMutex sync.RWMutex
salt *salt.Salt
saltConfig *salt.Config
saltView logical.Storage
2015-04-24 18:06:19 +00:00
}
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 {
2015-04-24 18:06:19 +00:00
var buf bytes.Buffer
if err := b.formatter.FormatRequest(ctx, &buf, b.formatConfig, in); err != nil {
2015-04-24 18:06:19 +00:00
return err
}
// Write out to syslog
2015-04-24 18:06:19 +00:00
_, err := b.logger.Write(buf.Bytes())
return err
}
func (b *Backend) LogResponse(ctx context.Context, in *logical.LogInput) error {
2015-04-24 18:06:19 +00:00
var buf bytes.Buffer
if err := b.formatter.FormatResponse(ctx, &buf, b.formatConfig, in); err != nil {
2015-04-24 18:06:19 +00:00
return err
}
2016-12-19 20:35:55 +00:00
// Write out to syslog
_, err := b.logger.Write(buf.Bytes())
2015-04-24 18:06:19 +00:00
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
}