Add HMAC capability to salt. Pass a salt into audit backends. Require it for audit.Hash.

This commit is contained in:
Jeff Mitchell 2015-09-18 12:18:37 -04:00
parent d775445efe
commit b655f6b858
11 changed files with 137 additions and 72 deletions

View File

@ -1,6 +1,9 @@
package audit
import "github.com/hashicorp/vault/logical"
import (
"github.com/hashicorp/vault/helper/salt"
"github.com/hashicorp/vault/logical"
)
// Backend interface must be implemented for an audit
// mechanism to be made available. Audit backends can be enabled to
@ -20,5 +23,13 @@ type Backend interface {
LogResponse(*logical.Auth, *logical.Request, *logical.Response, error) error
}
type BackendConfig struct {
// The salt that should be used for any secret obfuscation
Salt *salt.Salt
// Config is the opaque user configuration provided when mounting
Config map[string]string
}
// Factory is the factory function to create an audit backend.
type Factory func(map[string]string) (Backend, error)
type Factory func(BackendConfig) (Backend, error)

View File

@ -1,12 +1,10 @@
package audit
import (
"crypto/sha1"
"encoding/hex"
"fmt"
"reflect"
"strings"
"github.com/hashicorp/vault/helper/salt"
"github.com/hashicorp/vault/logical"
"github.com/mitchellh/copystructure"
"github.com/mitchellh/reflectwalk"
@ -17,8 +15,8 @@ import (
// it will be passed through.
//
// The structure is modified in-place.
func Hash(raw interface{}) error {
fn := HashSHA1("")
func Hash(salter *salt.Salt, raw interface{}) error {
fn := salter.GetHMAC
switch s := raw.(type) {
case *logical.Auth:
@ -26,11 +24,7 @@ func Hash(raw interface{}) error {
return nil
}
if s.ClientToken != "" {
token, err := fn(s.ClientToken)
if err != nil {
return err
}
token := fn(s.ClientToken)
s.ClientToken = token
}
case *logical.Request:
@ -38,7 +32,7 @@ func Hash(raw interface{}) error {
return nil
}
if s.Auth != nil {
if err := Hash(s.Auth); err != nil {
if err := Hash(salter, s.Auth); err != nil {
return err
}
}
@ -54,7 +48,7 @@ func Hash(raw interface{}) error {
return nil
}
if s.Auth != nil {
if err := Hash(s.Auth); err != nil {
if err := Hash(salter, s.Auth); err != nil {
return err
}
}
@ -90,16 +84,18 @@ func HashStructure(s interface{}, cb HashCallback) (interface{}, error) {
// HashCallback is the callback called for HashStructure to hash
// a value.
type HashCallback func(string) (string, error)
type HashCallback func(string) string
// HashSHA1 returns a HashCallback that hashes data with SHA1 and
// with an optional salt. If salt is a blank string, no salt is used.
/*
func HashSHA1(salt string) HashCallback {
return func(v string) (string, error) {
hashed := sha1.Sum([]byte(v + salt))
return "sha1:" + hex.EncodeToString(hashed[:]), nil
}
}
*/
// hashWalker implements interfaces for the reflectwalk package
// (github.com/mitchellh/reflectwalk) that can be used to automatically
@ -188,10 +184,7 @@ func (w *hashWalker) Primitive(v reflect.Value) error {
return nil
}
replaceVal, err := w.Callback(v.Interface().(string))
if err != nil {
return fmt.Errorf("Error hashing value: %s", err)
}
replaceVal := w.Callback(v.Interface().(string))
resultVal := reflect.ValueOf(replaceVal)
switch w.loc {

View File

@ -8,19 +8,24 @@ import (
"sync"
"github.com/hashicorp/vault/audit"
"github.com/hashicorp/vault/helper/salt"
"github.com/hashicorp/vault/logical"
"github.com/mitchellh/copystructure"
)
func Factory(conf map[string]string) (audit.Backend, error) {
path, ok := conf["path"]
func Factory(conf audit.BackendConfig) (audit.Backend, error) {
if conf.Salt == nil {
return nil, fmt.Errorf("Nil salt passed in")
}
path, ok := conf.Config["path"]
if !ok {
return nil, fmt.Errorf("path is required")
}
// Check if raw logging is enabled
logRaw := false
if raw, ok := conf["log_raw"]; ok {
if raw, ok := conf.Config["log_raw"]; ok {
b, err := strconv.ParseBool(raw)
if err != nil {
return nil, err
@ -29,8 +34,9 @@ func Factory(conf map[string]string) (audit.Backend, error) {
}
b := &Backend{
Path: path,
LogRaw: logRaw,
path: path,
logRaw: logRaw,
salt: conf.Salt,
}
// Ensure that the file can be successfully opened for writing;
@ -49,8 +55,9 @@ func Factory(conf map[string]string) (audit.Backend, error) {
// 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 {
Path string
LogRaw bool
path string
logRaw bool
salt *salt.Salt
once sync.Once
f *os.File
@ -60,7 +67,7 @@ func (b *Backend) LogRequest(auth *logical.Auth, req *logical.Request, outerErr
if err := b.open(); err != nil {
return err
}
if !b.LogRaw {
if !b.logRaw {
// Before we copy the structure we must nil out some data
// otherwise we will cause reflection to panic and die
if req.Connection != nil && req.Connection.ConnState != nil {
@ -86,10 +93,10 @@ func (b *Backend) LogRequest(auth *logical.Auth, req *logical.Request, outerErr
req = cp.(*logical.Request)
// Hash any sensitive information
if err := audit.Hash(auth); err != nil {
if err := audit.Hash(b.salt, auth); err != nil {
return err
}
if err := audit.Hash(req); err != nil {
if err := audit.Hash(b.salt, req); err != nil {
return err
}
}
@ -106,7 +113,7 @@ func (b *Backend) LogResponse(
if err := b.open(); err != nil {
return err
}
if !b.LogRaw {
if !b.logRaw {
// Before we copy the structure we must nil out some data
// otherwise we will cause reflection to panic and die
if req.Connection != nil && req.Connection.ConnState != nil {
@ -138,13 +145,13 @@ func (b *Backend) LogResponse(
resp = cp.(*logical.Response)
// Hash any sensitive information
if err := audit.Hash(auth); err != nil {
if err := audit.Hash(b.salt, auth); err != nil {
return err
}
if err := audit.Hash(req); err != nil {
if err := audit.Hash(b.salt, req); err != nil {
return err
}
if err := audit.Hash(resp); err != nil {
if err := audit.Hash(b.salt, resp); err != nil {
return err
}
}
@ -157,12 +164,12 @@ func (b *Backend) open() error {
if b.f != nil {
return nil
}
if err := os.MkdirAll(filepath.Dir(b.Path), 0600); err != nil {
if err := os.MkdirAll(filepath.Dir(b.path), 0600); err != nil {
return err
}
var err error
b.f, err = os.OpenFile(b.Path, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
b.f, err = os.OpenFile(b.path, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
if err != nil {
return err
}

View File

@ -2,30 +2,36 @@ package file
import (
"bytes"
"fmt"
"strconv"
"github.com/hashicorp/go-syslog"
"github.com/hashicorp/vault/audit"
"github.com/hashicorp/vault/helper/salt"
"github.com/hashicorp/vault/logical"
"github.com/mitchellh/copystructure"
)
func Factory(conf map[string]string) (audit.Backend, error) {
func Factory(conf audit.BackendConfig) (audit.Backend, error) {
if conf.Salt == nil {
return nil, fmt.Errorf("Nil salt passed in")
}
// Get facility or default to AUTH
facility, ok := conf["facility"]
facility, ok := conf.Config["facility"]
if !ok {
facility = "AUTH"
}
// Get tag or default to 'vault'
tag, ok := conf["tag"]
tag, ok := conf.Config["tag"]
if !ok {
tag = "vault"
}
// Check if raw logging is enabled
logRaw := false
if raw, ok := conf["log_raw"]; ok {
if raw, ok := conf.Config["log_raw"]; ok {
b, err := strconv.ParseBool(raw)
if err != nil {
return nil, err
@ -42,6 +48,7 @@ func Factory(conf map[string]string) (audit.Backend, error) {
b := &Backend{
logger: logger,
logRaw: logRaw,
salt: conf.Salt,
}
return b, nil
}
@ -50,6 +57,7 @@ func Factory(conf map[string]string) (audit.Backend, error) {
type Backend struct {
logger gsyslog.Syslogger
logRaw bool
salt *salt.Salt
}
func (b *Backend) LogRequest(auth *logical.Auth, req *logical.Request, outerErr error) error {
@ -79,10 +87,10 @@ func (b *Backend) LogRequest(auth *logical.Auth, req *logical.Request, outerErr
req = cp.(*logical.Request)
// Hash any sensitive information
if err := audit.Hash(auth); err != nil {
if err := audit.Hash(b.salt, auth); err != nil {
return err
}
if err := audit.Hash(req); err != nil {
if err := audit.Hash(b.salt, req); err != nil {
return err
}
}
@ -133,13 +141,13 @@ func (b *Backend) LogResponse(auth *logical.Auth, req *logical.Request,
resp = cp.(*logical.Response)
// Hash any sensitive information
if err := audit.Hash(auth); err != nil {
if err := audit.Hash(b.salt, auth); err != nil {
return err
}
if err := audit.Hash(req); err != nil {
if err := audit.Hash(b.salt, req); err != nil {
return err
}
if err := audit.Hash(resp); err != nil {
if err := audit.Hash(b.salt, resp); err != nil {
return err
}
}

View File

@ -18,7 +18,9 @@ func Factory(conf *logical.BackendConfig) (logical.Backend, error) {
func Backend(conf *logical.BackendConfig) (*framework.Backend, error) {
// Initialize the salt
salt, err := salt.NewSalt(conf.StorageView, nil)
salt, err := salt.NewSalt(conf.StorageView, &salt.Config{
HashFunc: salt.SHA1Hash,
})
if err != nil {
return nil, err
}

View File

@ -22,7 +22,9 @@ func Factory(conf *logical.BackendConfig) (logical.Backend, error) {
}
func Backend(conf *logical.BackendConfig) (*framework.Backend, error) {
salt, err := salt.NewSalt(conf.StorageView, nil)
salt, err := salt.NewSalt(conf.StorageView, &salt.Config{
HashFunc: salt.SHA1Hash,
})
if err != nil {
return nil, err
}

View File

@ -1,10 +1,12 @@
package salt
import (
"crypto/hmac"
"crypto/sha1"
"crypto/sha256"
"encoding/hex"
"fmt"
"hash"
"github.com/hashicorp/vault/helper/uuid"
"github.com/hashicorp/vault/logical"
@ -24,6 +26,7 @@ type Salt struct {
config *Config
salt string
generated bool
hmac hash.Hash
}
type HashFunc func([]byte) []byte
@ -37,6 +40,10 @@ type Config struct {
// HashFunc is the hashing function to use for salting.
// Defaults to SHA1 if not provided.
HashFunc HashFunc
// HMAC allows specification of a hash function to use for
// the HMAC helpers
HMAC func() hash.Hash
}
// NewSalt creates a new salt based on the configuration
@ -49,7 +56,7 @@ func NewSalt(view logical.Storage, config *Config) (*Salt, error) {
config.Location = DefaultLocation
}
if config.HashFunc == nil {
config.HashFunc = SHA1Hash
config.HashFunc = SHA256Hash
}
// Create the salt
@ -80,23 +87,42 @@ func NewSalt(view logical.Storage, config *Config) (*Salt, error) {
return nil, fmt.Errorf("failed to persist salt: %v", err)
}
}
if config.HMAC != nil {
s.hmac = hmac.New(config.HMAC, []byte(s.salt))
if s.hmac == nil {
return nil, fmt.Errorf("failed to instantiate HMAC function")
}
}
return s, nil
}
// SaltID is used to apply a salt and hash functio to an ID to make sure
// it is not reversable
// SaltID is used to apply a salt and hash function to an ID to make sure
// it is not reversible
func (s *Salt) SaltID(id string) string {
return SaltID(s.salt, id, s.config.HashFunc)
}
// SaltIDandHMAC is used to apply a salt and hash function to an ID to make sure
// it is not reversible, with an additional HMAC
func (s *Salt) GetHMAC(id string) string {
if s.hmac == nil {
return ""
}
s.hmac.Reset()
s.hmac.Write([]byte(id))
return string(s.hmac.Sum(nil))
}
// DidGenerate returns if the underlying salt value was generated
// on initialization or if an existing salt value was loaded
func (s *Salt) DidGenerate() bool {
return s.generated
}
// SaltID is used to apply a salt and hash functio to an ID to make sure
// it is not reversable
// SaltID is used to apply a salt and hash function to an ID to make sure
// it is not reversible
func SaltID(salt, id string, hash HashFunc) string {
comb := salt + id
hashVal := hash([]byte(comb))
@ -109,7 +135,7 @@ func SHA1Hash(inp []byte) []byte {
return hashed[:]
}
// SHA256Hash returns teh SHA256 of the input
// SHA256Hash returns the SHA256 of the input
func SHA256Hash(inp []byte) []byte {
hashed := sha256.Sum256(inp)
return hashed[:]

View File

@ -125,7 +125,9 @@ func TestPathMap_routes(t *testing.T) {
func TestPathMap_Salted(t *testing.T) {
storage := new(logical.InmemStorage)
salt, err := salt.NewSalt(storage, nil)
salt, err := salt.NewSalt(storage, &salt.Config{
HashFunc: salt.SHA1Hash,
})
if err != nil {
t.Fatalf("err: %v", err)
}

View File

@ -1,6 +1,7 @@
package vault
import (
"crypto/sha256"
"encoding/json"
"errors"
"fmt"
@ -11,6 +12,7 @@ import (
"github.com/armon/go-metrics"
"github.com/hashicorp/vault/audit"
"github.com/hashicorp/vault/helper/salt"
"github.com/hashicorp/vault/helper/uuid"
"github.com/hashicorp/vault/logical"
)
@ -28,7 +30,7 @@ const (
var (
// loadAuditFailed if loading audit tables encounters an error
loadAuditFailed = errors.New("failed to setup audit table")
errLoadAuditFailed = errors.New("failed to setup audit table")
)
// enableAudit is used to enable a new audit backend
@ -58,16 +60,16 @@ func (c *Core) enableAudit(entry *MountEntry) error {
}
}
// Lookup the new backend
backend, err := c.newAuditBackend(entry.Type, entry.Options)
if err != nil {
return err
}
// Generate a new UUID and view
entry.UUID = uuid.GenerateUUID()
view := NewBarrierView(c.barrier, auditBarrierPrefix+entry.UUID+"/")
// Lookup the new backend
backend, err := c.newAuditBackend(entry.Type, view, entry.Options)
if err != nil {
return err
}
// Update the audit table
newTable := c.audit.ShallowClone()
newTable.Entries = append(newTable.Entries, entry)
@ -120,13 +122,13 @@ func (c *Core) loadAudits() error {
raw, err := c.barrier.Get(coreAuditConfigPath)
if err != nil {
c.logger.Printf("[ERR] core: failed to read audit table: %v", err)
return loadAuditFailed
return errLoadAuditFailed
}
if raw != nil {
c.audit = &MountTable{}
if err := json.Unmarshal(raw.Value, c.audit); err != nil {
c.logger.Printf("[ERR] core: failed to decode audit table: %v", err)
return loadAuditFailed
return errLoadAuditFailed
}
}
@ -138,7 +140,7 @@ func (c *Core) loadAudits() error {
// Create and persist the default audit table
c.audit = defaultAuditTable()
if err := c.persistAudit(c.audit); err != nil {
return loadAuditFailed
return errLoadAuditFailed
}
return nil
}
@ -171,18 +173,18 @@ func (c *Core) persistAudit(table *MountTable) error {
func (c *Core) setupAudits() error {
broker := NewAuditBroker(c.logger)
for _, entry := range c.audit.Entries {
// Create a barrier view using the UUID
view := NewBarrierView(c.barrier, auditBarrierPrefix+entry.UUID+"/")
// Initialize the backend
audit, err := c.newAuditBackend(entry.Type, entry.Options)
audit, err := c.newAuditBackend(entry.Type, view, entry.Options)
if err != nil {
c.logger.Printf(
"[ERR] core: failed to create audit entry %#v: %v",
entry, err)
return loadAuditFailed
return errLoadAuditFailed
}
// Create a barrier view using the UUID
view := NewBarrierView(c.barrier, auditBarrierPrefix+entry.UUID+"/")
// Mount the backend
broker.Register(entry.Path, audit, view)
}
@ -199,12 +201,22 @@ func (c *Core) teardownAudits() error {
}
// newAuditBackend is used to create and configure a new audit backend by name
func (c *Core) newAuditBackend(t string, conf map[string]string) (audit.Backend, error) {
func (c *Core) newAuditBackend(t string, view logical.Storage, conf map[string]string) (audit.Backend, error) {
f, ok := c.auditBackends[t]
if !ok {
return nil, fmt.Errorf("unknown backend type: %s", t)
}
return f(conf)
salter, err := salt.NewSalt(view, &salt.Config{
HashFunc: salt.SHA256Hash,
HMAC: sha256.New,
})
if err != nil {
return nil, fmt.Errorf("[ERR] core: unable to generate salt: %v", err)
}
return f(audit.BackendConfig{
Salt: salter,
Config: conf,
})
}
// defaultAuditTable creates a default audit table

View File

@ -57,7 +57,7 @@ oOyBJU/HMVvBfv4g+OVFLVgSwwm6owwsouZ0+D/LasbuHqYyqYqdyPJQYzWA2Y+F
// TestCore returns a pure in-memory, uninitialized core for testing.
func TestCore(t *testing.T) *Core {
noopAudits := map[string]audit.Factory{
"noop": func(map[string]string) (audit.Backend, error) {
"noop": func(audit.BackendConfig) (audit.Backend, error) {
return new(noopAudit), nil
},
}

View File

@ -60,7 +60,9 @@ func NewTokenStore(c *Core, config *logical.BackendConfig) (*TokenStore, error)
}
// Setup the salt
salt, err := salt.NewSalt(view, nil)
salt, err := salt.NewSalt(view, &salt.Config{
HashFunc: salt.SHA1Hash,
})
if err != nil {
return nil, err
}