6d00f0c483
Adds HUP support for audit log files to close and reopen. This makes it much easier to deal with normal log rotation methods. As part of testing this I noticed that HUP and other items that come out of command/server.go are going to stderr, which is where our normal log lines go. This isn't so much problematic with our normal output but as we officially move to supporting other formats this can cause interleaving issues, so I moved those to stdout instead.
339 lines
7.5 KiB
Go
339 lines
7.5 KiB
Go
package vault
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
"errors"
|
|
|
|
"github.com/hashicorp/errwrap"
|
|
"github.com/hashicorp/go-uuid"
|
|
"github.com/hashicorp/vault/audit"
|
|
"github.com/hashicorp/vault/helper/logformat"
|
|
"github.com/hashicorp/vault/logical"
|
|
log "github.com/mgutz/logxi/v1"
|
|
)
|
|
|
|
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 (n *NoopAudit) Reload() error {
|
|
return nil
|
|
}
|
|
|
|
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{
|
|
Table: auditTableType,
|
|
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 := TestCoreUnseal(c2, 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
|
|
}
|
|
|
|
existed, err := c.disableAudit("foo")
|
|
if existed && err != nil {
|
|
t.Fatalf("existed: %v; err: %v", existed, err)
|
|
}
|
|
|
|
me := &MountEntry{
|
|
Table: auditTableType,
|
|
Path: "foo",
|
|
Type: "noop",
|
|
}
|
|
err = c.enableAudit(me)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
|
|
existed, err = c.disableAudit("foo")
|
|
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)
|
|
}
|
|
unseal, err := TestCoreUnseal(c2, 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 := TestCoreUnseal(c2, 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)
|
|
}
|
|
if table.Type != auditTableType {
|
|
t.Fatalf("bad: %v", *table)
|
|
}
|
|
}
|
|
|
|
func TestAuditBroker_LogRequest(t *testing.T) {
|
|
l := logformat.NewVaultLogger(log.LevelTrace)
|
|
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",
|
|
}
|
|
|
|
// Create an identifier for the request to verify against
|
|
var err error
|
|
req.ID, err = uuid.GenerateUUID()
|
|
if err != nil {
|
|
t.Fatalf("failed to generate identifier for the request: path%s err: %v", req.Path, err)
|
|
}
|
|
|
|
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); !errwrap.Contains(err, "no audit backend succeeded in logging the request") {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestAuditBroker_LogResponse(t *testing.T) {
|
|
l := logformat.NewVaultLogger(log.LevelTrace)
|
|
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)
|
|
}
|
|
}
|