Skip to content

Commit

Permalink
Rotate cloud secrets provider
Browse files Browse the repository at this point in the history
  • Loading branch information
Frassle committed Dec 15, 2022
1 parent 33cd3e3 commit 1d1fe80
Show file tree
Hide file tree
Showing 7 changed files with 37 additions and 24 deletions.
@@ -0,0 +1,4 @@
changes:
- type: feat
scope: cli
description: Allow rotating the encrpytion key for cloud secrets.
12 changes: 5 additions & 7 deletions pkg/backend/filestate/crypto.go
Expand Up @@ -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())
Expand All @@ -36,24 +36,22 @@ func NewPassphraseSecretsManager(stackName tokens.Name, configFile string,
return nil, err
}

if rotatePassphraseSecretsProvider {
if rotateSecretsProvider {
info.EncryptionSalt = ""
}

// If there are any other secrets providers set in the config, remove them, as the passphrase
// provider deals only with EncryptionSalt, not EncryptedKey or SecretsProvider.
if info.EncryptedKey != "" || info.SecretsProvider != "" {
info.EncryptedKey = ""
info.SecretsProvider = ""
}
info.EncryptedKey = ""
info.SecretsProvider = ""

// If we have a salt, we can just use it.
if info.EncryptionSalt != "" {
return passphrase.NewPromptingPassphraseSecretsManager(info.EncryptionSalt)
}

// 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
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/cmd/pulumi/crypto.go
Expand Up @@ -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)
Expand Down
13 changes: 9 additions & 4 deletions pkg/cmd/pulumi/crypto_cloud.go
Expand Up @@ -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 {
Expand All @@ -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

Expand All @@ -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 {
Expand Down
8 changes: 4 additions & 4 deletions pkg/cmd/pulumi/crypto_cloud_test.go
Expand Up @@ -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)
})
Expand All @@ -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)
})
})
Expand Down
9 changes: 7 additions & 2 deletions pkg/cmd/pulumi/stack_change_secrets_provider.go
Expand Up @@ -95,9 +95,14 @@ func newStackChangeSecretsProviderCmd() *cobra.Command {
}

secretsProvider := args[0]
rotatePassphraseProvider := secretsProvider == "passphrase"
rotateProvider :=
// If we're setting the secrets provider to the same provider then do a rotation.
secretsProvider == currentProjectStack.SecretsProvider ||
// passphrase doesn't get saved to stack state, so if we're changing to passphrase see if
// the current secrets provider is empty
((secretsProvider == "passphrase") && (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
}
Expand Down
11 changes: 6 additions & 5 deletions pkg/cmd/pulumi/util.go
Expand Up @@ -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
Expand Down Expand Up @@ -190,14 +190,15 @@ func createSecretsManager(
}

if secretsProvider == passphrase.Type {
if _, pharseErr := filestate.NewPassphraseSecretsManager(stack.Ref().Name(), configFile,
rotatePassphraseSecretsProvider); pharseErr != nil {
return pharseErr
if _, phraseErr := filestate.NewPassphraseSecretsManager(stack.Ref().Name(),
configFile, rotateSecretsProvider); phraseErr != nil {
return phraseErr
}
} 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
}
}
Expand Down

0 comments on commit 1d1fe80

Please sign in to comment.