Do not prompt for passphrase multiple times

The change does two things:

- Reorders some calls in the CLI to prevent trying to create a secrets
  manager twice (which would end up prompting twice).

- Adds a cache inside the passphrase secrets manager such that when
  decrypting a deployment, we can re-use the one created earlier in
  the update. This is sort of a hack, but is needed because otherwise
  we would fail to decrypt the deployment, meaning that if you had a
  secret value in your deployment *and* you were using local
  passphrase encryption *and* you had not set PULUMI_CONFIG_PASSPHRASE
  you would get an error asking you to do so.

Fixes #2729
This commit is contained in:
Matt Ellis 2019-05-14 23:23:03 -07:00
parent 247edadd69
commit c91ddf996b
9 changed files with 69 additions and 35 deletions

View file

@ -2,6 +2,9 @@
### Improvements
- Pulumi no longer prompts for your passphrase twice during operations when you
are using the passphrase based secrets provider. (fixes [pulumi/pulumi#2729](https://github.com/pulumi/pulumi/issues/2729)).
## 0.17.11 (Released May 13, 2019)
### Major Changes

View file

@ -31,6 +31,7 @@ import (
"github.com/pulumi/pulumi/pkg/backend"
"github.com/pulumi/pulumi/pkg/backend/display"
"github.com/pulumi/pulumi/pkg/resource/config"
"github.com/pulumi/pulumi/pkg/secrets"
"github.com/pulumi/pulumi/pkg/tokens"
"github.com/pulumi/pulumi/pkg/util/cmdutil"
"github.com/pulumi/pulumi/pkg/workspace"
@ -533,7 +534,7 @@ func looksLikeSecret(k config.Key, v string) bool {
// getStackConfiguration loads configuration information for a given stack. If stackConfigFile is non empty,
// it is uses instead of the default configuration file for the stack
func getStackConfiguration(stack backend.Stack) (backend.StackConfiguration, error) {
func getStackConfiguration(stack backend.Stack, sm secrets.Manager) (backend.StackConfiguration, error) {
workspaceStack, err := loadProjectStack(stack)
if err != nil {
return backend.StackConfiguration{}, errors.Wrap(err, "loading stack configuration")
@ -549,7 +550,7 @@ func getStackConfiguration(stack backend.Stack) (backend.StackConfiguration, err
}, nil
}
crypter, err := getStackDencrypter(stack)
crypter, err := sm.Decrypter()
if err != nil {
return backend.StackConfiguration{}, errors.Wrap(err, "getting configuration decrypter")
}

View file

@ -98,16 +98,16 @@ func newDestroyCmd() *cobra.Command {
return result.FromError(errors.Wrap(err, "gathering environment metadata"))
}
cfg, err := getStackConfiguration(s)
if err != nil {
return result.FromError(errors.Wrap(err, "getting stack configuration"))
}
sm, err := getStackSecretsManager(s)
if err != nil {
return result.FromError(errors.Wrap(err, "getting secrets manager"))
}
cfg, err := getStackConfiguration(s, sm)
if err != nil {
return result.FromError(errors.Wrap(err, "getting stack configuration"))
}
opts.Engine = engine.UpdateOptions{
Analyzers: analyzers,
Parallel: parallel,

View file

@ -55,7 +55,12 @@ func newLogsCmd() *cobra.Command {
return err
}
cfg, err := getStackConfiguration(s)
sm, err := getStackSecretsManager(s)
if err != nil {
return errors.Wrap(err, "getting secrets manager")
}
cfg, err := getStackConfiguration(s, sm)
if err != nil {
return errors.Wrap(err, "getting stack configuration")
}

View file

@ -98,16 +98,16 @@ func newPreviewCmd() *cobra.Command {
return result.FromError(errors.Wrap(err, "gathering environment metadata"))
}
cfg, err := getStackConfiguration(s)
if err != nil {
return result.FromError(errors.Wrap(err, "getting stack configuration"))
}
sm, err := getStackSecretsManager(s)
if err != nil {
return result.FromError(errors.Wrap(err, "getting secrets manager"))
}
cfg, err := getStackConfiguration(s, sm)
if err != nil {
return result.FromError(errors.Wrap(err, "getting stack configuration"))
}
changes, res := s.Preview(commandContext(), backend.UpdateOperation{
Proj: proj,
Root: root,

View file

@ -64,7 +64,12 @@ func newQueryCmd() *cobra.Command {
return result.FromError(err)
}
cfg, err := getStackConfiguration(s)
sm, err := getStackSecretsManager(s)
if err != nil {
return result.FromError(errors.Wrap(err, "getting secrets manager"))
}
cfg, err := getStackConfiguration(s, sm)
if err != nil {
return result.FromError(errors.Wrap(err, "getting stack configuration"))
}
@ -76,6 +81,7 @@ func newQueryCmd() *cobra.Command {
Root: root,
Opts: opts,
StackConfiguration: cfg,
SecretsManager: sm,
Scopes: cancellationScopes,
})
switch {

View file

@ -99,16 +99,16 @@ func newRefreshCmd() *cobra.Command {
return result.FromError(errors.Wrap(err, "gathering environment metadata"))
}
cfg, err := getStackConfiguration(s)
if err != nil {
return result.FromError(errors.Wrap(err, "getting stack configuration"))
}
sm, err := getStackSecretsManager(s)
if err != nil {
return result.FromError(errors.Wrap(err, "getting secrets manager"))
}
cfg, err := getStackConfiguration(s, sm)
if err != nil {
return result.FromError(errors.Wrap(err, "getting stack configuration"))
}
opts.Engine = engine.UpdateOptions{
Analyzers: analyzers,
Parallel: parallel,

View file

@ -92,16 +92,16 @@ func newUpCmd() *cobra.Command {
return result.FromError(errors.Wrap(err, "gathering environment metadata"))
}
cfg, err := getStackConfiguration(s)
if err != nil {
return result.FromError(errors.Wrap(err, "getting stack configuration"))
}
sm, err := getStackSecretsManager(s)
if err != nil {
return result.FromError(errors.Wrap(err, "getting secrets manager"))
}
cfg, err := getStackConfiguration(s, sm)
if err != nil {
return result.FromError(errors.Wrap(err, "getting stack configuration"))
}
opts.Engine = engine.UpdateOptions{
Analyzers: analyzers,
Parallel: parallel,
@ -244,18 +244,16 @@ func newUpCmd() *cobra.Command {
return result.FromError(errors.Wrap(err, "gathering environment metadata"))
}
cfg, err := getStackConfiguration(s)
if err != nil {
return result.FromError(errors.Wrap(err, "getting stack configuration"))
}
// TODO(ellismg): Is there UX here what we want? Do we end up double prompting for a passphrase
// when using passphrase based secrets management?
sm, err := getStackSecretsManager(s)
if err != nil {
return result.FromError(errors.Wrap(err, "getting secrets manager"))
}
cfg, err := getStackConfiguration(s, sm)
if err != nil {
return result.FromError(errors.Wrap(err, "getting stack configuration"))
}
opts.Engine = engine.UpdateOptions{
Analyzers: analyzers,
Parallel: parallel,

View file

@ -18,6 +18,7 @@ import (
"encoding/json"
"os"
"strings"
"sync"
"github.com/pkg/errors"
@ -103,18 +104,38 @@ func (sm *localSecretsManager) Encrypter() (config.Encrypter, error) {
return sm.crypter, nil
}
var lock sync.Mutex
var cache map[string]secrets.Manager
func NewPassphaseSecretsManager(phrase string, state string) (secrets.Manager, error) {
// check the cache first, if we have an already seen this state before, return a cached value
lock.Lock()
if cache == nil {
cache = make(map[string]secrets.Manager)
}
cachedValue := cache[state]
lock.Unlock()
if cachedValue != nil {
return cachedValue, nil
}
// wasn't in the cache so try to construct it and add it if there's no error.
crypter, err := symmetricCrypterFromPhraseAndState(phrase, state)
if err != nil {
return nil, err
}
return &localSecretsManager{
lock.Lock()
defer lock.Unlock()
sm := &localSecretsManager{
crypter: crypter,
state: localSecretsManagerState{
Salt: state,
},
}, nil
}
cache[state] = sm
return sm, nil
}
type provider struct{}
@ -162,11 +183,11 @@ func newLockedPasspharseSecretsManager(state localSecretsManagerState) secrets.M
type errorCrypter struct{}
func (ec *errorCrypter) EncryptValue(v string) (string, error) {
return "", errors.New("failed to encrypt: incorrect passphrase, please set PULUMI_CONFIG_PASSPHRASE to the" +
return "", errors.New("failed to encrypt: incorrect passphrase, please set PULUMI_CONFIG_PASSPHRASE to the " +
"correct passphrase")
}
func (ec *errorCrypter) DecryptValue(v string) (string, error) {
return "", errors.New("failed to decrypt: incorrect passphrase, please set PULUMI_CONFIG_PASSPHRASE to the" +
return "", errors.New("failed to decrypt: incorrect passphrase, please set PULUMI_CONFIG_PASSPHRASE to the " +
"correct passphrase")
}