Add support for hashing time.Time within slices (#6767)

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.
This commit is contained in:
ncabatoff 2019-07-02 18:18:40 -04:00 committed by GitHub
parent 8fc4a63796
commit d2beeefe79
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 472 additions and 410 deletions

View File

@ -3,7 +3,6 @@ package audit
import (
"context"
"crypto/tls"
"encoding/json"
"fmt"
"io"
"strings"
@ -15,12 +14,14 @@ import (
"github.com/hashicorp/vault/helper/namespace"
"github.com/hashicorp/vault/sdk/helper/salt"
"github.com/hashicorp/vault/sdk/logical"
"github.com/mitchellh/copystructure"
)
type AuditFormatWriter interface {
// WriteRequest writes the request entry to the writer or returns an error.
WriteRequest(io.Writer, *AuditRequestEntry) error
// WriteResponse writes the response entry to the writer or returns an error.
WriteResponse(io.Writer, *AuditResponseEntry) error
// Salt returns a non-nil salt or an error.
Salt(context.Context) (*salt.Salt, error)
}
@ -54,79 +55,26 @@ func (f *AuditFormatter) FormatRequest(ctx context.Context, w io.Writer, config
auth := in.Auth
req := in.Request
var connState *tls.ConnectionState
if auth == nil {
auth = new(logical.Auth)
}
if in.Request.Connection != nil && in.Request.Connection.ConnState != nil {
connState = in.Request.Connection.ConnState
}
if !config.Raw {
// Before we copy the structure we must nil out some data
// otherwise we will cause reflection to panic and die
if connState != nil {
in.Request.Connection.ConnState = nil
defer func() {
in.Request.Connection.ConnState = connState
}()
}
// Copy the auth structure
if in.Auth != nil {
cp, err := copystructure.Copy(in.Auth)
if err != nil {
return err
}
auth = cp.(*logical.Auth)
}
cp, err := copystructure.Copy(in.Request)
auth, err = HashAuth(salt, auth, config.HMACAccessor)
if err != nil {
return err
}
req = cp.(*logical.Request)
for k, v := range req.Data {
if o, ok := v.(logical.OptMarshaler); ok {
marshaled, err := o.MarshalJSONWithOptions(&logical.MarshalOptions{
ValueHasher: salt.GetIdentifiedHMAC,
})
if err != nil {
return err
}
req.Data[k] = json.RawMessage(marshaled)
}
}
// Hash any sensitive information
if auth != nil {
// Cache and restore accessor in the auth
var authAccessor string
if !config.HMACAccessor && auth.Accessor != "" {
authAccessor = auth.Accessor
}
if err := Hash(salt, auth, nil); err != nil {
return err
}
if authAccessor != "" {
auth.Accessor = authAccessor
}
}
// Cache and restore accessor in the request
var clientTokenAccessor string
if !config.HMACAccessor && req != nil && req.ClientTokenAccessor != "" {
clientTokenAccessor = req.ClientTokenAccessor
}
if err := Hash(salt, req, in.NonHMACReqDataKeys); err != nil {
req, err = HashRequest(salt, req, config.HMACAccessor, in.NonHMACReqDataKeys)
if err != nil {
return err
}
if clientTokenAccessor != "" {
req.ClientTokenAccessor = clientTokenAccessor
}
}
// If auth is nil, make an empty one
if auth == nil {
auth = new(logical.Auth)
}
var errString string
if in.OuterErr != nil {
errString = in.OuterErr.Error()
@ -209,9 +157,13 @@ func (f *AuditFormatter) FormatResponse(ctx context.Context, w io.Writer, config
}
// Set these to the input values at first
auth := in.Auth
req := in.Request
resp := in.Response
auth, req, resp := in.Auth, in.Request, in.Response
if auth == nil {
auth = new(logical.Auth)
}
if resp == nil {
resp = new(logical.Response)
}
var connState *tls.ConnectionState
if in.Request.Connection != nil && in.Request.Connection.ConnState != nil {
@ -219,120 +171,22 @@ func (f *AuditFormatter) FormatResponse(ctx context.Context, w io.Writer, config
}
if !config.Raw {
// Before we copy the structure we must nil out some data
// otherwise we will cause reflection to panic and die
if connState != nil {
in.Request.Connection.ConnState = nil
defer func() {
in.Request.Connection.ConnState = connState
}()
}
// Copy the auth structure
if in.Auth != nil {
cp, err := copystructure.Copy(in.Auth)
if err != nil {
return err
}
auth = cp.(*logical.Auth)
}
cp, err := copystructure.Copy(in.Request)
auth, err = HashAuth(salt, auth, config.HMACAccessor)
if err != nil {
return err
}
req = cp.(*logical.Request)
for k, v := range req.Data {
if o, ok := v.(logical.OptMarshaler); ok {
marshaled, err := o.MarshalJSONWithOptions(&logical.MarshalOptions{
ValueHasher: salt.GetIdentifiedHMAC,
})
if err != nil {
return err
}
req.Data[k] = json.RawMessage(marshaled)
}
}
if in.Response != nil {
cp, err := copystructure.Copy(in.Response)
if err != nil {
return err
}
resp = cp.(*logical.Response)
for k, v := range resp.Data {
if o, ok := v.(logical.OptMarshaler); ok {
marshaled, err := o.MarshalJSONWithOptions(&logical.MarshalOptions{
ValueHasher: salt.GetIdentifiedHMAC,
})
if err != nil {
return err
}
resp.Data[k] = json.RawMessage(marshaled)
}
}
}
// Hash any sensitive information
// Cache and restore accessor in the auth
if auth != nil {
var accessor string
if !config.HMACAccessor && auth.Accessor != "" {
accessor = auth.Accessor
}
if err := Hash(salt, auth, nil); err != nil {
return err
}
if accessor != "" {
auth.Accessor = accessor
}
}
// Cache and restore accessor in the request
var clientTokenAccessor string
if !config.HMACAccessor && req != nil && req.ClientTokenAccessor != "" {
clientTokenAccessor = req.ClientTokenAccessor
}
if err := Hash(salt, req, in.NonHMACReqDataKeys); err != nil {
req, err = HashRequest(salt, req, config.HMACAccessor, in.NonHMACReqDataKeys)
if err != nil {
return err
}
if clientTokenAccessor != "" {
req.ClientTokenAccessor = clientTokenAccessor
}
// Cache and restore accessor in the response
if resp != nil {
var accessor, wrappedAccessor, wrappingAccessor string
if !config.HMACAccessor && resp != nil && resp.Auth != nil && resp.Auth.Accessor != "" {
accessor = resp.Auth.Accessor
}
if !config.HMACAccessor && resp != nil && resp.WrapInfo != nil && resp.WrapInfo.WrappedAccessor != "" {
wrappedAccessor = resp.WrapInfo.WrappedAccessor
wrappingAccessor = resp.WrapInfo.Accessor
}
if err := Hash(salt, resp, in.NonHMACRespDataKeys); err != nil {
return err
}
if accessor != "" {
resp.Auth.Accessor = accessor
}
if wrappedAccessor != "" {
resp.WrapInfo.WrappedAccessor = wrappedAccessor
}
if wrappingAccessor != "" {
resp.WrapInfo.Accessor = wrappingAccessor
}
resp, err = HashResponse(salt, resp, config.HMACAccessor, in.NonHMACRespDataKeys)
if err != nil {
return err
}
}
// If things are nil, make empty to avoid panics
if auth == nil {
auth = new(logical.Auth)
}
if resp == nil {
resp = new(logical.Response)
}
var errString string
if in.OuterErr != nil {
errString = in.OuterErr.Error()

View File

@ -1,9 +1,9 @@
package audit
import (
"encoding/json"
"errors"
"reflect"
"strings"
"time"
"github.com/hashicorp/vault/sdk/helper/salt"
@ -19,107 +19,157 @@ func HashString(salter *salt.Salt, data string) string {
return salter.GetIdentifiedHMAC(data)
}
// Hash will hash the given type. This has built-in support for auth,
// requests, and responses. If it is a type that isn't recognized, then
// it will be passed through.
//
// The structure is modified in-place.
func Hash(salter *salt.Salt, raw interface{}, nonHMACDataKeys []string) error {
// HashAuth returns a hashed copy of the logical.Auth input.
func HashAuth(salter *salt.Salt, in *logical.Auth, HMACAccessor bool) (*logical.Auth, error) {
if in == nil {
return nil, nil
}
fn := salter.GetIdentifiedHMAC
auth := *in
switch s := raw.(type) {
case *logical.Auth:
if s == nil {
return nil
}
if s.ClientToken != "" {
s.ClientToken = fn(s.ClientToken)
}
if s.Accessor != "" {
s.Accessor = fn(s.Accessor)
}
if auth.ClientToken != "" {
auth.ClientToken = fn(auth.ClientToken)
}
if HMACAccessor && auth.Accessor != "" {
auth.Accessor = fn(auth.Accessor)
}
return &auth, nil
}
case *logical.Request:
if s == nil {
return nil
}
if s.Auth != nil {
if err := Hash(salter, s.Auth, nil); err != nil {
return err
}
}
// HashRequest returns a hashed copy of the logical.Request input.
func HashRequest(salter *salt.Salt, in *logical.Request, HMACAccessor bool, nonHMACDataKeys []string) (*logical.Request, error) {
if in == nil {
return nil, nil
}
if s.ClientToken != "" {
s.ClientToken = fn(s.ClientToken)
}
fn := salter.GetIdentifiedHMAC
req := *in
if s.ClientTokenAccessor != "" {
s.ClientTokenAccessor = fn(s.ClientTokenAccessor)
}
data, err := HashStructure(s.Data, fn, nonHMACDataKeys)
if req.Auth != nil {
cp, err := copystructure.Copy(req.Auth)
if err != nil {
return err
return nil, err
}
s.Data = data.(map[string]interface{})
case *logical.Response:
if s == nil {
return nil
}
if s.Auth != nil {
if err := Hash(salter, s.Auth, nil); err != nil {
return err
}
}
if s.WrapInfo != nil {
if err := Hash(salter, s.WrapInfo, nil); err != nil {
return err
}
}
data, err := HashStructure(s.Data, fn, nonHMACDataKeys)
req.Auth, err = HashAuth(salter, cp.(*logical.Auth), HMACAccessor)
if err != nil {
return err
}
s.Data = data.(map[string]interface{})
case *wrapping.ResponseWrapInfo:
if s == nil {
return nil
}
s.Token = fn(s.Token)
s.Accessor = fn(s.Accessor)
if s.WrappedAccessor != "" {
s.WrappedAccessor = fn(s.WrappedAccessor)
return nil, err
}
}
return nil
if req.ClientToken != "" {
req.ClientToken = fn(req.ClientToken)
}
if HMACAccessor && req.ClientTokenAccessor != "" {
req.ClientTokenAccessor = fn(req.ClientTokenAccessor)
}
data, err := hashMap(fn, req.Data, nonHMACDataKeys)
if err != nil {
return nil, err
}
req.Data = data
return &req, nil
}
func hashMap(fn func(string) string, data map[string]interface{}, nonHMACDataKeys []string) (map[string]interface{}, error) {
if data == nil {
return nil, nil
}
copy, err := copystructure.Copy(data)
if err != nil {
return nil, err
}
newData := copy.(map[string]interface{})
for k, v := range newData {
if o, ok := v.(logical.OptMarshaler); ok {
marshaled, err := o.MarshalJSONWithOptions(&logical.MarshalOptions{
ValueHasher: fn,
})
if err != nil {
return nil, err
}
newData[k] = json.RawMessage(marshaled)
}
}
if err := HashStructure(newData, fn, nonHMACDataKeys); err != nil {
return nil, err
}
return newData, nil
}
// HashResponse returns a hashed copy of the logical.Request input.
func HashResponse(salter *salt.Salt, in *logical.Response, HMACAccessor bool, nonHMACDataKeys []string) (*logical.Response, error) {
if in == nil {
return nil, nil
}
fn := salter.GetIdentifiedHMAC
resp := *in
if resp.Auth != nil {
cp, err := copystructure.Copy(resp.Auth)
if err != nil {
return nil, err
}
resp.Auth, err = HashAuth(salter, cp.(*logical.Auth), HMACAccessor)
if err != nil {
return nil, err
}
}
data, err := hashMap(fn, resp.Data, nonHMACDataKeys)
if err != nil {
return nil, err
}
resp.Data = data
if resp.WrapInfo != nil {
var err error
resp.WrapInfo, err = HashWrapInfo(salter, resp.WrapInfo, HMACAccessor)
if err != nil {
return nil, err
}
}
return &resp, nil
}
// HashWrapInfo returns a hashed copy of the wrapping.ResponseWrapInfo input.
func HashWrapInfo(salter *salt.Salt, in *wrapping.ResponseWrapInfo, HMACAccessor bool) (*wrapping.ResponseWrapInfo, error) {
if in == nil {
return nil, nil
}
fn := salter.GetIdentifiedHMAC
wrapinfo := *in
wrapinfo.Token = fn(wrapinfo.Token)
if HMACAccessor {
wrapinfo.Accessor = fn(wrapinfo.Accessor)
if wrapinfo.WrappedAccessor != "" {
wrapinfo.WrappedAccessor = fn(wrapinfo.WrappedAccessor)
}
}
return &wrapinfo, nil
}
// HashStructure takes an interface and hashes all the values within
// the structure. Only _values_ are hashed: keys of objects are not.
//
// For the HashCallback, see the built-in HashCallbacks below.
func HashStructure(s interface{}, cb HashCallback, ignoredKeys []string) (interface{}, error) {
s, err := copystructure.Copy(s)
if err != nil {
return nil, err
}
func HashStructure(s interface{}, cb HashCallback, ignoredKeys []string) error {
walker := &hashWalker{Callback: cb, IgnoredKeys: ignoredKeys}
if err := reflectwalk.Walk(s, walker); err != nil {
return nil, err
}
return s, nil
return reflectwalk.Walk(s, walker)
}
// HashCallback is the callback called for HashStructure to hash
@ -134,18 +184,25 @@ type hashWalker struct {
// to be hashed. If there is an error, walking will be halted
// immediately and the error returned.
Callback HashCallback
// IgnoreKeys are the keys that wont have the HashCallback applied
IgnoredKeys []string
key []string
lastValue reflect.Value
loc reflectwalk.Location
cs []reflect.Value
csKey []reflect.Value
csData interface{}
sliceIndex int
unknownKeys []string
// MapElem appends the key itself (not the reflect.Value) to key.
// The last element in key is the most recently entered map key.
// Since Exit pops the last element of key, only nesting to another
// structure increases the size of this slice.
key []string
lastValue reflect.Value
// Enter appends to loc and exit pops loc. The last element of loc is thus
// the current location.
loc []reflectwalk.Location
// Map and Slice append to cs, Exit pops the last element off cs.
// The last element in cs is the most recently entered map or slice.
cs []reflect.Value
// MapElem and SliceElem append to csKey. The last element in csKey is the
// most recently entered map key or slice index. Since Exit pops the last
// element of csKey, only nesting to another structure increases the size of
// this slice.
csKey []reflect.Value
}
// hashTimeType stores a pre-computed reflect.Type for a time.Time so
@ -155,12 +212,12 @@ type hashWalker struct {
var hashTimeType = reflect.TypeOf(time.Time{})
func (w *hashWalker) Enter(loc reflectwalk.Location) error {
w.loc = loc
w.loc = append(w.loc, loc)
return nil
}
func (w *hashWalker) Exit(loc reflectwalk.Location) error {
w.loc = reflectwalk.None
w.loc = w.loc[:len(w.loc)-1]
switch loc {
case reflectwalk.Map:
@ -183,7 +240,6 @@ func (w *hashWalker) Map(m reflect.Value) error {
}
func (w *hashWalker) MapElem(m, k, v reflect.Value) error {
w.csData = k
w.csKey = append(w.csKey, k)
w.key = append(w.key, k.String())
w.lastValue = v
@ -197,7 +253,6 @@ func (w *hashWalker) Slice(s reflect.Value) error {
func (w *hashWalker) SliceElem(i int, elem reflect.Value) error {
w.csKey = append(w.csKey, reflect.ValueOf(i))
w.sliceIndex = i
return nil
}
@ -207,20 +262,37 @@ func (w *hashWalker) Struct(v reflect.Value) error {
return nil
}
// If we aren't in a map value, return an error to prevent a panic
if v.Interface() != w.lastValue.Interface() {
return errors.New("time.Time value in a non map key cannot be hashed for audits")
if len(w.loc) < 3 {
// The last element of w.loc is reflectwalk.Struct, by definition.
// If len(w.loc) < 3 that means hashWalker.Walk was given a struct
// value and this is the very first step in the walk, and we don't
// currently support structs as inputs,
return errors.New("structs as direct inputs not supported")
}
// Create a string value of the time. IMPORTANT: this must never change
// across Vault versions or the hash value of equivalent time.Time will
// change.
strVal := v.Interface().(time.Time).Format(time.RFC3339Nano)
// Second to last element of w.loc is location that contains this struct.
switch w.loc[len(w.loc)-2] {
case reflectwalk.MapValue:
// Create a string value of the time. IMPORTANT: this must never change
// across Vault versions or the hash value of equivalent time.Time will
// change.
strVal := v.Interface().(time.Time).Format(time.RFC3339Nano)
// Set the map value to the string instead of the time.Time object
m := w.cs[len(w.cs)-1]
mk := w.csData.(reflect.Value)
m.SetMapIndex(mk, reflect.ValueOf(strVal))
// Set the map value to the string instead of the time.Time object
m := w.cs[len(w.cs)-1]
mk := w.csKey[len(w.cs)-1]
m.SetMapIndex(mk, reflect.ValueOf(strVal))
case reflectwalk.SliceElem:
// Create a string value of the time. IMPORTANT: this must never change
// across Vault versions or the hash value of equivalent time.Time will
// change.
strVal := v.Interface().(time.Time).Format(time.RFC3339Nano)
// Set the map value to the string instead of the time.Time object
s := w.cs[len(w.cs)-1]
si := int(w.csKey[len(w.cs)-1].Int())
s.Slice(si, si+1).Index(0).Set(reflect.ValueOf(strVal))
}
// Skip this entry so that we don't walk the struct.
return reflectwalk.SkipEntry
@ -230,13 +302,15 @@ func (w *hashWalker) StructField(reflect.StructField, reflect.Value) error {
return nil
}
// Primitive calls Callback to transform strings in-place, except for map keys.
// Strings hiding within interfaces are also transformed.
func (w *hashWalker) Primitive(v reflect.Value) error {
if w.Callback == nil {
return nil
}
// We don't touch map keys
if w.loc == reflectwalk.MapKey {
if w.loc[len(w.loc)-1] == reflectwalk.MapKey {
return nil
}
@ -244,7 +318,6 @@ func (w *hashWalker) Primitive(v reflect.Value) error {
// We only care about strings
if v.Kind() == reflect.Interface {
setV = v
v = v.Elem()
}
if v.Kind() != reflect.String {
@ -260,25 +333,17 @@ func (w *hashWalker) Primitive(v reflect.Value) error {
replaceVal := w.Callback(v.String())
resultVal := reflect.ValueOf(replaceVal)
switch w.loc {
case reflectwalk.MapKey:
m := w.cs[len(w.cs)-1]
// Delete the old value
var zero reflect.Value
m.SetMapIndex(w.csData.(reflect.Value), zero)
// Set the new key with the existing value
m.SetMapIndex(resultVal, w.lastValue)
// Set the key to be the new key
w.csData = resultVal
switch w.loc[len(w.loc)-1] {
case reflectwalk.MapValue:
// If we're in a map, then the only way to set a map value is
// to set it directly.
m := w.cs[len(w.cs)-1]
mk := w.csData.(reflect.Value)
mk := w.csKey[len(w.cs)-1]
m.SetMapIndex(mk, resultVal)
case reflectwalk.SliceElem:
s := w.cs[len(w.cs)-1]
si := int(w.csKey[len(w.cs)-1].Int())
s.Slice(si, si+1).Index(0).Set(resultVal)
default:
// Otherwise, we should be addressable
setV.Set(resultVal)
@ -286,34 +351,3 @@ func (w *hashWalker) Primitive(v reflect.Value) error {
return nil
}
func (w *hashWalker) removeCurrent() {
// Append the key to the unknown keys
w.unknownKeys = append(w.unknownKeys, strings.Join(w.key, "."))
for i := 1; i <= len(w.cs); i++ {
c := w.cs[len(w.cs)-i]
switch c.Kind() {
case reflect.Map:
// Zero value so that we delete the map key
var val reflect.Value
// Get the key and delete it
k := w.csData.(reflect.Value)
c.SetMapIndex(k, val)
return
}
}
panic("No container found for removeCurrent")
}
func (w *hashWalker) replaceCurrent(v reflect.Value) {
c := w.cs[len(w.cs)-2]
switch c.Kind() {
case reflect.Map:
// Get the key and delete it
k := w.csKey[len(w.csKey)-1]
c.SetMapIndex(k, v)
}
}

View File

@ -3,7 +3,9 @@ package audit
import (
"context"
"crypto/sha256"
"encoding/json"
"fmt"
"github.com/go-test/deep"
"reflect"
"testing"
"time"
@ -111,73 +113,16 @@ func TestHashString(t *testing.T) {
}
}
func TestHash(t *testing.T) {
now := time.Now()
func TestHashAuth(t *testing.T) {
cases := []struct {
Input interface{}
Output interface{}
NonHMACDataKeys []string
Input *logical.Auth
Output *logical.Auth
HMACAccessor bool
}{
{
&logical.Auth{ClientToken: "foo"},
&logical.Auth{ClientToken: "hmac-sha256:08ba357e274f528065766c770a639abf6809b39ccfd37c2a3157c7f51954da0a"},
nil,
},
{
&logical.Request{
Data: map[string]interface{}{
"foo": "bar",
"baz": "foobar",
"private_key_type": certutil.PrivateKeyType("rsa"),
},
},
&logical.Request{
Data: map[string]interface{}{
"foo": "hmac-sha256:f9320baf0249169e73850cd6156ded0106e2bb6ad8cab01b7bbbebe6d1065317",
"baz": "foobar",
"private_key_type": "hmac-sha256:995230dca56fffd310ff591aa404aab52b2abb41703c787cfa829eceb4595bf1",
},
},
[]string{"baz"},
},
{
&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,
},
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),
},
WrapInfo: &wrapping.ResponseWrapInfo{
TTL: 60,
Token: "hmac-sha256:f9320baf0249169e73850cd6156ded0106e2bb6ad8cab01b7bbbebe6d1065317",
Accessor: "hmac-sha256:7c9c6fe666d0af73b3ebcfbfabe6885015558213208e6635ba104047b22f6390",
CreationTime: now,
WrappedAccessor: "hmac-sha256:f9320baf0249169e73850cd6156ded0106e2bb6ad8cab01b7bbbebe6d1065317",
},
},
[]string{"baz"},
},
{
"foo",
"foo",
nil,
false,
},
{
&logical.Auth{
@ -194,7 +139,7 @@ func TestHash(t *testing.T) {
ClientToken: "hmac-sha256:08ba357e274f528065766c770a639abf6809b39ccfd37c2a3157c7f51954da0a",
},
nil,
false,
},
}
@ -212,16 +157,147 @@ func TestHash(t *testing.T) {
}
for _, tc := range cases {
input := fmt.Sprintf("%#v", tc.Input)
if err := Hash(localSalt, tc.Input, tc.NonHMACDataKeys); err != nil {
out, err := HashAuth(localSalt, tc.Input, tc.HMACAccessor)
if err != nil {
t.Fatalf("err: %s\n\n%s", err, input)
}
if _, ok := tc.Input.(*logical.Response); ok {
if !reflect.DeepEqual(tc.Input.(*logical.Response).WrapInfo, tc.Output.(*logical.Response).WrapInfo) {
t.Fatalf("bad:\nInput:\n%s\nTest case input:\n%#v\nTest case output:\n%#v", input, tc.Input.(*logical.Response).WrapInfo, tc.Output.(*logical.Response).WrapInfo)
}
if !reflect.DeepEqual(out, tc.Output) {
t.Fatalf("bad:\nInput:\n%s\nOutput:\n%#v\nExpected output:\n%#v", input, out, tc.Output)
}
if !reflect.DeepEqual(tc.Input, tc.Output) {
t.Fatalf("bad:\nInput:\n%s\nTest case input:\n%#v\nTest case output:\n%#v", input, tc.Input, 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)
}
}
}
@ -230,8 +306,8 @@ func TestHashWalker(t *testing.T) {
replaceText := "foo"
cases := []struct {
Input interface{}
Output interface{}
Input map[string]interface{}
Output map[string]interface{}
}{
{
map[string]interface{}{
@ -253,14 +329,68 @@ func TestHashWalker(t *testing.T) {
}
for _, tc := range cases {
output, err := HashStructure(tc.Input, func(string) string {
err := HashStructure(tc.Input, func(string) string {
return replaceText
}, []string{})
}, nil)
if err != nil {
t.Fatalf("err: %s\n\n%#v", err, tc.Input)
}
if !reflect.DeepEqual(output, tc.Output) {
t.Fatalf("bad:\n\n%#v\n\n%#v", tc.Input, output)
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)
}
}
}

View File

@ -190,9 +190,14 @@ func testCoreConfig(t testing.T, physicalBackend physical.Backend, logger log.Lo
HMACType: "hmac-sha256",
}
config.SaltView = view
return &noopAudit{
n := &noopAudit{
Config: config,
}, nil
}
n.formatter.AuditFormatWriter = &audit.JSONFormatWriter{
SaltFunc: n.Salt,
}
return n, nil
},
}
@ -591,6 +596,8 @@ type noopAudit struct {
Config *audit.BackendConfig
salt *salt.Salt
saltMutex sync.RWMutex
formatter audit.AuditFormatter
records [][]byte
}
func (n *noopAudit) GetHash(ctx context.Context, data string) (string, error) {
@ -601,11 +608,23 @@ func (n *noopAudit) GetHash(ctx context.Context, data string) (string, error) {
return salt.GetIdentifiedHMAC(data), nil
}
func (n *noopAudit) LogRequest(_ context.Context, _ *logical.LogInput) error {
func (n *noopAudit) LogRequest(ctx context.Context, in *logical.LogInput) error {
var w bytes.Buffer
err := n.formatter.FormatRequest(ctx, &w, audit.FormatterConfig{}, in)
if err != nil {
return err
}
n.records = append(n.records, w.Bytes())
return nil
}
func (n *noopAudit) LogResponse(_ context.Context, _ *logical.LogInput) error {
func (n *noopAudit) LogResponse(ctx context.Context, in *logical.LogInput) error {
var w bytes.Buffer
err := n.formatter.FormatResponse(ctx, &w, audit.FormatterConfig{}, in)
if err != nil {
return err
}
n.records = append(n.records, w.Bytes())
return nil
}
@ -647,14 +666,13 @@ func AddNoopAudit(conf *CoreConfig) {
Key: "salt",
Value: []byte("foo"),
})
config.SaltConfig = &salt.Config{
HMAC: sha256.New,
HMACType: "hmac-sha256",
}
config.SaltView = view
return &noopAudit{
n := &noopAudit{
Config: config,
}, nil
}
n.formatter.AuditFormatWriter = &audit.JSONFormatWriter{
SaltFunc: n.Salt,
}
return n, nil
},
}
}
@ -1348,6 +1366,12 @@ func NewTestCluster(t testing.T, base *CoreConfig, opts *TestClusterOptions) *Te
coreConfig.DevToken = base.DevToken
coreConfig.CounterSyncInterval = base.CounterSyncInterval
}
addAuditBackend := len(coreConfig.AuditBackends) == 0
if addAuditBackend {
AddNoopAudit(coreConfig)
}
if coreConfig.Physical == nil && (opts == nil || opts.PhysicalFactory == nil) {
@ -1567,6 +1591,26 @@ func NewTestCluster(t testing.T, base *CoreConfig, opts *TestClusterOptions) *Te
t.Fatal(err)
}
testCluster.ID = cluster.ID
if addAuditBackend {
// Enable auditing.
auditReq := &logical.Request{
Operation: logical.UpdateOperation,
ClientToken: testCluster.RootToken,
Path: "sys/audit/noop",
Data: map[string]interface{}{
"type": "noop",
},
}
resp, err = cores[0].HandleRequest(namespace.RootContext(ctx), auditReq)
if err != nil {
t.Fatal(err)
}
if resp.IsError() {
t.Fatal(err)
}
}
}
getAPIClient := func(port int, tlsConfig *tls.Config) *api.Client {