1c7157e632
with a new endpoint '/sys/audit-hash', which returns the given input string hashed with the given audit backend's hash function and salt (currently, always HMAC-SHA256 and a backend-specific salt). In the process of adding the HTTP handler, this also removes the custom HTTP handlers for the other audit endpoints, which were simply forwarding to the logical system backend. This means that the various audit functions will now redirect correctly from a standby to master. (Tests all pass.) Fixes #784
320 lines
6.9 KiB
Go
320 lines
6.9 KiB
Go
package vault
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
"errors"
|
|
|
|
"github.com/hashicorp/vault/audit"
|
|
"github.com/hashicorp/vault/logical"
|
|
)
|
|
|
|
type NoopAudit struct {
|
|
Config *audit.BackendConfig
|
|
ReqErr error
|
|
ReqAuth []*logical.Auth
|
|
Req []*logical.Request
|
|
ReqErrs []error
|
|
|
|
RespErr error
|
|
RespAuth []*logical.Auth
|
|
RespReq []*logical.Request
|
|
Resp []*logical.Response
|
|
RespErrs []error
|
|
}
|
|
|
|
func (n *NoopAudit) LogRequest(a *logical.Auth, r *logical.Request, err error) error {
|
|
n.ReqAuth = append(n.ReqAuth, a)
|
|
n.Req = append(n.Req, r)
|
|
n.ReqErrs = append(n.ReqErrs, err)
|
|
return n.ReqErr
|
|
}
|
|
|
|
func (n *NoopAudit) LogResponse(a *logical.Auth, r *logical.Request, re *logical.Response, err error) error {
|
|
n.RespAuth = append(n.RespAuth, a)
|
|
n.RespReq = append(n.RespReq, r)
|
|
n.Resp = append(n.Resp, re)
|
|
n.RespErrs = append(n.RespErrs, err)
|
|
return n.RespErr
|
|
}
|
|
|
|
func (n *NoopAudit) GetHash(data string) string {
|
|
return n.Config.Salt.GetIdentifiedHMAC(data)
|
|
}
|
|
|
|
func TestCore_EnableAudit(t *testing.T) {
|
|
c, key, _ := TestCoreUnsealed(t)
|
|
c.auditBackends["noop"] = func(config *audit.BackendConfig) (audit.Backend, error) {
|
|
return &NoopAudit{
|
|
Config: config,
|
|
}, nil
|
|
}
|
|
|
|
me := &MountEntry{
|
|
Path: "foo",
|
|
Type: "noop",
|
|
}
|
|
err := c.enableAudit(me)
|
|
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(config *audit.BackendConfig) (audit.Backend, error) {
|
|
return &NoopAudit{
|
|
Config: config,
|
|
}, nil
|
|
}
|
|
c2, err := NewCore(conf)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
unseal, err := c2.Unseal(key)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if !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_DisableAudit(t *testing.T) {
|
|
c, key, _ := TestCoreUnsealed(t)
|
|
c.auditBackends["noop"] = func(config *audit.BackendConfig) (audit.Backend, error) {
|
|
return &NoopAudit{
|
|
Config: config,
|
|
}, nil
|
|
}
|
|
|
|
err := c.disableAudit("foo")
|
|
if err.Error() != "no matching backend" {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
me := &MountEntry{
|
|
Path: "foo",
|
|
Type: "noop",
|
|
}
|
|
err = c.enableAudit(me)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
err = c.disableAudit("foo")
|
|
if err != nil {
|
|
t.Fatalf("err: %v", 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)
|
|
}
|
|
unseal, err := c2.Unseal(key)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if !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 TestCore_DefaultAuditTable(t *testing.T) {
|
|
c, key, _ := 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)
|
|
}
|
|
unseal, err := c2.Unseal(key)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if !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)
|
|
}
|
|
}
|
|
|
|
func TestAuditBroker_LogRequest(t *testing.T) {
|
|
l := log.New(os.Stderr, "", log.LstdFlags)
|
|
b := NewAuditBroker(l)
|
|
a1 := &NoopAudit{}
|
|
a2 := &NoopAudit{}
|
|
b.Register("foo", a1, nil)
|
|
b.Register("bar", a2, nil)
|
|
|
|
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",
|
|
}
|
|
reqErrs := errors.New("errs")
|
|
|
|
err := b.LogRequest(auth, req, reqErrs)
|
|
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", a.Req[0])
|
|
}
|
|
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")
|
|
if err := b.LogRequest(auth, req, nil); err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Should FAIL work with both failing backends
|
|
a2.ReqErr = fmt.Errorf("failed")
|
|
if err := b.LogRequest(auth, req, nil); err.Error() != "no audit backend succeeded in logging the request" {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestAuditBroker_LogResponse(t *testing.T) {
|
|
l := log.New(os.Stderr, "", log.LstdFlags)
|
|
b := NewAuditBroker(l)
|
|
a1 := &NoopAudit{}
|
|
a2 := &NoopAudit{}
|
|
b.Register("foo", a1, nil)
|
|
b.Register("bar", a2, nil)
|
|
|
|
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",
|
|
}
|
|
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")
|
|
|
|
err := b.LogResponse(auth, req, resp, respErr)
|
|
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("Bad: %#v", a.RespErrs[0])
|
|
}
|
|
}
|
|
|
|
// Should still work with one failing backend
|
|
a1.RespErr = fmt.Errorf("failed")
|
|
err = b.LogResponse(auth, req, resp, respErr)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
// Should FAIL work with both failing backends
|
|
a2.RespErr = fmt.Errorf("failed")
|
|
err = b.LogResponse(auth, req, resp, respErr)
|
|
if err.Error() != "no audit backend succeeded in logging the response" {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
}
|