Pulumi Boot Camp Part 1
Defining infrastructure as code (IaC) with Pulumi is a great way of automating your infrastructure without having to deal with the steep learning curve of custom tool-specific language. With Pulumi, you can codify your infrastructure with one of the supported common programming languages. But since everything is a code, it also means that you need to commit this code to some version control system. And that means that you should be careful to not commit secrets and access tokens in plaintext. In this post, you’ll learn how to securely handle secrets in Pulumi.
Pulumi Secrets: The Basics
Whenever you create a resource with Pulumi, you can pass and receive values to or from it. These input and output values are saved in the state, which is stored in a file, a provider, or a cloud-based Pulumi Service.
Most of these input and output values are resources, names, or IP addresses. But sometimes they’ll contain passwords, access tokens, or other sensitive data. For that purpose, Pulumi allows you to encrypt some values as “secrets.” This means they won’t be stored as plaintext values in your Pulumi state file.
To use Pulumi secrets, you only need to pass the –config parameter to the config set command. For example:
pulumi config set --secret DB_PASS secretpassword123
This will add configuration entry to your stack with a ciphertext value instead of plaintext:
$ pulumi config
KEY VALUE
DB_USERNAME plaintextusername
DB_PASS [secret]
The same thing happens when your program prints some values for you. If some of them are secrets, then their values will be masked in the output too.
Using Secrets in the Code
So, you already know how to create secrets in Pulumi. Now, it’s time to see how to actually use their values in your Pulumi code. This will of course differ depending on which programming language you use, but the process will be similar in all languages.
Traditionally, to access standard (non-secret) configuration values in Pulumi, you can do something like this—it’s a JavaScript example:
var db_user = config.require("DB_USERNAME");
Accessing secret values is similar. You only need to change the keyword require to requireSecret, like so:
var db_password = config.requireSecret("DB_PASS");
Protecting Sensitive Outputs
So far, you’ve learned how to provide secrets to Pulumi. But sometimes you’ll get sensitive data from it in the form of output.
For example, Pulumi may create a database for you and print the username and password to it to the output. In such cases, you can explicitly mark some output values as sensitive. Pulumi will automatically encrypt those and treat them as secrets.
Let’s see an example:
let database = new Database("dynamic_database",
{ /* options */ },
{ additionalSecretOutputs: ["pass"] });
The above code will create a database and save the pass output value as secret.
Bring Your Own Encryption
By default, Pulumi uses its own encryption keys (per-stack) if your backend is a Pulumi Service. What if you want to keep the state locally or use other backend plugins? Or maybe you simply want to have more control over the encryption process. In either case, you can use an alternative encryption provider. To do that, you need to specify one when initializing a stack:
pulumi stack init prod_stack --secrets-provider="<provider-name>://<provider-settings>"
You can see the list of available providers here.
Wrapping Up
IaC provides many benefits. It’s quicker to deploy infrastructure with one execution of the tool than clicking around manually in the user interface. And IaC gives you the ability to quickly recreate missing pieces in case of disaster. But it also means that instead of trying to hack the application, an attacker may try to get secrets and passwords from your IaC code instead.
Fortunately, as you can see, managing secrets with Pulumi isn’t that hard. Pulumi does most of the job for you. You just need to tell it which values are sensitive.
For more information about Pulumi, read part 2 of this blog series.