29 KiB
title_tag, meta_desc, title, h1, meta_image, menu, aliases
title_tag | meta_desc | title | h1 | meta_image | menu | aliases | ||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
Secrets | Pulumi Concepts | This page provides an overview of how Pulumi manages sensitive configuration data using secrets. | Secrets | Secrets | /images/docs/meta-images/docs-meta.png |
|
|
All resource input and output values are recorded as state
, and are stored in the Pulumi Cloud, a file, or a pluggable provider that you choose. These raw values are usually just server names, configuration settings, etc. However, these values sometimes contain sensitive data, such as database passwords or service tokens. Pulumi never sends authentication secrets or credentials to the Pulumi Cloud.
The Pulumi Cloud always transmits and stores entire state files securely; however, Pulumi also supports encrypting specific values as “secrets” for extra protection. Encryption ensures that these values never appear as plaintext in your state file. By default, the encryption method uses automatic, per-stack encryption keys provided by the Pulumi Cloud or you can use a provider of your own choosing instead.
To encrypt a configuration setting before runtime, you can use the CLI command config set
command with a --secret
flag. You can also set a secret during runtime. Any Output<T>
value can be marked secret. If an output is a secret, any computed values derived from it—such as those derived through an apply
call —will also be marked secret. All these encrypted values are stored in your state file.
An Output<T>
can be marked secret in a number of ways:
- By reading a secret from configuration using {{< pulumi-config-getsecret >}} or {{< pulumi-config-requiresecret >}}.
- By creating a new secret value with {{< pulumi-secret-new >}}, such as when generating a new random password.
- By marking a resource as having secret properties using
additionalSecretOutputs
. - By computing a secret value by using
apply
or {{< pulumi-all >}} with another secret value.
As soon as an Output<T>
is marked secret, the Pulumi engine will encrypt it wherever it is stored.
{{% notes "info" %}}
Inside of an apply
or Output.all
call, your secret is decrypted into plaintext for use within the callback. It is up to your program to treat this value sensitively and only pass the plaintext value to code that you trust.
{{% /notes %}}
Programmatically Creating Secrets
There are two ways to programmatically create secret values:
{{< chooser language "javascript,typescript,python,go,csharp,java,yaml" >}}
{{% choosable language javascript %}}
- Using
getSecret(key)
orrequireSecret(key)
when reading a value from config. - Calling
pulumi.secret(value)
to construct a secret from an existing value.
{{% /choosable %}} {{% choosable language typescript %}}
- Using
getSecret(key)
orrequireSecret(key)
when reading a value from config. - Calling
pulumi.secret(value)
to construct a secret from an existing value.
{{% /choosable %}} {{% choosable language python %}}
- Using
get_secret
orrequire_secret
when reading a value from config. - Calling
Output.secret
to construct a secret from an existing value.
{{% /choosable %}} {{% choosable language go %}}
- Using
config.GetSecret(key)
orconfig.RequireSecret(key)
when reading a value from config. - Calling
pulumi.ToSecret(value)
to construct a secret from an existing value.
{{% /choosable %}} {{% choosable language csharp %}}
- Using
Config.GetSecret(key)
orConfig.RequireSecret(key)
when reading a value from config. - Calling
Output.CreateSecret(value)
to construct a secret from an existing value.
{{% /choosable %}} {{% choosable language java %}}
- Using
ctx.config().getSecret(key)
orctx.config().requireSecret(key)
when reading a value from config. - Calling
Output.of(value).asSecret()
to construct a secret from an existing value.
{{% /choosable %}} {{% choosable language yaml %}}
- Setting
configuration.${KEY}.Secret: true
when reading a value from the config. - Calling
Fn::Secret
to construct a secret from an existing value.
{{% /choosable %}}
{{< /chooser >}}
As an example, let’s create an AWS Parameter Store secure value. Parameter Store is an AWS service that stores strings. Those strings can either be secret or not. To create an encrypted value, we need to pass an argument to initialize the store’s value
property. Unfortunately, the obvious thing to do —passing a raw, unencrypted value— means that the value is also stored in the Pulumi state, unencrypted so we need to ensure that the value is a secret:
{{< chooser language "javascript,typescript,python,go,csharp,java,yaml" >}}
{{% choosable language javascript %}}
const cfg = new pulumi.Config()
const param = new aws.ssm.Parameter("a-secret-param", {
type: "SecureString",
value: cfg.requireSecret("my-secret-value"),
});
{{% /choosable %}} {{% choosable language typescript %}}
const cfg = new pulumi.Config()
const param = new aws.ssm.Parameter("a-secret-param", {
type: "SecureString",
value: cfg.requireSecret("my-secret-value"),
});
{{% /choosable %}} {{% choosable language python %}}
cfg = pulumi.Config()
param = ssm.Parameter("a-secret-param",
type="SecureString",
value=cfg.require_secret("my-secret-value"))
{{% /choosable %}} {{% choosable language go %}}
package main
import (
"github.com/pulumi/pulumi-aws/sdk/v4/go/aws/ssm"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi/config"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
cfg := config.New(ctx, "")
param, err := ssm.NewParameter(ctx, "a-secret-param", &ssm.ParameterArgs{
Type: "SecureString",
Value: cfg.RequireSecret("my-secret-value"),
})
if err != nil {
return err
}
return nil
}
}
{{% /choosable %}} {{% choosable language csharp %}}
var cfg = new Pulumi.Config()
var param = new Aws.Ssm.Parameter("a-secret-param", new Aws.Ssm.ParameterArgs
{
type = pulumi.String("SecureString"),
value = cfg.RequireSecret("my-secret-value"),
});
{{% /choosable %}} {{% choosable language java %}}
var config = ctx.config();
var param = new com.pulumi.aws.ssm.Parameter("a-secret-param",
com.pulumi.aws.ssm.ParameterArgs.builder()
.type("SecureString")
.value(config.requireSecret("my-secret-value"))
.build());
{{% /choosable %}} {{% choosable language yaml %}}
configuration:
mySecretValue:
secret: true
resources:
param:
type: aws:ssm:Parameter
properties:
type: SecureString
value: mySecretValue
{{% /choosable %}}
{{< /chooser >}}
The Parameter
resource’s value
property is encrypted in the Pulumi state file.
Pulumi tracks the transitive use of secrets, so that your secret won’t end up accidentally leaking into the state file. Tracking includes automatically marking data generated from secret inputs as secret themselves, as well as fully encrypting any resource properties that include secrets in them.
How Secrets Relate to Outputs
Secrets have the same type, Output<T>
, as do unencrypted resource outputs. The difference is that they are marked internally as needing encryption before persisting in the state file. When you combine an existing output that is marked as a secret using apply
or Output.all
, the resulting output is also marked as a secret.
An apply
’s callback is given the plaintext value of the underlying secret. Although Pulumi ensures that the value returned from an apply
on a secret is also marked as secret, Pulumi cannot guarantee that the apply
callback itself will not expose the secret value —for instance, by explicitly printing the value to the console or saving it to a file.
{{% notes "warning" %}} Be careful that you do not pass this plaintext value to code that might expose it. {{% /notes %}}
Explicitly Marking Resource Outputs as Secrets
It is possible to mark resource outputs as containing secrets. In this case, Pulumi will automatically treat those outputs as secrets and encrypt them in the state file and anywhere they flow to. To do so, use the additional secret outputs
option.
Encrypted Secrets in Configuration Data
Some configuration data is sensitive, such as database passwords or service tokens. For such cases, passing the --secret
flag to the config set
command encrypts the data and stores the resulting ciphertext instead of plaintext.
{{% notes "info" %}} By default, the Pulumi CLI uses a per-stack encryption key managed by the Pulumi Cloud, and a per-value salt, to encrypt values. To use an alternative encryption provider, refer to Configuring Secrets Encryption. {{% /notes %}}
For example, this command sets a configuration variable named dbPassword
to the plaintext value S3cr37
:
$ pulumi config set --secret dbPassword S3cr37
If we list the configuration for our stack, the plaintext value for dbPassword
will not be printed:
$ pulumi config
KEY VALUE
aws:region us-west-1
dbPassword [secret]
Similarly, if our program attempts to print the value of dbPassword
to the console-either intentionally or accidentally-Pulumi will mask it out:
{{< chooser language "javascript,typescript,python,go,csharp,java,yaml" >}}
{{% choosable language javascript %}}
var pulumi = require("@pulumi/pulumi");
var config = new pulumi.Config();
console.log("Password: " + config.require("dbPassword"));
{{% /choosable %}} {{% choosable language typescript %}}
import * as pulumi from "@pulumi/pulumi";
const config = new pulumi.Config();
console.log(`Password: ${config.require("dbPassword")}`);
{{% /choosable %}} {{% choosable language python %}}
import pulumi
config = pulumi.Config()
print('Password: {}'.format(config.require('dbPassword')))
{{% /choosable %}} {{% choosable language go %}}
c := config.New(ctx, "")
fmt.Println("Password: "+c.Require("dbPassword"))
{{% /choosable %}} {{% choosable language csharp %}}
var config = new Pulumi.Config();
Console.WriteLine($"Password: {config.Require("dbPassword")}");
{{% /choosable %}} {{% choosable language java %}}
var config = ctx.config();
ctx.log().info(String.format("Password: %s", config.require("dbPassword")));
{{% /choosable %}} {{% choosable language yaml %}}
config:
dbPassword:
type: string
secret: true
outputs:
password: ${dbPassword}
{{% /choosable %}}
{{< /chooser >}}
Running this program yields the following result:
$ pulumi up
Password: [secret]
By default, configuration values are saved in plaintext. To explicitly denote a plaintext or unencrypted configuration value, pass the --plaintext
flag. This flag can be used to indicate that you did not want an encrypted secret.
$ pulumi config set --plaintext aws:region us-west-2
Using Configuration and Secrets in Code
To access configuration or secret values for your package, project, or component, use the pulumi.Config
type. This type offers a collection of getters and setters for retrieving configuration values of various types by their key.
To begin, allocate an instance of the pulumi.Config
object. Its constructor takes an optional namespace for all configuration keys being read back. Similar rules to the CLI usage apply here, in that if you omit the namespace argument, the current project is used. This is the common case for project configuration but is not what you want for packages and components which need their own isolated configuration.
For example, assume the following configuration values have been set:
$ pulumi config set name BroomeLLC # set a plaintext value
$ pulumi config set --secret dbPassword S3cr37 # set an encrypted secret value
Use the following code to access these configuration values in your Pulumi program:
{{< chooser language "javascript,typescript,python,go,csharp,java,yaml" >}}
{{% choosable language javascript %}}
var pulumi = require("@pulumi/pulumi");
var config = new pulumi.Config();
var name = config.require("name");
var dbPassword = config.requireSecret("dbPassword");
{{% /choosable %}} {{% choosable language typescript %}}
import * as pulumi from "@pulumi/pulumi";
const config = new pulumi.Config();
const name = config.require("name");
const dbPassword = config.requireSecret("dbPassword");
{{% /choosable %}} {{% choosable language python %}}
import pulumi
config = pulumi.Config()
print(config.require('name'))
print(config.require_secret('dbPassword'))
{{% /choosable %}} {{% choosable language go %}}
package main
import (
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi/config"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
c := config.New(ctx, "")
name := c.Require("name")
dbPassword := c.RequireSecret("dbPassword")
}
}
{{% /choosable %}} {{% choosable language csharp %}}
using System.Threading.Tasks;
using Pulumi;
class MyStack : Stack
{
public MyStack()
{
var config = new Config();
var name = config.Require("name");
var dbPassword = config.RequireSecret("dbPassword");
}
}
{{% /choosable %}} {{% choosable language java %}}
package myproject;
import com.pulumi.Context;
import com.pulumi.Exports;
import com.pulumi.Pulumi;
public class App {
public static void main(String[] args) {
Pulumi.run(App::stack);
}
public static void stack(Context ctx) {
var config = ctx.config();
var name = config.require("name");
var dbPassword = config.requireSecret("dbPassword");
}
}
{{% /choosable %}} {{% choosable language yaml %}}
config:
name:
type: string
dbPassword:
type: string
secret: true
{{% /choosable %}}
{{< /chooser >}}
In this example, we have read back the name
and dbPassword
configuration variables programmatically. The name
is just the string BroomeLLC
, while the dbPassword
is a secret output value that is encrypted.
Notice the keys used above have no namespaces, both in the CLI gestures and in the pulumi.Config
constructor. This means they have taken our project name as the default namespace. We could have specified this explicitly, as in pulumi config set broome-proj:name BroomeLLC
and new pulumi.Config("broome-proj")
.
Secrets within structured config are also supported. Consider a list of endpoints, each having a url
and token
property. The token
value could be stored as a secret:
$ pulumi config set --path endpoints[0].url https://example.com
$ pulumi config set --path --secret endpoints[0].token accesstokenvalue
A Warning: Using Secrets in Code
On pulumi up
, secret values are decrypted and made available in plaintext at runtime. These may be read through any of the standard pulumi.Config
getters shown above. While it is possible to read a secret using the ordinary non-secret getters, this is almost certainly not what you want. Use the secret variants of the configuration APIs instead, since this ensures that all transitive uses of that secret are themselves also marked as secrets.
Configuring Secrets Encryption
The Pulumi Cloud automatically manages per-stack encryption keys on your behalf. Anytime you encrypt a value using --secret
or by programmatically wrapping it as a secret at runtime, a secure protocol is used between the CLI and Pulumi Cloud that ensures secret data is encrypted in transit, at rest, and physically anywhere it gets stored. For more details about the concept of state files and backends, refer to State and Backends.
The default encryption mechanism may be insufficient in the following scenarios:
-
If you are using the Pulumi CLI independent of the Pulumi Cloud-either in local mode, or by using one of the available backend plugins (such as those that store state in AWS S3, Azure Blob Store, or Google Object Storage).
-
If your team already has a preferred cloud encryption provider that you would like to use.
In both cases, you can continue using secrets management as described above, but instruct Pulumi to use an alternative encryption provider.
Initializing a Stack with Alternative Encryption
To specify an alternative encryption provider, specify it at stack initialization time:
$ pulumi stack init <name> --secrets-provider="<provider>://<provider-settings>"
After doing so, all encryption operations for your stack will use the custom provider settings. The <provider>
and <provider-settings>
are specific to your chosen encryption provider. See below for the available providers and their options.
Pulumi uses the Go Cloud Development Kit to implement pluggable secrets providers. In the event configuration or authentication options below do not work, the Go CDK documentation can be consulted for debugging information.
Available Encryption Providers
Pulumi supports the following encryption providers:
awskms
: AWS Key Management Service (KMS)azurekeyvault
: Azure Key Vaultgcpkms
: Google Cloud Key Management Service (KMS)hashivault
: HashiCorp Vault Transit Secrets Engine
Each provider has its own unique <provider-settings>
and authentication mechanisms.
AWS Key Management Service (KMS)
The awskms
provider uses an existing KMS key in your AWS account for encryption. This key can be specified using one of three approaches:
- By ID:
awskms://1234abcd-12ab-34cd-56ef-1234567890ab?region=us-east-1
. - By alias:
awskms://alias/ExampleAlias?region=us-east-1
. - By ARN:
awskms:///arn:aws:kms:us-east-1:111122223333:key/1234abcd-12ab-34bc-56ef-1234567890ab?region=us-east-1
.
For example, this configures a stack to use an AWS KMS key with ID 1234abcd-12ab-34cd-56ef-1234567890ab
:
$ pulumi stack init my-stack \
--secrets-provider="awskms://1234abcd-12ab-34cd-56ef-1234567890ab?region=us-east-1"
If you have previously configured the AWS CLI, the same credentials will be used. These can also be overridden using the standard AWS_ACCESS_KEY_ID
and AWS_SECRET_ACCESS_KEY
environment variables. For more options, refer to the AWS Go SDK documentation.
{{% notes "info" %}}
As of Pulumi CLI v3.33.1, instead of specifying the AWS Profile using the AWS_PROFILE
environment variable, add awssdk=v2
and profile=
followed by the profile name to the query string.
- By ID:
awskms://1234abcd-12ab-34cd-56ef-1234567890ab?region=us-east-1&awssdk=v2&profile=dev
. - By alias:
awskms://alias/ExampleAlias?region=us-east-1&awssdk=v2&profile=qa
. - By ARN:
awskms:///arn:aws:kms:us-east-1:111122223333:key/1234abcd-12ab-34bc-56ef-1234567890ab?region=us-east-1&awssdk=v2&profile=prod
. {{% /notes %}} {{% notes "info" %}} As of Pulumi CLI v3.41.1, this secrets backend supports encryption context by settingcontext_{key}={value}
in the query string. Encryption context can be used in IAM policies conditions and it appears in Cloudtrail logs.
For example, take a look at awskms://1234abcd-12ab-34cd-56ef-1234567890ab?region=us-east-1&awssdk=v2&profile=dev&context_project=myproject&context_environment=staging
.
The encryption context here is {"project": "myproject", "environment": "staging"}
. Together with an appropriate IAM policy with conditions, one can grant some user permissions only to
encrypt/decrypt secrets for staging
environment of the myproject
project.
{{% /notes %}}
Azure Key Vault
The azurekeyvault
provider uses an Azure Key Vault key for encryption. This key is specified using an Azure Key object identifier, which includes both your key vault's name and the key to use: azurekeyvault://mykeyvaultname.vault.azure.net/keys/mykeyname
.
For example, this configures a stack to use an Azure Key Vault key named payroll
in vault acmecorpsec
:
$ pulumi stack init my-stack \
--secrets-provider="azurekeyvault://acmecorpsec.vault.azure.net/keys/payroll"
By default, this provider will use Azure Environment Authentication. If you wish to login using the az
command for authentication instead, set AZURE_KEYVAULT_AUTH_VIA_CLI
to "true"
(using double quotes).
Google Cloud Key Management Service (KMS)
The gcpkms
provider uses an existing Google Cloud KMS key for encryption. Specify the key resource ID for the key to use, which is a URL including your project, location, keyring, and key name: gcpkms://projects/MYPROJECT/locations/MYLOCATION/keyRings/MYKEYRING/cryptoKeys/MYKEY
. The key's purpose needs to be ENCRYPT_DECRYPT
.
For example, this configures a stack to use a Google Cloud KMS key payroll
in project acmecorpsec
, location us-west1
, and key ring named prod
:
$ pulumi stack init my-stack \
--secrets-provider="gcpkms://projects/acmecorpsec/locations/us-west1/keyRings/prod/cryptoKeys/payroll"
This provider will use your Google Cloud Application Default Credentials. If you've previously configured the gcloud
CLI, the same credentials will be used for authentication. For alternative configuration mechanisms, refer to Authenticating as a service account.
HashiCorp Vault Transit Secrets Engine
The hashivault
provider uses Vault's Transit Secrets Engine to encrypt and decrypt information. You only need to pass a key name for the provider setting: hashivault://mykey
. The Vault server endpoint and authentication token to use are provided with the VAULT_SERVER_URL
and VAULT_SERVER_TOKEN
, respectively.
For example, this configures a stack to use a HashiCorp Vault transit key named payroll
:
$ pulumi stack init my-stack \
--secrets-provider="hashivault://payroll"
Changing the Secrets Provider for a Stack
To change the secrets provider for an existing stack use the pulumi stack change-secrets-provider
command.
$ pulumi stack change-secrets-provider "<secrets-provider>"
This will change the encrypted secrets in the provider configuration and the stack's state file to use the new secrets provider. The supported secrets providers are:
default
passphrase
awskms
azurekeyvault
gcpkms
hashivault
After the provider has been changed, you should be able to run pulumi preview
and see no proposed changes. Your configuration secrets
and state files are now encrypted using the new secrets provider.
Manage Secrets using Pulumi ESC environments
With Pulumi ESC, you can manage secrets wherever they live. Pulumi ESC provides a centralized abstraction in front of the most common secrets manager/vaults while providing security through RBAC and audit controls.
Sharing Secrets Across Multiple Teams
You may have multiple teams that each own different secrets and manage their lifetimes, and choose to store them in various AWS secret manager secrets.
For example, let's say you have a centralized billing service team that manages your team's payment process API keys. They can have a Billing
environment defined like this:
values:
aws:
creds:
fn::open::aws-login:
oidc:
duration: 1h
roleArn: arn:aws:iam::************:role/billing-oidc
sessionName: pulumi-environments-session
team:
secrets:
fn::open::aws-secrets:
region: us-west-2
login: ${aws.creds}
get:
paymentApiKey:
secretId: production/paymentAPIKey
backupPaymentAPIKey:
secretId: production/backupPaymentAPIKey
There could be another environment for the Subscription Management team, Subscription_Management_Prod
, that imports the Billing
environment.
imports:
- Prod
- Billing
values:
serviceName: Subscription Management
numInstances: 3
# more service specific values here
secrets:
fn::open::aws-secrets:
region: us-west-2
login: ${aws.creds}
get:
dbPassword:
secretId: production/rdsPassword
We can use the command line to open this environment and access this secret, if access controls allow:
$ pulumi env open myorg/subscription_management_prod
Which should look like this:
{
"aws": {
"creds": {
"accessKeyId": "AKIAIOSFODNN7EXAMPLE",
"secretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
"sessionToken": "eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE2OTY1NzA3NTIsImV4cCI6MTcyODEwNjc1MiwiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSIsIkdpdmVuTmFtZSI6IkpvaG5ueSIsIlN1cm5hbWUiOiJSb2NrZXQiLCJFbWFpbCI6Impyb2NrZXRAZXhhbXBsZS5jb20iLCJSb2xlIjpbIk1hbmFnZXIiLCJQcm9qZWN0IEFkbWluaXN0cmF0b3IiXX0.yUSeKObQ29c6VHBdmOA4a35yW3SyhZ5DPiG96_u6Tvk"
}
},
"secrets": {
"backupPaymentAPIKey": "prod_3c88Pc8ZfdBdpa135DUEXAMPLE",
"dbPassword": "correct horse battery staple",
"paymentApiKey": "prod_4kcdWj8ZfdBfgjQea135DU00EXAMPLE"
}
}
Cross-cloud secrets
Imagine you have a cross-cloud product that leverages services in GCP and Azure, and you have to manage secrets to access those services in GCP Secrets Manager and in Azure KeyVault. With Pulumi ESC, you can coalesce your secret access to a single entry point.
Here is an example environment config named Cross_Cloud
:
values:
azure:
login:
fn::open::azure-login:
clientId: aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee
tenantId: aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee
subscriptionId: /subscriptions/00000000-0000-0000-0000-000000000000
oidc: true
secrets:
fn::open::azure-secrets:
login: ${azure.login}
vault: https://vault-name.vault.azure.net/type/name/version
get:
api-key:
name: api-key
app-secret:
name: app-secret
# GCP Provider examples
gcp:
login:
fn::open::gcp-login:
project: 123456789
oidc:
workloadPoolId: pulumi-esc
providerId: pulumi-esc
serviceAccount: pulumi-esc@foo-bar-123456.iam.gserviceaccount.com
secrets:
fn::open::gcp-secrets:
login: ${gcp.login}
access:
dbPassword:
name: db-key
Now stacks or other environments that import this environment will have access to the Azure and GCP secrets from one easy access point.
$ pulumi env open myorg/cross_cloud
Which should look like this:
{
"gcp": {
"login": {
// removed for brevity
}
},
"azure": {
"login": {
// removed for brevity
}
},
"secrets": {
"api-key": "ZPUpfjKtY2PDWq3EnyVN",
"app-secret": "vW62BqN9uewuoTtKJB2W3BCxUbHDXc",
"dbPassword": "Rule-Danger-Gray-Dust-9"
}
}