From c0ea332341596c4929f382902dc2b70a4818627a Mon Sep 17 00:00:00 2001 From: Thomas Kappler Date: Mon, 9 Sep 2024 12:08:34 +0200 Subject: [PATCH] Sanitize exported stack state by scrubbing secrets (#107) When upgrade tests persist stack.json, it contains secrets, making it unsuitable for committing. This PR adds a sanitization step that scrubs secrets, as identified by the p/p signature, from the state. Fixes https://github.com/pulumi/providertest/issues/106 --------- Co-authored-by: Daniel Bradley --- README.md | 2 +- grpclog/grpclog.go | 17 +- previewProviderUpgrade.go | 1 + pulumitest/run.go | 9 +- pulumitest/sanitize/sanitize.go | 94 ++++++ pulumitest/sanitize/sanitize_test.go | 428 +++++++++++++++++++++++++++ upgrade.go | 13 +- 7 files changed, 559 insertions(+), 5 deletions(-) create mode 100644 pulumitest/sanitize/sanitize.go create mode 100644 pulumitest/sanitize/sanitize_test.go diff --git a/README.md b/README.md index dbd9f33..3a57ecb 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ snapshots need to be recorded anew on the new version. ### Fixing failing tests - If the tests fail by flagging unwanted resource updates or replacements that are actually - acceptable, configure or custom + acceptable, configure a custom [DiffValidation](https://github.com/pulumi/providertest/blob/5f23c3ec7cee882392ea356a54c0f74f56b0f7d5/upgrade.go#L241) setting with more relaxed asserts. diff --git a/grpclog/grpclog.go b/grpclog/grpclog.go index 7a3d642..9d12091 100644 --- a/grpclog/grpclog.go +++ b/grpclog/grpclog.go @@ -6,6 +6,7 @@ import ( "path/filepath" "strings" + "github.com/pulumi/providertest/pulumitest/sanitize" rpc "github.com/pulumi/pulumi/sdk/v3/proto/go" jsonpb "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/reflect/protoreflect" @@ -147,10 +148,11 @@ func unmarshalTypedEntries[TRequest, TResponse any](entries []GrpcLogEntry) ([]T func unmarshalTypedEntry[TRequest, TResponse any](entry GrpcLogEntry) (*TypedEntry[TRequest, TResponse], error) { reqSlot := new(TRequest) resSlot := new(TResponse) - if err := jsonpb.Unmarshal([]byte(entry.Request), any(reqSlot).(protoreflect.ProtoMessage)); err != nil { + jsonOpts := jsonpb.UnmarshalOptions{DiscardUnknown: true, AllowPartial: true} + if err := jsonOpts.Unmarshal([]byte(entry.Request), any(reqSlot).(protoreflect.ProtoMessage)); err != nil { return nil, err } - if err := jsonpb.Unmarshal([]byte(entry.Response), any(resSlot).(protoreflect.ProtoMessage)); err != nil { + if err := jsonOpts.Unmarshal([]byte(entry.Response), any(resSlot).(protoreflect.ProtoMessage)); err != nil { return nil, err } typedEntry := TypedEntry[TRequest, TResponse]{ @@ -196,6 +198,17 @@ func (l *GrpcLog) WhereMethod(method Method) []GrpcLogEntry { return matching } +func (l *GrpcLog) SanitizeSecrets() { + for i := range l.Entries { + l.Entries[i].SanitizeSecrets() + } +} + +func (e *GrpcLogEntry) SanitizeSecrets() { + e.Request = sanitize.SanitizeSecretsInGrpcLog(e.Request) + e.Response = sanitize.SanitizeSecretsInGrpcLog(e.Response) +} + // WriteTo writes the log to the given path. // Creates any directories needed. func (l *GrpcLog) WriteTo(path string) error { diff --git a/previewProviderUpgrade.go b/previewProviderUpgrade.go index ec521ac..14aefe9 100644 --- a/previewProviderUpgrade.go +++ b/previewProviderUpgrade.go @@ -31,6 +31,7 @@ func PreviewProviderUpgrade(t pulumitest.PT, pulumiTest *pulumitest.PulumiTest, grptLog := test.GrpcLog(t) grpcLogPath := filepath.Join(cacheDir, "grpc.json") t.Log(fmt.Sprintf("writing grpc log to %s", grpcLogPath)) + grptLog.SanitizeSecrets() grptLog.WriteTo(grpcLogPath) }, optrun.WithCache(filepath.Join(cacheDir, "stack.json")), diff --git a/pulumitest/run.go b/pulumitest/run.go index 1a9a437..ebc1ab5 100644 --- a/pulumitest/run.go +++ b/pulumitest/run.go @@ -9,6 +9,7 @@ import ( "strings" "github.com/pulumi/providertest/pulumitest/optrun" + "github.com/pulumi/providertest/pulumitest/sanitize" "github.com/pulumi/pulumi/sdk/v3/go/common/apitype" ) @@ -42,8 +43,14 @@ func (pulumiTest *PulumiTest) Run(t PT, execute func(test *PulumiTest), opts ... execute(isolatedTest) exportedStack := isolatedTest.ExportStack(t) if options.EnableCache { + ptLogF(t, "sanitizing secrets from stack state") + sanitizedStack, err := sanitize.SanitizeSecretsInStackState(&exportedStack) + if err != nil { + ptError(t, "failed to sanitize secrets from stack state: %v", err) + } + ptLogF(t, "writing stack state to %s", options.CachePath) - err = writeStackExport(options.CachePath, &exportedStack, false /* overwrite */) + err = writeStackExport(options.CachePath, sanitizedStack, false /* overwrite */) if err != nil { ptFatalF(t, "failed to write snapshot to %s: %v", options.CachePath, err) } diff --git a/pulumitest/sanitize/sanitize.go b/pulumitest/sanitize/sanitize.go new file mode 100644 index 0000000..92a7af8 --- /dev/null +++ b/pulumitest/sanitize/sanitize.go @@ -0,0 +1,94 @@ +// 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 sanitize + +import ( + "encoding/json" + + "github.com/pulumi/pulumi/sdk/v3/go/common/apitype" +) + +const plaintextSub = "REDACTED BY PROVIDERTEST" +const secretSignature = "4dabf18193072939515e22adb298388d" + +// SanitizeSecretsInStackState sanitizes secrets in the stack state by replacing them with a placeholder. +// secrets are identified by their magic signature, copied from pulumi/pulumi. +func SanitizeSecretsInStackState(stack *apitype.UntypedDeployment) (*apitype.UntypedDeployment, error) { + var d apitype.DeploymentV3 + err := json.Unmarshal(stack.Deployment, &d) + if err != nil { + return nil, err + } + + sanitizeSecretsInResources(d.Resources) + + marshaledDeployment, err := json.Marshal(d) + if err != nil { + return nil, err + } + + return &apitype.UntypedDeployment{ + Version: stack.Version, + Deployment: json.RawMessage(marshaledDeployment), + }, nil +} + +func SanitizeSecretsInGrpcLog(log json.RawMessage) json.RawMessage { + var data map[string]any + if err := json.Unmarshal(log, &data); err != nil { + return log + } + + sanitized := sanitizeSecretsInObject(data, map[string]any{ + secretSignature: "1b47061264138c4ac30d75fd1eb44270", + "value": plaintextSub, + }) + sanitizedBytes, err := json.Marshal(sanitized) + if err != nil { + return log + } + return sanitizedBytes +} + +func sanitizeSecretsInResources(resources []apitype.ResourceV3) { + for i, r := range resources { + r.Inputs = sanitizeSecretsInObject(r.Inputs, stateSecretReplacement) + r.Outputs = sanitizeSecretsInObject(r.Outputs, stateSecretReplacement) + resources[i] = r + } +} + +var stateSecretReplacement = map[string]any{ + secretSignature: "1b47061264138c4ac30d75fd1eb44270", + "plaintext": `"` + plaintextSub + `"`, // must be valid JSON, hence quoted +} + +func sanitizeSecretsInObject(obj map[string]any, secretReplacement map[string]any) map[string]any { + copy := map[string]any{} + for k, v := range obj { + innerObj, ok := v.(map[string]any) + if ok { + _, hasSecret := innerObj[secretSignature] + if hasSecret { + copy[k] = secretReplacement + } else { + copy[k] = sanitizeSecretsInObject(innerObj, secretReplacement) + } + } else { + copy[k] = v + } + } + return copy +} diff --git a/pulumitest/sanitize/sanitize_test.go b/pulumitest/sanitize/sanitize_test.go new file mode 100644 index 0000000..e91681c --- /dev/null +++ b/pulumitest/sanitize/sanitize_test.go @@ -0,0 +1,428 @@ +// 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 sanitize + +import ( + "bytes" + "encoding/json" + "testing" + + "github.com/pulumi/pulumi/sdk/v3/go/common/apitype" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestSanitizeSecretsInObject(t *testing.T) { + t.Parallel() + + t.Run("simple", func(t *testing.T) { + input := map[string]any{ + "secondaryAccessKey": map[string]any{ + secretSignature: "1b47061264138c4ac30d75fd1eb44270", + "plaintext": "secret", + }, + } + + expected := map[string]any{ + "secondaryAccessKey": stateSecretReplacement, + } + + assert.Equal(t, expected, sanitizeSecretsInObject(input, stateSecretReplacement)) + }) + + t.Run("nested", func(t *testing.T) { + input := map[string]any{ + "bar": 1, + "foo": map[string]any{ + "inner": map[string]any{ + "secondaryAccessKey": map[string]any{ + secretSignature: "1b47061264138c4ac30d75fd1eb44270", + "plaintext": "secret", + }, + }, + }, + } + + expected := map[string]any{ + "bar": 1, + "foo": map[string]any{ + "inner": map[string]any{ + "secondaryAccessKey": stateSecretReplacement, + }, + }, + } + + assert.Equal(t, expected, sanitizeSecretsInObject(input, stateSecretReplacement)) + }) +} + +func TestSanitizeSecretsInStackState(t *testing.T) { + t.Parallel() + + var stack apitype.UntypedDeployment + err := json.Unmarshal(realStack, &stack) + require.NoError(t, err) + + sanitized, err := SanitizeSecretsInStackState(&stack) + require.NoError(t, err) + + expected := bytes.ReplaceAll(realStack, []byte(`\"SECRET\"`), []byte(`\"`+plaintextSub+`\"`)) + var expectedDeployment apitype.UntypedDeployment + err = json.Unmarshal(expected, &expectedDeployment) + require.NoError(t, err) + + sanitizedPretty := prettyPrintJson(t, sanitized.Deployment) + expectedPretty := prettyPrintJson(t, expectedDeployment.Deployment) + assert.JSONEq(t, string(expectedPretty), string(sanitizedPretty)) + + // Sanity check that `plaintext` has a valid JSON string since we could have gotten `expected` wrong. + var d apitype.DeploymentV3 + err = json.Unmarshal(stack.Deployment, &d) + require.NoError(t, err) + p := d.Resources[1].Outputs["subscriptionId"].(map[string]any)["plaintext"].(string) + var s string + err = json.Unmarshal([]byte(p), &s) + require.NoError(t, err) +} + +func prettyPrintJson(t *testing.T, jsonStr []byte) []byte { + var v any + err := json.Unmarshal(jsonStr, &v) + require.NoError(t, err) + pretty, err := json.MarshalIndent(v, "", " ") + require.NoError(t, err) + return pretty +} + +var realStack = []byte(`{ + "version": 3, + "deployment": { + "manifest": { + "time": "2024-09-05T11:23:42.551264+02:00", + "magic": "59ab42470ec682a2eb8566128a64ecaee8e5d25c6d5902576977eb325cf4d7b3", + "version": "v3.130.0" + }, + "secrets_providers": { + "type": "passphrase", + "state": { + "salt": "v1:YMv/Yx+VlW0=:v1:sbgZHJ6QDAq8dzEQ:gbsJTyFyS7GU0svVisIL+uQyDJYqqA==" + } + }, + "resources": [ + { + "urn": "urn:pulumi:test::storage::pulumi:pulumi:Stack::storage-test", + "custom": false, + "type": "pulumi:pulumi:Stack", + "created": "2024-09-05T09:22:04.581633Z", + "modified": "2024-09-05T09:22:04.581633Z" + }, + { + "urn": "urn:pulumi:test::storage::pulumi:providers:azure::default", + "custom": true, + "id": "515481f4-90eb-46e4-a36e-29ad4413fb22", + "type": "pulumi:providers:azure", + "inputs": { + "subscriptionId": { + "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", + "plaintext": "\"SECRET\"" + } + }, + "outputs": { + "subscriptionId": { + "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", + "plaintext": "\"SECRET\"" + } + }, + "created": "2024-09-05T09:22:05.050659Z", + "modified": "2024-09-05T09:22:05.050659Z" + }, + { + "urn": "urn:pulumi:test::storage::azure:core/resourceGroup:ResourceGroup::exampleResourceGroup", + "custom": true, + "id": "/subscriptions/12345/resourceGroups/exampleresourcegroup35548da3", + "type": "azure:core/resourceGroup:ResourceGroup", + "inputs": { + "__defaults": [ + "name" + ], + "location": "East US", + "name": "exampleresourcegroup35548da3" + }, + "outputs": { + "__meta": "{\"e2bfb730-ecaa-11e6-8f88-34363bc7c4c0\":{\"create\":5400000000000,\"delete\":5400000000000,\"read\":300000000000,\"update\":5400000000000}}", + "id": "/subscriptions/12345/resourceGroups/exampleresourcegroup35548da3", + "location": "eastus", + "managedBy": "", + "name": "exampleresourcegroup35548da3", + "tags": null + }, + "parent": "urn:pulumi:test::storage::pulumi:pulumi:Stack::storage-test", + "provider": "urn:pulumi:test::storage::pulumi:providers:azure::default::515481f4-90eb-46e4-a36e-29ad4413fb22", + "propertyDependencies": { + "location": [] + }, + "created": "2024-09-05T09:22:23.554439Z", + "modified": "2024-09-05T09:22:23.554439Z" + }, + { + "urn": "urn:pulumi:test::storage::azure:storage/account:Account::exampleAccount", + "custom": true, + "id": "/subscriptions/12345/resourceGroups/exampleresourcegroup35548da3/providers/Microsoft.Storage/storageAccounts/exampleaccount4cb2982b", + "type": "azure:storage/account:Account", + "inputs": { + "__defaults": [ + "accountKind", + "allowNestedItemsToBePublic", + "crossTenantReplicationEnabled", + "defaultToOauthAuthentication", + "dnsEndpointType", + "infrastructureEncryptionEnabled", + "isHnsEnabled", + "localUserEnabled", + "minTlsVersion", + "name", + "nfsv3Enabled", + "publicNetworkAccessEnabled", + "queueEncryptionKeyType", + "sftpEnabled", + "sharedAccessKeyEnabled", + "tableEncryptionKeyType" + ], + "accountKind": "StorageV2", + "accountReplicationType": "LRS", + "accountTier": "Standard", + "allowNestedItemsToBePublic": true, + "crossTenantReplicationEnabled": true, + "defaultToOauthAuthentication": false, + "dnsEndpointType": "Standard", + "infrastructureEncryptionEnabled": false, + "isHnsEnabled": false, + "localUserEnabled": true, + "location": "eastus", + "minTlsVersion": "TLS1_2", + "name": "exampleaccount4cb2982b", + "nfsv3Enabled": false, + "publicNetworkAccessEnabled": true, + "queueEncryptionKeyType": "Service", + "resourceGroupName": "exampleresourcegroup35548da3", + "sftpEnabled": false, + "sharedAccessKeyEnabled": true, + "tableEncryptionKeyType": "Service", + "tags": { + "environment": "staging" + } + }, + "outputs": { + "__meta": "{\"e2bfb730-ecaa-11e6-8f88-34363bc7c4c0\":{\"create\":3600000000000,\"delete\":3600000000000,\"read\":300000000000,\"update\":3600000000000},\"schema_version\":\"4\"}", + "accessTier": "Hot", + "accountKind": "StorageV2", + "accountReplicationType": "LRS", + "accountTier": "Standard", + "allowNestedItemsToBePublic": true, + "allowedCopyScope": "", + "azureFilesAuthentication": null, + "blobProperties": { + "changeFeedEnabled": false, + "changeFeedRetentionInDays": 0, + "containerDeleteRetentionPolicy": null, + "corsRules": [], + "defaultServiceVersion": "", + "deleteRetentionPolicy": null, + "lastAccessTimeEnabled": false, + "restorePolicy": null, + "versioningEnabled": false + }, + "crossTenantReplicationEnabled": true, + "customDomain": null, + "customerManagedKey": null, + "defaultToOauthAuthentication": false, + "dnsEndpointType": "Standard", + "edgeZone": "", + "enableHttpsTrafficOnly": true, + "httpsTrafficOnlyEnabled": true, + "id": "/subscriptions/12345/resourceGroups/exampleresourcegroup35548da3/providers/Microsoft.Storage/storageAccounts/exampleaccount4cb2982b", + "identity": null, + "immutabilityPolicy": null, + "infrastructureEncryptionEnabled": false, + "isHnsEnabled": false, + "largeFileShareEnabled": false, + "localUserEnabled": true, + "location": "eastus", + "minTlsVersion": "TLS1_2", + "name": "exampleaccount4cb2982b", + "networkRules": null, + "nfsv3Enabled": false, + "primaryAccessKey": { + "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", + "plaintext": "\"SECRET\"" + }, + "primaryBlobConnectionString": { + "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", + "plaintext": "\"SECRET\"" + }, + "primaryBlobEndpoint": "https://exampleaccount4cb2982b.blob.core.windows.net/", + "primaryBlobHost": "exampleaccount4cb2982b.blob.core.windows.net", + "primaryBlobInternetEndpoint": "", + "primaryBlobInternetHost": "", + "primaryBlobMicrosoftEndpoint": "", + "primaryBlobMicrosoftHost": "", + "primaryConnectionString": { + "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", + "plaintext": "\"SECRET\"" + }, + "primaryDfsEndpoint": "https://exampleaccount4cb2982b.dfs.core.windows.net/", + "primaryDfsHost": "exampleaccount4cb2982b.dfs.core.windows.net", + "primaryDfsInternetEndpoint": "", + "primaryDfsInternetHost": "", + "primaryDfsMicrosoftEndpoint": "", + "primaryDfsMicrosoftHost": "", + "primaryFileEndpoint": "https://exampleaccount4cb2982b.file.core.windows.net/", + "primaryFileHost": "exampleaccount4cb2982b.file.core.windows.net", + "primaryFileInternetEndpoint": "", + "primaryFileInternetHost": "", + "primaryFileMicrosoftEndpoint": "", + "primaryFileMicrosoftHost": "", + "primaryLocation": "eastus", + "primaryQueueEndpoint": "https://exampleaccount4cb2982b.queue.core.windows.net/", + "primaryQueueHost": "exampleaccount4cb2982b.queue.core.windows.net", + "primaryQueueMicrosoftEndpoint": "", + "primaryQueueMicrosoftHost": "", + "primaryTableEndpoint": "https://exampleaccount4cb2982b.table.core.windows.net/", + "primaryTableHost": "exampleaccount4cb2982b.table.core.windows.net", + "primaryTableMicrosoftEndpoint": "", + "primaryTableMicrosoftHost": "", + "primaryWebEndpoint": "https://exampleaccount4cb2982b.z13.web.core.windows.net/", + "primaryWebHost": "exampleaccount4cb2982b.z13.web.core.windows.net", + "primaryWebInternetEndpoint": "", + "primaryWebInternetHost": "", + "primaryWebMicrosoftEndpoint": "", + "primaryWebMicrosoftHost": "", + "publicNetworkAccessEnabled": true, + "queueEncryptionKeyType": "Service", + "queueProperties": { + "corsRules": [], + "hourMetrics": { + "enabled": true, + "includeApis": true, + "retentionPolicyDays": 7, + "version": "1.0" + }, + "logging": { + "delete": false, + "read": false, + "retentionPolicyDays": 0, + "version": "1.0", + "write": false + }, + "minuteMetrics": { + "enabled": false, + "includeApis": false, + "retentionPolicyDays": 0, + "version": "1.0" + } + }, + "resourceGroupName": "exampleresourcegroup35548da3", + "routing": null, + "sasPolicy": null, + "secondaryAccessKey": { + "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", + "plaintext": "\"SECRET\"" + }, + "secondaryBlobConnectionString": { + "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", + "plaintext": "\"SECRET\"" + }, + "secondaryBlobEndpoint": "", + "secondaryBlobHost": "", + "secondaryBlobInternetEndpoint": "", + "secondaryBlobInternetHost": "", + "secondaryBlobMicrosoftEndpoint": "", + "secondaryBlobMicrosoftHost": "", + "secondaryConnectionString": { + "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", + "plaintext": "\"SECRET\"" + }, + "secondaryDfsEndpoint": "", + "secondaryDfsHost": "", + "secondaryDfsInternetEndpoint": "", + "secondaryDfsInternetHost": "", + "secondaryDfsMicrosoftEndpoint": "", + "secondaryDfsMicrosoftHost": "", + "secondaryFileEndpoint": "", + "secondaryFileHost": "", + "secondaryFileInternetEndpoint": "", + "secondaryFileInternetHost": "", + "secondaryFileMicrosoftEndpoint": "", + "secondaryFileMicrosoftHost": "", + "secondaryLocation": "", + "secondaryQueueEndpoint": "", + "secondaryQueueHost": "", + "secondaryQueueMicrosoftEndpoint": "", + "secondaryQueueMicrosoftHost": "", + "secondaryTableEndpoint": "", + "secondaryTableHost": "", + "secondaryTableMicrosoftEndpoint": "", + "secondaryTableMicrosoftHost": "", + "secondaryWebEndpoint": "", + "secondaryWebHost": "", + "secondaryWebInternetEndpoint": "", + "secondaryWebInternetHost": "", + "secondaryWebMicrosoftEndpoint": "", + "secondaryWebMicrosoftHost": "", + "sftpEnabled": false, + "shareProperties": { + "corsRules": [], + "retentionPolicy": { + "days": 7 + }, + "smb": null + }, + "sharedAccessKeyEnabled": true, + "staticWebsite": null, + "tableEncryptionKeyType": "Service", + "tags": { + "environment": "staging" + } + }, + "parent": "urn:pulumi:test::storage::pulumi:pulumi:Stack::storage-test", + "dependencies": [ + "urn:pulumi:test::storage::azure:core/resourceGroup:ResourceGroup::exampleResourceGroup" + ], + "provider": "urn:pulumi:test::storage::pulumi:providers:azure::default::515481f4-90eb-46e4-a36e-29ad4413fb22", + "propertyDependencies": { + "accountReplicationType": [], + "accountTier": [], + "location": [ + "urn:pulumi:test::storage::azure:core/resourceGroup:ResourceGroup::exampleResourceGroup" + ], + "resourceGroupName": [ + "urn:pulumi:test::storage::azure:core/resourceGroup:ResourceGroup::exampleResourceGroup" + ], + "tags": [] + }, + "additionalSecretOutputs": [ + "primaryAccessKey", + "primaryBlobConnectionString", + "primaryConnectionString", + "secondaryAccessKey", + "secondaryBlobConnectionString", + "secondaryConnectionString" + ], + "created": "2024-09-05T09:23:38.680385Z", + "modified": "2024-09-05T09:23:38.680385Z" + } + ] + } +}`) diff --git a/upgrade.go b/upgrade.go index 99c6d60..18d8311 100644 --- a/upgrade.go +++ b/upgrade.go @@ -29,12 +29,14 @@ import ( "github.com/stretchr/testify/require" "github.com/pulumi/pulumi/pkg/v3/testing/integration" + "github.com/pulumi/pulumi/sdk/v3/go/common/apitype" "github.com/pulumi/pulumi/sdk/v3/go/common/util/contract" pulumirpc "github.com/pulumi/pulumi/sdk/v3/proto/go" jsonpb "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/reflect/protoreflect" "github.com/pulumi/providertest/flags" + "github.com/pulumi/providertest/pulumitest/sanitize" "github.com/pulumi/providertest/replay" "github.com/pulumi/pulumi/sdk/v3/go/common/resource" ) @@ -662,7 +664,16 @@ func (b *providerUpgradeBuilder) providerUpgradeRecordBaselines(t *testing.T) { fmt.Sprintf("PULUMI_DEBUG_GRPC=%s", info.grpcFile), ), ExportStateValidator: func(t *testing.T, state []byte) { - writeFile(t, info.stateFile, state) + var stack apitype.UntypedDeployment + err := json.Unmarshal(state, &stack) + require.NoError(t, err) + + newStack, err := sanitize.SanitizeSecretsInStackState(&stack) + require.NoError(t, err) + + newState, err := json.MarshalIndent(newStack, "", " ") + require.NoError(t, err) + writeFile(t, info.stateFile, newState) t.Logf("wrote %s", info.stateFile) }, Config: b.config,