/
stack_change_secrets_provider.go
186 lines (161 loc) 路 5.97 KB
/
stack_change_secrets_provider.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
// Copyright 2016-2022, 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"
"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/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/workspace"
"github.com/spf13/cobra"
)
func newStackChangeSecretsProviderCmd() *cobra.Command {
var stack string
var 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 := commandContext()
opts := display.Options{
Color: cmdutil.GetGlobalColorization(),
}
project, _, err := readProject()
if err != nil {
return err
}
// Validate secrets provider type
if err := validateSecretsProvider(args[0]); err != nil {
return err
}
// Get the current stack and its project
currentStack, err := requireStack(ctx, stack, false, opts, false /*setCurrent*/)
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
currentConfig := currentProjectStack.Config
if currentConfig.HasSecureValue() {
dec, decerr := getStackDecrypter(currentStack)
if decerr != nil {
return decerr
}
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
// 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.Printf("Migrating old configuration and state to new secrets provider\n")
return migrateOldConfigAndCheckpointToNewSecretsProvider(ctx, project, currentStack, currentConfig, decrypter)
}),
}
cmd.PersistentFlags().StringVarP(
&stack, "stack", "s", "",
"The name of the stack to operate on. Defaults to the current stack")
return cmd
}
func migrateOldConfigAndCheckpointToNewSecretsProvider(ctx context.Context,
project *workspace.Project,
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
}
// 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.Copy(decrypter, newEncrypter)
if err != nil {
return err
}
// Reload the project stack after the new secretsProvider is in place
reloadedProjectStack, err := loadProjectStack(project, currentStack)
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, 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)
}