mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-11-01 19:17:58 +00:00
Add support for hashing time.Time within slices, which unbreaks auditing of requests returning the request counters. Break Hash into struct-specific func like HashAuth, HashRequest. Move all the copying/hashing logic from FormatRequest/FormatResponse into the new Hash* funcs. HashStructure now modifies in place instead of copying. Instead of returning an error when trying to hash map keys of type time.Time, ignore them, i.e. pass them through unhashed. Enable auditing on test clusters by default if the caller didn't specify any audit backends. If they do, they're responsible for setting it up.
397 lines
9.6 KiB
Go
397 lines
9.6 KiB
Go
package audit
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"encoding/json"
|
|
"fmt"
|
|
"github.com/go-test/deep"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/hashicorp/vault/sdk/helper/certutil"
|
|
"github.com/hashicorp/vault/sdk/helper/salt"
|
|
"github.com/hashicorp/vault/sdk/helper/wrapping"
|
|
"github.com/hashicorp/vault/sdk/logical"
|
|
"github.com/mitchellh/copystructure"
|
|
)
|
|
|
|
func TestCopy_auth(t *testing.T) {
|
|
// Make a non-pointer one so that it can't be modified directly
|
|
expected := logical.Auth{
|
|
LeaseOptions: logical.LeaseOptions{
|
|
TTL: 1 * time.Hour,
|
|
},
|
|
|
|
ClientToken: "foo",
|
|
}
|
|
auth := expected
|
|
|
|
// Copy it
|
|
dup, err := copystructure.Copy(&auth)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Check equality
|
|
auth2 := dup.(*logical.Auth)
|
|
if !reflect.DeepEqual(*auth2, expected) {
|
|
t.Fatalf("bad:\n\n%#v\n\n%#v", *auth2, expected)
|
|
}
|
|
}
|
|
|
|
func TestCopy_request(t *testing.T) {
|
|
// Make a non-pointer one so that it can't be modified directly
|
|
expected := logical.Request{
|
|
Data: map[string]interface{}{
|
|
"foo": "bar",
|
|
},
|
|
WrapInfo: &logical.RequestWrapInfo{
|
|
TTL: 60 * time.Second,
|
|
},
|
|
}
|
|
arg := expected
|
|
|
|
// Copy it
|
|
dup, err := copystructure.Copy(&arg)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Check equality
|
|
arg2 := dup.(*logical.Request)
|
|
if !reflect.DeepEqual(*arg2, expected) {
|
|
t.Fatalf("bad:\n\n%#v\n\n%#v", *arg2, expected)
|
|
}
|
|
}
|
|
|
|
func TestCopy_response(t *testing.T) {
|
|
// Make a non-pointer one so that it can't be modified directly
|
|
expected := logical.Response{
|
|
Data: map[string]interface{}{
|
|
"foo": "bar",
|
|
},
|
|
WrapInfo: &wrapping.ResponseWrapInfo{
|
|
TTL: 60,
|
|
Token: "foo",
|
|
CreationTime: time.Now(),
|
|
WrappedAccessor: "abcd1234",
|
|
},
|
|
}
|
|
arg := expected
|
|
|
|
// Copy it
|
|
dup, err := copystructure.Copy(&arg)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
// Check equality
|
|
arg2 := dup.(*logical.Response)
|
|
if !reflect.DeepEqual(*arg2, expected) {
|
|
t.Fatalf("bad:\n\n%#v\n\n%#v", *arg2, expected)
|
|
}
|
|
}
|
|
|
|
func TestHashString(t *testing.T) {
|
|
inmemStorage := &logical.InmemStorage{}
|
|
inmemStorage.Put(context.Background(), &logical.StorageEntry{
|
|
Key: "salt",
|
|
Value: []byte("foo"),
|
|
})
|
|
localSalt, err := salt.NewSalt(context.Background(), inmemStorage, &salt.Config{
|
|
HMAC: sha256.New,
|
|
HMACType: "hmac-sha256",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error instantiating salt: %s", err)
|
|
}
|
|
out := HashString(localSalt, "foo")
|
|
if out != "hmac-sha256:08ba357e274f528065766c770a639abf6809b39ccfd37c2a3157c7f51954da0a" {
|
|
t.Fatalf("err: HashString output did not match expected")
|
|
}
|
|
}
|
|
|
|
func TestHashAuth(t *testing.T) {
|
|
cases := []struct {
|
|
Input *logical.Auth
|
|
Output *logical.Auth
|
|
HMACAccessor bool
|
|
}{
|
|
{
|
|
&logical.Auth{ClientToken: "foo"},
|
|
&logical.Auth{ClientToken: "hmac-sha256:08ba357e274f528065766c770a639abf6809b39ccfd37c2a3157c7f51954da0a"},
|
|
false,
|
|
},
|
|
{
|
|
&logical.Auth{
|
|
LeaseOptions: logical.LeaseOptions{
|
|
TTL: 1 * time.Hour,
|
|
},
|
|
|
|
ClientToken: "foo",
|
|
},
|
|
&logical.Auth{
|
|
LeaseOptions: logical.LeaseOptions{
|
|
TTL: 1 * time.Hour,
|
|
},
|
|
|
|
ClientToken: "hmac-sha256:08ba357e274f528065766c770a639abf6809b39ccfd37c2a3157c7f51954da0a",
|
|
},
|
|
false,
|
|
},
|
|
}
|
|
|
|
inmemStorage := &logical.InmemStorage{}
|
|
inmemStorage.Put(context.Background(), &logical.StorageEntry{
|
|
Key: "salt",
|
|
Value: []byte("foo"),
|
|
})
|
|
localSalt, err := salt.NewSalt(context.Background(), inmemStorage, &salt.Config{
|
|
HMAC: sha256.New,
|
|
HMACType: "hmac-sha256",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error instantiating salt: %s", err)
|
|
}
|
|
for _, tc := range cases {
|
|
input := fmt.Sprintf("%#v", tc.Input)
|
|
out, err := HashAuth(localSalt, tc.Input, tc.HMACAccessor)
|
|
if err != nil {
|
|
t.Fatalf("err: %s\n\n%s", err, input)
|
|
}
|
|
if !reflect.DeepEqual(out, tc.Output) {
|
|
t.Fatalf("bad:\nInput:\n%s\nOutput:\n%#v\nExpected output:\n%#v", input, out, tc.Output)
|
|
}
|
|
}
|
|
}
|
|
|
|
type testOptMarshaler struct {
|
|
S string
|
|
I int
|
|
}
|
|
|
|
func (o *testOptMarshaler) MarshalJSONWithOptions(options *logical.MarshalOptions) ([]byte, error) {
|
|
return json.Marshal(&testOptMarshaler{S: options.ValueHasher(o.S), I: o.I})
|
|
}
|
|
|
|
var _ logical.OptMarshaler = &testOptMarshaler{}
|
|
|
|
func TestHashRequest(t *testing.T) {
|
|
cases := []struct {
|
|
Input *logical.Request
|
|
Output *logical.Request
|
|
NonHMACDataKeys []string
|
|
HMACAccessor bool
|
|
}{
|
|
{
|
|
&logical.Request{
|
|
Data: map[string]interface{}{
|
|
"foo": "bar",
|
|
"baz": "foobar",
|
|
"private_key_type": certutil.PrivateKeyType("rsa"),
|
|
"om": &testOptMarshaler{S: "bar", I: 1},
|
|
},
|
|
},
|
|
&logical.Request{
|
|
Data: map[string]interface{}{
|
|
"foo": "hmac-sha256:f9320baf0249169e73850cd6156ded0106e2bb6ad8cab01b7bbbebe6d1065317",
|
|
"baz": "foobar",
|
|
"private_key_type": "hmac-sha256:995230dca56fffd310ff591aa404aab52b2abb41703c787cfa829eceb4595bf1",
|
|
"om": json.RawMessage(`{"S":"hmac-sha256:f9320baf0249169e73850cd6156ded0106e2bb6ad8cab01b7bbbebe6d1065317","I":1}`),
|
|
},
|
|
},
|
|
[]string{"baz"},
|
|
false,
|
|
},
|
|
}
|
|
|
|
inmemStorage := &logical.InmemStorage{}
|
|
inmemStorage.Put(context.Background(), &logical.StorageEntry{
|
|
Key: "salt",
|
|
Value: []byte("foo"),
|
|
})
|
|
localSalt, err := salt.NewSalt(context.Background(), inmemStorage, &salt.Config{
|
|
HMAC: sha256.New,
|
|
HMACType: "hmac-sha256",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error instantiating salt: %s", err)
|
|
}
|
|
for _, tc := range cases {
|
|
input := fmt.Sprintf("%#v", tc.Input)
|
|
out, err := HashRequest(localSalt, tc.Input, tc.HMACAccessor, tc.NonHMACDataKeys)
|
|
if err != nil {
|
|
t.Fatalf("err: %s\n\n%s", err, input)
|
|
}
|
|
if diff := deep.Equal(out, tc.Output); len(diff) > 0 {
|
|
t.Fatalf("bad:\nInput:\n%s\nDiff:\n%#v", input, diff)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestHashResponse(t *testing.T) {
|
|
now := time.Now()
|
|
|
|
cases := []struct {
|
|
Input *logical.Response
|
|
Output *logical.Response
|
|
NonHMACDataKeys []string
|
|
HMACAccessor bool
|
|
}{
|
|
{
|
|
&logical.Response{
|
|
Data: map[string]interface{}{
|
|
"foo": "bar",
|
|
"baz": "foobar",
|
|
// Responses can contain time values, so test that with
|
|
// a known fixed value.
|
|
"bar": now,
|
|
"om": &testOptMarshaler{S: "bar", I: 1},
|
|
},
|
|
WrapInfo: &wrapping.ResponseWrapInfo{
|
|
TTL: 60,
|
|
Token: "bar",
|
|
Accessor: "flimflam",
|
|
CreationTime: now,
|
|
WrappedAccessor: "bar",
|
|
},
|
|
},
|
|
&logical.Response{
|
|
Data: map[string]interface{}{
|
|
"foo": "hmac-sha256:f9320baf0249169e73850cd6156ded0106e2bb6ad8cab01b7bbbebe6d1065317",
|
|
"baz": "foobar",
|
|
"bar": now.Format(time.RFC3339Nano),
|
|
"om": json.RawMessage(`{"S":"hmac-sha256:f9320baf0249169e73850cd6156ded0106e2bb6ad8cab01b7bbbebe6d1065317","I":1}`),
|
|
},
|
|
WrapInfo: &wrapping.ResponseWrapInfo{
|
|
TTL: 60,
|
|
Token: "hmac-sha256:f9320baf0249169e73850cd6156ded0106e2bb6ad8cab01b7bbbebe6d1065317",
|
|
Accessor: "hmac-sha256:7c9c6fe666d0af73b3ebcfbfabe6885015558213208e6635ba104047b22f6390",
|
|
CreationTime: now,
|
|
WrappedAccessor: "hmac-sha256:f9320baf0249169e73850cd6156ded0106e2bb6ad8cab01b7bbbebe6d1065317",
|
|
},
|
|
},
|
|
[]string{"baz"},
|
|
true,
|
|
},
|
|
}
|
|
|
|
inmemStorage := &logical.InmemStorage{}
|
|
inmemStorage.Put(context.Background(), &logical.StorageEntry{
|
|
Key: "salt",
|
|
Value: []byte("foo"),
|
|
})
|
|
localSalt, err := salt.NewSalt(context.Background(), inmemStorage, &salt.Config{
|
|
HMAC: sha256.New,
|
|
HMACType: "hmac-sha256",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error instantiating salt: %s", err)
|
|
}
|
|
for _, tc := range cases {
|
|
input := fmt.Sprintf("%#v", tc.Input)
|
|
out, err := HashResponse(localSalt, tc.Input, tc.HMACAccessor, tc.NonHMACDataKeys)
|
|
if err != nil {
|
|
t.Fatalf("err: %s\n\n%s", err, input)
|
|
}
|
|
if diff := deep.Equal(out, tc.Output); len(diff) > 0 {
|
|
t.Fatalf("bad:\nInput:\n%s\nDiff:\n%#v", input, diff)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestHashWalker(t *testing.T) {
|
|
replaceText := "foo"
|
|
|
|
cases := []struct {
|
|
Input map[string]interface{}
|
|
Output map[string]interface{}
|
|
}{
|
|
{
|
|
map[string]interface{}{
|
|
"hello": "foo",
|
|
},
|
|
map[string]interface{}{
|
|
"hello": replaceText,
|
|
},
|
|
},
|
|
|
|
{
|
|
map[string]interface{}{
|
|
"hello": []interface{}{"world"},
|
|
},
|
|
map[string]interface{}{
|
|
"hello": []interface{}{replaceText},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
err := HashStructure(tc.Input, func(string) string {
|
|
return replaceText
|
|
}, nil)
|
|
if err != nil {
|
|
t.Fatalf("err: %s\n\n%#v", err, tc.Input)
|
|
}
|
|
if !reflect.DeepEqual(tc.Input, tc.Output) {
|
|
t.Fatalf("bad:\n\n%#v\n\n%#v", tc.Input, tc.Output)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestHashWalker_TimeStructs(t *testing.T) {
|
|
replaceText := "bar"
|
|
|
|
now := time.Now()
|
|
cases := []struct {
|
|
Input map[string]interface{}
|
|
Output map[string]interface{}
|
|
}{
|
|
// Should not touch map keys of type time.Time.
|
|
{
|
|
map[string]interface{}{
|
|
"hello": map[time.Time]struct{}{
|
|
now: {},
|
|
},
|
|
},
|
|
map[string]interface{}{
|
|
"hello": map[time.Time]struct{}{
|
|
now: {},
|
|
},
|
|
},
|
|
},
|
|
// Should handle map values of type time.Time.
|
|
{
|
|
map[string]interface{}{
|
|
"hello": now,
|
|
},
|
|
map[string]interface{}{
|
|
"hello": now.Format(time.RFC3339Nano),
|
|
},
|
|
},
|
|
// Should handle slice values of type time.Time.
|
|
{
|
|
map[string]interface{}{
|
|
"hello": []interface{}{"foo", now, "foo2"},
|
|
},
|
|
map[string]interface{}{
|
|
"hello": []interface{}{"foobar", now.Format(time.RFC3339Nano), "foo2bar"},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
err := HashStructure(tc.Input, func(s string) string {
|
|
return s + replaceText
|
|
}, nil)
|
|
if err != nil {
|
|
t.Fatalf("err: %v\n\n%#v", err, tc.Input)
|
|
}
|
|
if !reflect.DeepEqual(tc.Input, tc.Output) {
|
|
t.Fatalf("bad:\n\n%#v\n\n%#v", tc.Input, tc.Output)
|
|
}
|
|
}
|
|
}
|