github.com/hashicorp/vault/sdk@v0.13.0/helper/custommetadata/custom_metadata.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package custommetadata 5 6 import ( 7 "fmt" 8 9 "github.com/hashicorp/go-multierror" 10 "github.com/hashicorp/go-secure-stdlib/strutil" 11 "github.com/mitchellh/mapstructure" 12 ) 13 14 // The following constants are used by Validate and are meant to be imposed 15 // broadly for consistency. 16 const ( 17 maxKeys = 64 18 maxKeyLength = 128 19 maxValueLength = 512 20 validationErrorPrefix = "custom_metadata validation failed" 21 ) 22 23 // Parse is used to effectively convert the TypeMap 24 // (map[string]interface{}) into a TypeKVPairs (map[string]string) 25 // which is how custom_metadata is stored. Defining custom_metadata 26 // as a TypeKVPairs will convert nulls into empty strings. A null, 27 // however, is essential for a PATCH operation in that it signals 28 // the handler to remove the field. The filterNils flag should 29 // only be used during a patch operation. 30 func Parse(raw map[string]interface{}, filterNils bool) (map[string]string, error) { 31 customMetadata := map[string]string{} 32 for k, v := range raw { 33 if filterNils && v == nil { 34 continue 35 } 36 37 var s string 38 if err := mapstructure.WeakDecode(v, &s); err != nil { 39 return nil, err 40 } 41 42 customMetadata[k] = s 43 } 44 45 return customMetadata, nil 46 } 47 48 // Validate will perform input validation for custom metadata. 49 // CustomMetadata should be arbitrary user-provided key-value pairs meant to 50 // provide supplemental information about a resource. If the key count 51 // exceeds maxKeys, the validation will be short-circuited to prevent 52 // unnecessary (and potentially costly) validation to be run. If the key count 53 // falls at or below maxKeys, multiple checks will be made per key and value. 54 // These checks include: 55 // - 0 < length of key <= maxKeyLength 56 // - 0 < length of value <= maxValueLength 57 // - keys and values cannot include unprintable characters 58 func Validate(cm map[string]string) error { 59 var errs *multierror.Error 60 61 if keyCount := len(cm); keyCount > maxKeys { 62 errs = multierror.Append(errs, fmt.Errorf("%s: payload must contain at most %d keys, provided %d", 63 validationErrorPrefix, 64 maxKeys, 65 keyCount)) 66 67 return errs.ErrorOrNil() 68 } 69 70 // Perform validation on each key and value and return ALL errors 71 for key, value := range cm { 72 if keyLen := len(key); 0 == keyLen || keyLen > maxKeyLength { 73 errs = multierror.Append(errs, fmt.Errorf("%s: length of key %q is %d but must be 0 < len(key) <= %d", 74 validationErrorPrefix, 75 key, 76 keyLen, 77 maxKeyLength)) 78 } 79 80 if valueLen := len(value); 0 == valueLen || valueLen > maxValueLength { 81 errs = multierror.Append(errs, fmt.Errorf("%s: length of value for key %q is %d but must be 0 < len(value) <= %d", 82 validationErrorPrefix, 83 key, 84 valueLen, 85 maxValueLength)) 86 } 87 88 if !strutil.Printable(key) { 89 // Include unquoted format (%s) to also include the string without the unprintable 90 // characters visible to allow for easier debug and key identification 91 errs = multierror.Append(errs, fmt.Errorf("%s: key %q (%s) contains unprintable characters", 92 validationErrorPrefix, 93 key, 94 key)) 95 } 96 97 if !strutil.Printable(value) { 98 errs = multierror.Append(errs, fmt.Errorf("%s: value for key %q contains unprintable characters", 99 validationErrorPrefix, 100 key)) 101 } 102 } 103 104 return errs.ErrorOrNil() 105 }