Skip to content

Commit

Permalink
Add 'secret' to config
Browse files Browse the repository at this point in the history
  • Loading branch information
Frassle committed Oct 19, 2022
1 parent 10dd3d0 commit b415590
Show file tree
Hide file tree
Showing 16 changed files with 213 additions and 82 deletions.
@@ -0,0 +1,4 @@
changes:
- type: fix
scope: engine
description: Fix type validation of stack config with secure values.
21 changes: 4 additions & 17 deletions pkg/cmd/pulumi/config.go
Expand Up @@ -791,24 +791,16 @@ func listConfig(ctx context.Context,
return err
}

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
}

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,11 +879,6 @@ 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
}

cfg := ps.Config

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.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.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.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
28 changes: 24 additions & 4 deletions sdk/go/common/workspace/config.go
Expand Up @@ -67,7 +67,8 @@ func missingProjectConfigurationKeysError(missingProjectKeys []string, stackName
func ValidateStackConfigAndApplyProjectConfig(
stackName string,
project *Project,
stackConfig config.Map) error {
stackConfig config.Map,
dec config.Decrypter) error {

if len(project.Config) > 0 {
// only when the project defines config values, do we need to validate the stack config
Expand Down Expand Up @@ -146,9 +147,28 @@ func ValidateStackConfigAndApplyProjectConfig(
// 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

// 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) {
Expand Down
25 changes: 18 additions & 7 deletions sdk/go/common/workspace/project.go
Expand Up @@ -19,6 +19,7 @@ import (
"encoding/json"
"fmt"
"io"
"math"
"os"
"path/filepath"
"strconv"
Expand Down Expand Up @@ -107,6 +108,7 @@ type ProjectConfigType struct {
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Items *ProjectConfigItemsType `json:"items,omitempty" yaml:"items,omitempty"`
Default interface{} `json:"default,omitempty" yaml:"default,omitempty"`
Secret bool `json:"secret,omitempty" yaml:"secret,omitempty"`
}

// Project is a Pulumi project manifest.
Expand Down Expand Up @@ -345,14 +347,23 @@ func ValidateConfigValue(typeName string, itemsType *ProjectConfigItemsType, val

if typeName == "integer" {
_, ok := value.(int)
if !ok {
valueAsText, isText := value.(string)
if isText {
_, integerParseError := strconv.Atoi(valueAsText)
return integerParseError == nil
}
if ok {
return true
}
return ok
// Config values come from YAML which by default will return floats not int. If it's a whole number
// we'll allow it here though
f, ok := value.(float64)
if ok && f == math.Trunc(f) {
return true
}
// Allow strings here if they parse as integers
valueAsText, isText := value.(string)
if isText {
_, integerParseError := strconv.Atoi(valueAsText)
return integerParseError == nil
}

return false
}

if typeName == "boolean" {
Expand Down
11 changes: 8 additions & 3 deletions sdk/go/common/workspace/project.json
Expand Up @@ -78,6 +78,13 @@
"string",
"null"
],
"properties":{
"secret":{
"description":"If true this configuration value should be encrypted.",
"type":"boolean",
"default":false
}
},
"additionalProperties":{
"oneOf":[
{
Expand Down Expand Up @@ -317,9 +324,7 @@
"secret":{
"type":"boolean"
},
"default":{

}
"default":{ }
}
}
}
Expand Down

0 comments on commit b415590

Please sign in to comment.