27bb03bbc0
* adding copyright header * fix fmt and a test
376 lines
10 KiB
Go
376 lines
10 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package audit
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"reflect"
|
|
"time"
|
|
|
|
"github.com/hashicorp/go-secure-stdlib/strutil"
|
|
"github.com/hashicorp/vault/sdk/helper/salt"
|
|
"github.com/hashicorp/vault/sdk/helper/wrapping"
|
|
"github.com/hashicorp/vault/sdk/logical"
|
|
"github.com/mitchellh/copystructure"
|
|
"github.com/mitchellh/reflectwalk"
|
|
)
|
|
|
|
// HashString hashes the given opaque string and returns it
|
|
func HashString(salter *salt.Salt, data string) string {
|
|
return salter.GetIdentifiedHMAC(data)
|
|
}
|
|
|
|
// 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
|
|
|
|
if auth.ClientToken != "" {
|
|
auth.ClientToken = fn(auth.ClientToken)
|
|
}
|
|
if HMACAccessor && auth.Accessor != "" {
|
|
auth.Accessor = fn(auth.Accessor)
|
|
}
|
|
return &auth, nil
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
fn := salter.GetIdentifiedHMAC
|
|
req := *in
|
|
|
|
if req.Auth != nil {
|
|
cp, err := copystructure.Copy(req.Auth)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
req.Auth, err = HashAuth(salter, cp.(*logical.Auth), HMACAccessor)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
if req.ClientToken != "" {
|
|
req.ClientToken = fn(req.ClientToken)
|
|
}
|
|
if HMACAccessor && req.ClientTokenAccessor != "" {
|
|
req.ClientTokenAccessor = fn(req.ClientTokenAccessor)
|
|
}
|
|
|
|
if req.Data != nil {
|
|
copy, err := copystructure.Copy(req.Data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = hashMap(fn, copy.(map[string]interface{}), nonHMACDataKeys)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
req.Data = copy.(map[string]interface{})
|
|
}
|
|
|
|
return &req, nil
|
|
}
|
|
|
|
func hashMap(fn func(string) string, data map[string]interface{}, nonHMACDataKeys []string) error {
|
|
for k, v := range data {
|
|
if o, ok := v.(logical.OptMarshaler); ok {
|
|
marshaled, err := o.MarshalJSONWithOptions(&logical.MarshalOptions{
|
|
ValueHasher: fn,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
data[k] = json.RawMessage(marshaled)
|
|
}
|
|
}
|
|
|
|
return HashStructure(data, fn, nonHMACDataKeys)
|
|
}
|
|
|
|
// HashResponse returns a hashed copy of the logical.Request input.
|
|
func HashResponse(
|
|
salter *salt.Salt,
|
|
in *logical.Response,
|
|
HMACAccessor bool,
|
|
nonHMACDataKeys []string,
|
|
elideListResponseData bool,
|
|
) (*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
|
|
}
|
|
}
|
|
|
|
if resp.Data != nil {
|
|
copy, err := copystructure.Copy(resp.Data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
mapCopy := copy.(map[string]interface{})
|
|
if b, ok := mapCopy[logical.HTTPRawBody].([]byte); ok {
|
|
mapCopy[logical.HTTPRawBody] = string(b)
|
|
}
|
|
|
|
// Processing list response data elision takes place at this point in the code for performance reasons:
|
|
// - take advantage of the deep copy of resp.Data that was going to be done anyway for hashing
|
|
// - but elide data before potentially spending time hashing it
|
|
if elideListResponseData {
|
|
doElideListResponseData(mapCopy)
|
|
}
|
|
|
|
err = hashMap(fn, mapCopy, nonHMACDataKeys)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
resp.Data = mapCopy
|
|
}
|
|
|
|
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) error {
|
|
walker := &hashWalker{Callback: cb, IgnoredKeys: ignoredKeys}
|
|
return reflectwalk.Walk(s, walker)
|
|
}
|
|
|
|
// HashCallback is the callback called for HashStructure to hash
|
|
// a value.
|
|
type HashCallback func(string) string
|
|
|
|
// hashWalker implements interfaces for the reflectwalk package
|
|
// (github.com/mitchellh/reflectwalk) that can be used to automatically
|
|
// replace primitives with a hashed value.
|
|
type hashWalker struct {
|
|
// Callback is the function to call with the primitive that is
|
|
// 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
|
|
// 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
|
|
// we can quickly compare in hashWalker.Struct. We create an empty/invalid
|
|
// time.Time{} so we don't need to incur any additional startup cost vs.
|
|
// Now() or Unix().
|
|
var hashTimeType = reflect.TypeOf(time.Time{})
|
|
|
|
func (w *hashWalker) Enter(loc reflectwalk.Location) error {
|
|
w.loc = append(w.loc, loc)
|
|
return nil
|
|
}
|
|
|
|
func (w *hashWalker) Exit(loc reflectwalk.Location) error {
|
|
w.loc = w.loc[:len(w.loc)-1]
|
|
|
|
switch loc {
|
|
case reflectwalk.Map:
|
|
w.cs = w.cs[:len(w.cs)-1]
|
|
case reflectwalk.MapValue:
|
|
w.key = w.key[:len(w.key)-1]
|
|
w.csKey = w.csKey[:len(w.csKey)-1]
|
|
case reflectwalk.Slice:
|
|
w.cs = w.cs[:len(w.cs)-1]
|
|
case reflectwalk.SliceElem:
|
|
w.csKey = w.csKey[:len(w.csKey)-1]
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (w *hashWalker) Map(m reflect.Value) error {
|
|
w.cs = append(w.cs, m)
|
|
return nil
|
|
}
|
|
|
|
func (w *hashWalker) MapElem(m, k, v reflect.Value) error {
|
|
w.csKey = append(w.csKey, k)
|
|
w.key = append(w.key, k.String())
|
|
w.lastValue = v
|
|
return nil
|
|
}
|
|
|
|
func (w *hashWalker) Slice(s reflect.Value) error {
|
|
w.cs = append(w.cs, s)
|
|
return nil
|
|
}
|
|
|
|
func (w *hashWalker) SliceElem(i int, elem reflect.Value) error {
|
|
w.csKey = append(w.csKey, reflect.ValueOf(i))
|
|
return nil
|
|
}
|
|
|
|
func (w *hashWalker) Struct(v reflect.Value) error {
|
|
// We are looking for time values. If it isn't one, ignore it.
|
|
if v.Type() != hashTimeType {
|
|
return nil
|
|
}
|
|
|
|
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")
|
|
}
|
|
|
|
// 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.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
|
|
}
|
|
|
|
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[len(w.loc)-1] == reflectwalk.MapKey {
|
|
return nil
|
|
}
|
|
|
|
setV := v
|
|
|
|
// We only care about strings
|
|
if v.Kind() == reflect.Interface {
|
|
v = v.Elem()
|
|
}
|
|
if v.Kind() != reflect.String {
|
|
return nil
|
|
}
|
|
|
|
// See if the current key is part of the ignored keys
|
|
currentKey := w.key[len(w.key)-1]
|
|
if strutil.StrListContains(w.IgnoredKeys, currentKey) {
|
|
return nil
|
|
}
|
|
|
|
replaceVal := w.Callback(v.String())
|
|
|
|
resultVal := reflect.ValueOf(replaceVal)
|
|
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.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)
|
|
}
|
|
|
|
return nil
|
|
}
|