github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/core/secrets/secretvalue.go (about)

     1  // Copyright 2021 Canonical Ltd.
     2  // Licensed under the LGPLv3, see LICENCE file for details.
     3  
     4  package secrets
     5  
     6  import (
     7  	"bytes"
     8  	"encoding/base64"
     9  	"io"
    10  	"strings"
    11  
    12  	"github.com/juju/errors"
    13  )
    14  
    15  // SecretValue holds the value of a secret.
    16  // Instances of SecretValue are returned by a secret store
    17  // when a secret look up is performed. The underlying value
    18  // is a map of base64 encoded values represented as []byte.
    19  type SecretValue interface {
    20  	// EncodedValues returns the key values of a secret as
    21  	// the raw base64 encoded strings.
    22  	// For the special case where the secret only has a
    23  	// single key value "data", then use BinaryValue()
    24  	//to get the result.
    25  	EncodedValues() map[string]string
    26  
    27  	// Values returns the key values of a secret as strings.
    28  	// For the special case where the secret only has a
    29  	// single key value "data", then use StringValue()
    30  	//to get the result.
    31  	Values() (map[string]string, error)
    32  
    33  	// KeyValue returns the specified secret value for the key.
    34  	// If the key has a #base64 suffix, the returned value is base64 encoded.
    35  	KeyValue(string) (string, error)
    36  
    37  	// IsEmpty checks if the value is empty.
    38  	IsEmpty() bool
    39  }
    40  
    41  type secretValue struct {
    42  	// Data holds the key values of a secret.
    43  	// We use a map to hold multiple values, eg cert and key
    44  	// The serialised form of any string values is a
    45  	// base64 encoded string, representing arbitrary values.
    46  	data map[string][]byte
    47  }
    48  
    49  // NewSecretValue returns a secret using the specified map of values.
    50  // The map values are assumed to be already base64 encoded.
    51  func NewSecretValue(data map[string]string) SecretValue {
    52  	dataCopy := make(map[string][]byte, len(data))
    53  	for k, v := range data {
    54  		dataCopy[k] = append([]byte(nil), v...)
    55  	}
    56  	return &secretValue{data: dataCopy}
    57  }
    58  
    59  // NewSecretBytes returns a secret using the specified map of values.
    60  // The map values are assumed to be already base64 encoded.
    61  func NewSecretBytes(data map[string][]byte) SecretValue {
    62  	dataCopy := make(map[string][]byte, len(data))
    63  	for k, v := range data {
    64  		dataCopy[k] = append([]byte(nil), v...)
    65  	}
    66  	return &secretValue{data: dataCopy}
    67  }
    68  
    69  // IsEmpty checks if the value is empty.
    70  func (v secretValue) IsEmpty() bool {
    71  	return len(v.data) == 0
    72  }
    73  
    74  // EncodedValues implements SecretValue.
    75  func (v *secretValue) EncodedValues() map[string]string {
    76  	dataCopy := make(map[string]string, len(v.data))
    77  	for k, val := range v.data {
    78  		dataCopy[k] = string(val)
    79  	}
    80  	return dataCopy
    81  }
    82  
    83  // Values implements SecretValue.
    84  func (v *secretValue) Values() (map[string]string, error) {
    85  	dataCopy := v.EncodedValues()
    86  	for k, v := range dataCopy {
    87  		data, err := base64.StdEncoding.DecodeString(v)
    88  		if err != nil {
    89  			return nil, errors.Trace(err)
    90  		}
    91  		dataCopy[k] = string(data)
    92  	}
    93  	return dataCopy, nil
    94  }
    95  
    96  // KeyValue implements SecretValue.
    97  func (v *secretValue) KeyValue(key string) (string, error) {
    98  	useBase64 := false
    99  	if strings.HasSuffix(key, base64Suffix) {
   100  		key = strings.TrimSuffix(key, base64Suffix)
   101  		useBase64 = true
   102  	}
   103  	val, ok := v.data[key]
   104  	if !ok {
   105  		return "", errors.NotFoundf("secret key value %q", key)
   106  	}
   107  	// The stored value is always base64 encoded.
   108  	if useBase64 {
   109  		return string(val), nil
   110  	}
   111  	b64 := base64.NewDecoder(base64.StdEncoding, bytes.NewReader(val))
   112  	result, err := io.ReadAll(b64)
   113  	if err != nil {
   114  		return "", errors.Trace(err)
   115  	}
   116  	return string(result), nil
   117  }