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  }