Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pulumi output with a single secret taints all other outputs when using Go SDK #9564

Closed
calufornia opened this issue May 6, 2022 · 5 comments · Fixed by #9842
Closed

Pulumi output with a single secret taints all other outputs when using Go SDK #9564

calufornia opened this issue May 6, 2022 · 5 comments · Fixed by #9842
Assignees
Labels
area/secrets kind/bug Some behavior is incorrect or out of spec language/go resolution/fixed This issue was fixed
Milestone

Comments

@calufornia
Copy link

What happened?

When using the Go SDK, if a stack has any secret in the output, and we reference any of the outputs (secret or not), Pulumi will treat that output as a secret. This is problematic since pulumi preview will mask the output even though it isn't secret and we'd like to see the change. Additionally, if the output is used in the context of something like a Kubernetes env var, this issue in conjunction with pulumi/pulumi-kubernetes#1576 causes the entire env var array to be labeled as secret.

I believe that there was a fix for this for the Node and Python SDKs in #2744, but no such fix was created for the Go SDK. In the meantime, I've just been wrapping all the outputs in pulumi.Unsecret - let me know if there's a better workaround here. If this is indeed an issue, I can also go ahead and start working on a fix.

Steps to reproduce

In my case, I created a Kubernetes deployment, which had the following:

Env: core.EnvVarArray{
	core.EnvVarArgs{
		Name: pulumi.String("some-name"),
		Value: stackRef.GetStringOutput(pulumi.String("some-output")),
	},
}

where some-output is a non-secret output from another stack.

Then, after running pulumi preview, I see something like

~ spec: {
  ~ template: {
      ~ spec: {
          ~ containers: [
              ~ [0]: {
                      ~ env: [
                          ~ [0]: {
                                  ~ name: [secret] => [secret]
                                }
                        ]
                    }
            ]
        }
    }
}

Expected Behavior

Pulumi not to treat the output as a secret (and causing the env var to be considered a secret)

Actual Behavior

Pulumi considered the output to be a secret which caused the preview to show [secret] => [secret]

Versions used

pulumi about
CLI
Version      3.32.1
Go Version   go1.18.1
Go Compiler  gc

Plugins
NAME        VERSION
gcp         6.20.0
go          unknown
kubernetes  3.18.3

Host
OS       darwin
Version  12.3.1
Arch     arm64

This project is written in go (/opt/homebrew/bin/go v1.18.1 darwin/arm64)

...

NAME                                        VERSION
github.com/MakeNowJust/heredoc/v2           v2.0.1
github.com/pkg/errors                       v0.9.1
github.com/pulumi/pulumi-gcp/sdk/v6         v6.20.0
github.com/pulumi/pulumi-kubernetes/sdk/v3  v3.18.3
github.com/pulumi/pulumi/sdk                v1.14.1
github.com/pulumi/pulumi/sdk/v3             v3.30.0
k8s.io/apimachinery                         v0.23.6

Additional context

No response

Contributing

Vote on this issue by adding a 👍 reaction.
To contribute a fix for this issue, leave a comment (and link to your pull request, if you've opened one already).

@calufornia calufornia added the kind/bug Some behavior is incorrect or out of spec label May 6, 2022
@mikhailshilkov
Copy link
Member

@calufornia Thank you for reporting this.

I'm failing to understand the exact scenario... Your example is a bit short and has no secrets - what is actually a secret in your program? What are you Unsecret-ing? Do you mind sharing a larger snippet or a full repro program?

@mikhailshilkov mikhailshilkov added the awaiting-feedback Blocked on input from the author label May 8, 2022
@calufornia
Copy link
Author

calufornia commented May 9, 2022

@mikhailshilkov Sure thing, here's a more full-fledged example below:

Let's say I have a stack (call it test for simplicity) that just creates two outputs, like so:

func main() {
    pulumi.Run(func(ctx *pulumi.Context) error {
        ctx.Export("non_secret_output", pulumi.String("TEST_VALUE"))
        ctx.Export("secret_output", pulumi.ToSecret("MY_API_KEY"))
        return nil
    }
}

After initializing the stack:

> pulumi stack
...
Current stack outputs (2):
    OUTPUT             VALUE
    non_secret_output  TEST_VALUE
    secret_output      [secret]

You can see that there's two outputs, one that's a secret and one that isn't.

Now let's say I have another stack that references the non-secret output in the test stack, like so:

func main() {
    pulumi.Run(func(ctx *pulumi.Context) error {
        testRef, err := pulumi.NewStackReference(ctx, "test", &pulumi.StackReferenceArgs{
            Name: pulumi.String("test"),
        })
        if err != nil {
            return err
        }
        _, err := appsv1.NewDeployment(ctx, "nginx", &appsv1.DeploymentArgs{
            Metadata: &metav1.ObjectMetaArgs{
                Labels: pulumi.StringMap{
                    "app": pulumi.String("nginx"),
                },
            },
            Spec: &appsv1.DeploymentSpecArgs{
	        Selector: &metav1.LabelSelectorArgs{
	            MatchLabels: pulumi.StringMap{
		        "app": pulumi.String("nginx"),
		    },
	        },
	        Template: &corev1.PodTemplateSpecArgs{
	            Metadata: &metav1.ObjectMetaArgs{
		        Labels: pulumi.StringMap{
			    "app": pulumi.String("nginx"),
		        },
		    },
		    Spec: &corev1.PodSpecArgs{
		        Containers: corev1.ContainerArray{
		            &corev1.ContainerArgs{
		                Name:  pulumi.String("nginx"),
			        Image: pulumi.String("nginx:1.14.2"),
			        Env: corev1.EnvVarArray{
			            corev1.EnvVarArgs{
			                Name:  pulumi.String("my_env_var"),
				        Value: testRef.GetStringOutput(pulumi.String("non_secret_output")),
			            },
			        },
		            },
                        },
                    },
                },
            },
        })
        if err != nil {
            return err
        }
        return nil
    })
}

Now, when running pulumi preview --diff, it shows something like:

+ kubernetes:apps/v1:Deployment: (create)
        apiVersion: "apps/v1"
        kind      : "Deployment"
        metadata  : {
            annotations: {
                pulumi.com/autonamed: "true"
            }
            labels     : {
                app                         : "nginx"
                app.kubernetes.io/managed-by: "pulumi"
            }
            name       : "nginx-b718a782"
            namespace  : "default"
        }
        spec      : {
            selector: {
                matchLabels: {
                    app: "nginx"
                }
            }
            template: {
                metadata: {
                    labels: {
                        app: "nginx"
                    }
                }
                spec    : {
                    containers: [secret]
                }
            }
        }

As you can see, Pulumi masks the containers diff as it considers testRef.GetStringOutput(pulumi.String("non_secret_output")) to be a secret value. As a workaround, I've been wrapping that output in pulumi.Unsecret, like so:

corev1.EnvVarArgs{
    Name:  pulumi.String("my_env_var"),
    Value: pulumi.Unsecret(testRef.GetStringOutput(pulumi.String("non_secret_output"))).(pulumi.StringOutput),
},

Which produces the expected non-masked pulumi preview --diff:

+ kubernetes:apps/v1:Deployment: (create)
        apiVersion: "apps/v1"
        kind      : "Deployment"
        metadata  : {
            annotations: {
                pulumi.com/autonamed: "true"
            }
            labels     : {
                app                         : "nginx"
                app.kubernetes.io/managed-by: "pulumi"
            }
            name       : "nginx-b718a782"
            namespace  : "default"
        }
        spec      : {
            selector: {
                matchLabels: {
                    app: "nginx"
                }
            }
            template: {
                metadata: {
                    labels: {
                        app: "nginx"
                    }
                }
                spec    : {
                    containers: [
                        [0]: {
                            env  : [
                                [0]: {
                                    name : "my_env_var"
                                    value: "TEST_VALUE"
                                }
                            ]
                            image: "nginx:1.14.2"
                            name : "nginx"
                        }
                    ]
                }
            }
        }

However, I feel that I shouldn't need to use pulumi.Unsecret - the output non_secret_output was never a secret in the first place, and I'm also not referencing secret_output anywhere in the code. Furthermore, if I stop exporting the secret output from the test stack (i.e. remove the ctx.Export("secret_output", pulumi.ToSecret("MY_API_KEY")) line), Pulumi doesn't consider the non-secret output a secret anymore, and no pulumi.Unsecret is necessary to display a non-masked diff - this is why I believe the single secret output is "tainting" the other output values.

@mikhailshilkov
Copy link
Member

Thank you for the detailed response! Now I understand the problem - and agree this is sub-optimal.

@mikhailshilkov mikhailshilkov added language/go area/secrets and removed awaiting-feedback Blocked on input from the author labels May 9, 2022
@Frassle
Copy link
Member

Frassle commented May 12, 2022

I think this is pretty similar to the issue @AaronFriel was having with YAML. We use MapOutput which is really an Output<Map<string, any>> while what we really want is a type with the semantics of Output<Map<string, Output<any>>> that is all the keys are unknown, but also each value separately tracks it's unknowness/secretness.

@AaronFriel
Copy link
Member

If that's the case, we now have a finer grained "RawOutput" we can use. @Frassle I can take this on Monday.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/secrets kind/bug Some behavior is incorrect or out of spec language/go resolution/fixed This issue was fixed
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants