ad28263b69
Move audit.LogInput to sdk/logical. Allow the Data values in audited logical.Request and Response to implement OptMarshaler, in which case we delegate hashing/serializing responsibility to them. Add new ClientCertificateSerialNumber audit request field. SystemView can now be cast to ExtendedSystemView to expose the Auditor interface, which allows submitting requests and responses to the audit broker.
711 lines
17 KiB
Go
711 lines
17 KiB
Go
package vault
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"errors"
|
|
|
|
"github.com/hashicorp/errwrap"
|
|
log "github.com/hashicorp/go-hclog"
|
|
uuid "github.com/hashicorp/go-uuid"
|
|
"github.com/hashicorp/vault/audit"
|
|
"github.com/hashicorp/vault/helper/namespace"
|
|
"github.com/hashicorp/vault/sdk/helper/jsonutil"
|
|
"github.com/hashicorp/vault/sdk/helper/logging"
|
|
"github.com/hashicorp/vault/sdk/helper/salt"
|
|
"github.com/hashicorp/vault/sdk/logical"
|
|
"github.com/mitchellh/copystructure"
|
|
)
|
|
|
|
type NoopAudit struct {
|
|
Config *audit.BackendConfig
|
|
ReqErr error
|
|
ReqAuth []*logical.Auth
|
|
Req []*logical.Request
|
|
ReqHeaders []map[string][]string
|
|
ReqNonHMACKeys []string
|
|
ReqErrs []error
|
|
|
|
RespErr error
|
|
RespAuth []*logical.Auth
|
|
RespReq []*logical.Request
|
|
Resp []*logical.Response
|
|
RespNonHMACKeys []string
|
|
RespReqNonHMACKeys []string
|
|
RespErrs []error
|
|
|
|
salt *salt.Salt
|
|
saltMutex sync.RWMutex
|
|
}
|
|
|
|
func (n *NoopAudit) LogRequest(ctx context.Context, in *logical.LogInput) error {
|
|
n.ReqAuth = append(n.ReqAuth, in.Auth)
|
|
n.Req = append(n.Req, in.Request)
|
|
n.ReqHeaders = append(n.ReqHeaders, in.Request.Headers)
|
|
n.ReqNonHMACKeys = in.NonHMACReqDataKeys
|
|
n.ReqErrs = append(n.ReqErrs, in.OuterErr)
|
|
return n.ReqErr
|
|
}
|
|
|
|
func (n *NoopAudit) LogResponse(ctx context.Context, in *logical.LogInput) error {
|
|
n.RespAuth = append(n.RespAuth, in.Auth)
|
|
n.RespReq = append(n.RespReq, in.Request)
|
|
n.Resp = append(n.Resp, in.Response)
|
|
n.RespErrs = append(n.RespErrs, in.OuterErr)
|
|
|
|
if in.Response != nil {
|
|
n.RespNonHMACKeys = in.NonHMACRespDataKeys
|
|
n.RespReqNonHMACKeys = in.NonHMACReqDataKeys
|
|
}
|
|
|
|
return n.RespErr
|
|
}
|
|
|
|
func (n *NoopAudit) Salt(ctx context.Context) (*salt.Salt, error) {
|
|
n.saltMutex.RLock()
|
|
if n.salt != nil {
|
|
defer n.saltMutex.RUnlock()
|
|
return n.salt, nil
|
|
}
|
|
n.saltMutex.RUnlock()
|
|
n.saltMutex.Lock()
|
|
defer n.saltMutex.Unlock()
|
|
if n.salt != nil {
|
|
return n.salt, nil
|
|
}
|
|
salt, err := salt.NewSalt(ctx, n.Config.SaltView, n.Config.SaltConfig)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
n.salt = salt
|
|
return salt, nil
|
|
}
|
|
|
|
func (n *NoopAudit) GetHash(ctx context.Context, data string) (string, error) {
|
|
salt, err := n.Salt(ctx)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return salt.GetIdentifiedHMAC(data), nil
|
|
}
|
|
|
|
func (n *NoopAudit) Reload(ctx context.Context) error {
|
|
return nil
|
|
}
|
|
|
|
func (n *NoopAudit) Invalidate(ctx context.Context) {
|
|
n.saltMutex.Lock()
|
|
defer n.saltMutex.Unlock()
|
|
n.salt = nil
|
|
}
|
|
|
|
func TestAudit_ReadOnlyViewDuringMount(t *testing.T) {
|
|
c, _, _ := TestCoreUnsealed(t)
|
|
c.auditBackends["noop"] = func(ctx context.Context, config *audit.BackendConfig) (audit.Backend, error) {
|
|
err := config.SaltView.Put(ctx, &logical.StorageEntry{
|
|
Key: "bar",
|
|
Value: []byte("baz"),
|
|
})
|
|
if err == nil || !strings.Contains(err.Error(), logical.ErrSetupReadOnly.Error()) {
|
|
t.Fatalf("expected a read-only error")
|
|
}
|
|
return &NoopAudit{}, nil
|
|
}
|
|
|
|
me := &MountEntry{
|
|
Table: auditTableType,
|
|
Path: "foo",
|
|
Type: "noop",
|
|
}
|
|
err := c.enableAudit(namespace.RootContext(nil), me, true)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestCore_EnableAudit(t *testing.T) {
|
|
c, keys, _ := TestCoreUnsealed(t)
|
|
c.auditBackends["noop"] = func(ctx context.Context, config *audit.BackendConfig) (audit.Backend, error) {
|
|
return &NoopAudit{
|
|
Config: config,
|
|
}, nil
|
|
}
|
|
|
|
me := &MountEntry{
|
|
Table: auditTableType,
|
|
Path: "foo",
|
|
Type: "noop",
|
|
}
|
|
err := c.enableAudit(namespace.RootContext(nil), me, true)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
if !c.auditBroker.IsRegistered("foo/") {
|
|
t.Fatalf("missing audit backend")
|
|
}
|
|
|
|
conf := &CoreConfig{
|
|
Physical: c.physical,
|
|
AuditBackends: make(map[string]audit.Factory),
|
|
DisableMlock: true,
|
|
}
|
|
conf.AuditBackends["noop"] = func(ctx context.Context, config *audit.BackendConfig) (audit.Backend, error) {
|
|
return &NoopAudit{
|
|
Config: config,
|
|
}, nil
|
|
}
|
|
c2, err := NewCore(conf)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
for i, key := range keys {
|
|
unseal, err := TestCoreUnseal(c2, key)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if i+1 == len(keys) && !unseal {
|
|
t.Fatalf("should be unsealed")
|
|
}
|
|
}
|
|
|
|
// Verify matching audit tables
|
|
if !reflect.DeepEqual(c.audit, c2.audit) {
|
|
t.Fatalf("mismatch: %v %v", c.audit, c2.audit)
|
|
}
|
|
|
|
// Check for registration
|
|
if !c2.auditBroker.IsRegistered("foo/") {
|
|
t.Fatalf("missing audit backend")
|
|
}
|
|
}
|
|
|
|
func TestCore_EnableAudit_MixedFailures(t *testing.T) {
|
|
c, _, _ := TestCoreUnsealed(t)
|
|
c.auditBackends["noop"] = func(ctx context.Context, config *audit.BackendConfig) (audit.Backend, error) {
|
|
return &NoopAudit{
|
|
Config: config,
|
|
}, nil
|
|
}
|
|
|
|
c.auditBackends["fail"] = func(ctx context.Context, config *audit.BackendConfig) (audit.Backend, error) {
|
|
return nil, fmt.Errorf("failing enabling")
|
|
}
|
|
|
|
c.audit = &MountTable{
|
|
Type: auditTableType,
|
|
Entries: []*MountEntry{
|
|
&MountEntry{
|
|
Table: auditTableType,
|
|
Path: "noop/",
|
|
Type: "noop",
|
|
UUID: "abcd",
|
|
},
|
|
&MountEntry{
|
|
Table: auditTableType,
|
|
Path: "noop2/",
|
|
Type: "noop",
|
|
UUID: "bcde",
|
|
},
|
|
},
|
|
}
|
|
|
|
// Both should set up successfully
|
|
err := c.setupAudits(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// We expect this to work because the other entry is still valid
|
|
c.audit.Entries[0].Type = "fail"
|
|
err = c.setupAudits(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// No audit backend set up successfully, so expect error
|
|
c.audit.Entries[1].Type = "fail"
|
|
err = c.setupAudits(context.Background())
|
|
if err == nil {
|
|
t.Fatal("expected error")
|
|
}
|
|
}
|
|
|
|
// Test that the local table actually gets populated as expected with local
|
|
// entries, and that upon reading the entries from both are recombined
|
|
// correctly
|
|
func TestCore_EnableAudit_Local(t *testing.T) {
|
|
c, _, _ := TestCoreUnsealed(t)
|
|
c.auditBackends["noop"] = func(ctx context.Context, config *audit.BackendConfig) (audit.Backend, error) {
|
|
return &NoopAudit{
|
|
Config: config,
|
|
}, nil
|
|
}
|
|
|
|
c.auditBackends["fail"] = func(ctx context.Context, config *audit.BackendConfig) (audit.Backend, error) {
|
|
return nil, fmt.Errorf("failing enabling")
|
|
}
|
|
|
|
c.audit = &MountTable{
|
|
Type: auditTableType,
|
|
Entries: []*MountEntry{
|
|
&MountEntry{
|
|
Table: auditTableType,
|
|
Path: "noop/",
|
|
Type: "noop",
|
|
UUID: "abcd",
|
|
Accessor: "noop-abcd",
|
|
NamespaceID: namespace.RootNamespaceID,
|
|
namespace: namespace.RootNamespace,
|
|
},
|
|
&MountEntry{
|
|
Table: auditTableType,
|
|
Path: "noop2/",
|
|
Type: "noop",
|
|
UUID: "bcde",
|
|
Accessor: "noop-bcde",
|
|
NamespaceID: namespace.RootNamespaceID,
|
|
namespace: namespace.RootNamespace,
|
|
},
|
|
},
|
|
}
|
|
|
|
// Both should set up successfully
|
|
err := c.setupAudits(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
rawLocal, err := c.barrier.Get(context.Background(), coreLocalAuditConfigPath)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if rawLocal == nil {
|
|
t.Fatal("expected non-nil local audit")
|
|
}
|
|
localAuditTable := &MountTable{}
|
|
if err := jsonutil.DecodeJSON(rawLocal.Value, localAuditTable); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(localAuditTable.Entries) > 0 {
|
|
t.Fatalf("expected no entries in local audit table, got %#v", localAuditTable)
|
|
}
|
|
|
|
c.audit.Entries[1].Local = true
|
|
if err := c.persistAudit(context.Background(), c.audit, false); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
rawLocal, err = c.barrier.Get(context.Background(), coreLocalAuditConfigPath)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if rawLocal == nil {
|
|
t.Fatal("expected non-nil local audit")
|
|
}
|
|
localAuditTable = &MountTable{}
|
|
if err := jsonutil.DecodeJSON(rawLocal.Value, localAuditTable); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(localAuditTable.Entries) != 1 {
|
|
t.Fatalf("expected one entry in local audit table, got %#v", localAuditTable)
|
|
}
|
|
|
|
oldAudit := c.audit
|
|
if err := c.loadAudits(context.Background()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if !reflect.DeepEqual(oldAudit, c.audit) {
|
|
t.Fatalf("expected\n%#v\ngot\n%#v\n", oldAudit, c.audit)
|
|
}
|
|
|
|
if len(c.audit.Entries) != 2 {
|
|
t.Fatalf("expected two audit entries, got %#v", localAuditTable)
|
|
}
|
|
}
|
|
|
|
func TestCore_DisableAudit(t *testing.T) {
|
|
c, keys, _ := TestCoreUnsealed(t)
|
|
c.auditBackends["noop"] = func(ctx context.Context, config *audit.BackendConfig) (audit.Backend, error) {
|
|
return &NoopAudit{
|
|
Config: config,
|
|
}, nil
|
|
}
|
|
|
|
existed, err := c.disableAudit(namespace.RootContext(nil), "foo", true)
|
|
if existed && err != nil {
|
|
t.Fatalf("existed: %v; err: %v", existed, err)
|
|
}
|
|
|
|
me := &MountEntry{
|
|
Table: auditTableType,
|
|
Path: "foo",
|
|
Type: "noop",
|
|
}
|
|
err = c.enableAudit(namespace.RootContext(nil), me, true)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
existed, err = c.disableAudit(namespace.RootContext(nil), "foo", true)
|
|
if !existed || err != nil {
|
|
t.Fatalf("existed: %v; err: %v", existed, err)
|
|
}
|
|
|
|
// Check for registration
|
|
if c.auditBroker.IsRegistered("foo") {
|
|
t.Fatalf("audit backend present")
|
|
}
|
|
|
|
conf := &CoreConfig{
|
|
Physical: c.physical,
|
|
DisableMlock: true,
|
|
}
|
|
c2, err := NewCore(conf)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
for i, key := range keys {
|
|
unseal, err := TestCoreUnseal(c2, key)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if i+1 == len(keys) && !unseal {
|
|
t.Fatalf("should be unsealed")
|
|
}
|
|
}
|
|
|
|
// Verify matching mount tables
|
|
if !reflect.DeepEqual(c.audit, c2.audit) {
|
|
t.Fatalf("mismatch:\n%#v\n%#v", c.audit, c2.audit)
|
|
}
|
|
}
|
|
|
|
func TestCore_DefaultAuditTable(t *testing.T) {
|
|
c, keys, _ := TestCoreUnsealed(t)
|
|
verifyDefaultAuditTable(t, c.audit)
|
|
|
|
// Verify we have an audit broker
|
|
if c.auditBroker == nil {
|
|
t.Fatalf("missing audit broker")
|
|
}
|
|
|
|
// Start a second core with same physical
|
|
conf := &CoreConfig{
|
|
Physical: c.physical,
|
|
DisableMlock: true,
|
|
}
|
|
c2, err := NewCore(conf)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
for i, key := range keys {
|
|
unseal, err := TestCoreUnseal(c2, key)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if i+1 == len(keys) && !unseal {
|
|
t.Fatalf("should be unsealed")
|
|
}
|
|
}
|
|
|
|
// Verify matching mount tables
|
|
if !reflect.DeepEqual(c.audit, c2.audit) {
|
|
t.Fatalf("mismatch: %v %v", c.audit, c2.audit)
|
|
}
|
|
}
|
|
|
|
func TestDefaultAuditTable(t *testing.T) {
|
|
table := defaultAuditTable()
|
|
verifyDefaultAuditTable(t, table)
|
|
}
|
|
|
|
func verifyDefaultAuditTable(t *testing.T, table *MountTable) {
|
|
if len(table.Entries) != 0 {
|
|
t.Fatalf("bad: %v", table.Entries)
|
|
}
|
|
if table.Type != auditTableType {
|
|
t.Fatalf("bad: %v", *table)
|
|
}
|
|
}
|
|
|
|
func TestAuditBroker_LogRequest(t *testing.T) {
|
|
l := logging.NewVaultLogger(log.Trace)
|
|
b := NewAuditBroker(l)
|
|
a1 := &NoopAudit{}
|
|
a2 := &NoopAudit{}
|
|
b.Register("foo", a1, nil, false)
|
|
b.Register("bar", a2, nil, false)
|
|
|
|
auth := &logical.Auth{
|
|
ClientToken: "foo",
|
|
Policies: []string{"dev", "ops"},
|
|
Metadata: map[string]string{
|
|
"user": "armon",
|
|
"source": "github",
|
|
},
|
|
}
|
|
req := &logical.Request{
|
|
Operation: logical.ReadOperation,
|
|
Path: "sys/mounts",
|
|
}
|
|
|
|
// Copy so we can verify nothing changed
|
|
authCopyRaw, err := copystructure.Copy(auth)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
authCopy := authCopyRaw.(*logical.Auth)
|
|
|
|
reqCopyRaw, err := copystructure.Copy(req)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
reqCopy := reqCopyRaw.(*logical.Request)
|
|
|
|
// Create an identifier for the request to verify against
|
|
req.ID, err = uuid.GenerateUUID()
|
|
if err != nil {
|
|
t.Fatalf("failed to generate identifier for the request: path%s err: %v", req.Path, err)
|
|
}
|
|
reqCopy.ID = req.ID
|
|
|
|
reqErrs := errors.New("errs")
|
|
|
|
headersConf := &AuditedHeadersConfig{
|
|
Headers: make(map[string]*auditedHeaderSettings),
|
|
}
|
|
|
|
logInput := &logical.LogInput{
|
|
Auth: authCopy,
|
|
Request: reqCopy,
|
|
OuterErr: reqErrs,
|
|
}
|
|
err = b.LogRequest(context.Background(), logInput, headersConf)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
for _, a := range []*NoopAudit{a1, a2} {
|
|
if !reflect.DeepEqual(a.ReqAuth[0], auth) {
|
|
t.Fatalf("Bad: %#v", a.ReqAuth[0])
|
|
}
|
|
if !reflect.DeepEqual(a.Req[0], req) {
|
|
t.Fatalf("Bad: %#v\n wanted %#v", a.Req[0], req)
|
|
}
|
|
if !reflect.DeepEqual(a.ReqErrs[0], reqErrs) {
|
|
t.Fatalf("Bad: %#v", a.ReqErrs[0])
|
|
}
|
|
}
|
|
|
|
// Should still work with one failing backend
|
|
a1.ReqErr = fmt.Errorf("failed")
|
|
logInput = &logical.LogInput{
|
|
Auth: auth,
|
|
Request: req,
|
|
}
|
|
if err := b.LogRequest(context.Background(), logInput, headersConf); err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Should FAIL work with both failing backends
|
|
a2.ReqErr = fmt.Errorf("failed")
|
|
if err := b.LogRequest(context.Background(), logInput, headersConf); !errwrap.Contains(err, "no audit backend succeeded in logging the request") {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestAuditBroker_LogResponse(t *testing.T) {
|
|
l := logging.NewVaultLogger(log.Trace)
|
|
b := NewAuditBroker(l)
|
|
a1 := &NoopAudit{}
|
|
a2 := &NoopAudit{}
|
|
b.Register("foo", a1, nil, false)
|
|
b.Register("bar", a2, nil, false)
|
|
|
|
auth := &logical.Auth{
|
|
NumUses: 10,
|
|
ClientToken: "foo",
|
|
Policies: []string{"dev", "ops"},
|
|
Metadata: map[string]string{
|
|
"user": "armon",
|
|
"source": "github",
|
|
},
|
|
}
|
|
req := &logical.Request{
|
|
Operation: logical.ReadOperation,
|
|
Path: "sys/mounts",
|
|
}
|
|
resp := &logical.Response{
|
|
Secret: &logical.Secret{
|
|
LeaseOptions: logical.LeaseOptions{
|
|
TTL: 1 * time.Hour,
|
|
},
|
|
},
|
|
Data: map[string]interface{}{
|
|
"user": "root",
|
|
"password": "password",
|
|
},
|
|
}
|
|
respErr := fmt.Errorf("permission denied")
|
|
|
|
// Copy so we can verify nothing changed
|
|
authCopyRaw, err := copystructure.Copy(auth)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
authCopy := authCopyRaw.(*logical.Auth)
|
|
|
|
reqCopyRaw, err := copystructure.Copy(req)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
reqCopy := reqCopyRaw.(*logical.Request)
|
|
|
|
respCopyRaw, err := copystructure.Copy(resp)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
respCopy := respCopyRaw.(*logical.Response)
|
|
|
|
headersConf := &AuditedHeadersConfig{
|
|
Headers: make(map[string]*auditedHeaderSettings),
|
|
}
|
|
|
|
logInput := &logical.LogInput{
|
|
Auth: authCopy,
|
|
Request: reqCopy,
|
|
Response: respCopy,
|
|
OuterErr: respErr,
|
|
}
|
|
err = b.LogResponse(context.Background(), logInput, headersConf)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
for _, a := range []*NoopAudit{a1, a2} {
|
|
if !reflect.DeepEqual(a.RespAuth[0], auth) {
|
|
t.Fatalf("Bad: %#v", a.ReqAuth[0])
|
|
}
|
|
if !reflect.DeepEqual(a.RespReq[0], req) {
|
|
t.Fatalf("Bad: %#v", a.Req[0])
|
|
}
|
|
if !reflect.DeepEqual(a.Resp[0], resp) {
|
|
t.Fatalf("Bad: %#v", a.Resp[0])
|
|
}
|
|
if !reflect.DeepEqual(a.RespErrs[0], respErr) {
|
|
t.Fatalf("Expected\n%v\nGot\n%#v", respErr, a.RespErrs[0])
|
|
}
|
|
}
|
|
|
|
// Should still work with one failing backend
|
|
a1.RespErr = fmt.Errorf("failed")
|
|
logInput = &logical.LogInput{
|
|
Auth: auth,
|
|
Request: req,
|
|
Response: resp,
|
|
OuterErr: respErr,
|
|
}
|
|
err = b.LogResponse(context.Background(), logInput, headersConf)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Should FAIL work with both failing backends
|
|
a2.RespErr = fmt.Errorf("failed")
|
|
err = b.LogResponse(context.Background(), logInput, headersConf)
|
|
if !strings.Contains(err.Error(), "no audit backend succeeded in logging the response") {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestAuditBroker_AuditHeaders(t *testing.T) {
|
|
logger := logging.NewVaultLogger(log.Trace)
|
|
b := NewAuditBroker(logger)
|
|
_, barrier, _ := mockBarrier(t)
|
|
view := NewBarrierView(barrier, "headers/")
|
|
a1 := &NoopAudit{}
|
|
a2 := &NoopAudit{}
|
|
b.Register("foo", a1, nil, false)
|
|
b.Register("bar", a2, nil, false)
|
|
|
|
auth := &logical.Auth{
|
|
ClientToken: "foo",
|
|
Policies: []string{"dev", "ops"},
|
|
Metadata: map[string]string{
|
|
"user": "armon",
|
|
"source": "github",
|
|
},
|
|
}
|
|
req := &logical.Request{
|
|
Operation: logical.ReadOperation,
|
|
Path: "sys/mounts",
|
|
Headers: map[string][]string{
|
|
"X-Test-Header": []string{"foo"},
|
|
"X-Vault-Header": []string{"bar"},
|
|
"Content-Type": []string{"baz"},
|
|
},
|
|
}
|
|
respErr := fmt.Errorf("permission denied")
|
|
|
|
// Copy so we can verify nothing changed
|
|
reqCopyRaw, err := copystructure.Copy(req)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
reqCopy := reqCopyRaw.(*logical.Request)
|
|
|
|
headersConf := &AuditedHeadersConfig{
|
|
view: view,
|
|
}
|
|
headersConf.add(context.Background(), "X-Test-Header", false)
|
|
headersConf.add(context.Background(), "X-Vault-Header", false)
|
|
|
|
logInput := &logical.LogInput{
|
|
Auth: auth,
|
|
Request: reqCopy,
|
|
OuterErr: respErr,
|
|
}
|
|
err = b.LogRequest(context.Background(), logInput, headersConf)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
expected := map[string][]string{
|
|
"x-test-header": []string{"foo"},
|
|
"x-vault-header": []string{"bar"},
|
|
}
|
|
|
|
for _, a := range []*NoopAudit{a1, a2} {
|
|
if !reflect.DeepEqual(a.ReqHeaders[0], expected) {
|
|
t.Fatalf("Bad audited headers: %#v", a.Req[0].Headers)
|
|
}
|
|
}
|
|
|
|
// Should still work with one failing backend
|
|
a1.ReqErr = fmt.Errorf("failed")
|
|
logInput = &logical.LogInput{
|
|
Auth: auth,
|
|
Request: req,
|
|
OuterErr: respErr,
|
|
}
|
|
err = b.LogRequest(context.Background(), logInput, headersConf)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Should FAIL work with both failing backends
|
|
a2.ReqErr = fmt.Errorf("failed")
|
|
err = b.LogRequest(context.Background(), logInput, headersConf)
|
|
if !errwrap.Contains(err, "no audit backend succeeded in logging the request") {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
}
|