diff --git a/changelog/pending/20221206--cli--allow-rotating-the-encrpytion-key-for-cloud-secrets.yaml b/changelog/pending/20221206--cli--allow-rotating-the-encrpytion-key-for-cloud-secrets.yaml new file mode 100644 index 000000000000..b1e4887d46f9 --- /dev/null +++ b/changelog/pending/20221206--cli--allow-rotating-the-encrpytion-key-for-cloud-secrets.yaml @@ -0,0 +1,4 @@ +changes: +- type: feat + scope: cli + description: Allow rotating the encrpytion key for cloud secrets. diff --git a/pkg/backend/filestate/crypto.go b/pkg/backend/filestate/crypto.go index 7379ea47dcbe..1265b57923b1 100644 --- a/pkg/backend/filestate/crypto.go +++ b/pkg/backend/filestate/crypto.go @@ -23,7 +23,7 @@ import ( ) func NewPassphraseSecretsManager(stackName tokens.Name, configFile string, - rotatePassphraseSecretsProvider bool) (secrets.Manager, error) { + rotateSecretsProvider bool) (secrets.Manager, error) { contract.Assertf(stackName != "", "stackName %s", "!= \"\"") project, _, err := workspace.DetectProjectStackPath(stackName.Q()) @@ -36,7 +36,7 @@ func NewPassphraseSecretsManager(stackName tokens.Name, configFile string, return nil, err } - if rotatePassphraseSecretsProvider { + if rotateSecretsProvider { info.EncryptionSalt = "" } @@ -53,7 +53,7 @@ func NewPassphraseSecretsManager(stackName tokens.Name, configFile string, } // Otherwise, prompt the user for a new passphrase. - salt, sm, err := passphrase.PromptForNewPassphrase(rotatePassphraseSecretsProvider) + salt, sm, err := passphrase.PromptForNewPassphrase(rotateSecretsProvider) if err != nil { return nil, err } diff --git a/pkg/cmd/pulumi/crypto.go b/pkg/cmd/pulumi/crypto.go index 17d26a0bc4b2..bec7370aee5f 100644 --- a/pkg/cmd/pulumi/crypto.go +++ b/pkg/cmd/pulumi/crypto.go @@ -64,11 +64,11 @@ func getStackSecretsManager(s backend.Stack) (secrets.Manager, error) { // nolint: goconst if ps.SecretsProvider != passphrase.Type && ps.SecretsProvider != "default" && ps.SecretsProvider != "" { - return newCloudSecretsManager(s.Ref().Name(), configFile, ps.SecretsProvider) + return newCloudSecretsManager(s.Ref().Name(), configFile, ps.SecretsProvider, false /* rotateSecretsProvider */) } if ps.EncryptionSalt != "" { - return filestate.NewPassphraseSecretsManager(s.Ref().Name(), configFile, false /* rotatePassphraseSecretsProvider */) + return filestate.NewPassphraseSecretsManager(s.Ref().Name(), configFile, false /* rotateSecretsProvider */) } return s.DefaultSecretManager(configFile) diff --git a/pkg/cmd/pulumi/crypto_cloud.go b/pkg/cmd/pulumi/crypto_cloud.go index 8b49cf3b09c2..81c2a231562a 100644 --- a/pkg/cmd/pulumi/crypto_cloud.go +++ b/pkg/cmd/pulumi/crypto_cloud.go @@ -25,7 +25,9 @@ import ( "github.com/pulumi/pulumi/sdk/v3/go/common/workspace" ) -func newCloudSecretsManager(stackName tokens.Name, configFile, secretsProvider string) (secrets.Manager, error) { +func newCloudSecretsManager(stackName tokens.Name, configFile, + secretsProvider string, rotateSecretsProvider bool) (secrets.Manager, error) { + contract.Assertf(stackName != "", "stackName %s", "!= \"\"") proj, _, err := workspace.DetectProjectStackPath(stackName.Q()) if err != nil { @@ -40,9 +42,7 @@ func newCloudSecretsManager(stackName tokens.Name, configFile, secretsProvider s // Only a passphrase provider has an encryption salt. So changing a secrets provider // from passphrase to a cloud secrets provider should ensure that we remove the enryptionsalt // as it's a legacy artifact and needs to be removed - if info.EncryptionSalt != "" { - info.EncryptionSalt = "" - } + info.EncryptionSalt = "" var secretsManager *cloud.Manager @@ -53,6 +53,11 @@ func newCloudSecretsManager(stackName tokens.Name, configFile, secretsProvider s secretsProvider = override } + // If we're rotating then just clear the key so we create a fresh one below + if rotateSecretsProvider { + info.EncryptedKey = "" + } + // if there is no key OR the secrets provider is changing // then we need to generate the new key based on the new secrets provider if info.EncryptedKey == "" || info.SecretsProvider != secretsProvider { diff --git a/pkg/cmd/pulumi/crypto_cloud_test.go b/pkg/cmd/pulumi/crypto_cloud_test.go index d609ff58f78f..5dc8bba543b2 100644 --- a/pkg/cmd/pulumi/crypto_cloud_test.go +++ b/pkg/cmd/pulumi/crypto_cloud_test.go @@ -64,10 +64,10 @@ func TestSecretsProviderOverride(t *testing.T) { t.Run("without override", func(t *testing.T) { createTempFiles(t, files, func() { opener.wantURL = "test://foo" - _, createSecretsManagerError := newCloudSecretsManager(stackName, stackConfigFileName, "test://foo") + _, createSecretsManagerError := newCloudSecretsManager(stackName, stackConfigFileName, "test://foo", false) assert.Nil(t, createSecretsManagerError, "Creating the cloud secret manager should succeed") - _, createSecretsManagerError = newCloudSecretsManager(stackName, stackConfigFileName, "test://bar") + _, createSecretsManagerError = newCloudSecretsManager(stackName, stackConfigFileName, "test://bar", false) msg := "newCloudSecretsManager with unexpected secretsProvider URL succeeded, expected an error" assert.NotNil(t, createSecretsManagerError, msg) }) @@ -82,9 +82,9 @@ func TestSecretsProviderOverride(t *testing.T) { // Last argument here shouldn't matter anymore, since it gets overridden // by the env var. Both calls should succeed. msg := "creating the secrets manager should succeed regardless of secrets provider" - _, createSecretsManagerError := newCloudSecretsManager(stackName, stackConfigFileName, "test://foo") + _, createSecretsManagerError := newCloudSecretsManager(stackName, stackConfigFileName, "test://foo", false) assert.Nil(t, createSecretsManagerError, msg) - _, createSecretsManagerError = newCloudSecretsManager(stackName, stackConfigFileName, "test://bar") + _, createSecretsManagerError = newCloudSecretsManager(stackName, stackConfigFileName, "test://bar", false) assert.Nil(t, createSecretsManagerError, msg) }) }) diff --git a/pkg/cmd/pulumi/stack_change_secrets_provider.go b/pkg/cmd/pulumi/stack_change_secrets_provider.go index d13e0ac9443f..3ad3532e39ea 100644 --- a/pkg/cmd/pulumi/stack_change_secrets_provider.go +++ b/pkg/cmd/pulumi/stack_change_secrets_provider.go @@ -95,9 +95,10 @@ func newStackChangeSecretsProviderCmd() *cobra.Command { } secretsProvider := args[0] - rotatePassphraseProvider := secretsProvider == "passphrase" + // If we're setting the secrets provider to the same provider then do a rotation. + rotateProvider := secretsProvider == currentProjectStack.SecretsProvider // Create the new secrets provider and set to the currentStack - if err := createSecretsManager(ctx, currentStack, secretsProvider, rotatePassphraseProvider, + if err := createSecretsManager(ctx, currentStack, secretsProvider, rotateProvider, false /*creatingStack*/); err != nil { return err } diff --git a/pkg/cmd/pulumi/util.go b/pkg/cmd/pulumi/util.go index c025129353f1..26e4a4e05be0 100644 --- a/pkg/cmd/pulumi/util.go +++ b/pkg/cmd/pulumi/util.go @@ -161,7 +161,7 @@ func commandContext() context.Context { func createSecretsManager( ctx context.Context, stack backend.Stack, secretsProvider string, - rotatePassphraseSecretsProvider, creatingStack bool) error { + rotateSecretsProvider, creatingStack bool) error { // As part of creating the stack, we also need to configure the secrets provider for the stack. // We need to do this configuration step for cases where we will be using with the passphrase @@ -190,14 +190,15 @@ func createSecretsManager( } if secretsProvider == passphrase.Type { - if _, pharseErr := filestate.NewPassphraseSecretsManager(stack.Ref().Name(), configFile, - rotatePassphraseSecretsProvider); pharseErr != nil { + if _, pharseErr := filestate.NewPassphraseSecretsManager(stack.Ref().Name(), + configFile, rotateSecretsProvider); pharseErr != nil { return pharseErr } } else { // All other non-default secrets providers are handled by the cloud secrets provider which // uses a URL schema to identify the provider - if _, secretsErr := newCloudSecretsManager(stack.Ref().Name(), configFile, secretsProvider); secretsErr != nil { + if _, secretsErr := newCloudSecretsManager(stack.Ref().Name(), + configFile, secretsProvider, rotateSecretsProvider); secretsErr != nil { return secretsErr } }