mirror of https://github.com/pulumi/pulumi.git
218 lines
6.9 KiB
Go
218 lines
6.9 KiB
Go
// Copyright 2016-2023, Pulumi Corporation.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
|
|
"github.com/pulumi/pulumi/pkg/v3/backend"
|
|
"github.com/pulumi/pulumi/pkg/v3/backend/display"
|
|
"github.com/pulumi/pulumi/pkg/v3/resource/stack"
|
|
"github.com/pulumi/pulumi/pkg/v3/secrets"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/apitype"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/resource/config"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/util/cmdutil"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
type stackChangeSecretsProviderCmd struct {
|
|
stdout io.Writer
|
|
|
|
stack string
|
|
|
|
secretsProvider secrets.Provider
|
|
}
|
|
|
|
func newStackChangeSecretsProviderCmd() *cobra.Command {
|
|
var scspcmd stackChangeSecretsProviderCmd
|
|
cmd := &cobra.Command{
|
|
Use: "change-secrets-provider <new-secrets-provider>",
|
|
Args: cmdutil.ExactArgs(1),
|
|
Short: "Change the secrets provider for a stack",
|
|
Long: "Change the secrets provider for a stack. " +
|
|
"Valid secret providers types are `default`, `passphrase`, `awskms`, `azurekeyvault`, `gcpkms`, `hashivault`.\n\n" +
|
|
"To change to using the Pulumi Default Secrets Provider, use the following:\n" +
|
|
"\n" +
|
|
"pulumi stack change-secrets-provider default" +
|
|
"\n" +
|
|
"\n" +
|
|
"To change the stack to use a cloud secrets backend, use one of the following:\n" +
|
|
"\n" +
|
|
"* `pulumi stack change-secrets-provider \"awskms://alias/ExampleAlias?region=us-east-1\"" +
|
|
"`\n" +
|
|
"* `pulumi stack change-secrets-provider " +
|
|
"\"awskms://1234abcd-12ab-34cd-56ef-1234567890ab?region=us-east-1\"`\n" +
|
|
"* `pulumi stack change-secrets-provider " +
|
|
"\"azurekeyvault://mykeyvaultname.vault.azure.net/keys/mykeyname\"`\n" +
|
|
"* `pulumi stack change-secrets-provider " +
|
|
"\"gcpkms://projects/<p>/locations/<l>/keyRings/<r>/cryptoKeys/<k>\"`\n" +
|
|
"* `pulumi stack change-secrets-provider \"hashivault://mykey\"`",
|
|
Run: cmdutil.RunFunc(func(cmd *cobra.Command, args []string) error {
|
|
ctx := cmd.Context()
|
|
return scspcmd.Run(ctx, args)
|
|
}),
|
|
}
|
|
|
|
cmd.PersistentFlags().StringVarP(
|
|
&scspcmd.stack, "stack", "s", "",
|
|
"The name of the stack to operate on. Defaults to the current stack")
|
|
|
|
return cmd
|
|
}
|
|
|
|
func (cmd *stackChangeSecretsProviderCmd) Run(ctx context.Context, args []string) error {
|
|
stdout := cmd.stdout
|
|
if stdout == nil {
|
|
stdout = os.Stdout
|
|
}
|
|
if cmd.secretsProvider == nil {
|
|
cmd.secretsProvider = stack.DefaultSecretsProvider
|
|
}
|
|
|
|
opts := display.Options{
|
|
Color: cmdutil.GetGlobalColorization(),
|
|
}
|
|
|
|
if err := validateSecretsProvider(args[0]); err != nil {
|
|
return err
|
|
}
|
|
|
|
project, _, err := readProject()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Get the current stack and its project
|
|
currentStack, err := requireStack(ctx, cmd.stack, stackLoadOnly, opts)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
currentProjectStack, err := loadProjectStack(project, currentStack)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Build decrypter based on the existing secrets provider
|
|
var decrypter config.Decrypter
|
|
if currentProjectStack.Config.HasSecureValue() {
|
|
dec, needsSave, decerr := getStackDecrypter(currentStack, currentProjectStack)
|
|
if decerr != nil {
|
|
return decerr
|
|
}
|
|
contract.Assertf(!needsSave, "We're reading a secure value so the encryption information must be present already")
|
|
decrypter = dec
|
|
} else {
|
|
decrypter = config.NewPanicCrypter()
|
|
}
|
|
|
|
secretsProvider := args[0]
|
|
// If we're setting the secrets provider to the same provider then do a rotation.
|
|
rotateProvider := 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, rotateProvider,
|
|
false /*creatingStack*/); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Fixup the checkpoint
|
|
fmt.Fprintf(stdout, "Migrating old configuration and state to new secrets provider\n")
|
|
return migrateOldConfigAndCheckpointToNewSecretsProvider(
|
|
ctx, cmd.secretsProvider, project, currentStack, currentProjectStack, decrypter)
|
|
}
|
|
|
|
func migrateOldConfigAndCheckpointToNewSecretsProvider(ctx context.Context,
|
|
secretsProvider secrets.Provider,
|
|
project *workspace.Project,
|
|
currentStack backend.Stack,
|
|
currentConfig *workspace.ProjectStack, decrypter config.Decrypter,
|
|
) error {
|
|
// Reload the project stack after the new secrets provider is in place
|
|
reloadedProjectStack, err := loadProjectStack(project, currentStack)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Get the newly created secrets manager for the stack
|
|
newSecretsManager, needsSave, err := getStackSecretsManager(currentStack, reloadedProjectStack, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
contract.Assertf(
|
|
!needsSave,
|
|
"We've just saved and reloaded the stack, so the encryption information must be present already")
|
|
|
|
// get the encrypter for the new secrets manager
|
|
newEncrypter, err := newSecretsManager.Encrypter()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Create a copy of the current config map and re-encrypt using the new secrets provider
|
|
newProjectConfig, err := currentConfig.Config.Copy(decrypter, newEncrypter)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for key, val := range newProjectConfig {
|
|
if err := reloadedProjectStack.Config.Set(key, val, false); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
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(ctx, checkpoint, secretsProvider)
|
|
if err != nil {
|
|
return checkDeploymentVersionError(err, currentStack.Ref().Name().String())
|
|
}
|
|
|
|
// Reserialize the Snapshopshot with the NewSecrets Manager
|
|
snap.SecretsManager = newSecretsManager
|
|
reserializedDeployment, err := stack.SerializeDeployment(ctx, snap, 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)
|
|
}
|