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

295 lines
6.1 KiB
Go
Raw Normal View History

2015-04-05 01:07:53 +00:00
package file
import (
"context"
2015-04-05 01:07:53 +00:00
"fmt"
"io/ioutil"
2015-04-05 01:07:53 +00:00
"os"
"path/filepath"
"strconv"
"strings"
2015-04-05 01:07:53 +00:00
"sync"
"github.com/hashicorp/vault/audit"
"github.com/hashicorp/vault/helper/salt"
2015-04-05 01:07:53 +00:00
"github.com/hashicorp/vault/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")
}
path, ok := conf.Config["file_path"]
2015-04-05 01:07:53 +00:00
if !ok {
path, ok = conf.Config["path"]
if !ok {
return nil, fmt.Errorf("file_path is required")
}
2015-04-05 01:07:53 +00:00
}
// normalize path if configured for stdout
if strings.ToLower(path) == "stdout" {
path = "stdout"
}
if strings.ToLower(path) == "discard" {
path = "discard"
}
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 %s", format)
}
// 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
logRaw := false
if raw, ok := conf.Config["log_raw"]; ok {
b, err := strconv.ParseBool(raw)
if err != nil {
return nil, err
}
logRaw = b
}
2016-10-10 15:58:26 +00:00
2016-10-08 23:52:49 +00:00
// Check if mode is provided
2016-10-10 15:58:26 +00:00
mode := os.FileMode(0600)
2016-10-08 23:52:49 +00:00
if modeRaw, ok := conf.Config["mode"]; ok {
m, err := strconv.ParseUint(modeRaw, 8, 32)
if err != nil {
return nil, err
}
if m != 0 {
mode = os.FileMode(m)
}
}
b := &Backend{
path: path,
mode: mode,
saltConfig: conf.SaltConfig,
saltView: conf.SaltView,
2016-09-21 14:29:42 +00:00
formatConfig: audit.FormatterConfig{
Raw: logRaw,
HMACAccessor: hmacAccessor,
},
}
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,
}
}
switch path {
case "stdout", "discard":
// no need to test opening file if outputting to stdout or discarding
default:
// Ensure that the file can be successfully opened for writing;
// otherwise it will be too late to catch later without problems
// (ref: https://github.com/hashicorp/vault/issues/550)
if err := b.open(); err != nil {
return nil, fmt.Errorf("sanity check failed; unable to open %s for writing: %v", path, err)
}
}
return b, nil
2015-04-05 01:07:53 +00:00
}
// Backend is the audit backend for the file-based audit store.
//
// NOTE: This audit backend is currently very simple: it appends to a file.
// It doesn't do anything more at the moment to assist with rotation
// or reset the write cursor, this should be done in the future.
type Backend struct {
2016-09-21 14:29:42 +00:00
path string
formatter audit.AuditFormatter
formatConfig audit.FormatterConfig
2015-04-05 01:07:53 +00:00
fileLock sync.RWMutex
f *os.File
2016-10-10 15:58:26 +00:00
mode os.FileMode
saltMutex sync.RWMutex
salt *salt.Salt
saltConfig *salt.Config
saltView logical.Storage
}
var _ audit.Backend = (*Backend)(nil)
func (b *Backend) Salt() (*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(b.saltView, b.saltConfig)
if err != nil {
return nil, err
}
b.salt = salt
return salt, nil
2015-04-05 01:07:53 +00:00
}
func (b *Backend) GetHash(data string) (string, error) {
salt, err := b.Salt()
if err != nil {
return "", err
}
return audit.HashString(salt, data), nil
}
func (b *Backend) LogRequest(_ context.Context, in *audit.LogInput) error {
b.fileLock.Lock()
defer b.fileLock.Unlock()
switch b.path {
case "stdout":
return b.formatter.FormatRequest(os.Stdout, b.formatConfig, in)
case "discard":
return b.formatter.FormatRequest(ioutil.Discard, b.formatConfig, in)
}
2015-04-05 01:07:53 +00:00
if err := b.open(); err != nil {
return err
}
if err := b.formatter.FormatRequest(b.f, b.formatConfig, in); err == nil {
return nil
}
// Opportunistically try to re-open the FD, once per call
b.f.Close()
b.f = nil
if err := b.open(); err != nil {
return err
}
return b.formatter.FormatRequest(b.f, b.formatConfig, in)
2015-04-05 01:07:53 +00:00
}
func (b *Backend) LogResponse(_ context.Context, in *audit.LogInput) error {
b.fileLock.Lock()
defer b.fileLock.Unlock()
switch b.path {
case "stdout":
return b.formatter.FormatResponse(os.Stdout, b.formatConfig, in)
case "discard":
return b.formatter.FormatResponse(ioutil.Discard, b.formatConfig, in)
}
2015-04-05 01:07:53 +00:00
if err := b.open(); err != nil {
return err
}
if err := b.formatter.FormatResponse(b.f, b.formatConfig, in); err == nil {
return nil
}
// Opportunistically try to re-open the FD, once per call
b.f.Close()
b.f = nil
if err := b.open(); err != nil {
return err
}
return b.formatter.FormatResponse(b.f, b.formatConfig, in)
2015-04-05 01:07:53 +00:00
}
// The file lock must be held before calling this
2015-04-05 01:07:53 +00:00
func (b *Backend) open() error {
if b.f != nil {
return nil
}
2016-10-10 15:58:26 +00:00
if err := os.MkdirAll(filepath.Dir(b.path), b.mode); err != nil {
return err
}
2015-04-05 01:07:53 +00:00
var err error
2016-10-10 15:58:26 +00:00
b.f, err = os.OpenFile(b.path, os.O_APPEND|os.O_WRONLY|os.O_CREATE, b.mode)
if err != nil {
return err
}
2016-10-10 15:58:26 +00:00
// Change the file mode in case the log file already existed. We special
// case /dev/null since we can't chmod it and bypass if the mode is zero
switch b.path {
case "/dev/null":
default:
if b.mode != 0 {
err = os.Chmod(b.path, b.mode)
if err != nil {
return err
}
}
2015-04-05 01:07:53 +00:00
}
return nil
}
func (b *Backend) Reload(_ context.Context) error {
switch b.path {
case "stdout", "discard":
return nil
}
b.fileLock.Lock()
defer b.fileLock.Unlock()
if b.f == nil {
return b.open()
}
err := b.f.Close()
// Set to nil here so that even if we error out, on the next access open()
// will be tried
b.f = nil
if err != nil {
return err
}
return b.open()
}
func (b *Backend) Invalidate(_ context.Context) {
b.saltMutex.Lock()
defer b.saltMutex.Unlock()
b.salt = nil
}