// 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 config import ( "context" "encoding/json" "fmt" "github.com/pulumi/pulumi/sdk/v3/go/common/resource" "github.com/pulumi/pulumi/sdk/v3/go/common/util/contract" ) // PlaintextType describes the allowed types for a Plaintext. type PlaintextType interface { bool | int64 | float64 | string | []Plaintext | map[string]Plaintext } // Plaintext is a single plaintext config value. type Plaintext struct { value any secure bool } // NewPlaintext creates a new plaintext config value. func NewPlaintext[T PlaintextType](v T) Plaintext { if m, ok := any(v).(map[string]Plaintext); ok && len(m) == 1 { if _, ok := m["secure"].Value().(string); ok { contract.Failf(errSecureReprReserved.Error()) } } return Plaintext{value: v} } // NewSecurePlaintext creates a new secure string with the given plaintext. func NewSecurePlaintext(plaintext string) Plaintext { return Plaintext{value: plaintext, secure: true} } // Secure returns true if the receiver is a secure string or a composite value that contains a secure string. func (c Plaintext) Secure() bool { switch v := c.Value().(type) { case []Plaintext: for _, v := range v { if v.Secure() { return true } } return false case map[string]Plaintext: for _, v := range v { if v.Secure() { return true } } return false case string: return c.secure default: return false } } // Value returns the inner plaintext value. // // The returned value satisfies the PlaintextType constraint. func (c Plaintext) Value() any { return c.value } // GoValue returns the inner plaintext value as a plain Go value: // // - secure strings are mapped to their plaintext // - []Plaintext values are mapped to []any values // - map[string]Plaintext values are mapped to map[string]any values func (c Plaintext) GoValue() any { switch v := c.Value().(type) { case []Plaintext: vs := make([]any, len(v)) for i, v := range v { vs[i] = v.GoValue() } return vs case map[string]Plaintext: vs := make(map[string]any, len(v)) for k, v := range v { vs[k] = v.GoValue() } return vs default: return v } } func (c Plaintext) PropertyValue() resource.PropertyValue { var prop resource.PropertyValue switch v := c.Value().(type) { case bool: prop = resource.NewBoolProperty(v) case int64: prop = resource.NewNumberProperty(float64(v)) case float64: prop = resource.NewNumberProperty(v) case string: prop = resource.NewStringProperty(v) case []Plaintext: vs := make([]resource.PropertyValue, len(v)) for i, v := range v { vs[i] = v.PropertyValue() } prop = resource.NewArrayProperty(vs) case map[string]Plaintext: vs := make(map[resource.PropertyKey]resource.PropertyValue, len(v)) for k, v := range v { vs[resource.PropertyKey(k)] = v.PropertyValue() } prop = resource.NewObjectProperty(vs) case nil: prop = resource.NewNullProperty() default: contract.Failf("unexpected value of type %T", v) return resource.PropertyValue{} } if c.secure { prop = resource.MakeSecret(prop) } return prop } // Encrypt converts the receiver as a Value. All secure strings in the result are encrypted using encrypter. func (c Plaintext) Encrypt(ctx context.Context, encrypter Encrypter) (Value, error) { obj, err := c.encrypt(ctx, nil, encrypter) if err != nil { return Value{}, err } return obj.marshalValue() } // encrypt converts the receiver to an object. All secure strings in the result are encrypted using encrypter. func (c Plaintext) encrypt(ctx context.Context, path resource.PropertyPath, encrypter Encrypter) (object, error) { switch v := c.Value().(type) { case nil: return object{}, nil case bool: return newObject(v), nil case int64: return newObject(v), nil case float64: return newObject(v), nil case string: if !c.secure { return newObject(v), nil } ciphertext, err := encrypter.EncryptValue(ctx, v) if err != nil { return object{}, fmt.Errorf("%v: %w", path, err) } return newSecureObject(ciphertext), nil case []Plaintext: vs := make([]object, len(v)) for i, v := range v { ev, err := v.encrypt(ctx, append(path, i), encrypter) if err != nil { return object{}, err } vs[i] = ev } return newObject(vs), nil case map[string]Plaintext: vs := make(map[string]object, len(v)) for k, v := range v { ev, err := v.encrypt(ctx, append(path, k), encrypter) if err != nil { return object{}, err } vs[k] = ev } return newObject(vs), nil default: contract.Failf("unexpected plaintext of type %T", v) return object{}, nil } } // marshalText returns the text representation of the plaintext. func (c Plaintext) marshalText() (string, error) { if str, ok := c.Value().(string); ok { return str, nil } bytes, err := json.Marshal(c.GoValue()) if err != nil { return "", err } return string(bytes), nil } func (c Plaintext) MarshalJSON() ([]byte, error) { contract.Failf("plaintext must be encrypted before marshaling") return nil, nil } func (c *Plaintext) UnmarshalJSON(b []byte) error { contract.Failf("plaintext cannot be unmarshaled") return nil } func (c Plaintext) MarshalYAML() (any, error) { contract.Failf("plaintext must be encrypted before marshaling") return nil, nil } func (c *Plaintext) UnmarshalYAML(unmarshal func(any) error) error { contract.Failf("plaintext cannot be unmarshaled") return nil }