Skip to content

Commit

Permalink
Add 'secret' to config
Browse files Browse the repository at this point in the history
Also separate the validation and merging of project-to-stack values, to
allow us to apply in values even if they're secure and we don't have an
available decrypter. We can't validate that they're all correct, but it
means at least `config get` can do a best effort retrival for config
values.
  • Loading branch information
Frassle committed Oct 24, 2022
1 parent 7711564 commit a4ae636
Show file tree
Hide file tree
Showing 16 changed files with 286 additions and 90 deletions.
@@ -0,0 +1,4 @@
changes:
- type: fix
scope: engine
description: Fix type validation of stack config with secure values.
23 changes: 12 additions & 11 deletions pkg/cmd/pulumi/config.go
Expand Up @@ -794,21 +794,21 @@ func listConfig(ctx context.Context,
stackName := stack.Ref().Name().String()
// when listing configuration values
// also show values coming from the project
configError := workspace.ValidateStackConfigAndApplyProjectConfig(stackName, project, ps.Config)
if configError != nil {
return configError
err = workspace.ApplyProjectConfig(stackName, project, ps.Config)
if err != nil {
return err
}

cfg := ps.Config

// By default, we will use a blinding decrypter to show "[secret]". If requested, display secrets in plaintext.
decrypter := config.NewBlindingDecrypter()
if cfg.HasSecureValue() && showSecrets {
dec, decerr := getStackDecrypter(stack)
if decerr != nil {
return decerr
stackDecrypter, err := getStackDecrypter(stack)
if err != nil {
return err
}
decrypter = dec
decrypter = stackDecrypter
}

var keys config.KeyArray
Expand Down Expand Up @@ -887,12 +887,13 @@ func getConfig(ctx context.Context, stack backend.Stack, key config.Key, path, j
if err != nil {
return err
}

stackName := stack.Ref().Name().String()
configError := workspace.ValidateStackConfigAndApplyProjectConfig(stackName, project, ps.Config)
if configError != nil {
return configError
// when asking for a configuration value, include values from the project config
err = workspace.ApplyProjectConfig(stackName, project, ps.Config)
if err != nil {
return err
}

cfg := ps.Config

v, ok, err := cfg.Get(key, path)
Expand Down
7 changes: 6 additions & 1 deletion pkg/cmd/pulumi/destroy.go
Expand Up @@ -165,8 +165,13 @@ func newDestroyCmd() *cobra.Command {
return result.FromError(fmt.Errorf("getting stack configuration: %w", err))
}

decrypter, err := sm.Decrypter()
if err != nil {
return result.FromError(fmt.Errorf("getting stack decrypter: %w", err))
}

stackName := s.Ref().Name().String()
configError := workspace.ValidateStackConfigAndApplyProjectConfig(stackName, proj, cfg.Config)
configError := workspace.ValidateStackConfigAndApplyProjectConfig(stackName, proj, cfg.Config, decrypter)
if configError != nil {
return result.FromError(fmt.Errorf("validating stack config: %w", configError))
}
Expand Down
7 changes: 6 additions & 1 deletion pkg/cmd/pulumi/import.go
Expand Up @@ -498,7 +498,12 @@ func newImportCmd() *cobra.Command {
return result.FromError(fmt.Errorf("getting stack configuration: %w", err))
}

configErr := workspace.ValidateStackConfigAndApplyProjectConfig(stack, proj, cfg.Config)
decrypter, err := sm.Decrypter()
if err != nil {
return result.FromError(fmt.Errorf("getting stack decrypter: %w", err))
}

configErr := workspace.ValidateStackConfigAndApplyProjectConfig(stack, proj, cfg.Config, decrypter)
if configErr != nil {
return result.FromError(fmt.Errorf("validating stack config: %w", configErr))
}
Expand Down
7 changes: 6 additions & 1 deletion pkg/cmd/pulumi/logs.go
Expand Up @@ -79,7 +79,12 @@ func newLogsCmd() *cobra.Command {
return fmt.Errorf("getting stack configuration: %w", err)
}

configErr := workspace.ValidateStackConfigAndApplyProjectConfig(stack, proj, cfg.Config)
decrypter, err := sm.Decrypter()
if err != nil {
return fmt.Errorf("getting stack decrypter: %w", err)
}

configErr := workspace.ValidateStackConfigAndApplyProjectConfig(stack, proj, cfg.Config, decrypter)
if configErr != nil {
return fmt.Errorf("validating stack config: %w", configErr)
}
Expand Down
7 changes: 6 additions & 1 deletion pkg/cmd/pulumi/preview.go
Expand Up @@ -154,7 +154,12 @@ func newPreviewCmd() *cobra.Command {
return result.FromError(fmt.Errorf("getting stack configuration: %w", err))
}

configErr := workspace.ValidateStackConfigAndApplyProjectConfig(stack, proj, cfg.Config)
decrypter, err := sm.Decrypter()
if err != nil {
return result.FromError(fmt.Errorf("getting stack decrypter: %w", err))
}

configErr := workspace.ValidateStackConfigAndApplyProjectConfig(stack, proj, cfg.Config, decrypter)
if configErr != nil {
return result.FromError(fmt.Errorf("validating stack config: %w", configErr))
}
Expand Down
7 changes: 6 additions & 1 deletion pkg/cmd/pulumi/refresh.go
Expand Up @@ -152,7 +152,12 @@ func newRefreshCmd() *cobra.Command {
return result.FromError(fmt.Errorf("getting stack configuration: %w", err))
}

configErr := workspace.ValidateStackConfigAndApplyProjectConfig(stack, proj, cfg.Config)
decrypter, err := sm.Decrypter()
if err != nil {
return result.FromError(fmt.Errorf("getting stack decrypter: %w", err))
}

configErr := workspace.ValidateStackConfigAndApplyProjectConfig(stack, proj, cfg.Config, decrypter)
if configErr != nil {
return result.FromError(fmt.Errorf("validating stack config: %w", configErr))
}
Expand Down
14 changes: 12 additions & 2 deletions pkg/cmd/pulumi/up.go
Expand Up @@ -111,7 +111,12 @@ func newUpCmd() *cobra.Command {
return result.FromError(fmt.Errorf("getting stack configuration: %w", err))
}

configErr := workspace.ValidateStackConfigAndApplyProjectConfig(stack, proj, cfg.Config)
decrypter, err := sm.Decrypter()
if err != nil {
return result.FromError(fmt.Errorf("getting stack decrypter: %w", err))
}

configErr := workspace.ValidateStackConfigAndApplyProjectConfig(stack, proj, cfg.Config, decrypter)
if configErr != nil {
return result.FromError(fmt.Errorf("validating stack config: %w", configErr))
}
Expand Down Expand Up @@ -350,7 +355,12 @@ func newUpCmd() *cobra.Command {
return result.FromError(fmt.Errorf("getting stack configuration: %w", err))
}

configErr := workspace.ValidateStackConfigAndApplyProjectConfig(stack, proj, cfg.Config)
decrypter, err := sm.Decrypter()
if err != nil {
return result.FromError(fmt.Errorf("getting stack decrypter: %w", err))
}

configErr := workspace.ValidateStackConfigAndApplyProjectConfig(stack, proj, cfg.Config, decrypter)
if configErr != nil {
return result.FromError(fmt.Errorf("validating stack config: %w", configErr))
}
Expand Down
7 changes: 6 additions & 1 deletion pkg/cmd/pulumi/watch.go
Expand Up @@ -117,7 +117,12 @@ func newWatchCmd() *cobra.Command {
return result.FromError(fmt.Errorf("getting stack configuration: %w", err))
}

configErr := workspace.ValidateStackConfigAndApplyProjectConfig(stack, proj, cfg.Config)
decrypter, err := sm.Decrypter()
if err != nil {
return result.FromError(fmt.Errorf("getting stack decrypter: %w", err))
}

configErr := workspace.ValidateStackConfigAndApplyProjectConfig(stack, proj, cfg.Config, decrypter)
if configErr != nil {
return result.FromError(fmt.Errorf("validating stack config: %w", configErr))
}
Expand Down
25 changes: 2 additions & 23 deletions pkg/secrets/b64/manager.go
Expand Up @@ -16,9 +16,6 @@
package b64

import (
"context"
"encoding/base64"

"github.com/pulumi/pulumi/pkg/v3/secrets"
"github.com/pulumi/pulumi/sdk/v3/go/common/resource/config"
)
Expand All @@ -34,23 +31,5 @@ type manager struct{}

func (m *manager) Type() string { return Type }
func (m *manager) State() interface{} { return map[string]string{} }
func (m *manager) Encrypter() (config.Encrypter, error) { return &base64Crypter{}, nil }
func (m *manager) Decrypter() (config.Decrypter, error) { return &base64Crypter{}, nil }

type base64Crypter struct{}

func (c *base64Crypter) EncryptValue(ctx context.Context, s string) (string, error) {
return base64.StdEncoding.EncodeToString([]byte(s)), nil
}

func (c *base64Crypter) DecryptValue(ctx context.Context, s string) (string, error) {
b, err := base64.StdEncoding.DecodeString(s)
if err != nil {
return "", err
}
return string(b), nil
}

func (c *base64Crypter) BulkDecrypt(ctx context.Context, ciphertexts []string) (map[string]string, error) {
return config.DefaultBulkDecrypt(ctx, c, ciphertexts)
}
func (m *manager) Encrypter() (config.Encrypter, error) { return config.Base64Crypter, nil }
func (m *manager) Decrypter() (config.Decrypter, error) { return config.Base64Crypter, nil }
21 changes: 21 additions & 0 deletions sdk/go/common/resource/config/crypt.go
Expand Up @@ -275,3 +275,24 @@ func DefaultBulkDecrypt(ctx context.Context,
}
return secretMap, nil
}

type base64Crypter struct{}

// Base64Crypter is a Crypter that "encrypts" by encoding the string to base64.
var Base64Crypter Crypter = &base64Crypter{}

func (c *base64Crypter) EncryptValue(ctx context.Context, s string) (string, error) {
return base64.StdEncoding.EncodeToString([]byte(s)), nil
}

func (c *base64Crypter) DecryptValue(ctx context.Context, s string) (string, error) {
b, err := base64.StdEncoding.DecodeString(s)
if err != nil {
return "", err
}
return string(b), nil
}

func (c *base64Crypter) BulkDecrypt(ctx context.Context, ciphertexts []string) (map[string]string, error) {
return DefaultBulkDecrypt(ctx, c, ciphertexts)
}
6 changes: 3 additions & 3 deletions sdk/go/common/resource/config/value.go
Expand Up @@ -140,7 +140,7 @@ func (c Value) ToObject() (interface{}, error) {
}

func (c Value) MarshalJSON() ([]byte, error) {
v, err := c.MarshalValue()
v, err := c.marshalValue()
if err != nil {
return nil, err
}
Expand All @@ -158,7 +158,7 @@ func (c *Value) UnmarshalJSON(b []byte) error {
}

func (c Value) MarshalYAML() (interface{}, error) {
return c.MarshalValue()
return c.marshalValue()
}

func (c *Value) UnmarshalYAML(unmarshal func(interface{}) error) error {
Expand Down Expand Up @@ -202,7 +202,7 @@ func (c *Value) unmarshalValue(unmarshal func(interface{}) error, fix func(inter
return nil
}

func (c Value) MarshalValue() (interface{}, error) {
func (c Value) marshalValue() (interface{}, error) {
if c.object {
return c.unmarshalObjectJSON()
}
Expand Down
107 changes: 89 additions & 18 deletions sdk/go/common/workspace/config.go
Expand Up @@ -64,10 +64,61 @@ func missingProjectConfigurationKeysError(missingProjectKeys []string, stackName
isOrAre)
}

func ValidateStackConfigAndApplyProjectConfig(
type StackName = string
type ProjectConfigKey = string
type StackConfigValidator = func(StackName, ProjectConfigKey, ProjectConfigType, config.Value, config.Decrypter) error

func DefaultStackConfigValidator() StackConfigValidator {
return func(
stackName string,
projectConfigKey string,
projectConfigType ProjectConfigType,
stackValue config.Value,
dec config.Decrypter) error {
// First check if the project says this should be secret, and if so that the stack value is
// secure.
if projectConfigType.Secret && !stackValue.Secure() {
validationError := fmt.Errorf(
"Stack '%v' with configuration key '%v' must be encrypted as it's secret",
stackName,
projectConfigKey)
return validationError
}

value, err := stackValue.Value(dec)
if err != nil {
return err
}
// Content will be a JSON string if object is true, so marshal that back into an actual structure
var content interface{} = value
if stackValue.Object() {
err = json.Unmarshal([]byte(value), &content)
if err != nil {
return err
}
}

if !ValidateConfigValue(projectConfigType.Type, projectConfigType.Items, content) {
typeName := InferFullTypeName(projectConfigType.Type, projectConfigType.Items)
validationError := fmt.Errorf(
"Stack '%v' with configuration key '%v' must be of type '%v'",
stackName,
projectConfigKey,
typeName)

return validationError
}

return nil
}
}

func ValidateStackConfigAndMergeProjectConfig(
stackName string,
project *Project,
stackConfig config.Map) error {
stackConfig config.Map,
lazyDecrypter func() config.Decrypter,
validate StackConfigValidator) error {

if len(project.Config) > 0 {
// only when the project defines config values, do we need to validate the stack config
Expand All @@ -90,6 +141,7 @@ func ValidateStackConfigAndApplyProjectConfig(
}
}

var decrypter config.Decrypter
missingConfigurationKeys := make([]string, 0)
for projectConfigKey, projectConfigType := range project.Config {
var key config.Key
Expand Down Expand Up @@ -143,23 +195,20 @@ func ValidateStackConfigAndApplyProjectConfig(
return setError
}
} else {
// found value on the stack level
// retrieve it and validate it against
// the config defined at the project level
content, contentError := stackValue.MarshalValue()
if contentError != nil {
return contentError
}

if !ValidateConfigValue(projectConfigType.Type, projectConfigType.Items, content) {
typeName := InferFullTypeName(projectConfigType.Type, projectConfigType.Items)
validationError := fmt.Errorf(
"Stack '%v' with configuration key '%v' must be of type '%v'",
stackName,
projectConfigKey,
typeName)
// Validate stack level value against the config defined at the project level
if validate != nil {
// we have a validator
if decrypter == nil && lazyDecrypter != nil {
// initialize the decrypter once
decrypter = lazyDecrypter()
}

return validationError
if decrypter != nil {
validationError := validate(stackName, projectConfigKey, projectConfigType, stackValue, decrypter)
if validationError != nil {
return validationError
}
}
}
}
}
Expand All @@ -172,3 +221,25 @@ func ValidateStackConfigAndApplyProjectConfig(

return nil
}

func ValidateStackConfigAndApplyProjectConfig(
stackName string,
project *Project,
stackConfig config.Map,
dec config.Decrypter) error {
decrypter := func() config.Decrypter {
return dec
}

configValidator := DefaultStackConfigValidator()

return ValidateStackConfigAndMergeProjectConfig(stackName, project, stackConfig, decrypter, configValidator)
}

// ApplyConfigDefaults applies the default values for the project configuration onto the stack configuration
// without validating the contents of stack config values.
// This is because sometimes during pulumi config ls and pulumi config get, if users are
// using PassphraseDecrypter, we don't want to always prompt for the values when not necessary
func ApplyProjectConfig(stackName string, project *Project, stackConfig config.Map) error {
return ValidateStackConfigAndMergeProjectConfig(stackName, project, stackConfig, nil, nil)
}

0 comments on commit a4ae636

Please sign in to comment.