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