Non-HMAC audit values (#4033)
* Add non-hmac request keys * Update comment * Initial audit request keys implementation * Add audit_non_hmac_response_keys * Move where req.NonHMACKeys gets set * Minor refactor * Add params to auth tune endpoints * Sync cache on loadCredentials * Explicitly unset req.NonHMACKeys * Do not error if entry is nil * Add tests * docs: Add params to api sections * Refactor audit.Backend and Formatter interfaces, update audit broker methods * Add audit_broker.go * Fix method call params in audit backends * Remove fields from logical.Request and logical.Response, pass keys via LogInput * Use data.GetOk to allow unsetting existing values * Remove debug lines * Add test for unsetting values * Address review feedback * Initialize values in FormatRequest and FormatResponse using input values * Update docs * Use strutil.StrListContains * Use strutil.StrListContains
This commit is contained in:
parent
49068a42be
commit
e2fb199ce5
|
@ -129,10 +129,12 @@ type MountInput struct {
|
|||
}
|
||||
|
||||
type MountConfigInput struct {
|
||||
DefaultLeaseTTL string `json:"default_lease_ttl" structs:"default_lease_ttl" mapstructure:"default_lease_ttl"`
|
||||
MaxLeaseTTL string `json:"max_lease_ttl" structs:"max_lease_ttl" mapstructure:"max_lease_ttl"`
|
||||
ForceNoCache bool `json:"force_no_cache" structs:"force_no_cache" mapstructure:"force_no_cache"`
|
||||
PluginName string `json:"plugin_name,omitempty" structs:"plugin_name,omitempty" mapstructure:"plugin_name"`
|
||||
DefaultLeaseTTL string `json:"default_lease_ttl" structs:"default_lease_ttl" mapstructure:"default_lease_ttl"`
|
||||
MaxLeaseTTL string `json:"max_lease_ttl" structs:"max_lease_ttl" mapstructure:"max_lease_ttl"`
|
||||
ForceNoCache bool `json:"force_no_cache" structs:"force_no_cache" mapstructure:"force_no_cache"`
|
||||
PluginName string `json:"plugin_name,omitempty" structs:"plugin_name,omitempty" mapstructure:"plugin_name"`
|
||||
AuditNonHMACRequestKeys []string `json:"audit_non_hmac_request_keys,omitempty" structs:"audit_non_hmac_request_keys" mapstructure:"audit_non_hmac_request_keys"`
|
||||
AuditNonHMACResponseKeys []string `json:"audit_non_hmac_response_keys,omitempty" structs:"audit_non_hmac_response_keys" mapstructure:"audit_non_hmac_response_keys"`
|
||||
}
|
||||
|
||||
type MountOutput struct {
|
||||
|
@ -145,8 +147,10 @@ type MountOutput struct {
|
|||
}
|
||||
|
||||
type MountConfigOutput struct {
|
||||
DefaultLeaseTTL int `json:"default_lease_ttl" structs:"default_lease_ttl" mapstructure:"default_lease_ttl"`
|
||||
MaxLeaseTTL int `json:"max_lease_ttl" structs:"max_lease_ttl" mapstructure:"max_lease_ttl"`
|
||||
ForceNoCache bool `json:"force_no_cache" structs:"force_no_cache" mapstructure:"force_no_cache"`
|
||||
PluginName string `json:"plugin_name,omitempty" structs:"plugin_name,omitempty" mapstructure:"plugin_name"`
|
||||
DefaultLeaseTTL int `json:"default_lease_ttl" structs:"default_lease_ttl" mapstructure:"default_lease_ttl"`
|
||||
MaxLeaseTTL int `json:"max_lease_ttl" structs:"max_lease_ttl" mapstructure:"max_lease_ttl"`
|
||||
ForceNoCache bool `json:"force_no_cache" structs:"force_no_cache" mapstructure:"force_no_cache"`
|
||||
PluginName string `json:"plugin_name,omitempty" structs:"plugin_name,omitempty" mapstructure:"plugin_name"`
|
||||
AuditNonHMACRequestKeys []string `json:"audit_non_hmac_request_keys,omitempty" structs:"audit_non_hmac_request_keys" mapstructure:"audit_non_hmac_request_keys"`
|
||||
AuditNonHMACResponseKeys []string `json:"audit_non_hmac_response_keys,omitempty" structs:"audit_non_hmac_response_keys" mapstructure:"audit_non_hmac_response_keys"`
|
||||
}
|
||||
|
|
|
@ -16,13 +16,13 @@ type Backend interface {
|
|||
// request is authorized but before the request is executed. The arguments
|
||||
// MUST not be modified in anyway. They should be deep copied if this is
|
||||
// a possibility.
|
||||
LogRequest(context.Context, *logical.Auth, *logical.Request, error) error
|
||||
LogRequest(context.Context, *LogInput) error
|
||||
|
||||
// LogResponse is used to synchronously log a response. This is done after
|
||||
// the request is processed but before the response is sent. The arguments
|
||||
// MUST not be modified in anyway. They should be deep copied if this is
|
||||
// a possibility.
|
||||
LogResponse(context.Context, *logical.Auth, *logical.Request, *logical.Response, error) error
|
||||
LogResponse(context.Context, *LogInput) error
|
||||
|
||||
// GetHash is used to return the given data with the backend's hash,
|
||||
// so that a caller can determine if a value in the audit log matches
|
||||
|
@ -36,6 +36,18 @@ type Backend interface {
|
|||
Invalidate(context.Context)
|
||||
}
|
||||
|
||||
// LogInput contains the input parameters passed into LogRequest and LogResponse
|
||||
type LogInput struct {
|
||||
Auth *logical.Auth
|
||||
Request *logical.Request
|
||||
Response *logical.Response
|
||||
OuterErr error
|
||||
NonHMACReqDataKeys []string
|
||||
NonHMACRespDataKeys []string
|
||||
}
|
||||
|
||||
// BackendConfig contains configuration parameters used in the factory func to
|
||||
// instantiate audit backends
|
||||
type BackendConfig struct {
|
||||
// The view to store the salt
|
||||
SaltView logical.Storage
|
||||
|
|
|
@ -25,14 +25,10 @@ type AuditFormatter struct {
|
|||
AuditFormatWriter
|
||||
}
|
||||
|
||||
func (f *AuditFormatter) FormatRequest(
|
||||
w io.Writer,
|
||||
config FormatterConfig,
|
||||
auth *logical.Auth,
|
||||
req *logical.Request,
|
||||
inErr error) error {
|
||||
var _ Formatter = (*AuditFormatter)(nil)
|
||||
|
||||
if req == nil {
|
||||
func (f *AuditFormatter) FormatRequest(w io.Writer, config FormatterConfig, in *LogInput) error {
|
||||
if in == nil || in.Request == nil {
|
||||
return fmt.Errorf("request to request-audit a nil request")
|
||||
}
|
||||
|
||||
|
@ -49,28 +45,31 @@ func (f *AuditFormatter) FormatRequest(
|
|||
return errwrap.Wrapf("error fetching salt: {{err}}", err)
|
||||
}
|
||||
|
||||
// Set these to the input values at first
|
||||
auth := in.Auth
|
||||
req := in.Request
|
||||
|
||||
if !config.Raw {
|
||||
// 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 {
|
||||
origReq := req
|
||||
origState := req.Connection.ConnState
|
||||
req.Connection.ConnState = nil
|
||||
if in.Request.Connection != nil && in.Request.Connection.ConnState != nil {
|
||||
origState := in.Request.Connection.ConnState
|
||||
in.Request.Connection.ConnState = nil
|
||||
defer func() {
|
||||
origReq.Connection.ConnState = origState
|
||||
in.Request.Connection.ConnState = origState
|
||||
}()
|
||||
}
|
||||
|
||||
// Copy the auth structure
|
||||
if auth != nil {
|
||||
cp, err := copystructure.Copy(auth)
|
||||
if in.Auth != nil {
|
||||
cp, err := copystructure.Copy(in.Auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
auth = cp.(*logical.Auth)
|
||||
}
|
||||
|
||||
cp, err := copystructure.Copy(req)
|
||||
cp, err := copystructure.Copy(in.Request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -83,7 +82,7 @@ func (f *AuditFormatter) FormatRequest(
|
|||
if !config.HMACAccessor && auth.Accessor != "" {
|
||||
authAccessor = auth.Accessor
|
||||
}
|
||||
if err := Hash(salt, auth); err != nil {
|
||||
if err := Hash(salt, auth, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
if authAccessor != "" {
|
||||
|
@ -96,7 +95,7 @@ func (f *AuditFormatter) FormatRequest(
|
|||
if !config.HMACAccessor && req != nil && req.ClientTokenAccessor != "" {
|
||||
clientTokenAccessor = req.ClientTokenAccessor
|
||||
}
|
||||
if err := Hash(salt, req); err != nil {
|
||||
if err := Hash(salt, req, in.NonHMACReqDataKeys); err != nil {
|
||||
return err
|
||||
}
|
||||
if clientTokenAccessor != "" {
|
||||
|
@ -109,8 +108,8 @@ func (f *AuditFormatter) FormatRequest(
|
|||
auth = new(logical.Auth)
|
||||
}
|
||||
var errString string
|
||||
if inErr != nil {
|
||||
errString = inErr.Error()
|
||||
if in.OuterErr != nil {
|
||||
errString = in.OuterErr.Error()
|
||||
}
|
||||
|
||||
reqEntry := &AuditRequestEntry{
|
||||
|
@ -152,15 +151,8 @@ func (f *AuditFormatter) FormatRequest(
|
|||
return f.AuditFormatWriter.WriteRequest(w, reqEntry)
|
||||
}
|
||||
|
||||
func (f *AuditFormatter) FormatResponse(
|
||||
w io.Writer,
|
||||
config FormatterConfig,
|
||||
auth *logical.Auth,
|
||||
req *logical.Request,
|
||||
resp *logical.Response,
|
||||
inErr error) error {
|
||||
|
||||
if req == nil {
|
||||
func (f *AuditFormatter) FormatResponse(w io.Writer, config FormatterConfig, in *LogInput) error {
|
||||
if in == nil || in.Request == nil {
|
||||
return fmt.Errorf("request to response-audit a nil request")
|
||||
}
|
||||
|
||||
|
@ -177,35 +169,39 @@ func (f *AuditFormatter) FormatResponse(
|
|||
return errwrap.Wrapf("error fetching salt: {{err}}", err)
|
||||
}
|
||||
|
||||
// Set these to the input values at first
|
||||
auth := in.Auth
|
||||
req := in.Request
|
||||
resp := in.Response
|
||||
|
||||
if !config.Raw {
|
||||
// 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 {
|
||||
origReq := req
|
||||
origState := req.Connection.ConnState
|
||||
req.Connection.ConnState = nil
|
||||
if in.Request.Connection != nil && in.Request.Connection.ConnState != nil {
|
||||
origState := in.Request.Connection.ConnState
|
||||
in.Request.Connection.ConnState = nil
|
||||
defer func() {
|
||||
origReq.Connection.ConnState = origState
|
||||
in.Request.Connection.ConnState = origState
|
||||
}()
|
||||
}
|
||||
|
||||
// Copy the auth structure
|
||||
if auth != nil {
|
||||
cp, err := copystructure.Copy(auth)
|
||||
if in.Auth != nil {
|
||||
cp, err := copystructure.Copy(in.Auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
auth = cp.(*logical.Auth)
|
||||
}
|
||||
|
||||
cp, err := copystructure.Copy(req)
|
||||
cp, err := copystructure.Copy(in.Request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req = cp.(*logical.Request)
|
||||
|
||||
if resp != nil {
|
||||
cp, err := copystructure.Copy(resp)
|
||||
if in.Response != nil {
|
||||
cp, err := copystructure.Copy(in.Response)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -220,7 +216,7 @@ func (f *AuditFormatter) FormatResponse(
|
|||
if !config.HMACAccessor && auth.Accessor != "" {
|
||||
accessor = auth.Accessor
|
||||
}
|
||||
if err := Hash(salt, auth); err != nil {
|
||||
if err := Hash(salt, auth, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
if accessor != "" {
|
||||
|
@ -233,7 +229,7 @@ func (f *AuditFormatter) FormatResponse(
|
|||
if !config.HMACAccessor && req != nil && req.ClientTokenAccessor != "" {
|
||||
clientTokenAccessor = req.ClientTokenAccessor
|
||||
}
|
||||
if err := Hash(salt, req); err != nil {
|
||||
if err := Hash(salt, req, in.NonHMACReqDataKeys); err != nil {
|
||||
return err
|
||||
}
|
||||
if clientTokenAccessor != "" {
|
||||
|
@ -250,7 +246,7 @@ func (f *AuditFormatter) FormatResponse(
|
|||
wrappedAccessor = resp.WrapInfo.WrappedAccessor
|
||||
wrappingAccessor = resp.WrapInfo.Accessor
|
||||
}
|
||||
if err := Hash(salt, resp); err != nil {
|
||||
if err := Hash(salt, resp, in.NonHMACRespDataKeys); err != nil {
|
||||
return err
|
||||
}
|
||||
if accessor != "" {
|
||||
|
@ -273,8 +269,8 @@ func (f *AuditFormatter) FormatResponse(
|
|||
resp = new(logical.Response)
|
||||
}
|
||||
var errString string
|
||||
if inErr != nil {
|
||||
errString = inErr.Error()
|
||||
if in.OuterErr != nil {
|
||||
errString = in.OuterErr.Error()
|
||||
}
|
||||
|
||||
var respAuth *AuditAuth
|
||||
|
@ -358,7 +354,7 @@ func (f *AuditFormatter) FormatResponse(
|
|||
return f.AuditFormatWriter.WriteResponse(w, respEntry)
|
||||
}
|
||||
|
||||
// AuditRequest is the structure of a request audit log entry in Audit.
|
||||
// AuditRequestEntry is the structure of a request audit log entry in Audit.
|
||||
type AuditRequestEntry struct {
|
||||
Time string `json:"time,omitempty"`
|
||||
Type string `json:"type"`
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"errors"
|
||||
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/vault/helper/jsonutil"
|
||||
"github.com/hashicorp/vault/helper/salt"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
|
@ -84,7 +85,12 @@ func TestFormatJSON_formatRequest(t *testing.T) {
|
|||
config := FormatterConfig{
|
||||
HMACAccessor: false,
|
||||
}
|
||||
if err := formatter.FormatRequest(&buf, config, tc.Auth, tc.Req, tc.Err); err != nil {
|
||||
in := &LogInput{
|
||||
Auth: tc.Auth,
|
||||
Request: tc.Req,
|
||||
OuterErr: tc.Err,
|
||||
}
|
||||
if err := formatter.FormatRequest(&buf, config, in); err != nil {
|
||||
t.Fatalf("bad: %s\nerr: %s", name, err)
|
||||
}
|
||||
|
||||
|
|
|
@ -89,7 +89,12 @@ func TestFormatJSONx_formatRequest(t *testing.T) {
|
|||
OmitTime: true,
|
||||
HMACAccessor: false,
|
||||
}
|
||||
if err := formatter.FormatRequest(&buf, config, tc.Auth, tc.Req, tc.Err); err != nil {
|
||||
in := &LogInput{
|
||||
Auth: tc.Auth,
|
||||
Request: tc.Req,
|
||||
OuterErr: tc.Err,
|
||||
}
|
||||
if err := formatter.FormatRequest(&buf, config, in); err != nil {
|
||||
t.Fatalf("bad: %s\nerr: %s", name, err)
|
||||
}
|
||||
|
||||
|
|
|
@ -40,10 +40,14 @@ func TestFormatRequestErrors(t *testing.T) {
|
|||
AuditFormatWriter: &noopFormatWriter{},
|
||||
}
|
||||
|
||||
if err := formatter.FormatRequest(ioutil.Discard, config, nil, nil, nil); err == nil {
|
||||
if err := formatter.FormatRequest(ioutil.Discard, config, &LogInput{}); err == nil {
|
||||
t.Fatal("expected error due to nil request")
|
||||
}
|
||||
if err := formatter.FormatRequest(nil, config, nil, &logical.Request{}, nil); err == nil {
|
||||
|
||||
in := &LogInput{
|
||||
Request: &logical.Request{},
|
||||
}
|
||||
if err := formatter.FormatRequest(nil, config, in); err == nil {
|
||||
t.Fatal("expected error due to nil writer")
|
||||
}
|
||||
}
|
||||
|
@ -54,10 +58,14 @@ func TestFormatResponseErrors(t *testing.T) {
|
|||
AuditFormatWriter: &noopFormatWriter{},
|
||||
}
|
||||
|
||||
if err := formatter.FormatResponse(ioutil.Discard, config, nil, nil, nil, nil); err == nil {
|
||||
if err := formatter.FormatResponse(ioutil.Discard, config, &LogInput{}); err == nil {
|
||||
t.Fatal("expected error due to nil request")
|
||||
}
|
||||
if err := formatter.FormatResponse(nil, config, nil, &logical.Request{}, nil, nil); err == nil {
|
||||
|
||||
in := &LogInput{
|
||||
Request: &logical.Request{},
|
||||
}
|
||||
if err := formatter.FormatResponse(nil, config, in); err == nil {
|
||||
t.Fatal("expected error due to nil writer")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,6 @@ package audit
|
|||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/hashicorp/vault/logical"
|
||||
)
|
||||
|
||||
// Formatter is an interface that is responsible for formating a
|
||||
|
@ -12,8 +10,8 @@ import (
|
|||
//
|
||||
// It is recommended that you pass data through Hash prior to formatting it.
|
||||
type Formatter interface {
|
||||
FormatRequest(io.Writer, FormatterConfig, *logical.Auth, *logical.Request, error) error
|
||||
FormatResponse(io.Writer, FormatterConfig, *logical.Auth, *logical.Request, *logical.Response, error) error
|
||||
FormatRequest(io.Writer, FormatterConfig, *LogInput) error
|
||||
FormatResponse(io.Writer, FormatterConfig, *LogInput) error
|
||||
}
|
||||
|
||||
type FormatterConfig struct {
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/hashicorp/vault/helper/salt"
|
||||
"github.com/hashicorp/vault/helper/strutil"
|
||||
"github.com/hashicorp/vault/helper/wrapping"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
"github.com/mitchellh/copystructure"
|
||||
|
@ -23,7 +24,7 @@ func HashString(salter *salt.Salt, data string) string {
|
|||
// it will be passed through.
|
||||
//
|
||||
// The structure is modified in-place.
|
||||
func Hash(salter *salt.Salt, raw interface{}) error {
|
||||
func Hash(salter *salt.Salt, raw interface{}, nonHMACDataKeys []string) error {
|
||||
fn := salter.GetIdentifiedHMAC
|
||||
|
||||
switch s := raw.(type) {
|
||||
|
@ -43,7 +44,7 @@ func Hash(salter *salt.Salt, raw interface{}) error {
|
|||
return nil
|
||||
}
|
||||
if s.Auth != nil {
|
||||
if err := Hash(salter, s.Auth); err != nil {
|
||||
if err := Hash(salter, s.Auth, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -56,7 +57,7 @@ func Hash(salter *salt.Salt, raw interface{}) error {
|
|||
s.ClientTokenAccessor = fn(s.ClientTokenAccessor)
|
||||
}
|
||||
|
||||
data, err := HashStructure(s.Data, fn)
|
||||
data, err := HashStructure(s.Data, fn, nonHMACDataKeys)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -69,18 +70,18 @@ func Hash(salter *salt.Salt, raw interface{}) error {
|
|||
}
|
||||
|
||||
if s.Auth != nil {
|
||||
if err := Hash(salter, s.Auth); err != nil {
|
||||
if err := Hash(salter, s.Auth, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if s.WrapInfo != nil {
|
||||
if err := Hash(salter, s.WrapInfo); err != nil {
|
||||
if err := Hash(salter, s.WrapInfo, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
data, err := HashStructure(s.Data, fn)
|
||||
data, err := HashStructure(s.Data, fn, nonHMACDataKeys)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -107,13 +108,13 @@ func Hash(salter *salt.Salt, raw interface{}) error {
|
|||
// the structure. Only _values_ are hashed: keys of objects are not.
|
||||
//
|
||||
// For the HashCallback, see the built-in HashCallbacks below.
|
||||
func HashStructure(s interface{}, cb HashCallback) (interface{}, error) {
|
||||
func HashStructure(s interface{}, cb HashCallback, ignoredKeys []string) (interface{}, error) {
|
||||
s, err := copystructure.Copy(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
walker := &hashWalker{Callback: cb}
|
||||
walker := &hashWalker{Callback: cb, IgnoredKeys: ignoredKeys}
|
||||
if err := reflectwalk.Walk(s, walker); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -134,6 +135,9 @@ type hashWalker struct {
|
|||
// immediately and the error returned.
|
||||
Callback HashCallback
|
||||
|
||||
// IgnoreKeys are the keys that wont have the HashCallback applied
|
||||
IgnoredKeys []string
|
||||
|
||||
key []string
|
||||
lastValue reflect.Value
|
||||
loc reflectwalk.Location
|
||||
|
@ -247,6 +251,12 @@ func (w *hashWalker) Primitive(v reflect.Value) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// See if the current key is part of the ignored keys
|
||||
currentKey := w.key[len(w.key)-1]
|
||||
if strutil.StrListContains(w.IgnoredKeys, currentKey) {
|
||||
return nil
|
||||
}
|
||||
|
||||
replaceVal := w.Callback(v.String())
|
||||
|
||||
resultVal := reflect.ValueOf(replaceVal)
|
||||
|
|
|
@ -116,32 +116,37 @@ func TestHash(t *testing.T) {
|
|||
now := time.Now()
|
||||
|
||||
cases := []struct {
|
||||
Input interface{}
|
||||
Output interface{}
|
||||
Input interface{}
|
||||
Output interface{}
|
||||
NonHMACDataKeys []string
|
||||
}{
|
||||
{
|
||||
&logical.Auth{ClientToken: "foo"},
|
||||
&logical.Auth{ClientToken: "hmac-sha256:08ba357e274f528065766c770a639abf6809b39ccfd37c2a3157c7f51954da0a"},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
&logical.Request{
|
||||
Data: map[string]interface{}{
|
||||
"foo": "bar",
|
||||
"baz": "foobar",
|
||||
"private_key_type": certutil.PrivateKeyType("rsa"),
|
||||
},
|
||||
},
|
||||
&logical.Request{
|
||||
Data: map[string]interface{}{
|
||||
"foo": "hmac-sha256:f9320baf0249169e73850cd6156ded0106e2bb6ad8cab01b7bbbebe6d1065317",
|
||||
"baz": "foobar",
|
||||
"private_key_type": "hmac-sha256:995230dca56fffd310ff591aa404aab52b2abb41703c787cfa829eceb4595bf1",
|
||||
},
|
||||
},
|
||||
[]string{"baz"},
|
||||
},
|
||||
{
|
||||
&logical.Response{
|
||||
Data: map[string]interface{}{
|
||||
"foo": "bar",
|
||||
|
||||
"baz": "foobar",
|
||||
// Responses can contain time values, so test that with
|
||||
// a known fixed value.
|
||||
"bar": now,
|
||||
|
@ -157,6 +162,7 @@ func TestHash(t *testing.T) {
|
|||
&logical.Response{
|
||||
Data: map[string]interface{}{
|
||||
"foo": "hmac-sha256:f9320baf0249169e73850cd6156ded0106e2bb6ad8cab01b7bbbebe6d1065317",
|
||||
"baz": "foobar",
|
||||
"bar": now.Format(time.RFC3339Nano),
|
||||
},
|
||||
WrapInfo: &wrapping.ResponseWrapInfo{
|
||||
|
@ -167,10 +173,12 @@ func TestHash(t *testing.T) {
|
|||
WrappedAccessor: "hmac-sha256:f9320baf0249169e73850cd6156ded0106e2bb6ad8cab01b7bbbebe6d1065317",
|
||||
},
|
||||
},
|
||||
[]string{"baz"},
|
||||
},
|
||||
{
|
||||
"foo",
|
||||
"foo",
|
||||
nil,
|
||||
},
|
||||
{
|
||||
&logical.Auth{
|
||||
|
@ -189,6 +197,7 @@ func TestHash(t *testing.T) {
|
|||
|
||||
ClientToken: "hmac-sha256:08ba357e274f528065766c770a639abf6809b39ccfd37c2a3157c7f51954da0a",
|
||||
},
|
||||
nil,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -206,16 +215,16 @@ func TestHash(t *testing.T) {
|
|||
}
|
||||
for _, tc := range cases {
|
||||
input := fmt.Sprintf("%#v", tc.Input)
|
||||
if err := Hash(localSalt, tc.Input); err != nil {
|
||||
if err := Hash(localSalt, tc.Input, tc.NonHMACDataKeys); err != nil {
|
||||
t.Fatalf("err: %s\n\n%s", err, input)
|
||||
}
|
||||
if _, ok := tc.Input.(*logical.Response); ok {
|
||||
if !reflect.DeepEqual(tc.Input.(*logical.Response).WrapInfo, tc.Output.(*logical.Response).WrapInfo) {
|
||||
t.Fatalf("bad:\nInput:\n%s\nTest case input:\n%#v\nTest case output\n%#v", input, tc.Input.(*logical.Response).WrapInfo, tc.Output.(*logical.Response).WrapInfo)
|
||||
t.Fatalf("bad:\nInput:\n%s\nTest case input:\n%#v\nTest case output:\n%#v", input, tc.Input.(*logical.Response).WrapInfo, tc.Output.(*logical.Response).WrapInfo)
|
||||
}
|
||||
}
|
||||
if !reflect.DeepEqual(tc.Input, tc.Output) {
|
||||
t.Fatalf("bad:\nInput:\n%s\nTest case input:\n%#v\nTest case output\n%#v", input, tc.Input, tc.Output)
|
||||
t.Fatalf("bad:\nInput:\n%s\nTest case input:\n%#v\nTest case output:\n%#v", input, tc.Input, tc.Output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -249,7 +258,7 @@ func TestHashWalker(t *testing.T) {
|
|||
for _, tc := range cases {
|
||||
output, err := HashStructure(tc.Input, func(string) string {
|
||||
return replaceText
|
||||
})
|
||||
}, []string{})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s\n\n%#v", err, tc.Input)
|
||||
}
|
||||
|
|
|
@ -141,6 +141,8 @@ type Backend struct {
|
|||
saltView logical.Storage
|
||||
}
|
||||
|
||||
var _ audit.Backend = (*Backend)(nil)
|
||||
|
||||
func (b *Backend) Salt() (*salt.Salt, error) {
|
||||
b.saltMutex.RLock()
|
||||
if b.salt != nil {
|
||||
|
@ -169,27 +171,22 @@ func (b *Backend) GetHash(data string) (string, error) {
|
|||
return audit.HashString(salt, data), nil
|
||||
}
|
||||
|
||||
func (b *Backend) LogRequest(
|
||||
_ context.Context,
|
||||
auth *logical.Auth,
|
||||
req *logical.Request,
|
||||
outerErr error) error {
|
||||
|
||||
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, auth, req, outerErr)
|
||||
return b.formatter.FormatRequest(os.Stdout, b.formatConfig, in)
|
||||
case "discard":
|
||||
return b.formatter.FormatRequest(ioutil.Discard, b.formatConfig, auth, req, outerErr)
|
||||
return b.formatter.FormatRequest(ioutil.Discard, b.formatConfig, in)
|
||||
}
|
||||
|
||||
if err := b.open(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := b.formatter.FormatRequest(b.f, b.formatConfig, auth, req, outerErr); err == nil {
|
||||
if err := b.formatter.FormatRequest(b.f, b.formatConfig, in); err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -201,31 +198,26 @@ func (b *Backend) LogRequest(
|
|||
return err
|
||||
}
|
||||
|
||||
return b.formatter.FormatRequest(b.f, b.formatConfig, auth, req, outerErr)
|
||||
return b.formatter.FormatRequest(b.f, b.formatConfig, in)
|
||||
}
|
||||
|
||||
func (b *Backend) LogResponse(
|
||||
_ context.Context,
|
||||
auth *logical.Auth,
|
||||
req *logical.Request,
|
||||
resp *logical.Response,
|
||||
err error) error {
|
||||
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, auth, req, resp, err)
|
||||
return b.formatter.FormatResponse(os.Stdout, b.formatConfig, in)
|
||||
case "discard":
|
||||
return b.formatter.FormatResponse(ioutil.Discard, b.formatConfig, auth, req, resp, err)
|
||||
return b.formatter.FormatResponse(ioutil.Discard, b.formatConfig, in)
|
||||
}
|
||||
|
||||
if err := b.open(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := b.formatter.FormatResponse(b.f, b.formatConfig, auth, req, resp, err); err == nil {
|
||||
if err := b.formatter.FormatResponse(b.f, b.formatConfig, in); err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -237,7 +229,7 @@ func (b *Backend) LogResponse(
|
|||
return err
|
||||
}
|
||||
|
||||
return b.formatter.FormatResponse(b.f, b.formatConfig, auth, req, resp, err)
|
||||
return b.formatter.FormatResponse(b.f, b.formatConfig, in)
|
||||
}
|
||||
|
||||
// The file lock must be held before calling this
|
||||
|
|
|
@ -121,6 +121,8 @@ type Backend struct {
|
|||
saltView logical.Storage
|
||||
}
|
||||
|
||||
var _ audit.Backend = (*Backend)(nil)
|
||||
|
||||
func (b *Backend) GetHash(data string) (string, error) {
|
||||
salt, err := b.Salt()
|
||||
if err != nil {
|
||||
|
@ -129,9 +131,9 @@ func (b *Backend) GetHash(data string) (string, error) {
|
|||
return audit.HashString(salt, data), nil
|
||||
}
|
||||
|
||||
func (b *Backend) LogRequest(ctx context.Context, auth *logical.Auth, req *logical.Request, outerErr error) error {
|
||||
func (b *Backend) LogRequest(ctx context.Context, in *audit.LogInput) error {
|
||||
var buf bytes.Buffer
|
||||
if err := b.formatter.FormatRequest(&buf, b.formatConfig, auth, req, outerErr); err != nil {
|
||||
if err := b.formatter.FormatRequest(&buf, b.formatConfig, in); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -152,10 +154,9 @@ func (b *Backend) LogRequest(ctx context.Context, auth *logical.Auth, req *logic
|
|||
return err
|
||||
}
|
||||
|
||||
func (b *Backend) LogResponse(ctx context.Context, auth *logical.Auth, req *logical.Request,
|
||||
resp *logical.Response, outerErr error) error {
|
||||
func (b *Backend) LogResponse(ctx context.Context, in *audit.LogInput) error {
|
||||
var buf bytes.Buffer
|
||||
if err := b.formatter.FormatResponse(&buf, b.formatConfig, auth, req, resp, outerErr); err != nil {
|
||||
if err := b.formatter.FormatResponse(&buf, b.formatConfig, in); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -108,6 +108,8 @@ type Backend struct {
|
|||
saltView logical.Storage
|
||||
}
|
||||
|
||||
var _ audit.Backend = (*Backend)(nil)
|
||||
|
||||
func (b *Backend) GetHash(data string) (string, error) {
|
||||
salt, err := b.Salt()
|
||||
if err != nil {
|
||||
|
@ -116,9 +118,9 @@ func (b *Backend) GetHash(data string) (string, error) {
|
|||
return audit.HashString(salt, data), nil
|
||||
}
|
||||
|
||||
func (b *Backend) LogRequest(_ context.Context, auth *logical.Auth, req *logical.Request, outerErr error) error {
|
||||
func (b *Backend) LogRequest(_ context.Context, in *audit.LogInput) error {
|
||||
var buf bytes.Buffer
|
||||
if err := b.formatter.FormatRequest(&buf, b.formatConfig, auth, req, outerErr); err != nil {
|
||||
if err := b.formatter.FormatRequest(&buf, b.formatConfig, in); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -127,14 +129,14 @@ func (b *Backend) LogRequest(_ context.Context, auth *logical.Auth, req *logical
|
|||
return err
|
||||
}
|
||||
|
||||
func (b *Backend) LogResponse(_ context.Context, auth *logical.Auth, req *logical.Request, resp *logical.Response, err error) error {
|
||||
func (b *Backend) LogResponse(_ context.Context, in *audit.LogInput) error {
|
||||
var buf bytes.Buffer
|
||||
if err := b.formatter.FormatResponse(&buf, b.formatConfig, auth, req, resp, err); err != nil {
|
||||
if err := b.formatter.FormatResponse(&buf, b.formatConfig, in); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write out to syslog
|
||||
_, err = b.logger.Write(buf.Bytes())
|
||||
_, err := b.logger.Write(buf.Bytes())
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -1072,3 +1072,90 @@ func TestSysTuneMount(t *testing.T) {
|
|||
t.Fatalf("bad:\nExpected: %#v\nActual:%#v", expected, structs.Map(result))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSysTuneMount_nonHMACKeys(t *testing.T) {
|
||||
core, _, token := vault.TestCoreUnsealed(t)
|
||||
ln, addr := TestServer(t, core)
|
||||
defer ln.Close()
|
||||
TestServerAuth(t, addr, token)
|
||||
|
||||
// Mount-tune the audit_non_hmac_request_keys
|
||||
resp := testHttpPost(t, token, addr+"/v1/sys/mounts/secret/tune", map[string]interface{}{
|
||||
"audit_non_hmac_request_keys": "foo",
|
||||
})
|
||||
testResponseStatus(t, resp, 204)
|
||||
|
||||
// Mount-tune the audit_non_hmac_response_keys
|
||||
resp = testHttpPost(t, token, addr+"/v1/sys/mounts/secret/tune", map[string]interface{}{
|
||||
"audit_non_hmac_response_keys": "bar",
|
||||
})
|
||||
testResponseStatus(t, resp, 204)
|
||||
|
||||
// Check results
|
||||
resp = testHttpGet(t, token, addr+"/v1/sys/mounts/secret/tune")
|
||||
testResponseStatus(t, resp, 200)
|
||||
|
||||
actual := map[string]interface{}{}
|
||||
expected := map[string]interface{}{
|
||||
"lease_id": "",
|
||||
"renewable": false,
|
||||
"lease_duration": json.Number("0"),
|
||||
"wrap_info": nil,
|
||||
"warnings": nil,
|
||||
"auth": nil,
|
||||
"data": map[string]interface{}{
|
||||
"default_lease_ttl": json.Number("2764800"),
|
||||
"max_lease_ttl": json.Number("2764800"),
|
||||
"force_no_cache": false,
|
||||
"audit_non_hmac_request_keys": []interface{}{"foo"},
|
||||
"audit_non_hmac_response_keys": []interface{}{"bar"},
|
||||
},
|
||||
"default_lease_ttl": json.Number("2764800"),
|
||||
"max_lease_ttl": json.Number("2764800"),
|
||||
"force_no_cache": false,
|
||||
"audit_non_hmac_request_keys": []interface{}{"foo"},
|
||||
"audit_non_hmac_response_keys": []interface{}{"bar"},
|
||||
}
|
||||
testResponseBody(t, resp, &actual)
|
||||
expected["request_id"] = actual["request_id"]
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("bad:\nExpected: %#v\nActual:%#v", expected, actual)
|
||||
}
|
||||
|
||||
// Unset those mount tune values
|
||||
resp = testHttpPost(t, token, addr+"/v1/sys/mounts/secret/tune", map[string]interface{}{
|
||||
"audit_non_hmac_request_keys": "",
|
||||
})
|
||||
testResponseStatus(t, resp, 204)
|
||||
|
||||
resp = testHttpPost(t, token, addr+"/v1/sys/mounts/secret/tune", map[string]interface{}{
|
||||
"audit_non_hmac_response_keys": "",
|
||||
})
|
||||
|
||||
// Check results
|
||||
resp = testHttpGet(t, token, addr+"/v1/sys/mounts/secret/tune")
|
||||
testResponseStatus(t, resp, 200)
|
||||
|
||||
actual = map[string]interface{}{}
|
||||
expected = map[string]interface{}{
|
||||
"lease_id": "",
|
||||
"renewable": false,
|
||||
"lease_duration": json.Number("0"),
|
||||
"wrap_info": nil,
|
||||
"warnings": nil,
|
||||
"auth": nil,
|
||||
"data": map[string]interface{}{
|
||||
"default_lease_ttl": json.Number("2764800"),
|
||||
"max_lease_ttl": json.Number("2764800"),
|
||||
"force_no_cache": false,
|
||||
},
|
||||
"default_lease_ttl": json.Number("2764800"),
|
||||
"max_lease_ttl": json.Number("2764800"),
|
||||
"force_no_cache": false,
|
||||
}
|
||||
testResponseBody(t, resp, &actual)
|
||||
expected["request_id"] = actual["request_id"]
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("bad:\nExpected: %#v\nActual:%#v", expected, actual)
|
||||
}
|
||||
}
|
||||
|
|
194
vault/audit.go
194
vault/audit.go
|
@ -6,13 +6,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
log "github.com/mgutz/logxi/v1"
|
||||
|
||||
"github.com/armon/go-metrics"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/hashicorp/go-uuid"
|
||||
"github.com/hashicorp/vault/audit"
|
||||
"github.com/hashicorp/vault/helper/jsonutil"
|
||||
|
@ -461,191 +455,3 @@ func defaultAuditTable() *MountTable {
|
|||
}
|
||||
return table
|
||||
}
|
||||
|
||||
type backendEntry struct {
|
||||
backend audit.Backend
|
||||
view *BarrierView
|
||||
}
|
||||
|
||||
// AuditBroker is used to provide a single ingest interface to auditable
|
||||
// events given that multiple backends may be configured.
|
||||
type AuditBroker struct {
|
||||
sync.RWMutex
|
||||
backends map[string]backendEntry
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
// NewAuditBroker creates a new audit broker
|
||||
func NewAuditBroker(log log.Logger) *AuditBroker {
|
||||
b := &AuditBroker{
|
||||
backends: make(map[string]backendEntry),
|
||||
logger: log,
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// Register is used to add new audit backend to the broker
|
||||
func (a *AuditBroker) Register(name string, b audit.Backend, v *BarrierView) {
|
||||
a.Lock()
|
||||
defer a.Unlock()
|
||||
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) {
|
||||
a.Lock()
|
||||
defer a.Unlock()
|
||||
delete(a.backends, name)
|
||||
}
|
||||
|
||||
// IsRegistered is used to check if a given audit backend is registered
|
||||
func (a *AuditBroker) IsRegistered(name string) bool {
|
||||
a.RLock()
|
||||
defer a.RUnlock()
|
||||
_, ok := a.backends[name]
|
||||
return ok
|
||||
}
|
||||
|
||||
// GetHash returns a hash using the salt of the given backend
|
||||
func (a *AuditBroker) GetHash(name string, input string) (string, error) {
|
||||
a.RLock()
|
||||
defer a.RUnlock()
|
||||
be, ok := a.backends[name]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("unknown audit backend %s", name)
|
||||
}
|
||||
|
||||
return be.backend.GetHash(input)
|
||||
}
|
||||
|
||||
// LogRequest is used to ensure all the audit backends have an opportunity to
|
||||
// log the given request and that *at least one* succeeds.
|
||||
func (a *AuditBroker) LogRequest(ctx context.Context, auth *logical.Auth, req *logical.Request, headersConfig *AuditedHeadersConfig, outerErr error) (ret error) {
|
||||
defer metrics.MeasureSince([]string{"audit", "log_request"}, time.Now())
|
||||
a.RLock()
|
||||
defer a.RUnlock()
|
||||
|
||||
var retErr *multierror.Error
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
a.logger.Error("audit: panic during logging", "request_path", req.Path, "error", r)
|
||||
retErr = multierror.Append(retErr, fmt.Errorf("panic generating audit log"))
|
||||
}
|
||||
|
||||
ret = retErr.ErrorOrNil()
|
||||
failure := float32(0.0)
|
||||
if ret != nil {
|
||||
failure = 1.0
|
||||
}
|
||||
metrics.IncrCounter([]string{"audit", "log_request_failure"}, failure)
|
||||
}()
|
||||
|
||||
// All logged requests must have an identifier
|
||||
//if req.ID == "" {
|
||||
// a.logger.Error("audit: missing identifier in request object", "request_path", req.Path)
|
||||
// retErr = multierror.Append(retErr, fmt.Errorf("missing identifier in request object: %s", req.Path))
|
||||
// return
|
||||
//}
|
||||
|
||||
headers := req.Headers
|
||||
defer func() {
|
||||
req.Headers = headers
|
||||
}()
|
||||
|
||||
// Ensure at least one backend logs
|
||||
anyLogged := false
|
||||
for name, be := range a.backends {
|
||||
req.Headers = nil
|
||||
transHeaders, thErr := headersConfig.ApplyConfig(headers, be.backend.GetHash)
|
||||
if thErr != nil {
|
||||
a.logger.Error("audit: backend failed to include headers", "backend", name, "error", thErr)
|
||||
continue
|
||||
}
|
||||
req.Headers = transHeaders
|
||||
|
||||
start := time.Now()
|
||||
lrErr := be.backend.LogRequest(ctx, auth, req, outerErr)
|
||||
metrics.MeasureSince([]string{"audit", name, "log_request"}, start)
|
||||
if lrErr != nil {
|
||||
a.logger.Error("audit: backend failed to log request", "backend", name, "error", lrErr)
|
||||
} else {
|
||||
anyLogged = true
|
||||
}
|
||||
}
|
||||
if !anyLogged && len(a.backends) > 0 {
|
||||
retErr = multierror.Append(retErr, fmt.Errorf("no audit backend succeeded in logging the request"))
|
||||
}
|
||||
|
||||
return retErr.ErrorOrNil()
|
||||
}
|
||||
|
||||
// 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(ctx context.Context, auth *logical.Auth, req *logical.Request,
|
||||
resp *logical.Response, headersConfig *AuditedHeadersConfig, err error) (ret error) {
|
||||
defer metrics.MeasureSince([]string{"audit", "log_response"}, time.Now())
|
||||
a.RLock()
|
||||
defer a.RUnlock()
|
||||
|
||||
var retErr *multierror.Error
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
a.logger.Error("audit: panic during logging", "request_path", req.Path, "error", r)
|
||||
retErr = multierror.Append(retErr, fmt.Errorf("panic generating audit log"))
|
||||
}
|
||||
|
||||
ret = retErr.ErrorOrNil()
|
||||
|
||||
failure := float32(0.0)
|
||||
if ret != nil {
|
||||
failure = 1.0
|
||||
}
|
||||
metrics.IncrCounter([]string{"audit", "log_response_failure"}, failure)
|
||||
}()
|
||||
|
||||
headers := req.Headers
|
||||
defer func() {
|
||||
req.Headers = headers
|
||||
}()
|
||||
|
||||
// Ensure at least one backend logs
|
||||
anyLogged := false
|
||||
for name, be := range a.backends {
|
||||
req.Headers = nil
|
||||
transHeaders, thErr := headersConfig.ApplyConfig(headers, be.backend.GetHash)
|
||||
if thErr != nil {
|
||||
a.logger.Error("audit: backend failed to include headers", "backend", name, "error", thErr)
|
||||
continue
|
||||
}
|
||||
req.Headers = transHeaders
|
||||
|
||||
start := time.Now()
|
||||
lrErr := be.backend.LogResponse(ctx, auth, req, resp, err)
|
||||
metrics.MeasureSince([]string{"audit", name, "log_response"}, start)
|
||||
if lrErr != nil {
|
||||
a.logger.Error("audit: backend failed to log response", "backend", name, "error", lrErr)
|
||||
} else {
|
||||
anyLogged = true
|
||||
}
|
||||
}
|
||||
if !anyLogged && len(a.backends) > 0 {
|
||||
retErr = multierror.Append(retErr, fmt.Errorf("no audit backend succeeded in logging the response"))
|
||||
}
|
||||
|
||||
return retErr.ErrorOrNil()
|
||||
}
|
||||
|
||||
func (a *AuditBroker) Invalidate(ctx context.Context, key string) {
|
||||
// For now we ignore the key as this would only apply to salts. We just
|
||||
// sort of brute force it on each one.
|
||||
a.Lock()
|
||||
defer a.Unlock()
|
||||
for _, be := range a.backends {
|
||||
be.backend.Invalidate(ctx)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,200 @@
|
|||
package vault
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
metrics "github.com/armon/go-metrics"
|
||||
multierror "github.com/hashicorp/go-multierror"
|
||||
"github.com/hashicorp/vault/audit"
|
||||
log "github.com/mgutz/logxi/v1"
|
||||
)
|
||||
|
||||
type backendEntry struct {
|
||||
backend audit.Backend
|
||||
view *BarrierView
|
||||
}
|
||||
|
||||
// AuditBroker is used to provide a single ingest interface to auditable
|
||||
// events given that multiple backends may be configured.
|
||||
type AuditBroker struct {
|
||||
sync.RWMutex
|
||||
backends map[string]backendEntry
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
// NewAuditBroker creates a new audit broker
|
||||
func NewAuditBroker(log log.Logger) *AuditBroker {
|
||||
b := &AuditBroker{
|
||||
backends: make(map[string]backendEntry),
|
||||
logger: log,
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// Register is used to add new audit backend to the broker
|
||||
func (a *AuditBroker) Register(name string, b audit.Backend, v *BarrierView) {
|
||||
a.Lock()
|
||||
defer a.Unlock()
|
||||
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) {
|
||||
a.Lock()
|
||||
defer a.Unlock()
|
||||
delete(a.backends, name)
|
||||
}
|
||||
|
||||
// IsRegistered is used to check if a given audit backend is registered
|
||||
func (a *AuditBroker) IsRegistered(name string) bool {
|
||||
a.RLock()
|
||||
defer a.RUnlock()
|
||||
_, ok := a.backends[name]
|
||||
return ok
|
||||
}
|
||||
|
||||
// GetHash returns a hash using the salt of the given backend
|
||||
func (a *AuditBroker) GetHash(name string, input string) (string, error) {
|
||||
a.RLock()
|
||||
defer a.RUnlock()
|
||||
be, ok := a.backends[name]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("unknown audit backend %s", name)
|
||||
}
|
||||
|
||||
return be.backend.GetHash(input)
|
||||
}
|
||||
|
||||
// LogRequest is used to ensure all the audit backends have an opportunity to
|
||||
// log the given request and that *at least one* succeeds.
|
||||
func (a *AuditBroker) LogRequest(ctx context.Context, in *audit.LogInput, headersConfig *AuditedHeadersConfig) (ret error) {
|
||||
defer metrics.MeasureSince([]string{"audit", "log_request"}, time.Now())
|
||||
a.RLock()
|
||||
defer a.RUnlock()
|
||||
|
||||
var retErr *multierror.Error
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
a.logger.Error("audit: panic during logging", "request_path", in.Request.Path, "error", r)
|
||||
retErr = multierror.Append(retErr, fmt.Errorf("panic generating audit log"))
|
||||
}
|
||||
|
||||
ret = retErr.ErrorOrNil()
|
||||
failure := float32(0.0)
|
||||
if ret != nil {
|
||||
failure = 1.0
|
||||
}
|
||||
metrics.IncrCounter([]string{"audit", "log_request_failure"}, failure)
|
||||
}()
|
||||
|
||||
// All logged requests must have an identifier
|
||||
//if req.ID == "" {
|
||||
// a.logger.Error("audit: missing identifier in request object", "request_path", req.Path)
|
||||
// retErr = multierror.Append(retErr, fmt.Errorf("missing identifier in request object: %s", req.Path))
|
||||
// return
|
||||
//}
|
||||
|
||||
headers := in.Request.Headers
|
||||
defer func() {
|
||||
in.Request.Headers = headers
|
||||
}()
|
||||
|
||||
// Ensure at least one backend logs
|
||||
anyLogged := false
|
||||
for name, be := range a.backends {
|
||||
in.Request.Headers = nil
|
||||
transHeaders, thErr := headersConfig.ApplyConfig(headers, be.backend.GetHash)
|
||||
if thErr != nil {
|
||||
a.logger.Error("audit: backend failed to include headers", "backend", name, "error", thErr)
|
||||
continue
|
||||
}
|
||||
in.Request.Headers = transHeaders
|
||||
|
||||
start := time.Now()
|
||||
lrErr := be.backend.LogRequest(ctx, in)
|
||||
metrics.MeasureSince([]string{"audit", name, "log_request"}, start)
|
||||
if lrErr != nil {
|
||||
a.logger.Error("audit: backend failed to log request", "backend", name, "error", lrErr)
|
||||
} else {
|
||||
anyLogged = true
|
||||
}
|
||||
}
|
||||
if !anyLogged && len(a.backends) > 0 {
|
||||
retErr = multierror.Append(retErr, fmt.Errorf("no audit backend succeeded in logging the request"))
|
||||
}
|
||||
|
||||
return retErr.ErrorOrNil()
|
||||
}
|
||||
|
||||
// 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(ctx context.Context, in *audit.LogInput, headersConfig *AuditedHeadersConfig) (ret error) {
|
||||
defer metrics.MeasureSince([]string{"audit", "log_response"}, time.Now())
|
||||
a.RLock()
|
||||
defer a.RUnlock()
|
||||
|
||||
var retErr *multierror.Error
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
a.logger.Error("audit: panic during logging", "request_path", in.Request.Path, "error", r)
|
||||
retErr = multierror.Append(retErr, fmt.Errorf("panic generating audit log"))
|
||||
}
|
||||
|
||||
ret = retErr.ErrorOrNil()
|
||||
|
||||
failure := float32(0.0)
|
||||
if ret != nil {
|
||||
failure = 1.0
|
||||
}
|
||||
metrics.IncrCounter([]string{"audit", "log_response_failure"}, failure)
|
||||
}()
|
||||
|
||||
headers := in.Request.Headers
|
||||
defer func() {
|
||||
in.Request.Headers = headers
|
||||
}()
|
||||
|
||||
// Ensure at least one backend logs
|
||||
anyLogged := false
|
||||
for name, be := range a.backends {
|
||||
in.Request.Headers = nil
|
||||
transHeaders, thErr := headersConfig.ApplyConfig(headers, be.backend.GetHash)
|
||||
if thErr != nil {
|
||||
a.logger.Error("audit: backend failed to include headers", "backend", name, "error", thErr)
|
||||
continue
|
||||
}
|
||||
in.Request.Headers = transHeaders
|
||||
|
||||
start := time.Now()
|
||||
lrErr := be.backend.LogResponse(ctx, in)
|
||||
metrics.MeasureSince([]string{"audit", name, "log_response"}, start)
|
||||
if lrErr != nil {
|
||||
a.logger.Error("audit: backend failed to log response", "backend", name, "error", lrErr)
|
||||
} else {
|
||||
anyLogged = true
|
||||
}
|
||||
}
|
||||
if !anyLogged && len(a.backends) > 0 {
|
||||
retErr = multierror.Append(retErr, fmt.Errorf("no audit backend succeeded in logging the response"))
|
||||
}
|
||||
|
||||
return retErr.ErrorOrNil()
|
||||
}
|
||||
|
||||
func (a *AuditBroker) Invalidate(ctx context.Context, key string) {
|
||||
// For now we ignore the key as this would only apply to salts. We just
|
||||
// sort of brute force it on each one.
|
||||
a.Lock()
|
||||
defer a.Unlock()
|
||||
for _, be := range a.backends {
|
||||
be.backend.Invalidate(ctx)
|
||||
}
|
||||
}
|
|
@ -23,36 +23,46 @@ import (
|
|||
)
|
||||
|
||||
type NoopAudit struct {
|
||||
Config *audit.BackendConfig
|
||||
ReqErr error
|
||||
ReqAuth []*logical.Auth
|
||||
Req []*logical.Request
|
||||
ReqHeaders []map[string][]string
|
||||
ReqErrs []error
|
||||
Config *audit.BackendConfig
|
||||
ReqErr error
|
||||
ReqAuth []*logical.Auth
|
||||
Req []*logical.Request
|
||||
ReqHeaders []map[string][]string
|
||||
ReqNonHMACKeys []string
|
||||
ReqErrs []error
|
||||
|
||||
RespErr error
|
||||
RespAuth []*logical.Auth
|
||||
RespReq []*logical.Request
|
||||
Resp []*logical.Response
|
||||
RespErrs []error
|
||||
RespErr error
|
||||
RespAuth []*logical.Auth
|
||||
RespReq []*logical.Request
|
||||
Resp []*logical.Response
|
||||
RespNonHMACKeys []string
|
||||
RespReqNonHMACKeys []string
|
||||
RespErrs []error
|
||||
|
||||
salt *salt.Salt
|
||||
saltMutex sync.RWMutex
|
||||
}
|
||||
|
||||
func (n *NoopAudit) LogRequest(ctx context.Context, a *logical.Auth, r *logical.Request, err error) error {
|
||||
n.ReqAuth = append(n.ReqAuth, a)
|
||||
n.Req = append(n.Req, r)
|
||||
n.ReqHeaders = append(n.ReqHeaders, r.Headers)
|
||||
n.ReqErrs = append(n.ReqErrs, err)
|
||||
func (n *NoopAudit) LogRequest(ctx context.Context, in *audit.LogInput) error {
|
||||
n.ReqAuth = append(n.ReqAuth, in.Auth)
|
||||
n.Req = append(n.Req, in.Request)
|
||||
n.ReqHeaders = append(n.ReqHeaders, in.Request.Headers)
|
||||
n.ReqNonHMACKeys = in.NonHMACReqDataKeys
|
||||
n.ReqErrs = append(n.ReqErrs, in.OuterErr)
|
||||
return n.ReqErr
|
||||
}
|
||||
|
||||
func (n *NoopAudit) LogResponse(ctx context.Context, a *logical.Auth, r *logical.Request, re *logical.Response, err error) error {
|
||||
n.RespAuth = append(n.RespAuth, a)
|
||||
n.RespReq = append(n.RespReq, r)
|
||||
n.Resp = append(n.Resp, re)
|
||||
n.RespErrs = append(n.RespErrs, err)
|
||||
func (n *NoopAudit) LogResponse(ctx context.Context, in *audit.LogInput) error {
|
||||
n.RespAuth = append(n.RespAuth, in.Auth)
|
||||
n.RespReq = append(n.RespReq, in.Request)
|
||||
n.Resp = append(n.Resp, in.Response)
|
||||
n.RespErrs = append(n.RespErrs, in.OuterErr)
|
||||
|
||||
if in.Response != nil {
|
||||
n.RespNonHMACKeys = in.NonHMACRespDataKeys
|
||||
n.RespReqNonHMACKeys = in.NonHMACReqDataKeys
|
||||
}
|
||||
|
||||
return n.RespErr
|
||||
}
|
||||
|
||||
|
@ -442,7 +452,7 @@ func TestAuditBroker_LogRequest(t *testing.T) {
|
|||
Path: "sys/mounts",
|
||||
}
|
||||
|
||||
// Copy so we can verify nothing canged
|
||||
// Copy so we can verify nothing changed
|
||||
authCopyRaw, err := copystructure.Copy(auth)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -468,7 +478,12 @@ func TestAuditBroker_LogRequest(t *testing.T) {
|
|||
Headers: make(map[string]*auditedHeaderSettings),
|
||||
}
|
||||
|
||||
err = b.LogRequest(context.Background(), authCopy, reqCopy, headersConf, reqErrs)
|
||||
logInput := &audit.LogInput{
|
||||
Auth: authCopy,
|
||||
Request: reqCopy,
|
||||
OuterErr: reqErrs,
|
||||
}
|
||||
err = b.LogRequest(context.Background(), logInput, headersConf)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
@ -487,13 +502,17 @@ func TestAuditBroker_LogRequest(t *testing.T) {
|
|||
|
||||
// Should still work with one failing backend
|
||||
a1.ReqErr = fmt.Errorf("failed")
|
||||
if err := b.LogRequest(context.Background(), auth, req, headersConf, nil); err != nil {
|
||||
logInput = &audit.LogInput{
|
||||
Auth: auth,
|
||||
Request: req,
|
||||
}
|
||||
if err := b.LogRequest(context.Background(), logInput, headersConf); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Should FAIL work with both failing backends
|
||||
a2.ReqErr = fmt.Errorf("failed")
|
||||
if err := b.LogRequest(context.Background(), auth, req, headersConf, nil); !errwrap.Contains(err, "no audit backend succeeded in logging the request") {
|
||||
if err := b.LogRequest(context.Background(), logInput, headersConf); !errwrap.Contains(err, "no audit backend succeeded in logging the request") {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
}
|
||||
|
@ -555,7 +574,13 @@ func TestAuditBroker_LogResponse(t *testing.T) {
|
|||
Headers: make(map[string]*auditedHeaderSettings),
|
||||
}
|
||||
|
||||
err = b.LogResponse(context.Background(), authCopy, reqCopy, respCopy, headersConf, respErr)
|
||||
logInput := &audit.LogInput{
|
||||
Auth: authCopy,
|
||||
Request: reqCopy,
|
||||
Response: respCopy,
|
||||
OuterErr: respErr,
|
||||
}
|
||||
err = b.LogResponse(context.Background(), logInput, headersConf)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
@ -577,14 +602,20 @@ func TestAuditBroker_LogResponse(t *testing.T) {
|
|||
|
||||
// Should still work with one failing backend
|
||||
a1.RespErr = fmt.Errorf("failed")
|
||||
err = b.LogResponse(context.Background(), auth, req, resp, headersConf, respErr)
|
||||
logInput = &audit.LogInput{
|
||||
Auth: auth,
|
||||
Request: req,
|
||||
Response: resp,
|
||||
OuterErr: respErr,
|
||||
}
|
||||
err = b.LogResponse(context.Background(), logInput, headersConf)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Should FAIL work with both failing backends
|
||||
a2.RespErr = fmt.Errorf("failed")
|
||||
err = b.LogResponse(context.Background(), auth, req, resp, headersConf, respErr)
|
||||
err = b.LogResponse(context.Background(), logInput, headersConf)
|
||||
if !strings.Contains(err.Error(), "no audit backend succeeded in logging the response") {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
@ -632,7 +663,12 @@ func TestAuditBroker_AuditHeaders(t *testing.T) {
|
|||
headersConf.add(context.Background(), "X-Test-Header", false)
|
||||
headersConf.add(context.Background(), "X-Vault-Header", false)
|
||||
|
||||
err = b.LogRequest(context.Background(), auth, reqCopy, headersConf, respErr)
|
||||
logInput := &audit.LogInput{
|
||||
Auth: auth,
|
||||
Request: reqCopy,
|
||||
OuterErr: respErr,
|
||||
}
|
||||
err = b.LogRequest(context.Background(), logInput, headersConf)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
@ -650,14 +686,19 @@ func TestAuditBroker_AuditHeaders(t *testing.T) {
|
|||
|
||||
// Should still work with one failing backend
|
||||
a1.ReqErr = fmt.Errorf("failed")
|
||||
err = b.LogRequest(context.Background(), auth, req, headersConf, respErr)
|
||||
logInput = &audit.LogInput{
|
||||
Auth: auth,
|
||||
Request: req,
|
||||
OuterErr: respErr,
|
||||
}
|
||||
err = b.LogRequest(context.Background(), logInput, headersConf)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Should FAIL work with both failing backends
|
||||
a2.ReqErr = fmt.Errorf("failed")
|
||||
err = b.LogRequest(context.Background(), auth, req, headersConf, respErr)
|
||||
err = b.LogRequest(context.Background(), logInput, headersConf)
|
||||
if !errwrap.Contains(err, "no audit backend succeeded in logging the request") {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
|
|
@ -94,9 +94,11 @@ func (c *Core) enableCredential(ctx context.Context, entry *MountEntry) error {
|
|||
}
|
||||
entry.Accessor = accessor
|
||||
}
|
||||
// Sync values to the cache
|
||||
entry.SyncCache()
|
||||
|
||||
viewPath := credentialBarrierPrefix + entry.UUID + "/"
|
||||
view := NewBarrierView(c.barrier, viewPath)
|
||||
|
||||
// Mark the view as read-only until the mounting is complete and
|
||||
// ensure that it is reset after. This ensures that there will be no
|
||||
// writes during the construction of the backend.
|
||||
|
@ -347,6 +349,9 @@ func (c *Core) loadCredentials(ctx context.Context) error {
|
|||
entry.Accessor = accessor
|
||||
needPersist = true
|
||||
}
|
||||
|
||||
// Sync values to the cache
|
||||
entry.SyncCache()
|
||||
}
|
||||
|
||||
if !needPersist {
|
||||
|
|
|
@ -1343,7 +1343,11 @@ func (c *Core) sealInitCommon(ctx context.Context, req *logical.Request) (retErr
|
|||
EntityID: te.EntityID,
|
||||
}
|
||||
|
||||
if err := c.auditBroker.LogRequest(ctx, auth, req, c.auditedHeaders, nil); err != nil {
|
||||
logInput := &audit.LogInput{
|
||||
Auth: auth,
|
||||
Request: req,
|
||||
}
|
||||
if err := c.auditBroker.LogRequest(ctx, logInput, c.auditedHeaders); err != nil {
|
||||
c.logger.Error("core: failed to audit request", "request_path", req.Path, "error", err)
|
||||
retErr = multierror.Append(retErr, errors.New("failed to audit request, cannot continue"))
|
||||
c.stateLock.RUnlock()
|
||||
|
@ -1452,7 +1456,11 @@ func (c *Core) StepDown(req *logical.Request) (retErr error) {
|
|||
EntityID: te.EntityID,
|
||||
}
|
||||
|
||||
if err := c.auditBroker.LogRequest(ctx, auth, req, c.auditedHeaders, nil); err != nil {
|
||||
logInput := &audit.LogInput{
|
||||
Auth: auth,
|
||||
Request: req,
|
||||
}
|
||||
if err := c.auditBroker.LogRequest(ctx, logInput, c.auditedHeaders); err != nil {
|
||||
c.logger.Error("core: failed to audit request", "request_path", req.Path, "error", err)
|
||||
retErr = multierror.Append(retErr, errors.New("failed to audit request, cannot continue"))
|
||||
return retErr
|
||||
|
|
|
@ -807,6 +807,106 @@ func TestCore_HandleRequest_AuditTrail(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestCore_HandleRequest_AuditTrail_noHMACKeys(t *testing.T) {
|
||||
// Create a noop audit backend
|
||||
var noop *NoopAudit
|
||||
c, _, root := TestCoreUnsealed(t)
|
||||
c.auditBackends["noop"] = func(ctx context.Context, config *audit.BackendConfig) (audit.Backend, error) {
|
||||
noop = &NoopAudit{
|
||||
Config: config,
|
||||
}
|
||||
return noop, nil
|
||||
}
|
||||
|
||||
// Specify some keys to not HMAC
|
||||
req := logical.TestRequest(t, logical.UpdateOperation, "sys/mounts/secret/tune")
|
||||
req.Data["audit_non_hmac_request_keys"] = "foo"
|
||||
req.ClientToken = root
|
||||
resp, err := c.HandleRequest(req)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
req = logical.TestRequest(t, logical.UpdateOperation, "sys/mounts/secret/tune")
|
||||
req.Data["audit_non_hmac_response_keys"] = "baz"
|
||||
req.ClientToken = root
|
||||
resp, err = c.HandleRequest(req)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Enable the audit backend
|
||||
req = logical.TestRequest(t, logical.UpdateOperation, "sys/audit/noop")
|
||||
req.Data["type"] = "noop"
|
||||
req.ClientToken = root
|
||||
resp, err = c.HandleRequest(req)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Make a request
|
||||
req = &logical.Request{
|
||||
Operation: logical.UpdateOperation,
|
||||
Path: "secret/test",
|
||||
Data: map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
ClientToken: root,
|
||||
}
|
||||
req.ClientToken = root
|
||||
if _, err := c.HandleRequest(req); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Check the audit trail on request and response
|
||||
if len(noop.ReqAuth) != 1 {
|
||||
t.Fatalf("bad: %#v", noop)
|
||||
}
|
||||
auth := noop.ReqAuth[0]
|
||||
if auth.ClientToken != root {
|
||||
t.Fatalf("bad client token: %#v", auth)
|
||||
}
|
||||
if len(auth.Policies) != 1 || auth.Policies[0] != "root" {
|
||||
t.Fatalf("bad: %#v", auth)
|
||||
}
|
||||
if len(noop.Req) != 1 || !reflect.DeepEqual(noop.Req[0], req) {
|
||||
t.Fatalf("Bad: %#v", noop.Req[0])
|
||||
}
|
||||
if len(noop.ReqNonHMACKeys) != 1 || noop.ReqNonHMACKeys[0] != "foo" {
|
||||
t.Fatalf("Bad: %#v", noop.ReqNonHMACKeys)
|
||||
}
|
||||
if len(noop.RespAuth) != 2 {
|
||||
t.Fatalf("bad: %#v", noop)
|
||||
}
|
||||
if !reflect.DeepEqual(noop.RespAuth[1], auth) {
|
||||
t.Fatalf("bad: %#v", auth)
|
||||
}
|
||||
if len(noop.RespReq) != 2 || !reflect.DeepEqual(noop.RespReq[1], req) {
|
||||
t.Fatalf("Bad: %#v", noop.RespReq[1])
|
||||
}
|
||||
if len(noop.Resp) != 2 || !reflect.DeepEqual(noop.Resp[1], resp) {
|
||||
t.Fatalf("Bad: %#v", noop.Resp[1])
|
||||
}
|
||||
|
||||
// Test for response keys
|
||||
// Make a request
|
||||
req = &logical.Request{
|
||||
Operation: logical.ReadOperation,
|
||||
Path: "secret/test",
|
||||
ClientToken: root,
|
||||
}
|
||||
req.ClientToken = root
|
||||
if _, err := c.HandleRequest(req); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if len(noop.RespNonHMACKeys) != 1 || noop.RespNonHMACKeys[0] != "baz" {
|
||||
t.Fatalf("Bad: %#v", noop.RespNonHMACKeys)
|
||||
}
|
||||
if len(noop.RespReqNonHMACKeys) != 1 || noop.RespReqNonHMACKeys[0] != "foo" {
|
||||
t.Fatalf("Bad: %#v", noop.RespReqNonHMACKeys)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure we get a client token
|
||||
func TestCore_HandleLogin_AuditTrail(t *testing.T) {
|
||||
// Create a badass credential backend that always logs in as armon
|
||||
|
|
|
@ -257,6 +257,14 @@ func NewSystemBackend(core *Core) *SystemBackend {
|
|||
Type: framework.TypeString,
|
||||
Description: strings.TrimSpace(sysHelp["auth_desc"][0]),
|
||||
},
|
||||
"audit_non_hmac_request_keys": &framework.FieldSchema{
|
||||
Type: framework.TypeCommaStringSlice,
|
||||
Description: strings.TrimSpace(sysHelp["tune_audit_non_hmac_request_keys"][0]),
|
||||
},
|
||||
"audit_non_hmac_response_keys": &framework.FieldSchema{
|
||||
Type: framework.TypeCommaStringSlice,
|
||||
Description: strings.TrimSpace(sysHelp["tune_audit_non_hmac_response_keys"][0]),
|
||||
},
|
||||
},
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
logical.ReadOperation: b.handleAuthTuneRead,
|
||||
|
@ -286,6 +294,14 @@ func NewSystemBackend(core *Core) *SystemBackend {
|
|||
Type: framework.TypeString,
|
||||
Description: strings.TrimSpace(sysHelp["auth_desc"][0]),
|
||||
},
|
||||
"audit_non_hmac_request_keys": &framework.FieldSchema{
|
||||
Type: framework.TypeCommaStringSlice,
|
||||
Description: strings.TrimSpace(sysHelp["tune_audit_non_hmac_request_keys"][0]),
|
||||
},
|
||||
"audit_non_hmac_response_keys": &framework.FieldSchema{
|
||||
Type: framework.TypeCommaStringSlice,
|
||||
Description: strings.TrimSpace(sysHelp["tune_audit_non_hmac_response_keys"][0]),
|
||||
},
|
||||
},
|
||||
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
|
@ -1675,6 +1691,14 @@ func (b *SystemBackend) handleTuneReadCommon(path string) (*logical.Response, er
|
|||
},
|
||||
}
|
||||
|
||||
if rawVal, ok := mountEntry.synthesizedConfigCache.Load("audit_non_hmac_request_keys"); ok {
|
||||
resp.Data["audit_non_hmac_request_keys"] = rawVal.([]string)
|
||||
}
|
||||
|
||||
if rawVal, ok := mountEntry.synthesizedConfigCache.Load("audit_non_hmac_response_keys"); ok {
|
||||
resp.Data["audit_non_hmac_response_keys"] = rawVal.([]string)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
|
@ -1808,6 +1832,58 @@ func (b *SystemBackend) handleTuneWriteCommon(ctx context.Context, path string,
|
|||
}
|
||||
}
|
||||
|
||||
if rawVal, ok := data.GetOk("audit_non_hmac_request_keys"); ok {
|
||||
auditNonHMACRequestKeys := rawVal.([]string)
|
||||
|
||||
oldVal := mountEntry.Config.AuditNonHMACRequestKeys
|
||||
mountEntry.Config.AuditNonHMACRequestKeys = auditNonHMACRequestKeys
|
||||
|
||||
// Update the mount table
|
||||
var err error
|
||||
switch {
|
||||
case strings.HasPrefix(path, "auth/"):
|
||||
err = b.Core.persistAuth(ctx, b.Core.auth, mountEntry.Local)
|
||||
default:
|
||||
err = b.Core.persistMounts(ctx, b.Core.mounts, mountEntry.Local)
|
||||
}
|
||||
if err != nil {
|
||||
mountEntry.Config.AuditNonHMACRequestKeys = oldVal
|
||||
return handleError(err)
|
||||
}
|
||||
|
||||
mountEntry.SyncCache()
|
||||
|
||||
if b.Core.logger.IsInfo() {
|
||||
b.Core.logger.Info("core: mount tuning of audit_non_hmac_request_keys successful", "path", path)
|
||||
}
|
||||
}
|
||||
|
||||
if rawVal, ok := data.GetOk("audit_non_hmac_response_keys"); ok {
|
||||
auditNonHMACResponseKeys := rawVal.([]string)
|
||||
|
||||
oldVal := mountEntry.Config.AuditNonHMACResponseKeys
|
||||
mountEntry.Config.AuditNonHMACResponseKeys = auditNonHMACResponseKeys
|
||||
|
||||
// Update the mount table
|
||||
var err error
|
||||
switch {
|
||||
case strings.HasPrefix(path, "auth/"):
|
||||
err = b.Core.persistAuth(ctx, b.Core.auth, mountEntry.Local)
|
||||
default:
|
||||
err = b.Core.persistMounts(ctx, b.Core.mounts, mountEntry.Local)
|
||||
}
|
||||
if err != nil {
|
||||
mountEntry.Config.AuditNonHMACResponseKeys = oldVal
|
||||
return handleError(err)
|
||||
}
|
||||
|
||||
mountEntry.SyncCache()
|
||||
|
||||
if b.Core.logger.IsInfo() {
|
||||
b.Core.logger.Info("core: mount tuning of audit_non_hmac_response_keys successful", "path", path)
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
@ -3099,6 +3175,14 @@ in the plugin catalog.`,
|
|||
`The max lease TTL for this mount.`,
|
||||
},
|
||||
|
||||
"tune_audit_non_hmac_request_keys": {
|
||||
`The list of keys in the request data object that will not be HMAC'ed by audit devices.`,
|
||||
},
|
||||
|
||||
"tune_audit_non_hmac_response_keys": {
|
||||
`The list of keys in the response data object that will not be HMAC'ed by audit devices.`,
|
||||
},
|
||||
|
||||
"remount": {
|
||||
"Move the mount point of an already-mounted backend.",
|
||||
`
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/go-uuid"
|
||||
|
@ -171,22 +172,29 @@ type MountEntry struct {
|
|||
Local bool `json:"local"` // Local mounts are not replicated or affected by replication
|
||||
SealWrap bool `json:"seal_wrap"` // Whether to wrap CSPs
|
||||
Tainted bool `json:"tainted,omitempty"` // Set as a Write-Ahead flag for unmount/remount
|
||||
|
||||
// synthesizedConfigCache is used to cache configuration values
|
||||
synthesizedConfigCache sync.Map
|
||||
}
|
||||
|
||||
// MountConfig is used to hold settable options
|
||||
type MountConfig struct {
|
||||
DefaultLeaseTTL time.Duration `json:"default_lease_ttl" structs:"default_lease_ttl" mapstructure:"default_lease_ttl"` // Override for global default
|
||||
MaxLeaseTTL time.Duration `json:"max_lease_ttl" structs:"max_lease_ttl" mapstructure:"max_lease_ttl"` // Override for global default
|
||||
ForceNoCache bool `json:"force_no_cache" structs:"force_no_cache" mapstructure:"force_no_cache"` // Override for global default
|
||||
PluginName string `json:"plugin_name,omitempty" structs:"plugin_name,omitempty" mapstructure:"plugin_name"`
|
||||
DefaultLeaseTTL time.Duration `json:"default_lease_ttl" structs:"default_lease_ttl" mapstructure:"default_lease_ttl"` // Override for global default
|
||||
MaxLeaseTTL time.Duration `json:"max_lease_ttl" structs:"max_lease_ttl" mapstructure:"max_lease_ttl"` // Override for global default
|
||||
ForceNoCache bool `json:"force_no_cache" structs:"force_no_cache" mapstructure:"force_no_cache"` // Override for global default
|
||||
PluginName string `json:"plugin_name,omitempty" structs:"plugin_name,omitempty" mapstructure:"plugin_name"`
|
||||
AuditNonHMACRequestKeys []string `json:"audit_non_hmac_request_keys,omitempty" structs:"audit_non_hmac_request_keys" mapstructure:"audit_non_hmac_request_keys"`
|
||||
AuditNonHMACResponseKeys []string `json:"audit_non_hmac_response_keys,omitempty" structs:"audit_non_hmac_response_keys" mapstructure:"audit_non_hmac_response_keys"`
|
||||
}
|
||||
|
||||
// APIMountConfig is an embedded struct of api.MountConfigInput
|
||||
type APIMountConfig struct {
|
||||
DefaultLeaseTTL string `json:"default_lease_ttl" structs:"default_lease_ttl" mapstructure:"default_lease_ttl"`
|
||||
MaxLeaseTTL string `json:"max_lease_ttl" structs:"max_lease_ttl" mapstructure:"max_lease_ttl"`
|
||||
ForceNoCache bool `json:"force_no_cache" structs:"force_no_cache" mapstructure:"force_no_cache"`
|
||||
PluginName string `json:"plugin_name,omitempty" structs:"plugin_name,omitempty" mapstructure:"plugin_name"`
|
||||
DefaultLeaseTTL string `json:"default_lease_ttl" structs:"default_lease_ttl" mapstructure:"default_lease_ttl"`
|
||||
MaxLeaseTTL string `json:"max_lease_ttl" structs:"max_lease_ttl" mapstructure:"max_lease_ttl"`
|
||||
ForceNoCache bool `json:"force_no_cache" structs:"force_no_cache" mapstructure:"force_no_cache"`
|
||||
PluginName string `json:"plugin_name,omitempty" structs:"plugin_name,omitempty" mapstructure:"plugin_name"`
|
||||
AuditNonHMACRequestKeys []string `json:"audit_non_hmac_request_keys,omitempty" structs:"audit_non_hmac_request_keys" mapstructure:"audit_non_hmac_request_keys"`
|
||||
AuditNonHMACResponseKeys []string `json:"audit_non_hmac_response_keys,omitempty" structs:"audit_non_hmac_response_keys" mapstructure:"audit_non_hmac_response_keys"`
|
||||
}
|
||||
|
||||
// Clone returns a deep copy of the mount entry
|
||||
|
@ -198,6 +206,21 @@ func (e *MountEntry) Clone() (*MountEntry, error) {
|
|||
return cp.(*MountEntry), nil
|
||||
}
|
||||
|
||||
// SyncCache syncs tunable configuration values to the cache
|
||||
func (e *MountEntry) SyncCache() {
|
||||
if len(e.Config.AuditNonHMACRequestKeys) == 0 {
|
||||
e.synthesizedConfigCache.Delete("audit_non_hmac_request_keys")
|
||||
} else {
|
||||
e.synthesizedConfigCache.Store("audit_non_hmac_request_keys", e.Config.AuditNonHMACRequestKeys)
|
||||
}
|
||||
|
||||
if len(e.Config.AuditNonHMACResponseKeys) == 0 {
|
||||
e.synthesizedConfigCache.Delete("audit_non_hmac_response_keys")
|
||||
} else {
|
||||
e.synthesizedConfigCache.Store("audit_non_hmac_response_keys", e.Config.AuditNonHMACResponseKeys)
|
||||
}
|
||||
}
|
||||
|
||||
// Mount is used to mount a new backend to the mount table.
|
||||
func (c *Core) mount(ctx context.Context, entry *MountEntry) error {
|
||||
// Ensure we end the path in a slash
|
||||
|
@ -245,6 +268,9 @@ func (c *Core) mountInternal(ctx context.Context, entry *MountEntry) error {
|
|||
}
|
||||
entry.Accessor = accessor
|
||||
}
|
||||
// Sync values to the cache
|
||||
entry.SyncCache()
|
||||
|
||||
viewPath := backendBarrierPrefix + entry.UUID + "/"
|
||||
view := NewBarrierView(c.barrier, viewPath)
|
||||
|
||||
|
@ -636,6 +662,9 @@ func (c *Core) loadMounts(ctx context.Context) error {
|
|||
entry.Accessor = accessor
|
||||
needPersist = true
|
||||
}
|
||||
|
||||
// Sync values to the cache
|
||||
entry.SyncCache()
|
||||
}
|
||||
|
||||
// Done if we have restored the mount table and we don't need
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
|
||||
"github.com/armon/go-metrics"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/hashicorp/vault/audit"
|
||||
"github.com/hashicorp/vault/helper/consts"
|
||||
"github.com/hashicorp/vault/helper/identity"
|
||||
"github.com/hashicorp/vault/helper/jsonutil"
|
||||
|
@ -113,8 +114,33 @@ func (c *Core) HandleRequest(req *logical.Request) (resp *logical.Response, err
|
|||
auditResp = logical.HTTPResponseToLogicalResponse(httpResp)
|
||||
}
|
||||
|
||||
var nonHMACReqDataKeys []string
|
||||
var nonHMACRespDataKeys []string
|
||||
entry := c.router.MatchingMountEntry(req.Path)
|
||||
if entry != nil {
|
||||
// Get and set ignored HMAC'd value. Reset those back to empty afterwards.
|
||||
if rawVals, ok := entry.synthesizedConfigCache.Load("audit_non_hmac_request_keys"); ok {
|
||||
nonHMACReqDataKeys = rawVals.([]string)
|
||||
}
|
||||
|
||||
// Get and set ignored HMAC'd value. Reset those back to empty afterwards.
|
||||
if auditResp != nil {
|
||||
if rawVals, ok := entry.synthesizedConfigCache.Load("audit_non_hmac_response_keys"); ok {
|
||||
nonHMACRespDataKeys = rawVals.([]string)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create an audit trail of the response
|
||||
if auditErr := c.auditBroker.LogResponse(ctx, auth, req, auditResp, c.auditedHeaders, err); auditErr != nil {
|
||||
logInput := &audit.LogInput{
|
||||
Auth: auth,
|
||||
Request: req,
|
||||
Response: auditResp,
|
||||
OuterErr: err,
|
||||
NonHMACReqDataKeys: nonHMACReqDataKeys,
|
||||
NonHMACRespDataKeys: nonHMACRespDataKeys,
|
||||
}
|
||||
if auditErr := c.auditBroker.LogResponse(ctx, logInput, c.auditedHeaders); auditErr != nil {
|
||||
c.logger.Error("core: failed to audit response", "request_path", req.Path, "error", auditErr)
|
||||
return nil, ErrInternalError
|
||||
}
|
||||
|
@ -125,6 +151,15 @@ func (c *Core) HandleRequest(req *logical.Request) (resp *logical.Response, err
|
|||
func (c *Core) handleRequest(ctx context.Context, req *logical.Request) (retResp *logical.Response, retAuth *logical.Auth, retErr error) {
|
||||
defer metrics.MeasureSince([]string{"core", "handle_request"}, time.Now())
|
||||
|
||||
var nonHMACReqDataKeys []string
|
||||
entry := c.router.MatchingMountEntry(req.Path)
|
||||
if entry != nil {
|
||||
// Get and set ignored HMAC'd value.
|
||||
if rawVals, ok := entry.synthesizedConfigCache.Load("audit_non_hmac_request_keys"); ok {
|
||||
nonHMACReqDataKeys = rawVals.([]string)
|
||||
}
|
||||
}
|
||||
|
||||
// Validate the token
|
||||
auth, te, ctErr := c.checkToken(ctx, req, false)
|
||||
// We run this logic first because we want to decrement the use count even in the case of an error
|
||||
|
@ -172,7 +207,13 @@ func (c *Core) handleRequest(ctx context.Context, req *logical.Request) (retResp
|
|||
errType = ctErr
|
||||
}
|
||||
|
||||
if err := c.auditBroker.LogRequest(ctx, auth, req, c.auditedHeaders, ctErr); err != nil {
|
||||
logInput := &audit.LogInput{
|
||||
Auth: auth,
|
||||
Request: req,
|
||||
OuterErr: ctErr,
|
||||
NonHMACReqDataKeys: nonHMACReqDataKeys,
|
||||
}
|
||||
if err := c.auditBroker.LogRequest(ctx, logInput, c.auditedHeaders); err != nil {
|
||||
c.logger.Error("core: failed to audit request", "path", req.Path, "error", err)
|
||||
}
|
||||
|
||||
|
@ -189,7 +230,12 @@ func (c *Core) handleRequest(ctx context.Context, req *logical.Request) (retResp
|
|||
req.DisplayName = auth.DisplayName
|
||||
|
||||
// Create an audit trail of the request
|
||||
if err := c.auditBroker.LogRequest(ctx, auth, req, c.auditedHeaders, nil); err != nil {
|
||||
logInput := &audit.LogInput{
|
||||
Auth: auth,
|
||||
Request: req,
|
||||
NonHMACReqDataKeys: nonHMACReqDataKeys,
|
||||
}
|
||||
if err := c.auditBroker.LogRequest(ctx, logInput, c.auditedHeaders); err != nil {
|
||||
c.logger.Error("core: failed to audit request", "path", req.Path, "error", err)
|
||||
retErr = multierror.Append(retErr, ErrInternalError)
|
||||
return nil, auth, retErr
|
||||
|
@ -358,7 +404,11 @@ func (c *Core) handleLoginRequest(ctx context.Context, req *logical.Request) (re
|
|||
// Create an audit trail of the request, auth is not available on login requests
|
||||
// Create an audit trail of the request. Attach auth if it was returned,
|
||||
// e.g. if a token was provided.
|
||||
if err := c.auditBroker.LogRequest(ctx, auth, req, c.auditedHeaders, nil); err != nil {
|
||||
logInput := &audit.LogInput{
|
||||
Auth: auth,
|
||||
Request: req,
|
||||
}
|
||||
if err := c.auditBroker.LogRequest(ctx, logInput, c.auditedHeaders); err != nil {
|
||||
c.logger.Error("core: failed to audit request", "path", req.Path, "error", err)
|
||||
return nil, nil, ErrInternalError
|
||||
}
|
||||
|
|
|
@ -611,11 +611,11 @@ func (n *noopAudit) GetHash(data string) (string, error) {
|
|||
return salt.GetIdentifiedHMAC(data), nil
|
||||
}
|
||||
|
||||
func (n *noopAudit) LogRequest(_ context.Context, _ *logical.Auth, _ *logical.Request, _ error) error {
|
||||
func (n *noopAudit) LogRequest(_ context.Context, _ *audit.LogInput) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *noopAudit) LogResponse(_ context.Context, _ *logical.Auth, _ *logical.Request, _ *logical.Response, _ error) error {
|
||||
func (n *noopAudit) LogResponse(_ context.Context, _ *audit.LogInput) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -194,6 +194,14 @@ can be achieved without `sudo` via `sys/mounts/auth/[auth-path]/tune`._
|
|||
- `description` `(string: "")` – Specifies the description of the mount. This
|
||||
overrides the current stored value, if any.
|
||||
|
||||
- `audit_non_hmac_request_keys` `(array: [])` - Specifies the comma-separated
|
||||
list of keys that will not be HMAC'd by audit devices in the request data
|
||||
object.
|
||||
|
||||
- `audit_non_hmac_response_keys` `(array: [])` - Specifies the comma-separated
|
||||
list of keys that will not be HMAC'd by audit devices in the response data
|
||||
object.
|
||||
|
||||
### Sample Payload
|
||||
|
||||
```json
|
||||
|
|
|
@ -202,6 +202,14 @@ This endpoint tunes configuration parameters for a given mount point.
|
|||
- `description` `(string: "")` – Specifies the description of the mount. This
|
||||
overrides the current stored value, if any.
|
||||
|
||||
- `audit_non_hmac_request_keys` `(array: [])` - Specifies the comma-separated
|
||||
list of keys that will not be HMAC'd by audit devices in the request data
|
||||
object.
|
||||
|
||||
- `audit_non_hmac_response_keys` `(array: [])` - Specifies the comma-separated
|
||||
list of keys that will not be HMAC'd by audit devices in the response data
|
||||
object.
|
||||
|
||||
### Sample Payload
|
||||
|
||||
```json
|
||||
|
|
Loading…
Reference in New Issue