pulumi/pkg/resource/stack/secrets_test.go
Ian Wahbe 272c4643b2
Update error handling (#8406)
This is the result of a change applied via `go-rewrap-errors`.
2021-11-12 18:37:17 -08:00

175 lines
5.8 KiB
Go

package stack
import (
"encoding/json"
"errors"
"fmt"
"strings"
"testing"
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
"github.com/pulumi/pulumi/sdk/v3/go/common/resource/config"
"github.com/stretchr/testify/assert"
)
type testSecretsManager struct {
encryptCalls int
decryptCalls int
}
func (t *testSecretsManager) Type() string { return "test" }
func (t *testSecretsManager) State() interface{} { return nil }
func (t *testSecretsManager) Encrypter() (config.Encrypter, error) {
return t, nil
}
func (t *testSecretsManager) Decrypter() (config.Decrypter, error) {
return t, nil
}
func (t *testSecretsManager) EncryptValue(plaintext string) (string, error) {
t.encryptCalls++
return fmt.Sprintf("%v:%v", t.encryptCalls, plaintext), nil
}
func (t *testSecretsManager) DecryptValue(ciphertext string) (string, error) {
t.decryptCalls++
i := strings.Index(ciphertext, ":")
if i == -1 {
return "", errors.New("invalid ciphertext format")
}
return ciphertext[i+1:], nil
}
func deserializeProperty(v interface{}, dec config.Decrypter) (resource.PropertyValue, error) {
b, err := json.Marshal(v)
if err != nil {
return resource.PropertyValue{}, err
}
if err := json.Unmarshal(b, &v); err != nil {
return resource.PropertyValue{}, err
}
return DeserializePropertyValue(v, dec, config.NewPanicCrypter())
}
func TestCachingCrypter(t *testing.T) {
sm := &testSecretsManager{}
csm := NewCachingSecretsManager(sm)
foo1 := resource.MakeSecret(resource.NewStringProperty("foo"))
foo2 := resource.MakeSecret(resource.NewStringProperty("foo"))
bar := resource.MakeSecret(resource.NewStringProperty("bar"))
enc, err := csm.Encrypter()
assert.NoError(t, err)
// Serialize the first copy of "foo". Encrypt should be called once, as this value has not yet been encrypted.
foo1Ser, err := SerializePropertyValue(foo1, enc, false /* showSecrets */)
assert.NoError(t, err)
assert.Equal(t, 1, sm.encryptCalls)
// Serialize the second copy of "foo". Because this is a different secret instance, Encrypt should be called
// a second time even though the plaintext is the same as the last value we encrypted.
foo2Ser, err := SerializePropertyValue(foo2, enc, false /* showSecrets */)
assert.NoError(t, err)
assert.Equal(t, 2, sm.encryptCalls)
assert.NotEqual(t, foo1Ser, foo2Ser)
// Serialize "bar". Encrypt should be called once, as this value has not yet been encrypted.
barSer, err := SerializePropertyValue(bar, enc, false /* showSecrets */)
assert.NoError(t, err)
assert.Equal(t, 3, sm.encryptCalls)
// Serialize the first copy of "foo" again. Encrypt should not be called, as this value has already been
// encrypted.
foo1Ser2, err := SerializePropertyValue(foo1, enc, false /* showSecrets */)
assert.NoError(t, err)
assert.Equal(t, 3, sm.encryptCalls)
assert.Equal(t, foo1Ser, foo1Ser2)
// Serialize the second copy of "foo" again. Encrypt should not be called, as this value has already been
// encrypted.
foo2Ser2, err := SerializePropertyValue(foo2, enc, false /* showSecrets */)
assert.NoError(t, err)
assert.Equal(t, 3, sm.encryptCalls)
assert.Equal(t, foo2Ser, foo2Ser2)
// Serialize "bar" again. Encrypt should not be called, as this value has already been encrypted.
barSer2, err := SerializePropertyValue(bar, enc, false /* showSecrets */)
assert.NoError(t, err)
assert.Equal(t, 3, sm.encryptCalls)
assert.Equal(t, barSer, barSer2)
dec, err := csm.Decrypter()
assert.NoError(t, err)
// Decrypt foo1Ser. Decrypt should be called.
foo1Dec, err := deserializeProperty(foo1Ser, dec)
assert.NoError(t, err)
assert.True(t, foo1.DeepEquals(foo1Dec))
assert.Equal(t, 1, sm.decryptCalls)
// Decrypt foo2Ser. Decrypt should be called.
foo2Dec, err := deserializeProperty(foo2Ser, dec)
assert.NoError(t, err)
assert.True(t, foo2.DeepEquals(foo2Dec))
assert.Equal(t, 2, sm.decryptCalls)
// Decrypt barSer. Decrypt should be called.
barDec, err := deserializeProperty(barSer, dec)
assert.NoError(t, err)
assert.True(t, bar.DeepEquals(barDec))
assert.Equal(t, 3, sm.decryptCalls)
// Create a new CachingSecretsManager and re-run the decrypts. Each decrypt should insert the plain- and
// ciphertext into the cache with the associated secret.
csm = NewCachingSecretsManager(sm)
dec, err = csm.Decrypter()
assert.NoError(t, err)
// Decrypt foo1Ser. Decrypt should be called.
foo1Dec, err = deserializeProperty(foo1Ser, dec)
assert.NoError(t, err)
assert.True(t, foo1.DeepEquals(foo1Dec))
assert.Equal(t, 4, sm.decryptCalls)
// Decrypt foo2Ser. Decrypt should be called.
foo2Dec, err = deserializeProperty(foo2Ser, dec)
assert.NoError(t, err)
assert.True(t, foo2.DeepEquals(foo2Dec))
assert.Equal(t, 5, sm.decryptCalls)
// Decrypt barSer. Decrypt should be called.
barDec, err = deserializeProperty(barSer, dec)
assert.NoError(t, err)
assert.True(t, bar.DeepEquals(barDec))
assert.Equal(t, 6, sm.decryptCalls)
enc, err = csm.Encrypter()
assert.NoError(t, err)
// Serialize the first copy of "foo" again. Encrypt should not be called, as this value has already been
// cached by the earlier calls to Decrypt.
foo1Ser2, err = SerializePropertyValue(foo1Dec, enc, false /* showSecrets */)
assert.NoError(t, err)
assert.Equal(t, 3, sm.encryptCalls)
assert.Equal(t, foo1Ser, foo1Ser2)
// Serialize the second copy of "foo" again. Encrypt should not be called, as this value has already been
// cached by the earlier calls to Decrypt.
foo2Ser2, err = SerializePropertyValue(foo2Dec, enc, false /* showSecrets */)
assert.NoError(t, err)
assert.Equal(t, 3, sm.encryptCalls)
assert.Equal(t, foo2Ser, foo2Ser2)
// Serialize "bar" again. Encrypt should not be called, as this value has already been cached by the
// earlier calls to Decrypt.
barSer2, err = SerializePropertyValue(barDec, enc, false /* showSecrets */)
assert.NoError(t, err)
assert.Equal(t, 3, sm.encryptCalls)
assert.Equal(t, barSer, barSer2)
}