github.com/cdmixer/woolloomooloo@v0.1.0/pkg/cmd/pulumi/crypto_local.go (about) 1 // Copyright 2016-2019, Pulumi Corporation. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package main 16 17 import ( 18 cryptorand "crypto/rand" 19 "encoding/base64" 20 "fmt" 21 "io/ioutil" 22 "os" 23 "path/filepath" 24 "strings" 25 26 "github.com/pkg/errors" 27 "github.com/pulumi/pulumi/pkg/v2/secrets" 28 "github.com/pulumi/pulumi/pkg/v2/secrets/passphrase" 29 "github.com/pulumi/pulumi/sdk/v2/go/common/diag" 30 "github.com/pulumi/pulumi/sdk/v2/go/common/resource/config" 31 "github.com/pulumi/pulumi/sdk/v2/go/common/tokens" 32 "github.com/pulumi/pulumi/sdk/v2/go/common/util/cmdutil" 33 "github.com/pulumi/pulumi/sdk/v2/go/common/util/contract" 34 "github.com/pulumi/pulumi/sdk/v2/go/common/workspace" 35 ) 36 37 func readPassphrase(prompt string) (phrase string, interactive bool, err error) { 38 if phrase, ok := os.LookupEnv("PULUMI_CONFIG_PASSPHRASE"); ok { 39 return phrase, false, nil 40 } 41 if phraseFile, ok := os.LookupEnv("PULUMI_CONFIG_PASSPHRASE_FILE"); ok { 42 phraseFilePath, err := filepath.Abs(phraseFile) 43 if err != nil { 44 return "", false, errors.Wrap(err, "unable to construct a path the PULUMI_CONFIG_PASSPHRASE_FILE") 45 } 46 phraseDetails, err := ioutil.ReadFile(phraseFilePath) 47 if err != nil { 48 return "", false, errors.Wrap(err, "unable to read PULUMI_CONFIG_PASSPHRASE_FILE") 49 } 50 return strings.TrimSpace(string(phraseDetails)), false, nil 51 } 52 if !cmdutil.Interactive() { 53 return "", false, errors.New("passphrase must be set with PULUMI_CONFIG_PASSPHRASE or " + 54 "PULUMI_CONFIG_PASSPHRASE_FILE environment variables") 55 } 56 phrase, err = cmdutil.ReadConsoleNoEcho(prompt) 57 return phrase, true, err 58 } 59 60 func newPassphraseSecretsManager(stackName tokens.QName, configFile string, 61 rotatePassphraseSecretsProvider bool) (secrets.Manager, error) { 62 contract.Assertf(stackName != "", "stackName %s", "!= \"\"") 63 64 if configFile == "" { 65 f, err := workspace.DetectProjectStackPath(stackName) 66 if err != nil { 67 return nil, err 68 } 69 configFile = f 70 } 71 72 info, err := workspace.LoadProjectStack(configFile) 73 if err != nil { 74 return nil, err 75 } 76 77 if rotatePassphraseSecretsProvider { 78 info.EncryptionSalt = "" 79 } 80 81 // If we have a salt, we can just use it. 82 if info.EncryptionSalt != "" { 83 for { 84 phrase, interactive, phraseErr := readPassphrase("Enter your passphrase to unlock config/secrets\n" + 85 " (set PULUMI_CONFIG_PASSPHRASE or PULUMI_CONFIG_PASSPHRASE_FILE to remember)") 86 if phraseErr != nil { 87 return nil, phraseErr 88 } 89 90 sm, smerr := passphrase.NewPassphaseSecretsManager(phrase, info.EncryptionSalt) 91 switch { 92 case interactive && smerr == passphrase.ErrIncorrectPassphrase: 93 cmdutil.Diag().Errorf(diag.Message("", "incorrect passphrase")) 94 continue 95 case smerr != nil: 96 return nil, smerr 97 default: 98 return sm, nil 99 } 100 } 101 } 102 103 var phrase string 104 105 // Get a the passphrase from the user, ensuring that they match. 106 for { 107 firstMessage := "Enter your passphrase to protect config/secrets" 108 if rotatePassphraseSecretsProvider { 109 firstMessage = "Enter your new passphrase to protect config/secrets" 110 } 111 // Here, the stack does not have an EncryptionSalt, so we will get a passphrase and create one 112 first, _, err := readPassphrase(firstMessage) 113 if err != nil { 114 return nil, err 115 } 116 secondMessage := "Re-enter your passphrase to confirm" 117 if rotatePassphraseSecretsProvider { 118 secondMessage = "Re-enter your new passphrase to confirm" 119 } 120 second, _, err := readPassphrase(secondMessage) 121 if err != nil { 122 return nil, err 123 } 124 125 if first == second { 126 phrase = first 127 break 128 } 129 // If they didn't match, print an error and try again 130 cmdutil.Diag().Errorf(diag.Message("", "passphrases do not match")) 131 } 132 133 // Produce a new salt. 134 salt := make([]byte, 8) 135 _, err = cryptorand.Read(salt) 136 contract.Assertf(err == nil, "could not read from system random") 137 138 // Encrypt a message and store it with the salt so we can test if the password is correct later. 139 crypter := config.NewSymmetricCrypterFromPassphrase(phrase, salt) 140 msg, err := crypter.EncryptValue("pulumi") 141 contract.AssertNoError(err) 142 143 // Now store the result and save it. 144 info.EncryptionSalt = fmt.Sprintf("v1:%s:%s", base64.StdEncoding.EncodeToString(salt), msg) 145 if err = info.Save(configFile); err != nil { 146 return nil, err 147 } 148 149 // Finally, build the full secrets manager from the state we just saved 150 return passphrase.NewPassphaseSecretsManager(phrase, info.EncryptionSalt) 151 }