open-vault/audit/hashstructure_test.go

401 lines
9.7 KiB
Go
Raw Normal View History

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
2015-04-21 15:02:03 +00:00
package audit
import (
"context"
"crypto/sha256"
"encoding/json"
"fmt"
2015-04-21 15:02:03 +00:00
"reflect"
"testing"
2015-04-27 22:54:14 +00:00
"time"
"github.com/go-test/deep"
"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"
2015-04-27 22:54:14 +00:00
"github.com/mitchellh/copystructure"
2015-04-21 15:02:03 +00:00
)
2015-04-27 22:54:14 +00:00
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,
2015-04-27 22:54:14 +00:00
},
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",
},
2017-01-04 21:44:03 +00:00
WrapInfo: &logical.RequestWrapInfo{
TTL: 60 * time.Second,
},
2015-04-27 22:54:14 +00:00
}
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",
2016-05-08 01:08:13 +00:00
},
2015-04-27 22:54:14 +00:00
}
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{}{
2016-01-26 17:47:04 +00:00
"foo": "bar",
"baz": "foobar",
2016-01-26 17:47:04 +00:00
"private_key_type": certutil.PrivateKeyType("rsa"),
"om": &testOptMarshaler{S: "bar", I: 1},
},
},
&logical.Request{
Data: map[string]interface{}{
2016-01-26 17:47:04 +00:00
"foo": "hmac-sha256:f9320baf0249169e73850cd6156ded0106e2bb6ad8cab01b7bbbebe6d1065317",
"baz": "foobar",
2016-01-26 17:47:04 +00:00
"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",
2017-11-13 20:31:32 +00:00
Accessor: "flimflam",
CreationTime: now,
WrappedAccessor: "bar",
2016-05-08 01:08:13 +00:00
},
},
&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",
2017-11-13 20:31:32 +00:00
Accessor: "hmac-sha256:7c9c6fe666d0af73b3ebcfbfabe6885015558213208e6635ba104047b22f6390",
CreationTime: now,
WrappedAccessor: "hmac-sha256:f9320baf0249169e73850cd6156ded0106e2bb6ad8cab01b7bbbebe6d1065317",
2016-05-08 01:08:13 +00:00
},
},
[]string{"baz"},
true,
2015-06-19 10:31:19 +00:00
},
}
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)
Add option 'elide_list_responses' to audit backends (#18128) This PR relates to a feature request logged through HashiCorp commercial support. Vault lacks pagination in its APIs. As a result, certain list operations can return **very** large responses. The user's chosen audit sinks may experience difficulty consuming audit records that swell to tens of megabytes of JSON. In our case, one of the systems consuming audit log data could not cope, and failed. The responses of list operations are typically not very interesting, as they are mostly lists of keys, or, even when they include a "key_info" field, are not returning confidential information. They become even less interesting once HMAC-ed by the audit system. Some example Vault "list" operations that are prone to becoming very large in an active Vault installation are: auth/token/accessors/ identity/entity/id/ identity/entity-alias/id/ pki/certs/ In response, I've coded a new option that can be applied to audit backends, `elide_list_responses`. When enabled, response data is elided from audit logs, only when the operation type is "list". For added safety, the elision only applies to the "keys" and "key_info" fields within the response data - these are conventionally the only fields present in a list response - see logical.ListResponse, and logical.ListResponseWithInfo. However, other fields are technically possible if a plugin author writes unusual code, and these will be preserved in the audit log even with this option enabled. The elision replaces the values of the "keys" and "key_info" fields with an integer count of the number of entries. This allows even the elided audit logs to still be useful for answering questions like "Was any data returned?" or "How many records were listed?".
2023-01-11 21:15:52 +00:00
out, err := HashResponse(localSalt, tc.Input, tc.HMACAccessor, tc.NonHMACDataKeys, false)
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)
}
}
}
2015-04-21 15:02:03 +00:00
func TestHashWalker(t *testing.T) {
replaceText := "foo"
cases := []struct {
Input map[string]interface{}
Output map[string]interface{}
2015-04-21 15:02:03 +00:00
}{
{
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)
2015-04-21 15:02:03 +00:00
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)
2015-04-21 15:02:03 +00:00
}
}
}