Skip to content

Commit

Permalink
Strip secret values from Configure
Browse files Browse the repository at this point in the history
  • Loading branch information
iwahbe committed Sep 24, 2024
1 parent 22452e6 commit 90e406e
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 0 deletions.
1 change: 1 addition & 0 deletions pf/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ require (
github.com/pulumi/pulumi-terraform-bridge/v3 v3.91.0
github.com/pulumi/pulumi-terraform-bridge/x/muxer v0.0.8
github.com/stretchr/testify v1.9.0
pgregory.net/rapid v0.6.1
)

require (
Expand Down
37 changes: 37 additions & 0 deletions pf/internal/plugin/provider_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package plugin
import (
"context"

"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
"github.com/pulumi/pulumi/sdk/v3/go/common/resource/plugin"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
pulumirpc "github.com/pulumi/pulumi/sdk/v3/proto/go"
Expand Down Expand Up @@ -80,5 +81,41 @@ func (p providerThunk) Configure(
if ctx.Value(setupConfigureKey) != nil {
return plugin.ConfigureResponse{}, nil
}
req.Inputs = removeSecrets(req.Inputs)
contract.Assertf(!req.Inputs.ContainsSecrets(),
"Inputs to configure should not contain secrets")
return p.GrpcProvider.Configure(ctx, req)
}

func removeSecrets(v resource.PropertyMap) resource.PropertyMap {
var remove func(resource.PropertyValue) resource.PropertyValue
remove = func(v resource.PropertyValue) resource.PropertyValue {
switch {
case v.IsArray():
arr := make([]resource.PropertyValue, 0, len(v.ArrayValue()))
for _, v := range v.ArrayValue() {
arr = append(arr, remove(v))
}
return resource.NewProperty(arr)
case v.IsObject():
obj := make(resource.PropertyMap, len(v.ObjectValue()))
for k, v := range v.ObjectValue() {
obj[k] = remove(v)
}
return resource.NewProperty(obj)
case v.IsComputed():
return resource.MakeComputed(remove(v.Input().Element))
case v.IsOutput():
o := v.OutputValue()
o.Secret = false
o.Element = remove(o.Element)
return resource.NewProperty(o)
case v.IsSecret():
return remove(v.SecretValue().Element)
default:
return v
}
}

return remove(resource.NewProperty(v)).ObjectValue()
}
110 changes: 110 additions & 0 deletions pf/internal/plugin/provider_server_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// Copyright 2016-2024, 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 plugin

import (
"testing"

"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
prapid "github.com/pulumi/pulumi/sdk/v3/go/property/testing"
"github.com/stretchr/testify/assert"
"pgregory.net/rapid"
)

// TestRemoveSecrets checks that removeSecrets removes [resource.Secret] values and unsets
// [resource.Output.Secret] fields without making any other changes.
func TestRemoveSecrets(t *testing.T) {

// These functions validate that a diff does not contain any non-secret changes.
var (
validateObjectDiff func(assert.TestingT, resource.ObjectDiff)
validateArrayDiff func(assert.TestingT, resource.ArrayDiff)
validateValueDiff func(assert.TestingT, resource.ValueDiff)
)

validateValueDiff = func(t assert.TestingT, v resource.ValueDiff) {
switch {
case v.Old.IsOutput():
oOld := v.Old.OutputValue()
oOld.Secret = false
if d := resource.NewProperty(oOld).DiffIncludeUnknowns(v.New); d != nil {
validateValueDiff(t, *d)
}
case v.Old.IsSecret():
if d := v.Old.SecretValue().Element.DiffIncludeUnknowns(v.New); d != nil {
validateValueDiff(t, *d)
}
case v.Old.IsObject():
validateObjectDiff(t, *v.Object)
case v.Old.IsArray():
validateArrayDiff(t, *v.Array)
default:
assert.Failf(t, "", "unexpected Update.Old type %q", v.Old.TypeString())
}
}

validateArrayDiff = func(t assert.TestingT, diff resource.ArrayDiff) {
assert.Empty(t, diff.Adds)
assert.Empty(t, diff.Deletes)

for _, v := range diff.Updates {
validateValueDiff(t, v)
}
}

validateObjectDiff = func(t assert.TestingT, diff resource.ObjectDiff) {
assert.Empty(t, diff.Adds)

// Diff does not distinguish from a missing key and a null property, so
// when we go from a map{k: secret(null)} to a map{k: null}, the diff
// machinery shows a delete.
//
// We have an explicit test for this behavior.
for _, v := range diff.Deletes {
assert.Equal(t, resource.MakeSecret(resource.NewNullProperty()), v)
}

for _, v := range diff.Updates {
validateValueDiff(t, v)
}
}

t.Run("rapid", rapid.MakeCheck(func(t *rapid.T) {
m := resource.ToResourcePropertyValue(prapid.Map(5).Draw(t, "top-level")).ObjectValue()
if m.ContainsSecrets() {
unsecreted := removeSecrets(m)
assert.False(t, unsecreted.ContainsSecrets())

// We need to assert that the only change between m and unsecreted
// is that secret values went to their element values.
if d := m.DiffIncludeUnknowns(unsecreted); d != nil {
validateObjectDiff(t, *d)
}
} else {
assert.Equal(t, m, removeSecrets(m))
}
}))

t.Run("map-null-secrets", func(t *testing.T) {
assert.Equal(t,
resource.PropertyMap{
"null": resource.NewNullProperty(),
},
removeSecrets(resource.PropertyMap{
"null": resource.MakeSecret(resource.NewNullProperty()),
}),
)
})
}

0 comments on commit 90e406e

Please sign in to comment.