2015-03-27 21:00:38 +00:00
package vault
import (
2015-09-18 16:18:37 +00:00
"crypto/sha256"
2015-03-27 21:00:38 +00:00
"encoding/json"
"errors"
"fmt"
2015-03-31 22:26:03 +00:00
"strings"
"sync"
2015-04-08 23:43:17 +00:00
"time"
2015-03-27 21:00:38 +00:00
2016-08-19 20:45:17 +00:00
log "github.com/mgutz/logxi/v1"
2015-04-08 23:43:17 +00:00
"github.com/armon/go-metrics"
2016-07-24 01:46:28 +00:00
"github.com/hashicorp/go-multierror"
2015-12-16 17:56:20 +00:00
"github.com/hashicorp/go-uuid"
2015-03-27 21:00:38 +00:00
"github.com/hashicorp/vault/audit"
2016-07-06 16:25:40 +00:00
"github.com/hashicorp/vault/helper/jsonutil"
2015-09-18 16:18:37 +00:00
"github.com/hashicorp/vault/helper/salt"
2015-04-01 20:55:07 +00:00
"github.com/hashicorp/vault/logical"
2015-03-27 21:00:38 +00:00
)
const (
// coreAuditConfigPath is used to store the audit configuration.
// Audit configuration is protected within the Vault itself, which means it
// can only be viewed or modified after an unseal.
coreAuditConfigPath = "core/audit"
2015-03-31 22:26:03 +00:00
// auditBarrierPrefix is the prefix to the UUID used in the
// barrier view for the audit backends.
auditBarrierPrefix = "audit/"
2016-05-26 17:38:51 +00:00
// auditTableType is the value we expect to find for the audit table and
// corresponding entries
auditTableType = "audit"
2015-03-27 21:00:38 +00:00
)
var (
// loadAuditFailed if loading audit tables encounters an error
2015-09-18 16:18:37 +00:00
errLoadAuditFailed = errors . New ( "failed to setup audit table" )
2015-03-27 21:00:38 +00:00
)
2015-03-31 22:26:03 +00:00
// enableAudit is used to enable a new audit backend
func ( c * Core ) enableAudit ( entry * MountEntry ) error {
2015-04-03 21:27:33 +00:00
// Ensure we end the path in a slash
if ! strings . HasSuffix ( entry . Path , "/" ) {
entry . Path += "/"
}
2015-03-31 22:26:03 +00:00
// Ensure there is a name
2015-04-03 21:27:33 +00:00
if entry . Path == "/" {
2015-03-31 22:26:03 +00:00
return fmt . Errorf ( "backend path must be specified" )
}
2015-11-11 16:44:07 +00:00
// Update the audit table
c . auditLock . Lock ( )
defer c . auditLock . Unlock ( )
2015-03-31 22:26:03 +00:00
// Look for matching name
for _ , ent := range c . audit . Entries {
2015-04-03 21:27:33 +00:00
switch {
// Existing is sql/mysql/ new is sql/ or
// existing is sql/ and new is sql/mysql/
case strings . HasPrefix ( ent . Path , entry . Path ) :
fallthrough
case strings . HasPrefix ( entry . Path , ent . Path ) :
2015-03-31 22:26:03 +00:00
return fmt . Errorf ( "path already in use" )
}
}
2015-09-18 16:18:37 +00:00
// Generate a new UUID and view
2016-01-13 18:40:08 +00:00
entryUUID , err := uuid . GenerateUUID ( )
if err != nil {
return err
}
entry . UUID = entryUUID
2015-09-18 16:18:37 +00:00
view := NewBarrierView ( c . barrier , auditBarrierPrefix + entry . UUID + "/" )
2015-03-31 22:26:03 +00:00
// Lookup the new backend
2016-09-30 19:04:50 +00:00
backend , err := c . newAuditBackend ( entry , view , entry . Options )
2015-03-31 22:26:03 +00:00
if err != nil {
return err
}
2016-09-13 15:50:14 +00:00
newTable := c . audit . shallowClone ( )
2015-03-31 22:26:03 +00:00
newTable . Entries = append ( newTable . Entries , entry )
if err := c . persistAudit ( newTable ) ; err != nil {
return errors . New ( "failed to update audit table" )
}
2015-11-11 16:44:07 +00:00
2015-03-31 22:26:03 +00:00
c . audit = newTable
// Register the backend
c . auditBroker . Register ( entry . Path , backend , view )
2016-08-19 20:45:17 +00:00
if c . logger . IsInfo ( ) {
c . logger . Info ( "core: enabled audit backend" , "path" , entry . Path , "type" , entry . Type )
}
2015-03-31 22:26:03 +00:00
return nil
}
// disableAudit is used to disable an existing audit backend
2016-09-19 17:02:25 +00:00
func ( c * Core ) disableAudit ( path string ) ( bool , error ) {
2015-04-03 21:27:33 +00:00
// Ensure we end the path in a slash
if ! strings . HasSuffix ( path , "/" ) {
path += "/"
}
2015-03-31 22:26:03 +00:00
// Remove the entry from the mount table
2015-11-11 16:44:07 +00:00
c . auditLock . Lock ( )
defer c . auditLock . Unlock ( )
2016-09-13 15:50:14 +00:00
newTable := c . audit . shallowClone ( )
2016-09-30 19:04:50 +00:00
entry := newTable . remove ( path )
2015-03-31 22:26:03 +00:00
// Ensure there was a match
2016-09-30 19:04:50 +00:00
if entry == nil {
2016-09-19 17:02:25 +00:00
return false , fmt . Errorf ( "no matching backend" )
2015-03-31 22:26:03 +00:00
}
2016-09-30 19:04:50 +00:00
c . removeAuditReloadFunc ( entry )
2015-03-31 22:26:03 +00:00
// Update the audit table
if err := c . persistAudit ( newTable ) ; err != nil {
2016-09-19 17:02:25 +00:00
return true , errors . New ( "failed to update audit table" )
2015-03-31 22:26:03 +00:00
}
2015-11-11 16:44:07 +00:00
2015-03-31 22:26:03 +00:00
c . audit = newTable
// Unmount the backend
c . auditBroker . Deregister ( path )
2016-08-19 20:45:17 +00:00
if c . logger . IsInfo ( ) {
c . logger . Info ( "core: disabled audit backend" , "path" , path )
}
2016-09-19 17:02:25 +00:00
return true , nil
2015-03-31 22:26:03 +00:00
}
2015-03-27 21:00:38 +00:00
// loadAudits is invoked as part of postUnseal to load the audit table
func ( c * Core ) loadAudits ( ) error {
2015-11-11 16:44:07 +00:00
auditTable := & MountTable { }
2015-03-27 21:00:38 +00:00
// Load the existing audit table
raw , err := c . barrier . Get ( coreAuditConfigPath )
if err != nil {
2016-08-19 20:45:17 +00:00
c . logger . Error ( "core: failed to read audit table" , "error" , err )
2015-09-18 16:18:37 +00:00
return errLoadAuditFailed
2015-03-27 21:00:38 +00:00
}
2015-11-11 16:44:07 +00:00
c . auditLock . Lock ( )
defer c . auditLock . Unlock ( )
2015-03-27 21:00:38 +00:00
if raw != nil {
2016-07-06 16:25:40 +00:00
if err := jsonutil . DecodeJSON ( raw . Value , auditTable ) ; err != nil {
2016-08-19 20:45:17 +00:00
c . logger . Error ( "core: failed to decode audit table" , "error" , err )
2015-09-18 16:18:37 +00:00
return errLoadAuditFailed
2015-03-27 21:00:38 +00:00
}
2015-11-11 16:44:07 +00:00
c . audit = auditTable
2015-03-27 21:00:38 +00:00
}
// Done if we have restored the audit table
if c . audit != nil {
2016-05-26 17:38:51 +00:00
needPersist := false
// Upgrade to typed auth table
if c . audit . Type == "" {
c . audit . Type = auditTableType
needPersist = true
}
// Upgrade to table-scoped entries
for _ , entry := range c . audit . Entries {
if entry . Table == "" {
entry . Table = c . audit . Type
needPersist = true
}
}
if needPersist {
return c . persistAudit ( c . audit )
}
2015-03-27 21:00:38 +00:00
return nil
}
// Create and persist the default audit table
c . audit = defaultAuditTable ( )
if err := c . persistAudit ( c . audit ) ; err != nil {
2015-09-18 16:18:37 +00:00
return errLoadAuditFailed
2015-03-27 21:00:38 +00:00
}
return nil
}
// persistAudit is used to persist the audit table after modification
func ( c * Core ) persistAudit ( table * MountTable ) error {
2016-05-26 17:38:51 +00:00
if table . Type != auditTableType {
2016-08-19 20:45:17 +00:00
c . logger . Error ( "core: given table to persist has wrong type" , "actual_type" , table . Type , "expected_type" , auditTableType )
2016-05-26 17:38:51 +00:00
return fmt . Errorf ( "invalid table type given, not persisting" )
}
for _ , entry := range table . Entries {
if entry . Table != table . Type {
2016-08-19 20:45:17 +00:00
c . logger . Error ( "core: given entry to persist in audit table has wrong table value" , "path" , entry . Path , "entry_table_type" , entry . Table , "actual_type" , table . Type )
2016-05-26 17:38:51 +00:00
return fmt . Errorf ( "invalid audit entry found, not persisting" )
}
}
2015-03-27 21:00:38 +00:00
// Marshal the table
raw , err := json . Marshal ( table )
if err != nil {
2016-08-19 20:45:17 +00:00
c . logger . Error ( "core: failed to encode audit table" , "error" , err )
2015-03-27 21:00:38 +00:00
return err
}
// Create an entry
entry := & Entry {
Key : coreAuditConfigPath ,
Value : raw ,
}
// Write to the physical backend
if err := c . barrier . Put ( entry ) ; err != nil {
2016-08-19 20:45:17 +00:00
c . logger . Error ( "core: failed to persist audit table" , "error" , err )
2015-03-27 21:00:38 +00:00
return err
}
return nil
}
// setupAudit is invoked after we've loaded the audit able to
// initialize the audit backends
func ( c * Core ) setupAudits ( ) error {
2015-04-01 20:55:07 +00:00
broker := NewAuditBroker ( c . logger )
2015-11-11 16:44:07 +00:00
c . auditLock . Lock ( )
defer c . auditLock . Unlock ( )
2016-12-02 20:09:01 +00:00
var successCount int
2015-03-27 21:00:38 +00:00
for _ , entry := range c . audit . Entries {
2015-09-18 16:18:37 +00:00
// Create a barrier view using the UUID
view := NewBarrierView ( c . barrier , auditBarrierPrefix + entry . UUID + "/" )
2015-03-27 21:00:38 +00:00
// Initialize the backend
2016-09-30 19:04:50 +00:00
audit , err := c . newAuditBackend ( entry , view , entry . Options )
2015-03-27 21:00:38 +00:00
if err != nil {
2016-08-19 20:45:17 +00:00
c . logger . Error ( "core: failed to create audit entry" , "path" , entry . Path , "error" , err )
2016-12-02 20:09:01 +00:00
continue
2015-03-27 21:00:38 +00:00
}
2015-03-31 20:22:40 +00:00
2015-03-31 22:26:03 +00:00
// Mount the backend
broker . Register ( entry . Path , audit , view )
2016-12-02 20:09:01 +00:00
successCount += 1
2015-03-31 22:26:03 +00:00
}
2016-12-02 20:09:01 +00:00
if len ( c . audit . Entries ) > 0 && successCount == 0 {
return errLoadAuditFailed
}
2015-03-31 22:26:03 +00:00
c . auditBroker = broker
2015-03-27 21:00:38 +00:00
return nil
}
// teardownAudit is used before we seal the vault to reset the audit
// backends to their unloaded state. This is reversed by loadAudits.
func ( c * Core ) teardownAudits ( ) error {
2015-11-11 16:44:07 +00:00
c . auditLock . Lock ( )
defer c . auditLock . Unlock ( )
2016-10-28 19:32:32 +00:00
if c . audit != nil {
for _ , entry := range c . audit . Entries {
c . removeAuditReloadFunc ( entry )
}
2016-09-30 19:04:50 +00:00
}
2015-03-27 21:00:38 +00:00
c . audit = nil
2015-03-31 20:22:40 +00:00
c . auditBroker = nil
2015-03-27 21:00:38 +00:00
return nil
}
2016-09-30 19:04:50 +00:00
// removeAuditReloadFunc removes the reload func from the working set. The
// audit lock needs to be held before calling this.
func ( c * Core ) removeAuditReloadFunc ( entry * MountEntry ) {
switch entry . Type {
case "file" :
key := "audit_file|" + entry . Path
c . reloadFuncsLock . Lock ( )
if c . logger . IsDebug ( ) {
c . logger . Debug ( "audit: removing reload function" , "path" , entry . Path )
}
delete ( c . reloadFuncs , key )
c . reloadFuncsLock . Unlock ( )
}
}
2015-03-27 21:00:38 +00:00
// newAuditBackend is used to create and configure a new audit backend by name
2016-09-30 19:04:50 +00:00
func ( c * Core ) newAuditBackend ( entry * MountEntry , view logical . Storage , conf map [ string ] string ) ( audit . Backend , error ) {
f , ok := c . auditBackends [ entry . Type ]
2015-03-27 21:00:38 +00:00
if ! ok {
2016-09-30 19:04:50 +00:00
return nil , fmt . Errorf ( "unknown backend type: %s" , entry . Type )
2015-03-27 21:00:38 +00:00
}
2015-09-18 16:18:37 +00:00
salter , err := salt . NewSalt ( view , & salt . Config {
HMAC : sha256 . New ,
2015-09-18 21:36:42 +00:00
HMACType : "hmac-sha256" ,
2015-09-18 16:18:37 +00:00
} )
if err != nil {
2016-08-19 20:45:17 +00:00
return nil , fmt . Errorf ( "core: unable to generate salt: %v" , err )
2015-09-18 16:18:37 +00:00
}
2016-09-30 19:04:50 +00:00
be , err := f ( & audit . BackendConfig {
2015-09-18 16:18:37 +00:00
Salt : salter ,
Config : conf ,
} )
2016-09-30 19:04:50 +00:00
if err != nil {
return nil , err
}
switch entry . Type {
case "file" :
key := "audit_file|" + entry . Path
c . reloadFuncsLock . Lock ( )
if c . logger . IsDebug ( ) {
c . logger . Debug ( "audit: adding reload function" , "path" , entry . Path )
}
c . reloadFuncs [ key ] = append ( c . reloadFuncs [ key ] , func ( map [ string ] string ) error {
if c . logger . IsInfo ( ) {
c . logger . Info ( "audit: reloading file audit backend" , "path" , entry . Path )
}
return be . Reload ( )
} )
c . reloadFuncsLock . Unlock ( )
}
return be , err
2015-03-27 21:00:38 +00:00
}
// defaultAuditTable creates a default audit table
func defaultAuditTable ( ) * MountTable {
2016-05-26 17:38:51 +00:00
table := & MountTable {
Type : auditTableType ,
}
2015-03-27 21:00:38 +00:00
return table
}
2015-03-31 20:22:40 +00:00
2015-03-31 22:26:03 +00:00
type backendEntry struct {
backend audit . Backend
view * BarrierView
}
2015-03-31 20:22:40 +00:00
// AuditBroker is used to provide a single ingest interface to auditable
// events given that multiple backends may be configured.
type AuditBroker struct {
2016-09-30 19:04:50 +00:00
sync . RWMutex
2015-03-31 22:26:03 +00:00
backends map [ string ] backendEntry
2016-08-19 20:45:17 +00:00
logger log . Logger
2015-03-31 20:22:40 +00:00
}
2015-03-31 22:26:03 +00:00
// NewAuditBroker creates a new audit broker
2016-08-19 20:45:17 +00:00
func NewAuditBroker ( log log . Logger ) * AuditBroker {
2015-03-31 20:22:40 +00:00
b := & AuditBroker {
2015-03-31 22:26:03 +00:00
backends : make ( map [ string ] backendEntry ) ,
2015-04-01 20:55:07 +00:00
logger : log ,
2015-03-31 20:22:40 +00:00
}
return b
}
2015-03-31 22:26:03 +00:00
// Register is used to add new audit backend to the broker
func ( a * AuditBroker ) Register ( name string , b audit . Backend , v * BarrierView ) {
2016-09-30 19:04:50 +00:00
a . Lock ( )
defer a . Unlock ( )
2015-03-31 22:26:03 +00:00
a . backends [ name ] = backendEntry {
backend : b ,
view : v ,
}
}
// Deregister is used to remove an audit backend from the broker
func ( a * AuditBroker ) Deregister ( name string ) {
2016-09-30 19:04:50 +00:00
a . Lock ( )
defer a . Unlock ( )
2015-03-31 22:26:03 +00:00
delete ( a . backends , name )
}
// IsRegistered is used to check if a given audit backend is registered
func ( a * AuditBroker ) IsRegistered ( name string ) bool {
2016-09-30 19:04:50 +00:00
a . RLock ( )
defer a . RUnlock ( )
2015-03-31 22:26:03 +00:00
_ , ok := a . backends [ name ]
return ok
}
2015-04-01 20:55:07 +00:00
2015-11-19 01:26:03 +00:00
// GetHash returns a hash using the salt of the given backend
func ( a * AuditBroker ) GetHash ( name string , input string ) ( string , error ) {
2016-09-30 19:04:50 +00:00
a . RLock ( )
defer a . RUnlock ( )
2015-11-19 01:26:03 +00:00
be , ok := a . backends [ name ]
if ! ok {
return "" , fmt . Errorf ( "unknown audit backend %s" , name )
}
return be . backend . GetHash ( input ) , nil
}
2015-04-01 20:55:07 +00:00
// LogRequest is used to ensure all the audit backends have an opportunity to
// log the given request and that *at least one* succeeds.
2017-02-02 19:49:20 +00:00
func ( a * AuditBroker ) LogRequest ( auth * logical . Auth , req * logical . Request , headersConfig * AuditedHeadersConfig , outerErr error ) ( retErr error ) {
2015-04-08 23:43:17 +00:00
defer metrics . MeasureSince ( [ ] string { "audit" , "log_request" } , time . Now ( ) )
2016-09-30 19:04:50 +00:00
a . RLock ( )
defer a . RUnlock ( )
2015-06-29 22:11:35 +00:00
defer func ( ) {
if r := recover ( ) ; r != nil {
2016-08-19 20:45:17 +00:00
a . logger . Error ( "audit: panic during logging" , "request_path" , req . Path , "error" , r )
2016-07-24 01:46:28 +00:00
retErr = multierror . Append ( retErr , fmt . Errorf ( "panic generating audit log" ) )
2015-06-29 22:11:35 +00:00
}
} ( )
2015-04-01 20:55:07 +00:00
2016-07-24 01:46:28 +00:00
// All logged requests must have an identifier
2016-07-26 19:50:37 +00:00
//if req.ID == "" {
2016-08-19 20:45:17 +00:00
// a.logger.Error("audit: missing identifier in request object", "request_path", req.Path)
2016-07-26 19:50:37 +00:00
// retErr = multierror.Append(retErr, fmt.Errorf("missing identifier in request object: %s", req.Path))
// return
//}
2016-07-24 01:46:28 +00:00
2017-02-02 19:49:20 +00:00
headers := req . Headers
defer func ( ) {
req . Headers = headers
} ( )
2015-04-01 20:55:07 +00:00
// Ensure at least one backend logs
anyLogged := false
for name , be := range a . backends {
2017-02-02 19:49:20 +00:00
req . Headers = nil
req . Headers = headersConfig . ApplyConfig ( headers , be . backend . GetHash )
2015-04-09 00:09:36 +00:00
start := time . Now ( )
2015-06-19 01:30:18 +00:00
err := be . backend . LogRequest ( auth , req , outerErr )
2015-04-09 00:09:36 +00:00
metrics . MeasureSince ( [ ] string { "audit" , name , "log_request" } , start )
if err != nil {
2016-08-19 20:45:17 +00:00
a . logger . Error ( "audit: backend failed to log request" , "backend" , name , "error" , err )
2015-04-01 20:55:07 +00:00
} else {
anyLogged = true
}
}
if ! anyLogged && len ( a . backends ) > 0 {
2016-07-24 01:46:28 +00:00
retErr = multierror . Append ( retErr , fmt . Errorf ( "no audit backend succeeded in logging the request" ) )
return
2015-04-01 20:55:07 +00:00
}
return nil
}
// LogResponse is used to ensure all the audit backends have an opportunity to
// log the given response and that *at least one* succeeds.
func ( a * AuditBroker ) LogResponse ( auth * logical . Auth , req * logical . Request ,
2017-02-02 19:49:20 +00:00
resp * logical . Response , headersConfig * AuditedHeadersConfig , err error ) ( reterr error ) {
2015-04-08 23:43:17 +00:00
defer metrics . MeasureSince ( [ ] string { "audit" , "log_response" } , time . Now ( ) )
2016-09-30 19:04:50 +00:00
a . RLock ( )
defer a . RUnlock ( )
2015-06-29 22:11:35 +00:00
defer func ( ) {
if r := recover ( ) ; r != nil {
2016-08-19 20:45:17 +00:00
a . logger . Error ( "audit: panic during logging" , "request_path" , req . Path , "error" , r )
2015-06-29 22:11:35 +00:00
reterr = fmt . Errorf ( "panic generating audit log" )
}
} ( )
2015-04-01 20:55:07 +00:00
2017-02-02 19:49:20 +00:00
headers := req . Headers
defer func ( ) {
req . Headers = headers
} ( )
2015-04-01 20:55:07 +00:00
// Ensure at least one backend logs
anyLogged := false
for name , be := range a . backends {
2017-02-02 19:49:20 +00:00
req . Headers = nil
req . Headers = headersConfig . ApplyConfig ( headers , be . backend . GetHash )
2015-04-09 00:09:36 +00:00
start := time . Now ( )
err := be . backend . LogResponse ( auth , req , resp , err )
metrics . MeasureSince ( [ ] string { "audit" , name , "log_response" } , start )
if err != nil {
2016-08-19 20:45:17 +00:00
a . logger . Error ( "audit: backend failed to log response" , "backend" , name , "error" , err )
2015-04-01 20:55:07 +00:00
} else {
anyLogged = true
}
}
if ! anyLogged && len ( a . backends ) > 0 {
return fmt . Errorf ( "no audit backend succeeded in logging the response" )
}
return nil
}