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 {
|
type MountConfigInput struct {
|
||||||
DefaultLeaseTTL string `json:"default_lease_ttl" structs:"default_lease_ttl" mapstructure:"default_lease_ttl"`
|
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"`
|
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"`
|
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"`
|
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 {
|
type MountOutput struct {
|
||||||
|
@ -145,8 +147,10 @@ type MountOutput struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type MountConfigOutput struct {
|
type MountConfigOutput struct {
|
||||||
DefaultLeaseTTL int `json:"default_lease_ttl" structs:"default_lease_ttl" mapstructure:"default_lease_ttl"`
|
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"`
|
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"`
|
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"`
|
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
|
// 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
|
// MUST not be modified in anyway. They should be deep copied if this is
|
||||||
// a possibility.
|
// 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
|
// LogResponse is used to synchronously log a response. This is done after
|
||||||
// the request is processed but before the response is sent. The arguments
|
// 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
|
// MUST not be modified in anyway. They should be deep copied if this is
|
||||||
// a possibility.
|
// 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,
|
// 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
|
// so that a caller can determine if a value in the audit log matches
|
||||||
|
@ -36,6 +36,18 @@ type Backend interface {
|
||||||
Invalidate(context.Context)
|
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 {
|
type BackendConfig struct {
|
||||||
// The view to store the salt
|
// The view to store the salt
|
||||||
SaltView logical.Storage
|
SaltView logical.Storage
|
||||||
|
|
|
@ -25,14 +25,10 @@ type AuditFormatter struct {
|
||||||
AuditFormatWriter
|
AuditFormatWriter
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *AuditFormatter) FormatRequest(
|
var _ Formatter = (*AuditFormatter)(nil)
|
||||||
w io.Writer,
|
|
||||||
config FormatterConfig,
|
|
||||||
auth *logical.Auth,
|
|
||||||
req *logical.Request,
|
|
||||||
inErr error) error {
|
|
||||||
|
|
||||||
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")
|
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)
|
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 {
|
if !config.Raw {
|
||||||
// Before we copy the structure we must nil out some data
|
// Before we copy the structure we must nil out some data
|
||||||
// otherwise we will cause reflection to panic and die
|
// otherwise we will cause reflection to panic and die
|
||||||
if req.Connection != nil && req.Connection.ConnState != nil {
|
if in.Request.Connection != nil && in.Request.Connection.ConnState != nil {
|
||||||
origReq := req
|
origState := in.Request.Connection.ConnState
|
||||||
origState := req.Connection.ConnState
|
in.Request.Connection.ConnState = nil
|
||||||
req.Connection.ConnState = nil
|
|
||||||
defer func() {
|
defer func() {
|
||||||
origReq.Connection.ConnState = origState
|
in.Request.Connection.ConnState = origState
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy the auth structure
|
// Copy the auth structure
|
||||||
if auth != nil {
|
if in.Auth != nil {
|
||||||
cp, err := copystructure.Copy(auth)
|
cp, err := copystructure.Copy(in.Auth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
auth = cp.(*logical.Auth)
|
auth = cp.(*logical.Auth)
|
||||||
}
|
}
|
||||||
|
|
||||||
cp, err := copystructure.Copy(req)
|
cp, err := copystructure.Copy(in.Request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -83,7 +82,7 @@ func (f *AuditFormatter) FormatRequest(
|
||||||
if !config.HMACAccessor && auth.Accessor != "" {
|
if !config.HMACAccessor && auth.Accessor != "" {
|
||||||
authAccessor = auth.Accessor
|
authAccessor = auth.Accessor
|
||||||
}
|
}
|
||||||
if err := Hash(salt, auth); err != nil {
|
if err := Hash(salt, auth, nil); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if authAccessor != "" {
|
if authAccessor != "" {
|
||||||
|
@ -96,7 +95,7 @@ func (f *AuditFormatter) FormatRequest(
|
||||||
if !config.HMACAccessor && req != nil && req.ClientTokenAccessor != "" {
|
if !config.HMACAccessor && req != nil && req.ClientTokenAccessor != "" {
|
||||||
clientTokenAccessor = req.ClientTokenAccessor
|
clientTokenAccessor = req.ClientTokenAccessor
|
||||||
}
|
}
|
||||||
if err := Hash(salt, req); err != nil {
|
if err := Hash(salt, req, in.NonHMACReqDataKeys); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if clientTokenAccessor != "" {
|
if clientTokenAccessor != "" {
|
||||||
|
@ -109,8 +108,8 @@ func (f *AuditFormatter) FormatRequest(
|
||||||
auth = new(logical.Auth)
|
auth = new(logical.Auth)
|
||||||
}
|
}
|
||||||
var errString string
|
var errString string
|
||||||
if inErr != nil {
|
if in.OuterErr != nil {
|
||||||
errString = inErr.Error()
|
errString = in.OuterErr.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
reqEntry := &AuditRequestEntry{
|
reqEntry := &AuditRequestEntry{
|
||||||
|
@ -152,15 +151,8 @@ func (f *AuditFormatter) FormatRequest(
|
||||||
return f.AuditFormatWriter.WriteRequest(w, reqEntry)
|
return f.AuditFormatWriter.WriteRequest(w, reqEntry)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *AuditFormatter) FormatResponse(
|
func (f *AuditFormatter) FormatResponse(w io.Writer, config FormatterConfig, in *LogInput) error {
|
||||||
w io.Writer,
|
if in == nil || in.Request == nil {
|
||||||
config FormatterConfig,
|
|
||||||
auth *logical.Auth,
|
|
||||||
req *logical.Request,
|
|
||||||
resp *logical.Response,
|
|
||||||
inErr error) error {
|
|
||||||
|
|
||||||
if req == nil {
|
|
||||||
return fmt.Errorf("request to response-audit a nil request")
|
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)
|
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 {
|
if !config.Raw {
|
||||||
// Before we copy the structure we must nil out some data
|
// Before we copy the structure we must nil out some data
|
||||||
// otherwise we will cause reflection to panic and die
|
// otherwise we will cause reflection to panic and die
|
||||||
if req.Connection != nil && req.Connection.ConnState != nil {
|
if in.Request.Connection != nil && in.Request.Connection.ConnState != nil {
|
||||||
origReq := req
|
origState := in.Request.Connection.ConnState
|
||||||
origState := req.Connection.ConnState
|
in.Request.Connection.ConnState = nil
|
||||||
req.Connection.ConnState = nil
|
|
||||||
defer func() {
|
defer func() {
|
||||||
origReq.Connection.ConnState = origState
|
in.Request.Connection.ConnState = origState
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy the auth structure
|
// Copy the auth structure
|
||||||
if auth != nil {
|
if in.Auth != nil {
|
||||||
cp, err := copystructure.Copy(auth)
|
cp, err := copystructure.Copy(in.Auth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
auth = cp.(*logical.Auth)
|
auth = cp.(*logical.Auth)
|
||||||
}
|
}
|
||||||
|
|
||||||
cp, err := copystructure.Copy(req)
|
cp, err := copystructure.Copy(in.Request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
req = cp.(*logical.Request)
|
req = cp.(*logical.Request)
|
||||||
|
|
||||||
if resp != nil {
|
if in.Response != nil {
|
||||||
cp, err := copystructure.Copy(resp)
|
cp, err := copystructure.Copy(in.Response)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -220,7 +216,7 @@ func (f *AuditFormatter) FormatResponse(
|
||||||
if !config.HMACAccessor && auth.Accessor != "" {
|
if !config.HMACAccessor && auth.Accessor != "" {
|
||||||
accessor = auth.Accessor
|
accessor = auth.Accessor
|
||||||
}
|
}
|
||||||
if err := Hash(salt, auth); err != nil {
|
if err := Hash(salt, auth, nil); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if accessor != "" {
|
if accessor != "" {
|
||||||
|
@ -233,7 +229,7 @@ func (f *AuditFormatter) FormatResponse(
|
||||||
if !config.HMACAccessor && req != nil && req.ClientTokenAccessor != "" {
|
if !config.HMACAccessor && req != nil && req.ClientTokenAccessor != "" {
|
||||||
clientTokenAccessor = req.ClientTokenAccessor
|
clientTokenAccessor = req.ClientTokenAccessor
|
||||||
}
|
}
|
||||||
if err := Hash(salt, req); err != nil {
|
if err := Hash(salt, req, in.NonHMACReqDataKeys); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if clientTokenAccessor != "" {
|
if clientTokenAccessor != "" {
|
||||||
|
@ -250,7 +246,7 @@ func (f *AuditFormatter) FormatResponse(
|
||||||
wrappedAccessor = resp.WrapInfo.WrappedAccessor
|
wrappedAccessor = resp.WrapInfo.WrappedAccessor
|
||||||
wrappingAccessor = resp.WrapInfo.Accessor
|
wrappingAccessor = resp.WrapInfo.Accessor
|
||||||
}
|
}
|
||||||
if err := Hash(salt, resp); err != nil {
|
if err := Hash(salt, resp, in.NonHMACRespDataKeys); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if accessor != "" {
|
if accessor != "" {
|
||||||
|
@ -273,8 +269,8 @@ func (f *AuditFormatter) FormatResponse(
|
||||||
resp = new(logical.Response)
|
resp = new(logical.Response)
|
||||||
}
|
}
|
||||||
var errString string
|
var errString string
|
||||||
if inErr != nil {
|
if in.OuterErr != nil {
|
||||||
errString = inErr.Error()
|
errString = in.OuterErr.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
var respAuth *AuditAuth
|
var respAuth *AuditAuth
|
||||||
|
@ -358,7 +354,7 @@ func (f *AuditFormatter) FormatResponse(
|
||||||
return f.AuditFormatWriter.WriteResponse(w, respEntry)
|
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 {
|
type AuditRequestEntry struct {
|
||||||
Time string `json:"time,omitempty"`
|
Time string `json:"time,omitempty"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/hashicorp/vault/helper/jsonutil"
|
"github.com/hashicorp/vault/helper/jsonutil"
|
||||||
"github.com/hashicorp/vault/helper/salt"
|
"github.com/hashicorp/vault/helper/salt"
|
||||||
"github.com/hashicorp/vault/logical"
|
"github.com/hashicorp/vault/logical"
|
||||||
|
@ -84,7 +85,12 @@ func TestFormatJSON_formatRequest(t *testing.T) {
|
||||||
config := FormatterConfig{
|
config := FormatterConfig{
|
||||||
HMACAccessor: false,
|
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)
|
t.Fatalf("bad: %s\nerr: %s", name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -89,7 +89,12 @@ func TestFormatJSONx_formatRequest(t *testing.T) {
|
||||||
OmitTime: true,
|
OmitTime: true,
|
||||||
HMACAccessor: false,
|
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)
|
t.Fatalf("bad: %s\nerr: %s", name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,10 +40,14 @@ func TestFormatRequestErrors(t *testing.T) {
|
||||||
AuditFormatWriter: &noopFormatWriter{},
|
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")
|
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")
|
t.Fatal("expected error due to nil writer")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,10 +58,14 @@ func TestFormatResponseErrors(t *testing.T) {
|
||||||
AuditFormatWriter: &noopFormatWriter{},
|
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")
|
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")
|
t.Fatal("expected error due to nil writer")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,6 @@ package audit
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/hashicorp/vault/logical"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Formatter is an interface that is responsible for formating a
|
// 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.
|
// It is recommended that you pass data through Hash prior to formatting it.
|
||||||
type Formatter interface {
|
type Formatter interface {
|
||||||
FormatRequest(io.Writer, FormatterConfig, *logical.Auth, *logical.Request, error) error
|
FormatRequest(io.Writer, FormatterConfig, *LogInput) error
|
||||||
FormatResponse(io.Writer, FormatterConfig, *logical.Auth, *logical.Request, *logical.Response, error) error
|
FormatResponse(io.Writer, FormatterConfig, *LogInput) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type FormatterConfig struct {
|
type FormatterConfig struct {
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/hashicorp/vault/helper/salt"
|
"github.com/hashicorp/vault/helper/salt"
|
||||||
|
"github.com/hashicorp/vault/helper/strutil"
|
||||||
"github.com/hashicorp/vault/helper/wrapping"
|
"github.com/hashicorp/vault/helper/wrapping"
|
||||||
"github.com/hashicorp/vault/logical"
|
"github.com/hashicorp/vault/logical"
|
||||||
"github.com/mitchellh/copystructure"
|
"github.com/mitchellh/copystructure"
|
||||||
|
@ -23,7 +24,7 @@ func HashString(salter *salt.Salt, data string) string {
|
||||||
// it will be passed through.
|
// it will be passed through.
|
||||||
//
|
//
|
||||||
// The structure is modified in-place.
|
// 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
|
fn := salter.GetIdentifiedHMAC
|
||||||
|
|
||||||
switch s := raw.(type) {
|
switch s := raw.(type) {
|
||||||
|
@ -43,7 +44,7 @@ func Hash(salter *salt.Salt, raw interface{}) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if s.Auth != nil {
|
if s.Auth != nil {
|
||||||
if err := Hash(salter, s.Auth); err != nil {
|
if err := Hash(salter, s.Auth, nil); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,7 +57,7 @@ func Hash(salter *salt.Salt, raw interface{}) error {
|
||||||
s.ClientTokenAccessor = fn(s.ClientTokenAccessor)
|
s.ClientTokenAccessor = fn(s.ClientTokenAccessor)
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := HashStructure(s.Data, fn)
|
data, err := HashStructure(s.Data, fn, nonHMACDataKeys)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -69,18 +70,18 @@ func Hash(salter *salt.Salt, raw interface{}) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.Auth != nil {
|
if s.Auth != nil {
|
||||||
if err := Hash(salter, s.Auth); err != nil {
|
if err := Hash(salter, s.Auth, nil); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.WrapInfo != nil {
|
if s.WrapInfo != nil {
|
||||||
if err := Hash(salter, s.WrapInfo); err != nil {
|
if err := Hash(salter, s.WrapInfo, nil); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := HashStructure(s.Data, fn)
|
data, err := HashStructure(s.Data, fn, nonHMACDataKeys)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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.
|
// the structure. Only _values_ are hashed: keys of objects are not.
|
||||||
//
|
//
|
||||||
// For the HashCallback, see the built-in HashCallbacks below.
|
// 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)
|
s, err := copystructure.Copy(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
walker := &hashWalker{Callback: cb}
|
walker := &hashWalker{Callback: cb, IgnoredKeys: ignoredKeys}
|
||||||
if err := reflectwalk.Walk(s, walker); err != nil {
|
if err := reflectwalk.Walk(s, walker); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -134,6 +135,9 @@ type hashWalker struct {
|
||||||
// immediately and the error returned.
|
// immediately and the error returned.
|
||||||
Callback HashCallback
|
Callback HashCallback
|
||||||
|
|
||||||
|
// IgnoreKeys are the keys that wont have the HashCallback applied
|
||||||
|
IgnoredKeys []string
|
||||||
|
|
||||||
key []string
|
key []string
|
||||||
lastValue reflect.Value
|
lastValue reflect.Value
|
||||||
loc reflectwalk.Location
|
loc reflectwalk.Location
|
||||||
|
@ -247,6 +251,12 @@ func (w *hashWalker) Primitive(v reflect.Value) error {
|
||||||
return nil
|
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())
|
replaceVal := w.Callback(v.String())
|
||||||
|
|
||||||
resultVal := reflect.ValueOf(replaceVal)
|
resultVal := reflect.ValueOf(replaceVal)
|
||||||
|
|
|
@ -116,32 +116,37 @@ func TestHash(t *testing.T) {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
Input interface{}
|
Input interface{}
|
||||||
Output interface{}
|
Output interface{}
|
||||||
|
NonHMACDataKeys []string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
&logical.Auth{ClientToken: "foo"},
|
&logical.Auth{ClientToken: "foo"},
|
||||||
&logical.Auth{ClientToken: "hmac-sha256:08ba357e274f528065766c770a639abf6809b39ccfd37c2a3157c7f51954da0a"},
|
&logical.Auth{ClientToken: "hmac-sha256:08ba357e274f528065766c770a639abf6809b39ccfd37c2a3157c7f51954da0a"},
|
||||||
|
nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
&logical.Request{
|
&logical.Request{
|
||||||
Data: map[string]interface{}{
|
Data: map[string]interface{}{
|
||||||
"foo": "bar",
|
"foo": "bar",
|
||||||
|
"baz": "foobar",
|
||||||
"private_key_type": certutil.PrivateKeyType("rsa"),
|
"private_key_type": certutil.PrivateKeyType("rsa"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
&logical.Request{
|
&logical.Request{
|
||||||
Data: map[string]interface{}{
|
Data: map[string]interface{}{
|
||||||
"foo": "hmac-sha256:f9320baf0249169e73850cd6156ded0106e2bb6ad8cab01b7bbbebe6d1065317",
|
"foo": "hmac-sha256:f9320baf0249169e73850cd6156ded0106e2bb6ad8cab01b7bbbebe6d1065317",
|
||||||
|
"baz": "foobar",
|
||||||
"private_key_type": "hmac-sha256:995230dca56fffd310ff591aa404aab52b2abb41703c787cfa829eceb4595bf1",
|
"private_key_type": "hmac-sha256:995230dca56fffd310ff591aa404aab52b2abb41703c787cfa829eceb4595bf1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
[]string{"baz"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
&logical.Response{
|
&logical.Response{
|
||||||
Data: map[string]interface{}{
|
Data: map[string]interface{}{
|
||||||
"foo": "bar",
|
"foo": "bar",
|
||||||
|
"baz": "foobar",
|
||||||
// Responses can contain time values, so test that with
|
// Responses can contain time values, so test that with
|
||||||
// a known fixed value.
|
// a known fixed value.
|
||||||
"bar": now,
|
"bar": now,
|
||||||
|
@ -157,6 +162,7 @@ func TestHash(t *testing.T) {
|
||||||
&logical.Response{
|
&logical.Response{
|
||||||
Data: map[string]interface{}{
|
Data: map[string]interface{}{
|
||||||
"foo": "hmac-sha256:f9320baf0249169e73850cd6156ded0106e2bb6ad8cab01b7bbbebe6d1065317",
|
"foo": "hmac-sha256:f9320baf0249169e73850cd6156ded0106e2bb6ad8cab01b7bbbebe6d1065317",
|
||||||
|
"baz": "foobar",
|
||||||
"bar": now.Format(time.RFC3339Nano),
|
"bar": now.Format(time.RFC3339Nano),
|
||||||
},
|
},
|
||||||
WrapInfo: &wrapping.ResponseWrapInfo{
|
WrapInfo: &wrapping.ResponseWrapInfo{
|
||||||
|
@ -167,10 +173,12 @@ func TestHash(t *testing.T) {
|
||||||
WrappedAccessor: "hmac-sha256:f9320baf0249169e73850cd6156ded0106e2bb6ad8cab01b7bbbebe6d1065317",
|
WrappedAccessor: "hmac-sha256:f9320baf0249169e73850cd6156ded0106e2bb6ad8cab01b7bbbebe6d1065317",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
[]string{"baz"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"foo",
|
"foo",
|
||||||
"foo",
|
"foo",
|
||||||
|
nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
&logical.Auth{
|
&logical.Auth{
|
||||||
|
@ -189,6 +197,7 @@ func TestHash(t *testing.T) {
|
||||||
|
|
||||||
ClientToken: "hmac-sha256:08ba357e274f528065766c770a639abf6809b39ccfd37c2a3157c7f51954da0a",
|
ClientToken: "hmac-sha256:08ba357e274f528065766c770a639abf6809b39ccfd37c2a3157c7f51954da0a",
|
||||||
},
|
},
|
||||||
|
nil,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -206,16 +215,16 @@ func TestHash(t *testing.T) {
|
||||||
}
|
}
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
input := fmt.Sprintf("%#v", tc.Input)
|
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)
|
t.Fatalf("err: %s\n\n%s", err, input)
|
||||||
}
|
}
|
||||||
if _, ok := tc.Input.(*logical.Response); ok {
|
if _, ok := tc.Input.(*logical.Response); ok {
|
||||||
if !reflect.DeepEqual(tc.Input.(*logical.Response).WrapInfo, tc.Output.(*logical.Response).WrapInfo) {
|
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) {
|
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 {
|
for _, tc := range cases {
|
||||||
output, err := HashStructure(tc.Input, func(string) string {
|
output, err := HashStructure(tc.Input, func(string) string {
|
||||||
return replaceText
|
return replaceText
|
||||||
})
|
}, []string{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s\n\n%#v", err, tc.Input)
|
t.Fatalf("err: %s\n\n%#v", err, tc.Input)
|
||||||
}
|
}
|
||||||
|
|
|
@ -141,6 +141,8 @@ type Backend struct {
|
||||||
saltView logical.Storage
|
saltView logical.Storage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ audit.Backend = (*Backend)(nil)
|
||||||
|
|
||||||
func (b *Backend) Salt() (*salt.Salt, error) {
|
func (b *Backend) Salt() (*salt.Salt, error) {
|
||||||
b.saltMutex.RLock()
|
b.saltMutex.RLock()
|
||||||
if b.salt != nil {
|
if b.salt != nil {
|
||||||
|
@ -169,27 +171,22 @@ func (b *Backend) GetHash(data string) (string, error) {
|
||||||
return audit.HashString(salt, data), nil
|
return audit.HashString(salt, data), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Backend) LogRequest(
|
func (b *Backend) LogRequest(_ context.Context, in *audit.LogInput) error {
|
||||||
_ context.Context,
|
|
||||||
auth *logical.Auth,
|
|
||||||
req *logical.Request,
|
|
||||||
outerErr error) error {
|
|
||||||
|
|
||||||
b.fileLock.Lock()
|
b.fileLock.Lock()
|
||||||
defer b.fileLock.Unlock()
|
defer b.fileLock.Unlock()
|
||||||
|
|
||||||
switch b.path {
|
switch b.path {
|
||||||
case "stdout":
|
case "stdout":
|
||||||
return b.formatter.FormatRequest(os.Stdout, b.formatConfig, auth, req, outerErr)
|
return b.formatter.FormatRequest(os.Stdout, b.formatConfig, in)
|
||||||
case "discard":
|
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 {
|
if err := b.open(); err != nil {
|
||||||
return err
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,31 +198,26 @@ func (b *Backend) LogRequest(
|
||||||
return err
|
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(
|
func (b *Backend) LogResponse(_ context.Context, in *audit.LogInput) error {
|
||||||
_ context.Context,
|
|
||||||
auth *logical.Auth,
|
|
||||||
req *logical.Request,
|
|
||||||
resp *logical.Response,
|
|
||||||
err error) error {
|
|
||||||
|
|
||||||
b.fileLock.Lock()
|
b.fileLock.Lock()
|
||||||
defer b.fileLock.Unlock()
|
defer b.fileLock.Unlock()
|
||||||
|
|
||||||
switch b.path {
|
switch b.path {
|
||||||
case "stdout":
|
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":
|
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 {
|
if err := b.open(); err != nil {
|
||||||
return err
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -237,7 +229,7 @@ func (b *Backend) LogResponse(
|
||||||
return err
|
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
|
// The file lock must be held before calling this
|
||||||
|
|
|
@ -121,6 +121,8 @@ type Backend struct {
|
||||||
saltView logical.Storage
|
saltView logical.Storage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ audit.Backend = (*Backend)(nil)
|
||||||
|
|
||||||
func (b *Backend) GetHash(data string) (string, error) {
|
func (b *Backend) GetHash(data string) (string, error) {
|
||||||
salt, err := b.Salt()
|
salt, err := b.Salt()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -129,9 +131,9 @@ func (b *Backend) GetHash(data string) (string, error) {
|
||||||
return audit.HashString(salt, data), nil
|
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
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,10 +154,9 @@ func (b *Backend) LogRequest(ctx context.Context, auth *logical.Auth, req *logic
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Backend) LogResponse(ctx context.Context, auth *logical.Auth, req *logical.Request,
|
func (b *Backend) LogResponse(ctx context.Context, in *audit.LogInput) error {
|
||||||
resp *logical.Response, outerErr error) error {
|
|
||||||
var buf bytes.Buffer
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -108,6 +108,8 @@ type Backend struct {
|
||||||
saltView logical.Storage
|
saltView logical.Storage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ audit.Backend = (*Backend)(nil)
|
||||||
|
|
||||||
func (b *Backend) GetHash(data string) (string, error) {
|
func (b *Backend) GetHash(data string) (string, error) {
|
||||||
salt, err := b.Salt()
|
salt, err := b.Salt()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -116,9 +118,9 @@ func (b *Backend) GetHash(data string) (string, error) {
|
||||||
return audit.HashString(salt, data), nil
|
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
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,14 +129,14 @@ func (b *Backend) LogRequest(_ context.Context, auth *logical.Auth, req *logical
|
||||||
return err
|
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
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write out to syslog
|
// Write out to syslog
|
||||||
_, err = b.logger.Write(buf.Bytes())
|
_, err := b.logger.Write(buf.Bytes())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1072,3 +1072,90 @@ func TestSysTuneMount(t *testing.T) {
|
||||||
t.Fatalf("bad:\nExpected: %#v\nActual:%#v", expected, structs.Map(result))
|
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"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"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/go-uuid"
|
||||||
"github.com/hashicorp/vault/audit"
|
"github.com/hashicorp/vault/audit"
|
||||||
"github.com/hashicorp/vault/helper/jsonutil"
|
"github.com/hashicorp/vault/helper/jsonutil"
|
||||||
|
@ -461,191 +455,3 @@ func defaultAuditTable() *MountTable {
|
||||||
}
|
}
|
||||||
return table
|
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 {
|
type NoopAudit struct {
|
||||||
Config *audit.BackendConfig
|
Config *audit.BackendConfig
|
||||||
ReqErr error
|
ReqErr error
|
||||||
ReqAuth []*logical.Auth
|
ReqAuth []*logical.Auth
|
||||||
Req []*logical.Request
|
Req []*logical.Request
|
||||||
ReqHeaders []map[string][]string
|
ReqHeaders []map[string][]string
|
||||||
ReqErrs []error
|
ReqNonHMACKeys []string
|
||||||
|
ReqErrs []error
|
||||||
|
|
||||||
RespErr error
|
RespErr error
|
||||||
RespAuth []*logical.Auth
|
RespAuth []*logical.Auth
|
||||||
RespReq []*logical.Request
|
RespReq []*logical.Request
|
||||||
Resp []*logical.Response
|
Resp []*logical.Response
|
||||||
RespErrs []error
|
RespNonHMACKeys []string
|
||||||
|
RespReqNonHMACKeys []string
|
||||||
|
RespErrs []error
|
||||||
|
|
||||||
salt *salt.Salt
|
salt *salt.Salt
|
||||||
saltMutex sync.RWMutex
|
saltMutex sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *NoopAudit) LogRequest(ctx context.Context, a *logical.Auth, r *logical.Request, err error) error {
|
func (n *NoopAudit) LogRequest(ctx context.Context, in *audit.LogInput) error {
|
||||||
n.ReqAuth = append(n.ReqAuth, a)
|
n.ReqAuth = append(n.ReqAuth, in.Auth)
|
||||||
n.Req = append(n.Req, r)
|
n.Req = append(n.Req, in.Request)
|
||||||
n.ReqHeaders = append(n.ReqHeaders, r.Headers)
|
n.ReqHeaders = append(n.ReqHeaders, in.Request.Headers)
|
||||||
n.ReqErrs = append(n.ReqErrs, err)
|
n.ReqNonHMACKeys = in.NonHMACReqDataKeys
|
||||||
|
n.ReqErrs = append(n.ReqErrs, in.OuterErr)
|
||||||
return n.ReqErr
|
return n.ReqErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *NoopAudit) LogResponse(ctx context.Context, a *logical.Auth, r *logical.Request, re *logical.Response, err error) error {
|
func (n *NoopAudit) LogResponse(ctx context.Context, in *audit.LogInput) error {
|
||||||
n.RespAuth = append(n.RespAuth, a)
|
n.RespAuth = append(n.RespAuth, in.Auth)
|
||||||
n.RespReq = append(n.RespReq, r)
|
n.RespReq = append(n.RespReq, in.Request)
|
||||||
n.Resp = append(n.Resp, re)
|
n.Resp = append(n.Resp, in.Response)
|
||||||
n.RespErrs = append(n.RespErrs, err)
|
n.RespErrs = append(n.RespErrs, in.OuterErr)
|
||||||
|
|
||||||
|
if in.Response != nil {
|
||||||
|
n.RespNonHMACKeys = in.NonHMACRespDataKeys
|
||||||
|
n.RespReqNonHMACKeys = in.NonHMACReqDataKeys
|
||||||
|
}
|
||||||
|
|
||||||
return n.RespErr
|
return n.RespErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -442,7 +452,7 @@ func TestAuditBroker_LogRequest(t *testing.T) {
|
||||||
Path: "sys/mounts",
|
Path: "sys/mounts",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy so we can verify nothing canged
|
// Copy so we can verify nothing changed
|
||||||
authCopyRaw, err := copystructure.Copy(auth)
|
authCopyRaw, err := copystructure.Copy(auth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -468,7 +478,12 @@ func TestAuditBroker_LogRequest(t *testing.T) {
|
||||||
Headers: make(map[string]*auditedHeaderSettings),
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -487,13 +502,17 @@ func TestAuditBroker_LogRequest(t *testing.T) {
|
||||||
|
|
||||||
// Should still work with one failing backend
|
// Should still work with one failing backend
|
||||||
a1.ReqErr = fmt.Errorf("failed")
|
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)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Should FAIL work with both failing backends
|
// Should FAIL work with both failing backends
|
||||||
a2.ReqErr = fmt.Errorf("failed")
|
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)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -555,7 +574,13 @@ func TestAuditBroker_LogResponse(t *testing.T) {
|
||||||
Headers: make(map[string]*auditedHeaderSettings),
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -577,14 +602,20 @@ func TestAuditBroker_LogResponse(t *testing.T) {
|
||||||
|
|
||||||
// Should still work with one failing backend
|
// Should still work with one failing backend
|
||||||
a1.RespErr = fmt.Errorf("failed")
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Should FAIL work with both failing backends
|
// Should FAIL work with both failing backends
|
||||||
a2.RespErr = fmt.Errorf("failed")
|
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") {
|
if !strings.Contains(err.Error(), "no audit backend succeeded in logging the response") {
|
||||||
t.Fatalf("err: %v", err)
|
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-Test-Header", false)
|
||||||
headersConf.add(context.Background(), "X-Vault-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 {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -650,14 +686,19 @@ func TestAuditBroker_AuditHeaders(t *testing.T) {
|
||||||
|
|
||||||
// Should still work with one failing backend
|
// Should still work with one failing backend
|
||||||
a1.ReqErr = fmt.Errorf("failed")
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Should FAIL work with both failing backends
|
// Should FAIL work with both failing backends
|
||||||
a2.ReqErr = fmt.Errorf("failed")
|
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") {
|
if !errwrap.Contains(err, "no audit backend succeeded in logging the request") {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,9 +94,11 @@ func (c *Core) enableCredential(ctx context.Context, entry *MountEntry) error {
|
||||||
}
|
}
|
||||||
entry.Accessor = accessor
|
entry.Accessor = accessor
|
||||||
}
|
}
|
||||||
|
// Sync values to the cache
|
||||||
|
entry.SyncCache()
|
||||||
|
|
||||||
viewPath := credentialBarrierPrefix + entry.UUID + "/"
|
viewPath := credentialBarrierPrefix + entry.UUID + "/"
|
||||||
view := NewBarrierView(c.barrier, viewPath)
|
view := NewBarrierView(c.barrier, viewPath)
|
||||||
|
|
||||||
// Mark the view as read-only until the mounting is complete and
|
// 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
|
// ensure that it is reset after. This ensures that there will be no
|
||||||
// writes during the construction of the backend.
|
// writes during the construction of the backend.
|
||||||
|
@ -347,6 +349,9 @@ func (c *Core) loadCredentials(ctx context.Context) error {
|
||||||
entry.Accessor = accessor
|
entry.Accessor = accessor
|
||||||
needPersist = true
|
needPersist = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sync values to the cache
|
||||||
|
entry.SyncCache()
|
||||||
}
|
}
|
||||||
|
|
||||||
if !needPersist {
|
if !needPersist {
|
||||||
|
|
|
@ -1343,7 +1343,11 @@ func (c *Core) sealInitCommon(ctx context.Context, req *logical.Request) (retErr
|
||||||
EntityID: te.EntityID,
|
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)
|
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"))
|
retErr = multierror.Append(retErr, errors.New("failed to audit request, cannot continue"))
|
||||||
c.stateLock.RUnlock()
|
c.stateLock.RUnlock()
|
||||||
|
@ -1452,7 +1456,11 @@ func (c *Core) StepDown(req *logical.Request) (retErr error) {
|
||||||
EntityID: te.EntityID,
|
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)
|
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"))
|
retErr = multierror.Append(retErr, errors.New("failed to audit request, cannot continue"))
|
||||||
return retErr
|
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
|
// Ensure we get a client token
|
||||||
func TestCore_HandleLogin_AuditTrail(t *testing.T) {
|
func TestCore_HandleLogin_AuditTrail(t *testing.T) {
|
||||||
// Create a badass credential backend that always logs in as armon
|
// Create a badass credential backend that always logs in as armon
|
||||||
|
|
|
@ -257,6 +257,14 @@ func NewSystemBackend(core *Core) *SystemBackend {
|
||||||
Type: framework.TypeString,
|
Type: framework.TypeString,
|
||||||
Description: strings.TrimSpace(sysHelp["auth_desc"][0]),
|
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{
|
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||||
logical.ReadOperation: b.handleAuthTuneRead,
|
logical.ReadOperation: b.handleAuthTuneRead,
|
||||||
|
@ -286,6 +294,14 @@ func NewSystemBackend(core *Core) *SystemBackend {
|
||||||
Type: framework.TypeString,
|
Type: framework.TypeString,
|
||||||
Description: strings.TrimSpace(sysHelp["auth_desc"][0]),
|
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{
|
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
|
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
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3099,6 +3175,14 @@ in the plugin catalog.`,
|
||||||
`The max lease TTL for this mount.`,
|
`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": {
|
"remount": {
|
||||||
"Move the mount point of an already-mounted backend.",
|
"Move the mount point of an already-mounted backend.",
|
||||||
`
|
`
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/hashicorp/go-uuid"
|
"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
|
Local bool `json:"local"` // Local mounts are not replicated or affected by replication
|
||||||
SealWrap bool `json:"seal_wrap"` // Whether to wrap CSPs
|
SealWrap bool `json:"seal_wrap"` // Whether to wrap CSPs
|
||||||
Tainted bool `json:"tainted,omitempty"` // Set as a Write-Ahead flag for unmount/remount
|
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
|
// MountConfig is used to hold settable options
|
||||||
type MountConfig struct {
|
type MountConfig struct {
|
||||||
DefaultLeaseTTL time.Duration `json:"default_lease_ttl" structs:"default_lease_ttl" mapstructure:"default_lease_ttl"` // Override for global default
|
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
|
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
|
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"`
|
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
|
// APIMountConfig is an embedded struct of api.MountConfigInput
|
||||||
type APIMountConfig struct {
|
type APIMountConfig struct {
|
||||||
DefaultLeaseTTL string `json:"default_lease_ttl" structs:"default_lease_ttl" mapstructure:"default_lease_ttl"`
|
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"`
|
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"`
|
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"`
|
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
|
// Clone returns a deep copy of the mount entry
|
||||||
|
@ -198,6 +206,21 @@ func (e *MountEntry) Clone() (*MountEntry, error) {
|
||||||
return cp.(*MountEntry), nil
|
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.
|
// Mount is used to mount a new backend to the mount table.
|
||||||
func (c *Core) mount(ctx context.Context, entry *MountEntry) error {
|
func (c *Core) mount(ctx context.Context, entry *MountEntry) error {
|
||||||
// Ensure we end the path in a slash
|
// 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
|
entry.Accessor = accessor
|
||||||
}
|
}
|
||||||
|
// Sync values to the cache
|
||||||
|
entry.SyncCache()
|
||||||
|
|
||||||
viewPath := backendBarrierPrefix + entry.UUID + "/"
|
viewPath := backendBarrierPrefix + entry.UUID + "/"
|
||||||
view := NewBarrierView(c.barrier, viewPath)
|
view := NewBarrierView(c.barrier, viewPath)
|
||||||
|
|
||||||
|
@ -636,6 +662,9 @@ func (c *Core) loadMounts(ctx context.Context) error {
|
||||||
entry.Accessor = accessor
|
entry.Accessor = accessor
|
||||||
needPersist = true
|
needPersist = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sync values to the cache
|
||||||
|
entry.SyncCache()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Done if we have restored the mount table and we don't need
|
// 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/armon/go-metrics"
|
||||||
"github.com/hashicorp/go-multierror"
|
"github.com/hashicorp/go-multierror"
|
||||||
|
"github.com/hashicorp/vault/audit"
|
||||||
"github.com/hashicorp/vault/helper/consts"
|
"github.com/hashicorp/vault/helper/consts"
|
||||||
"github.com/hashicorp/vault/helper/identity"
|
"github.com/hashicorp/vault/helper/identity"
|
||||||
"github.com/hashicorp/vault/helper/jsonutil"
|
"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)
|
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
|
// 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)
|
c.logger.Error("core: failed to audit response", "request_path", req.Path, "error", auditErr)
|
||||||
return nil, ErrInternalError
|
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) {
|
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())
|
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
|
// Validate the token
|
||||||
auth, te, ctErr := c.checkToken(ctx, req, false)
|
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
|
// 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
|
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)
|
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
|
req.DisplayName = auth.DisplayName
|
||||||
|
|
||||||
// Create an audit trail of the request
|
// 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)
|
c.logger.Error("core: failed to audit request", "path", req.Path, "error", err)
|
||||||
retErr = multierror.Append(retErr, ErrInternalError)
|
retErr = multierror.Append(retErr, ErrInternalError)
|
||||||
return nil, auth, retErr
|
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, auth is not available on login requests
|
||||||
// Create an audit trail of the request. Attach auth if it was returned,
|
// Create an audit trail of the request. Attach auth if it was returned,
|
||||||
// e.g. if a token was provided.
|
// 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)
|
c.logger.Error("core: failed to audit request", "path", req.Path, "error", err)
|
||||||
return nil, nil, ErrInternalError
|
return nil, nil, ErrInternalError
|
||||||
}
|
}
|
||||||
|
|
|
@ -611,11 +611,11 @@ func (n *noopAudit) GetHash(data string) (string, error) {
|
||||||
return salt.GetIdentifiedHMAC(data), nil
|
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
|
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
|
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
|
- `description` `(string: "")` – Specifies the description of the mount. This
|
||||||
overrides the current stored value, if any.
|
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
|
### Sample Payload
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
|
|
@ -202,6 +202,14 @@ This endpoint tunes configuration parameters for a given mount point.
|
||||||
- `description` `(string: "")` – Specifies the description of the mount. This
|
- `description` `(string: "")` – Specifies the description of the mount. This
|
||||||
overrides the current stored value, if any.
|
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
|
### Sample Payload
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
|
Loading…
Reference in New Issue