github.com/myhau/pulumi/pkg/v3@v3.70.2-0.20221116134521-f2775972e587/resource/stack/secrets.go (about)

     1  // Copyright 2016-2022, Pulumi Corporation.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package stack
    16  
    17  import (
    18  	"context"
    19  	"encoding/json"
    20  	"fmt"
    21  
    22  	"github.com/pulumi/pulumi/pkg/v3/secrets"
    23  	"github.com/pulumi/pulumi/pkg/v3/secrets/b64"
    24  	"github.com/pulumi/pulumi/pkg/v3/secrets/cloud"
    25  	"github.com/pulumi/pulumi/pkg/v3/secrets/passphrase"
    26  	"github.com/pulumi/pulumi/pkg/v3/secrets/service"
    27  	"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
    28  	"github.com/pulumi/pulumi/sdk/v3/go/common/resource/config"
    29  )
    30  
    31  // DefaultSecretsProvider is the default SecretsProvider to use when deserializing deployments.
    32  var DefaultSecretsProvider SecretsProvider = &defaultSecretsProvider{}
    33  
    34  // SecretsProvider allows for the creation of secrets managers based on a well-known type name.
    35  type SecretsProvider interface {
    36  	// OfType returns a secrets manager for the given type, initialized with its previous state.
    37  	OfType(ty string, state json.RawMessage) (secrets.Manager, error)
    38  }
    39  
    40  // defaultSecretsProvider implements the secrets.ManagerProviderFactory interface. Essentially
    41  // it is the global location where new secrets managers can be registered for use when
    42  // decrypting checkpoints.
    43  type defaultSecretsProvider struct{}
    44  
    45  // OfType returns a secrets manager for the given secrets type. Returns an error
    46  // if the type is uknown or the state is invalid.
    47  func (defaultSecretsProvider) OfType(ty string, state json.RawMessage) (secrets.Manager, error) {
    48  	var sm secrets.Manager
    49  	var err error
    50  	switch ty {
    51  	case b64.Type:
    52  		sm = b64.NewBase64SecretsManager()
    53  	case passphrase.Type:
    54  		sm, err = passphrase.NewPromptingPassphaseSecretsManagerFromState(state)
    55  	case service.Type:
    56  		sm, err = service.NewServiceSecretsManagerFromState(state)
    57  	case cloud.Type:
    58  		sm, err = cloud.NewCloudSecretsManagerFromState(state)
    59  	default:
    60  		return nil, fmt.Errorf("no known secrets provider for type %q", ty)
    61  	}
    62  	if err != nil {
    63  		return nil, fmt.Errorf("constructing secrets manager of type %q: %w", ty, err)
    64  	}
    65  
    66  	return NewCachingSecretsManager(sm), nil
    67  }
    68  
    69  type cacheEntry struct {
    70  	plaintext  string
    71  	ciphertext string
    72  }
    73  
    74  type cachingSecretsManager struct {
    75  	manager secrets.Manager
    76  	cache   map[*resource.Secret]cacheEntry
    77  }
    78  
    79  // NewCachingSecretsManager returns a new secrets.Manager that caches the ciphertext for secret property values. A
    80  // secrets.Manager that will be used to encrypt and decrypt values stored in a serialized deployment can be wrapped
    81  // in a caching secrets manager in order to avoid re-encrypting secrets each time the deployment is serialized.
    82  func NewCachingSecretsManager(manager secrets.Manager) secrets.Manager {
    83  	return &cachingSecretsManager{
    84  		manager: manager,
    85  		cache:   make(map[*resource.Secret]cacheEntry),
    86  	}
    87  }
    88  
    89  func (csm *cachingSecretsManager) Type() string {
    90  	return csm.manager.Type()
    91  }
    92  
    93  func (csm *cachingSecretsManager) State() interface{} {
    94  	return csm.manager.State()
    95  }
    96  
    97  func (csm *cachingSecretsManager) Encrypter() (config.Encrypter, error) {
    98  	enc, err := csm.manager.Encrypter()
    99  	if err != nil {
   100  		return nil, err
   101  	}
   102  	return &cachingCrypter{
   103  		encrypter: enc,
   104  		cache:     csm.cache,
   105  	}, nil
   106  }
   107  
   108  func (csm *cachingSecretsManager) Decrypter() (config.Decrypter, error) {
   109  	dec, err := csm.manager.Decrypter()
   110  	if err != nil {
   111  		return nil, err
   112  	}
   113  	return &cachingCrypter{
   114  		decrypter: dec,
   115  		cache:     csm.cache,
   116  	}, nil
   117  }
   118  
   119  type cachingCrypter struct {
   120  	encrypter config.Encrypter
   121  	decrypter config.Decrypter
   122  	cache     map[*resource.Secret]cacheEntry
   123  }
   124  
   125  func (c *cachingCrypter) EncryptValue(ctx context.Context, plaintext string) (string, error) {
   126  	return c.encrypter.EncryptValue(ctx, plaintext)
   127  }
   128  
   129  func (c *cachingCrypter) DecryptValue(ctx context.Context, ciphertext string) (string, error) {
   130  	return c.decrypter.DecryptValue(ctx, ciphertext)
   131  }
   132  
   133  func (c *cachingCrypter) BulkDecrypt(ctx context.Context, ciphertexts []string) (map[string]string, error) {
   134  	return c.decrypter.BulkDecrypt(ctx, ciphertexts)
   135  }
   136  
   137  // encryptSecret encrypts the plaintext associated with the given secret value.
   138  func (c *cachingCrypter) encryptSecret(secret *resource.Secret, plaintext string) (string, error) {
   139  	ctx := context.TODO()
   140  
   141  	// If the cache has an entry for this secret and the plaintext has not changed, re-use the ciphertext.
   142  	//
   143  	// Otherwise, re-encrypt the plaintext and update the cache.
   144  	entry, ok := c.cache[secret]
   145  	if ok && entry.plaintext == plaintext {
   146  		return entry.ciphertext, nil
   147  	}
   148  	ciphertext, err := c.encrypter.EncryptValue(ctx, plaintext)
   149  	if err != nil {
   150  		return "", err
   151  	}
   152  	c.insert(secret, plaintext, ciphertext)
   153  	return ciphertext, nil
   154  }
   155  
   156  // insert associates the given secret with the given plain- and ciphertext in the cache.
   157  func (c *cachingCrypter) insert(secret *resource.Secret, plaintext, ciphertext string) {
   158  	c.cache[secret] = cacheEntry{plaintext, ciphertext}
   159  }
   160  
   161  // mapDecrypter is a Decrypter with a preloaded cache. This decrypter is used specifically for deserialization,
   162  // where the deserializer is expected to prime the cache by scanning each resource for secrets, then decrypting all
   163  // of the discovered secrets en masse. Although each call to Decrypt _should_ hit the cache, a mapDecrypter does
   164  // carry an underlying Decrypter in the event that a secret was missed.
   165  //
   166  // Note that this is intentionally separate from cachingCrypter. A cachingCrypter is intended to prevent repeated
   167  // encryption of secrets when the same snapshot is repeatedly serialized over the lifetime of an update, and
   168  // therefore keys on the identity of the secret value itself. A mapDecrypter is intended to allow the deserializer
   169  // to decrypt secrets up-front and prevent repeated calls to decrypt within the context of a single deserialization,
   170  // and cannot key off of secret identity because secrets do not exist when the cache is initialized.
   171  type mapDecrypter struct {
   172  	decrypter config.Decrypter
   173  	cache     map[string]string
   174  }
   175  
   176  func newMapDecrypter(decrypter config.Decrypter, cache map[string]string) config.Decrypter {
   177  	return &mapDecrypter{decrypter: decrypter, cache: cache}
   178  }
   179  
   180  func (c *mapDecrypter) DecryptValue(ctx context.Context, ciphertext string) (string, error) {
   181  	if plaintext, ok := c.cache[ciphertext]; ok {
   182  		return plaintext, nil
   183  	}
   184  
   185  	// The value is not currently in the cache. Decrypt it and add it to the cache.
   186  	plaintext, err := c.decrypter.DecryptValue(ctx, ciphertext)
   187  	if err != nil {
   188  		return "", err
   189  	}
   190  
   191  	if c.cache == nil {
   192  		c.cache = make(map[string]string)
   193  	}
   194  	c.cache[ciphertext] = plaintext
   195  
   196  	return plaintext, nil
   197  }
   198  
   199  func (c *mapDecrypter) BulkDecrypt(ctx context.Context, ciphertexts []string) (map[string]string, error) {
   200  	// Loop and find the entries that are already cached, then BulkDecrypt the rest
   201  	secretMap := map[string]string{}
   202  	var toDecrypt []string
   203  	if c.cache == nil {
   204  		// Don't bother searching for the cached subset if the cache is nil
   205  		toDecrypt = ciphertexts
   206  	} else {
   207  		toDecrypt = make([]string, 0)
   208  		for _, ct := range ciphertexts {
   209  			if plaintext, ok := c.cache[ct]; ok {
   210  				secretMap[ct] = plaintext
   211  			} else {
   212  				toDecrypt = append(toDecrypt, ct)
   213  			}
   214  		}
   215  	}
   216  
   217  	// try and bulk decrypt the rest
   218  	decrypted, err := c.decrypter.BulkDecrypt(ctx, toDecrypt)
   219  	if err != nil {
   220  		return nil, err
   221  	}
   222  
   223  	// And add them to the cache
   224  	if c.cache == nil {
   225  		c.cache = make(map[string]string)
   226  	}
   227  
   228  	for ct, pt := range decrypted {
   229  		secretMap[ct] = pt
   230  		c.cache[ct] = pt
   231  	}
   232  
   233  	return secretMap, nil
   234  }