Ensure we can rotate pulumi passphrase secrets providers (#5865)
Fixes: #5452 When the user is requesting to change the secrets provider to a passphrase provider, we now calculate that has been requested. This means, we will prompt for a new passphrase for use in encrypting the stack. ``` pulumi stack change-secrets-provider passphrase Enter your passphrase to unlock config/secrets (set PULUMI_CONFIG_PASSPHRASE or PULUMI_CONFIG_PASSPHRASE_FILE to remember): Enter your new passphrase to protect config/secrets: Re-enter your new passphrase to confirm: Migrating old configuration and state to new secrets provider Enter your passphrase to unlock config/secrets (set PULUMI_CONFIG_PASSPHRASE or PULUMI_CONFIG_PASSPHRASE_FILE to remember): ```
This commit is contained in:
parent
5ab051cd97
commit
12dd0767ac
|
@ -9,6 +9,9 @@ CHANGELOG
|
|||
- Automatically install missing Python dependencies.
|
||||
[#5787](https://github.com/pulumi/pulumi/pull/5787)
|
||||
|
||||
- [cli] Ensure `pulumi stack change-secrets-provider` allows rotating the key for a passphrase provider
|
||||
[#5865](https://github.com/pulumi/pulumi/pull/5865/)
|
||||
|
||||
## 2.15.0 (2020-12-02)
|
||||
|
||||
- [sdk/python] Add deserialization support for enums.
|
||||
|
|
|
@ -58,12 +58,14 @@ func getStackSecretsManager(s backend.Stack) (secrets.Manager, error) {
|
|||
}
|
||||
|
||||
if ps.EncryptionSalt != "" {
|
||||
return newPassphraseSecretsManager(s.Ref().Name(), stackConfigFile)
|
||||
return newPassphraseSecretsManager(s.Ref().Name(), stackConfigFile,
|
||||
false /* rotatePassphraseSecretsProvider */)
|
||||
}
|
||||
|
||||
switch s.(type) {
|
||||
case filestate.Stack:
|
||||
return newPassphraseSecretsManager(s.Ref().Name(), stackConfigFile)
|
||||
return newPassphraseSecretsManager(s.Ref().Name(), stackConfigFile,
|
||||
false /* rotatePassphraseSecretsProvider */)
|
||||
case httpstate.Stack:
|
||||
return newServiceSecretsManager(s.(httpstate.Stack), s.Ref().Name(), stackConfigFile)
|
||||
}
|
||||
|
|
|
@ -57,7 +57,8 @@ func readPassphrase(prompt string) (phrase string, interactive bool, err error)
|
|||
return phrase, true, err
|
||||
}
|
||||
|
||||
func newPassphraseSecretsManager(stackName tokens.QName, configFile string) (secrets.Manager, error) {
|
||||
func newPassphraseSecretsManager(stackName tokens.QName, configFile string,
|
||||
rotatePassphraseSecretsProvider bool) (secrets.Manager, error) {
|
||||
contract.Assertf(stackName != "", "stackName %s", "!= \"\"")
|
||||
|
||||
if configFile == "" {
|
||||
|
@ -73,6 +74,10 @@ func newPassphraseSecretsManager(stackName tokens.QName, configFile string) (sec
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if rotatePassphraseSecretsProvider {
|
||||
info.EncryptionSalt = ""
|
||||
}
|
||||
|
||||
// If we have a salt, we can just use it.
|
||||
if info.EncryptionSalt != "" {
|
||||
for {
|
||||
|
@ -99,12 +104,20 @@ func newPassphraseSecretsManager(stackName tokens.QName, configFile string) (sec
|
|||
|
||||
// Get a the passphrase from the user, ensuring that they match.
|
||||
for {
|
||||
firstMessage := "Enter your passphrase to protect config/secrets"
|
||||
if rotatePassphraseSecretsProvider {
|
||||
firstMessage = "Enter your new passphrase to protect config/secrets"
|
||||
}
|
||||
// Here, the stack does not have an EncryptionSalt, so we will get a passphrase and create one
|
||||
first, _, err := readPassphrase("Enter your passphrase to protect config/secrets")
|
||||
first, _, err := readPassphrase(firstMessage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
second, _, err := readPassphrase("Re-enter your passphrase to confirm")
|
||||
secondMessage := "Re-enter your passphrase to confirm"
|
||||
if rotatePassphraseSecretsProvider {
|
||||
secondMessage = "Re-enter your new passphrase to confirm"
|
||||
}
|
||||
second, _, err := readPassphrase(secondMessage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ package main
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"fmt"
|
||||
"github.com/pulumi/pulumi/pkg/v2/backend"
|
||||
"github.com/pulumi/pulumi/pkg/v2/resource/stack"
|
||||
"github.com/pulumi/pulumi/sdk/v2/go/common/apitype"
|
||||
|
@ -91,66 +91,33 @@ func newStackChangeSecretsProviderCmd() *cobra.Command {
|
|||
decrypter = config.NewPanicCrypter()
|
||||
}
|
||||
|
||||
secretsProvider := args[0]
|
||||
rotatePassphraseProvider := secretsProvider == "passphrase"
|
||||
// Create the new secrets provider and set to the currentStack
|
||||
if err := createSecretsManager(b, currentStack.Ref(), args[0]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Change the config to use the new secrets provider
|
||||
err = migrateConfigToNewSecretsProvider(currentStack, currentConfig, decrypter)
|
||||
if err != nil {
|
||||
if err := createSecretsManager(b, currentStack.Ref(), secretsProvider, rotatePassphraseProvider); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Fixup the checkpoint
|
||||
return migrateCheckpointToNewSecretsProvider(commandContext(), currentStack)
|
||||
fmt.Printf("Migrating old configuration and state to new secrets provider\n")
|
||||
return migrateOldConfigAndCheckpointToNewSecretsProvider(commandContext(), currentStack, currentConfig, decrypter)
|
||||
}),
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func migrateCheckpointToNewSecretsProvider(ctx context.Context, currentStack backend.Stack) error {
|
||||
// Load the current checkpoint so those secrets can also be decrypted
|
||||
checkpoint, err := currentStack.ExportDeployment(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
snap, err := stack.DeserializeUntypedDeployment(checkpoint, stack.DefaultSecretsProvider)
|
||||
if err != nil {
|
||||
return checkDeploymentVersionError(err, currentStack.Ref().Name().String())
|
||||
}
|
||||
|
||||
func migrateOldConfigAndCheckpointToNewSecretsProvider(ctx context.Context, currentStack backend.Stack,
|
||||
currentConfig config.Map, decrypter config.Decrypter) error {
|
||||
// The order of operations here should be to load the secrets manager current stack
|
||||
// Get the newly created secrets manager for the stack
|
||||
newSecretsManager, err := getStackSecretsManager(currentStack)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Reserialize the Snapshopshot with the NewSecrets Manager
|
||||
reserializedDeployment, err := stack.SerializeDeployment(snap, newSecretsManager, false /*showSecrets*/)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bytes, err := json.Marshal(reserializedDeployment)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dep := apitype.UntypedDeployment{
|
||||
Version: apitype.DeploymentSchemaVersionCurrent,
|
||||
Deployment: bytes,
|
||||
}
|
||||
|
||||
// Import the newly changes Deployment
|
||||
return currentStack.ImportDeployment(ctx, &dep)
|
||||
}
|
||||
|
||||
func migrateConfigToNewSecretsProvider(currentStack backend.Stack, currentConfig config.Map,
|
||||
decrypter config.Decrypter) error {
|
||||
// Get the new encrypter for the current stack
|
||||
newEncrypter, err := getStackEncrypter(currentStack)
|
||||
// get the encrypter for the new secrets manager
|
||||
newEncrypter, err := newSecretsManager.Encrypter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -173,5 +140,36 @@ func migrateConfigToNewSecretsProvider(currentStack backend.Stack, currentConfig
|
|||
}
|
||||
}
|
||||
|
||||
return saveProjectStack(currentStack, reloadedProjectStack)
|
||||
if err := saveProjectStack(currentStack, reloadedProjectStack); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Load the current checkpoint so those secrets can also be decrypted
|
||||
checkpoint, err := currentStack.ExportDeployment(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
snap, err := stack.DeserializeUntypedDeployment(checkpoint, stack.DefaultSecretsProvider)
|
||||
if err != nil {
|
||||
return checkDeploymentVersionError(err, currentStack.Ref().Name().String())
|
||||
}
|
||||
|
||||
// Reserialize the Snapshopshot with the NewSecrets Manager
|
||||
reserializedDeployment, err := stack.SerializeDeployment(snap, newSecretsManager, false /*showSecrets*/)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bytes, err := json.Marshal(reserializedDeployment)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dep := apitype.UntypedDeployment{
|
||||
Version: apitype.DeploymentSchemaVersionCurrent,
|
||||
Deployment: bytes,
|
||||
}
|
||||
|
||||
// Import the newly changes Deployment
|
||||
return currentStack.ImportDeployment(ctx, &dep)
|
||||
}
|
||||
|
|
|
@ -119,11 +119,13 @@ func commandContext() context.Context {
|
|||
return ctx
|
||||
}
|
||||
|
||||
func createSecretsManager(b backend.Backend, stackRef backend.StackReference, secretsProvider string) error {
|
||||
func createSecretsManager(b backend.Backend, stackRef backend.StackReference, secretsProvider string,
|
||||
rotatePassphraseSecretsProvider 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
|
||||
// secrets provider or one of the cloud-backed secrets providers. We do not need to do this
|
||||
// for the Pulumi service backend secrets provider.
|
||||
// we have an explicit flag to rotate the secrets manager ONLY when it's a passphrase!
|
||||
isDefaultSecretsProvider := secretsProvider == "" || secretsProvider == "default"
|
||||
if _, ok := b.(filestate.Backend); ok && isDefaultSecretsProvider {
|
||||
// The default when using the filestate backend is the passphrase secrets provider
|
||||
|
@ -148,7 +150,8 @@ func createSecretsManager(b backend.Backend, stackRef backend.StackReference, se
|
|||
}
|
||||
|
||||
if secretsProvider == passphrase.Type {
|
||||
if _, pharseErr := newPassphraseSecretsManager(stackRef.Name(), stackConfigFile); pharseErr != nil {
|
||||
if _, pharseErr := newPassphraseSecretsManager(stackRef.Name(), stackConfigFile,
|
||||
rotatePassphraseSecretsProvider); pharseErr != nil {
|
||||
return pharseErr
|
||||
}
|
||||
} else if !isDefaultSecretsProvider {
|
||||
|
@ -194,7 +197,8 @@ func createStack(
|
|||
return nil, errors.Wrapf(err, "could not create stack")
|
||||
}
|
||||
|
||||
if err := createSecretsManager(b, stackRef, secretsProvider); err != nil {
|
||||
if err := createSecretsManager(b, stackRef, secretsProvider,
|
||||
false /* rotateSecretsManager */); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue