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  }