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

[esc] secret rotation blog post #13482

Merged
merged 9 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 136 additions & 0 deletions content/blog/esc-secret-rotation-with-iac/architecture.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
162 changes: 162 additions & 0 deletions content/blog/esc-secret-rotation-with-iac/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
---
title: "Secret Rotation with Pulumi ESC"
date: 2024-12-02T11:23:17-08:00
draft: false
meta_desc: "Extending ESC to perform automated secret rotation."
meta_image: meta.png
authors:
- claire-gaestel
tags:
- esc
- secrets
---

Managing secrets in modern cloud applications can be challenging, particularly when it comes to rotation policies. While dynamic secrets (like AWS IAM temporary credentials) handle this automatically, many systems still rely on static secrets that require periodic rotation.

Static secrets, like database passwords or API keys, [should be rotated regularly to maintain security](https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html#272-rotation), and services depending on these secrets need time to transition to new credentials to avoid downtime. This makes rotating credentials error-prone, and often forgotten.

In this post, we'll explore an approach for automating static secret rotation using [Pulumi ESC](https://www.pulumi.com/docs/esc/).
nyobe marked this conversation as resolved.
Show resolved Hide resolved

<!--more-->

## A pattern for extending ESC

We can take advantage of ESC’s integration with [Pulumi Deployments](https://www.pulumi.com/docs/pulumi-cloud/deployments/) to create an ergonomic way of managing rotation schedules ourselves.

We’ll start by defining a custom declarative configuration format for managing a rotation schedule:

```yaml
# we'll create our own "extended" fn config for ESC
xfn::pulumi-scheduled-update:
# stack reference to a pulumi program that manages a rotated secret
stack: "example-rotator-stack-reference/dev"
# schedule that will drive an automatic scheduled deployment
scheduleCron: "0 0 * * 0"
# trigger a manual unscheduled rotation whenever this value changes
trigger: "break-glass"
```

Using webhooks, we’ll monitor when the environment is updated, then search for these configuration blocks, and use them to drive scheduled deployments of a stack that rotates credentials. Using ESC’s composition primitives, we can combine this rotation schedule config with a `fn::open::pulumi-stacks` provider to read the current secret value from the rotator stack to provide to our application.
nyobe marked this conversation as resolved.
Show resolved Hide resolved

Here is how the solution fits together:
![Architecture](architecture.svg)
Let's break down each component and see how they work together to solve our rotation challenges.

### The Rotator: Managing Credential Lifecycles

At the heart of our solution is a [generic `Rotator` component](https://github.com/pulumi/esc-examples/blob/claire/esc-rotation-example/rotate/example/rotator/rotator.ts) that manages credential pairs. It's designed to handle any type of static secret while ensuring zero-downtime rotations. The way it does this is by maintaining two versions of each secret: “current” and “previous”. Each time the stack is deployed, “current” is replaced with a newly provisioned credential, the old value is demoted to “previous”, and the old previous credential is decommissioned. Crucially, the demoted secret _remains valid_, which allows consuming services enough time to switch over to the new secret.
nyobe marked this conversation as resolved.
Show resolved Hide resolved

```typescript
const creds = new Rotator("rotating-creds", {
// rotate credentials on every deployment.
trigger: Date().toString(),
// create a set of equivalent credentials that will be rotated between.
// a particular credential should be replaced whenever`trigger` is updated.
nyobe marked this conversation as resolved.
Show resolved Hide resolved
construct: (name, trigger) => {
// in this example we're just creating passwords,
// but these could be mysql users or anything else.
return new random.RandomPassword(name, {
length: 10,
keepers: {trigger}
})
},
})

// export when the last rotation happened
export const lastUpdate = creds.lastUpdate.apply(date => date.toISOString());
// export the currently active credential, which will be imported by the downstream ESC environment.
export const current = creds.current.result;
```

### The Scheduler: Orchestrating Rotations

The [scheduler component](https://github.com/pulumi/esc-examples/blob/claire/esc-rotation-example/rotate/example/scheduler/index.ts) acts as an orchestrator, watching an ESC environment for updates and managing deployment schedules of the rotator stacks. Whenever a change to the environment is saved, a webhook invokes the scheduler, which parses the environment definition to find `xfn::pulumi-scheduled-update` configuration blocks. Based on these schedule configurations, it creates [scheduled deployments](https://www.pulumi.com/docs/pulumi-cloud/deployments/schedules/) for the referenced rotator stacks automatically.

### Bringing It Together: Environment Configuration

Once wired together, the magic happens in the ESC environment configuration, where we compose a rotation schedule with dynamic credential retrieval, creating a rotated credential that is automatically kept up to date:

```yaml
values:
db-credentials:
stack: database-credential-rotator/dev
schedule:
xfn::pulumi-scheduled-update:
stack: ${db-credentials.stack}
scheduleCron: "0 0 * * 0" # weekly rotation
trigger: "break-glass" # manual trigger
secrets:
fn::open::pulumi-stacks:
stacks:
credentials:
stack: ${db-credentials.stack}

api-keys:
stack: api-key-rotator/dev
schedule:
xfn::pulumi-scheduled-update:
stack: ${api-keys.stack}
scheduleCron: "0 0 1 * *" # monthly rotation
secrets:
fn::open::pulumi-stacks:
stacks:
keys:
stack: ${api-keys.stack}

environmentVariables:
DB_CONNECTION_STRING: ${db-credentials.secrets.credentials.current}
API_KEY: ${api-keys.secrets.keys.current}
```

This configuration demonstrates how our custom extension is able to seamlessly integrate with ESC to manage multiple rotating secrets with different schedules declaratively, co-located with dynamic retrieval of the latest credentials from the rotator stack outputs. Applications consuming this environment will automatically receive the latest credentials without any additional configuration.

## Trying it out

Open the example environment and take note of the current secrets:

```
❯ pulumi env open esc-rotation-demo/test db-credentials.secrets
{
"credentials": {
"current": "OBA=wxS:VT",
"lastUpdate": "2024-11-26T17:49:03.000Z",
"previous": "MUgPXkJ+kE"
}
}
```

Now lets force a rotation by changing the manual trigger:

```
❯ pulumi env set esc-rotation-demo/test \
db-credentials.schedule.xfn::pulumi-scheduled-update.trigger \
break-glass2
```

After a few minutes we can see that the secret has rotated successfully! We can also observe that the previous secret remains valid, giving currently deployed consumers time to update to the new credentials.

```
$ pulumi env open esc-rotation-demo/test db-credentials.secrets
{
"credentials": {
"current": "X2WSmF3+29",
"lastUpdate": "2024-11-26T17:57:05.000Z",
"previous": "OBA=wxS:VT" <---- old "current" has been demoted
}
}
```

## Conclusion

Secret rotation doesn't have to be a manual, error-prone process. By leveraging Pulumi ESC and this component architecture, we can automate and streamline the rotation of static secrets while maintaining system stability and security. The solution is flexible enough to handle various types of secrets while being simple to configure and maintain. This approach offers several advantages:

- Automated: Set-and-forget secret rotation
- Zero-downtime: Smooth transitions between credentials
- Flexible: Works with any type of static secret
- Declarative: Configuration through ESC environments
- Auditable: Clear tracking of rotation history
- Scalable: Easy to manage across multiple environments

[The complete example is available for your perusal here.](https://github.com/pulumi/esc-examples/pull/3)
nyobe marked this conversation as resolved.
Show resolved Hide resolved

This pattern of extending ESC through configuration-driven components is a powerful technique that can be applied to other use cases. We’re excited to see what else you come up with 🙂
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions data/team/team/claire-gaestel.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
id = "claire-gaestel"
name = "Claire Gaestel"
title = "Software Engineer"
company = "Pulumi"
weight = 1

status = "active"

[social]
github = "nyobe"
linkedin = "claire-gaestel-01420046"
Binary file added static/images/team/claire-gaestel.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading