Open-Source Wikis

/

Pulumi

/

Systems

/

Secrets

pulumi/pulumi

Secrets

Active contributors: Justin Van Patten, Thomas Gummerer, Ian Wahbe

Purpose

Pulumi treats secret values as a first-class data type. A value marked secret stays encrypted in the snapshot, in the cloud history, and in all logs (rendered as [secret]). The secrets system controls how those values are encrypted at rest.

Where it lives

pkg/secrets/
├── manager.go              # secrets.Manager interface
├── provider.go             # Manager constructor abstraction
├── mock.go                 # In-memory mock
├── b64/                    # Base64 (no encryption — opt-in for testing)
├── passphrase/             # Local passphrase-derived AES-256-GCM
├── cloud/                  # Cloud KMS providers (AWS KMS, Azure Key Vault, GCP KMS, HashiCorp Vault)
└── service/                # Pulumi Cloud service-managed encryption

The Manager interface

From pkg/secrets/manager.go:

type Manager interface {
    Type() string
    State() interface{}
    Encrypter() Encrypter
    Decrypter() Decrypter
}

Each backend (DIY, Pulumi Cloud) selects a default manager when a stack is created. The CLI lets you override per-stack via pulumi stack init --secrets-provider=<scheme>.

Supported providers

Scheme Manager Where the key lives
default (DIY) passphrase/ Derived from PULUMI_CONFIG_PASSPHRASE (or prompted)
default (Cloud) service/ Pulumi Cloud-managed
passphrase passphrase/ Same as DIY default
awskms://... cloud/aws_kms.go AWS KMS
azurekeyvault://... cloud/azure_keyvault.go Azure Key Vault
gcpkms://... cloud/gcp_kms.go GCP KMS
hashivault://... cloud/hashivault.go HashiCorp Vault
b64 b64/ No encryption (testing only)

The cloud-KMS providers use gocloud.dev/secrets, which is also why URL syntax is consistent.

How secrets flow

graph LR
    Prog[User program] -->|RegisterResource<br/>with secret input| Engine
    Engine -->|provider Configure<br/>+ Check + Diff| Prov[Provider]
    Engine -->|persist| Snap[Snapshot]
    Snap -->|Encrypter.EncryptValue| Encrypted[Ciphertext blob]
    Encrypted -->|on next read| Snap2[Snapshot]
    Snap2 -->|Decrypter.DecryptValue| Plaintext[Plaintext]
    Plaintext --> Engine

The secret bit is sticky: once a property has been marked secret, it stays secret as it flows through outputs, dependencies, and stack references. AdditionalSecretOutputs on a resource state lets the user tag outputs that provider authors didn't already mark.

Snapshot format

Secrets in the snapshot look like:

{
  "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1265ec24",
  "ciphertext": "v1:abc123...:base64ciphertext"
}

The magic constant is the secret marker. Decrypting requires the corresponding Manager. The manager state is itself stored at the top of the snapshot, so the snapshot is self-describing for which provider it needs.

Backend integration

pkg/backend/secrets/ is the bridge between the chosen backend and the secrets system. The DIY backend persists the manager state in the stack file; Pulumi Cloud handles it server-side.

Security properties

  • No secret values transit through engine logs. The diagnostic stream sanitizes them.
  • Secret outputs are encrypted before being written to disk. A crashed update never leaves plaintext in the snapshot.
  • Cross-stack references (StackReference) preserve the secret bit — reading a secret output from another stack returns it as a secret.
  • Plaintext appears only in the user program's process. Provider plugins and language hosts may see it, but the engine itself encrypts it before persisting.

The b64 provider is not encryption — it's intentionally weak so you don't accidentally use it in production. The CLI prints a warning when b64 is selected.

Entry points for modification

  • Adding a new provider — add a subdirectory under pkg/secrets/cloud/ (or top-level if it's substantively different), implement Manager, register the URL scheme.
  • Tightening the redaction — the diagnostic-side scrubbing lives in sdk/go/common/diag/. Changes there affect every CLI command.
  • Changing the snapshot format — must be backwards compatible. New providers should be additive; old snapshots with old providers must keep decrypting.

See also

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Secrets – Pulumi wiki | Factory