<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0"><channel><title>Pulumi Blog: Claire Gaestel</title><link>https://www.pulumi.com/blog/author/claire-gaestel/</link><description>Pulumi blog posts: Claire Gaestel.</description><language>en-us</language><pubDate>Thu, 05 Mar 2026 00:00:00 +0000</pubDate><item><title>Now Generally Available: Store Terraform State in Pulumi Cloud</title><link>https://www.pulumi.com/blog/terraform-state-backend-pulumi-cloud/</link><pubDate>Thu, 05 Mar 2026 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/terraform-state-backend-pulumi-cloud/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/terraform-state-backend-pulumi-cloud/index.png" /&gt;
&lt;p&gt;Platform engineering teams managing infrastructure across Terraform and Pulumi now have a way to unify state management without rewriting a single line of HCL. Starting today, Pulumi Cloud can serve as a &lt;a href="https://www.pulumi.com/docs/iac/get-started/terraform/terraform-state-backend/"&gt;Terraform state backend&lt;/a&gt;, letting you store and manage Terraform state alongside your Pulumi stacks. Your team continues using the Terraform or OpenTofu CLI for day-to-day operations while gaining the benefits of Pulumi Cloud: AI-powered infrastructure management with &lt;a href="https://www.pulumi.com/docs/ai/"&gt;Pulumi Neo&lt;/a&gt; — our infrastructure agent — encrypted state storage, update history, state locking, role-based access control, audit policies, and unified resource visibility through &lt;a href="https://www.pulumi.com/docs/insights/discovery/"&gt;Insights&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This feature is now generally available.&lt;/p&gt;
&lt;h2 id="why-this-matters"&gt;Why this matters&lt;/h2&gt;
&lt;p&gt;Most organizations adopting Pulumi are not starting from scratch. They have years of Terraform deployments spread across teams, and migrating everything to a new IaC tool overnight is not realistic. We have heard from customers who are excited about the power of Pulumi Cloud but have had to manage migration projects before they can fully benefit from centralized visibility and governance.&lt;/p&gt;
&lt;p&gt;The Terraform state backend in Pulumi Cloud changes that equation. Instead of requiring a full code conversion before teams see value, you can migrate your state in minutes and immediately unlock Pulumi Cloud capabilities for your existing Terraform infrastructure — including Neo, Pulumi&amp;rsquo;s AI infrastructure agent. Once your Terraform state is in Pulumi Cloud, Neo can reason about those resources the same way it does for Pulumi IaC stacks: finding resources, troubleshooting issues, understanding dependencies, and writing infrastructure code PRs. Teams that prefer Terraform can keep using it, while platform engineers get a single AI-powered control plane across the entire infrastructure estate.&lt;/p&gt;
&lt;h2 id="what-you-get"&gt;What you get&lt;/h2&gt;
&lt;p&gt;When you store Terraform state in Pulumi Cloud, your Terraform-managed resources get the following added functionality:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Agentic infrastructure with Neo.&lt;/strong&gt; &lt;a href="https://www.pulumi.com/docs/ai/"&gt;Neo&lt;/a&gt;, Pulumi&amp;rsquo;s AI infrastructure agent, works across your entire cloud footprint — Terraform and Pulumi IaC alike. Once your Terraform state is in Pulumi Cloud, you can ask Neo to find resources across both tools, trace dependencies that span Terraform and Pulumi stacks, troubleshoot configuration issues, and generate new infrastructure code informed by your existing resources. This means platform teams get a single AI-powered interface regardless of which IaC tool manages each piece of infrastructure.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Encrypted state with update history.&lt;/strong&gt; State is encrypted in transit and at rest. Every change is tracked as a versioned checkpoint visible in the &lt;a href="https://www.pulumi.com/docs/iac/concepts/stacks/#stack-activity"&gt;stack activity tab&lt;/a&gt;, giving you full rollback capability. This is a common concern for teams currently storing state in S3 buckets.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Automatic state locking.&lt;/strong&gt; Pulumi Cloud prevents concurrent Terraform operations from corrupting state, without requiring you to configure DynamoDB tables or other external locking mechanisms.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Role-based access control.&lt;/strong&gt; Control who can read or modify each stack using &lt;a href="https://www.pulumi.com/docs/administration/access-identity/rbac/"&gt;teams and RBAC&lt;/a&gt;, applying the same access policies you use for Pulumi stacks.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Unified resource visibility.&lt;/strong&gt; View Terraform-managed resources alongside Pulumi-managed resources in &lt;a href="https://www.pulumi.com/docs/insights/discovery/search/"&gt;Resource Search&lt;/a&gt;. Each Terraform resource appears in the console using a &lt;code&gt;pulumi:terraform:&amp;lt;tf-type&amp;gt;&lt;/code&gt; naming convention, so you can search and filter using the attribute names you already know.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Audit policies.&lt;/strong&gt; Run &lt;a href="https://www.pulumi.com/docs/insights/policy/policy-groups/"&gt;audit (detective) policy packs&lt;/a&gt; against your Terraform-managed stacks, including Pulumi&amp;rsquo;s &lt;a href="https://www.pulumi.com/docs/insights/policy/policy-packs/pre-built-packs/"&gt;pre-built compliance packs&lt;/a&gt; for CIS, PCI, and more. Pulumi Cloud performs a best-effort schema mapping from Terraform resource shapes to Pulumi provider equivalents, so existing policy packs work without modification in most cases.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Stack outputs and references.&lt;/strong&gt; Terraform root module outputs are automatically mapped to Pulumi &lt;a href="https://www.pulumi.com/docs/iac/concepts/stacks/#outputs"&gt;stack outputs&lt;/a&gt;, making them available via &lt;a href="https://www.pulumi.com/docs/iac/concepts/stacks/#stackreferences"&gt;stack references&lt;/a&gt; and the &lt;a href="https://www.pulumi.com/docs/esc/providers/iac/pulumi-stacks/"&gt;&lt;code&gt;pulumi-stacks&lt;/code&gt; ESC provider&lt;/a&gt;. This is useful for sharing foundational infrastructure like VPC IDs or DNS zones between Terraform and Pulumi stacks, and for incremental migrations where legacy infrastructure stays in Terraform while new stacks are written in Pulumi.&lt;/p&gt;
&lt;h2 id="how-it-works"&gt;How it works&lt;/h2&gt;
&lt;p&gt;Pulumi Cloud implements the &lt;a href="https://developer.hashicorp.com/terraform/language/backend/remote"&gt;Terraform remote backend API&lt;/a&gt;. You point the Terraform CLI at Pulumi Cloud using the standard &lt;code&gt;backend &amp;quot;remote&amp;quot;&lt;/code&gt; configuration block, and no changes to your Terraform code or workflow are required.&lt;/p&gt;
&lt;p&gt;Each Terraform workspace maps to a Pulumi stack. The workspace name follows the convention &lt;code&gt;&amp;lt;project&amp;gt;_&amp;lt;stack&amp;gt;&lt;/code&gt;. For example, &lt;code&gt;networking_prod&lt;/code&gt; creates a stack named &lt;code&gt;prod&lt;/code&gt; in the &lt;code&gt;networking&lt;/code&gt; project.&lt;/p&gt;
&lt;h2 id="get-started"&gt;Get started&lt;/h2&gt;
&lt;p&gt;Migration from S3, Azure Blob, GCS, local backends, or HCP Terraform (Terraform Cloud) takes minutes and is documented in the &lt;a href="https://www.pulumi.com/docs/iac/get-started/terraform/terraform-state-backend/"&gt;Terraform state backend guide&lt;/a&gt;. From S3, Azure Blob, GCS, or local state, back up your state, update your backend block to point to Pulumi Cloud, set &lt;code&gt;TF_TOKEN_api_pulumi_com&lt;/code&gt;, and run &lt;code&gt;terraform init -migrate-state&lt;/code&gt;. From HCP Terraform, export state manually and push it to Pulumi Cloud.&lt;/p&gt;
&lt;p&gt;Each Terraform resource stored in Pulumi Cloud counts as a resource under management, the same as a Pulumi-managed resource. See the &lt;a href="https://www.pulumi.com/pricing/"&gt;pricing page&lt;/a&gt; for details.&lt;/p&gt;
&lt;a
href="https://www.pulumi.com/docs/iac/get-started/terraform/terraform-state-backend/"
class="btn btn-primary"
&gt;
Store Terraform State in Pulumi Cloud
&lt;/a&gt;
&lt;p&gt;If you have questions or feedback, join us in the &lt;a href="https://slack.pulumi.com/"&gt;Pulumi Community Slack&lt;/a&gt; or open an issue on &lt;a href="https://github.com/pulumi/pulumi"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;</description><author>Claire Gaestel</author><category>releases</category><category>features</category><category>terraform</category><category>pulumi-cloud</category><category>infrastructure-as-code</category></item><item><title>Introducing the Terraform State Provider for Pulumi ESC</title><link>https://www.pulumi.com/blog/esc-terraform-state-provider/</link><pubDate>Fri, 13 Feb 2026 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/esc-terraform-state-provider/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/esc-terraform-state-provider/index.png" /&gt;
&lt;p&gt;Many organizations have years of infrastructure built and managed with Terraform.
Outputs such as VPC IDs, subnet lists, database endpoints, and cluster names are the connective tissue between infrastructure layers.
Getting those values into other tools and workflows often means manual copy-paste, wrapper scripts, or brittle glue code.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://www.pulumi.com/docs/esc/providers/iac/terraform-state/"&gt;&lt;code&gt;terraform-state&lt;/code&gt; provider&lt;/a&gt; for Pulumi ESC helps bridge that gap.
It reads outputs directly from your Terraform state files and makes them available as first-class values in your ESC environments — no scripts, no duplication, no drift.
Any output marked as &lt;code&gt;sensitive&lt;/code&gt; in your Terraform state is automatically treated as a secret in ESC.
If you&amp;rsquo;ve used &lt;a href="https://www.pulumi.com/docs/esc/providers/iac/pulumi-stacks/"&gt;&lt;code&gt;pulumi-stacks&lt;/code&gt;&lt;/a&gt; to read outputs from Pulumi stacks, this is the same idea for Terraform.&lt;/p&gt;
&lt;h2 id="how-it-works"&gt;How it works&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;terraform-state&lt;/code&gt; provider uses &lt;code&gt;fn::open::terraform-state&lt;/code&gt; to read from a Terraform state file and surface its outputs as ESC values.
Here&amp;rsquo;s an example that reads from an S3 backend, using the &lt;code&gt;aws-login&lt;/code&gt; provider for credentials, and exports a &lt;code&gt;KUBECONFIG&lt;/code&gt; for an EKS cluster managed by Terraform:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;terraform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;fn::open::terraform-state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;login&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;fn::open::aws-login&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;oidc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;roleArn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;arn:aws:iam::123456789012:role/esc-oidc&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;sessionName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;pulumi-environments-session&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;my-terraform-state-bucket&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;path/to/terraform.tfstate&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;us-west-2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;KUBECONFIG&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${terraform.outputs.kubeconfig}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Once the environment is opened, &lt;code&gt;terraform.outputs&lt;/code&gt; contains every output from the Terraform state.
In this example we take the &lt;code&gt;kubeconfig&lt;/code&gt; output from a Terraform-managed EKS cluster and project it as a file,
so any tool that reads &lt;code&gt;KUBECONFIG&lt;/code&gt; - &lt;code&gt;kubectl&lt;/code&gt;, &lt;code&gt;helm&lt;/code&gt;, Pulumi - just works.
You can also reference outputs in &lt;code&gt;pulumiConfig&lt;/code&gt; to pass values like VPC IDs and subnet lists directly into Pulumi stacks.&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;h3 id="terraform-cloud-support"&gt;Terraform Cloud support&lt;/h3&gt;
&lt;p&gt;If your state lives in Terraform Cloud (or any compatible remote backend), the provider supports that too:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;terraform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;fn::open::terraform-state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;remote&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;organization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;my-terraform-org&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;workspace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;my-workspace&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;fn::secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;tfc-token-value&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;pulumiConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;vpcId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${terraform.outputs.vpc_id}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;subnetIds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${terraform.outputs.subnet_ids}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can point it at any Terraform Cloud-compatible backend by setting the optional &lt;code&gt;hostname&lt;/code&gt; property.&lt;/p&gt;
&lt;h2 id="get-started"&gt;Get started&lt;/h2&gt;
&lt;p&gt;Check out the full &lt;a href="https://www.pulumi.com/docs/esc/providers/iac/terraform-state/"&gt;&lt;code&gt;terraform-state&lt;/code&gt; provider documentation&lt;/a&gt; for the complete reference.&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;You can also consume Terraform outputs directly in a Pulumi program with the &lt;a href="https://www.pulumi.com/registry/packages/terraform/"&gt;Pulumi Terraform provider&lt;/a&gt;.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><author>Claire Gaestel</author><category>esc</category><category>terraform</category><category>features</category></item><item><title>Schema Validation Comes to Pulumi ESC with fn::validate</title><link>https://www.pulumi.com/blog/esc-schema-validation-fn-validate/</link><pubDate>Thu, 12 Feb 2026 11:00:00 -0300</pubDate><guid>https://www.pulumi.com/blog/esc-schema-validation-fn-validate/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/esc-schema-validation-fn-validate/index.png" /&gt;
&lt;p&gt;Pulumi ESC environments can now validate configuration values against JSON Schema with the new &lt;code&gt;fn::validate&lt;/code&gt; built-in function. Invalid configurations are caught immediately when you save, preventing misconfigurations from reaching your deployments.&lt;/p&gt;
&lt;p&gt;Configuration errors are often discovered too late during deployment or, worse, in production. With &lt;code&gt;fn::validate&lt;/code&gt;, you define validation rules directly in your environment, and ESC enforces them at save time. If a value doesn&amp;rsquo;t match its schema, the environment cannot be saved until the issue is resolved.&lt;/p&gt;
&lt;h2 id="how-it-works"&gt;How it works&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;fn::validate&lt;/code&gt; function takes a JSON Schema and a value. If the value conforms to the schema, it passes through unchanged. If not, ESC raises a validation error.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;fn::validate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;{&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;type: number, minimum: 1, maximum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;65535&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;}&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8080&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This validates that &lt;code&gt;port&lt;/code&gt; is a number between 1 and 65535. The evaluated result is simply &lt;code&gt;8080&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="validating-objects-with-required-fields"&gt;Validating objects with required fields&lt;/h2&gt;
&lt;p&gt;For complex configurations, you can enforce structure and required fields:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;database&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;fn::validate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;object&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;{&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;string }&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;{&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;number }&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;{&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;string }&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="l"&gt;host, port, name]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;db.example.com&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5432&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;myapp&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If any required field is missing or has the wrong type, the environment cannot be saved.&lt;/p&gt;
&lt;h2 id="reusing-schemas-across-environments"&gt;Reusing schemas across environments&lt;/h2&gt;
&lt;p&gt;Define schemas once and reference them across multiple environments. Using the &lt;a href="https://www.pulumi.com/docs/esc/concepts/interpolations-and-references/"&gt;&lt;code&gt;environments&lt;/code&gt; built-in property&lt;/a&gt; keeps the schema out of your environment&amp;rsquo;s output:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Schema environment (my-project/schemas)&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;database-schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;object&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;{&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;string }&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;{&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;number }&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="l"&gt;host, port]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Environment using the schema&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;database&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;fn::validate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${environments.my-project.schemas.database-schema}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;prod-db.example.com&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5432&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This pattern ensures consistent validation rules across teams and projects.&lt;/p&gt;
&lt;h2 id="what-happens-when-validation-fails"&gt;What happens when validation fails&lt;/h2&gt;
&lt;p&gt;When a value doesn&amp;rsquo;t conform to its schema, ESC returns a clear error message:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;fn::validate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;{&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;string }&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8080&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This raises: &lt;code&gt;expected string, got number&lt;/code&gt;. The environment cannot be saved until you fix the value or update the schema.&lt;/p&gt;
&lt;h2 id="when-to-use-schema-validation"&gt;When to use schema validation&lt;/h2&gt;
&lt;p&gt;Enable &lt;code&gt;fn::validate&lt;/code&gt; for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Values with specific type requirements (numbers, strings, arrays)&lt;/li&gt;
&lt;li&gt;Objects that must have certain fields present&lt;/li&gt;
&lt;li&gt;Numbers that must fall within a valid range&lt;/li&gt;
&lt;li&gt;Configurations shared across multiple environments&lt;/li&gt;
&lt;li&gt;Any value where catching errors early prevents downstream issues&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="getting-started"&gt;Getting started&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;fn::validate&lt;/code&gt; function is available now in all Pulumi ESC environments. Add schema validation to your existing environments or use it when creating new ones.&lt;/p&gt;
&lt;p&gt;For more information, see the &lt;a href="https://www.pulumi.com/docs/esc/concepts/builtin-functions/fn-validate/"&gt;fn::validate documentation&lt;/a&gt;.&lt;/p&gt;</description><author>Pablo Terradillos</author><author>Claire Gaestel</author><category>esc</category><category>features</category></item><item><title>Announcing OpenAPI support for the Pulumi Cloud REST API</title><link>https://www.pulumi.com/blog/announcing-openapi-support-pulumi-cloud/</link><pubDate>Thu, 05 Feb 2026 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/announcing-openapi-support-pulumi-cloud/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/announcing-openapi-support-pulumi-cloud/index.png" /&gt;
&lt;p&gt;We&amp;rsquo;re thrilled to announce that the Pulumi Cloud REST API is now described by an OpenAPI 3.0 specification, and we&amp;rsquo;re just getting started.&lt;/p&gt;
&lt;p&gt;This is a feature that has been a long time coming. We have heard your requests for OpenAPI support &lt;a href="https://github.com/pulumi/pulumi-cloud-requests/issues/100"&gt;loud and clear&lt;/a&gt;, and we&amp;rsquo;re excited to share that not only do we have a published specification for consumption, but our API code is now built from this specification as well. Moving forward, this single source of truth unlocks better tooling, tighter integration, and a more predictable API experience for everyone.&lt;/p&gt;
&lt;p&gt;You can fetch the spec directly from the API at runtime or use it for client generation, validation, and documentation, all from one machine-readable contract.&lt;/p&gt;
&lt;h2 id="a-single-contract-for-the-pulumi-cloud-rest-api"&gt;A single contract for the Pulumi Cloud REST API&lt;/h2&gt;
&lt;p&gt;The Pulumi Cloud API powers the Pulumi CLI, the Pulumi Console, and third-party integrations. Until now, there was no single, published machine-readable description of that API. We&amp;rsquo;ve changed that. The API is now defined and served as a standard OpenAPI 3.0.3 document.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Runtime discovery&lt;/strong&gt;: You can retrieve the spec from the API itself, so your tooling always sees the same surface the service implements.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Client generation&lt;/strong&gt;: Use your favorite OpenAPI tooling (e.g. OpenAPI Generator, Swagger Codegen) to generate API clients in the language of your choice.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Validation and testing&lt;/strong&gt;: Validate requests and responses, or build mocks and tests, from the same spec the service uses.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Documentation&lt;/strong&gt;: The spec is the source of truth, not a separate, hand-maintained API doc that can drift from reality. Load the spec into Swagger UI, Redoc, or another viewer to browse the Pulumi Cloud API interactively.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="how-to-get-the-spec"&gt;How to get the spec&lt;/h2&gt;
&lt;p&gt;Send a GET request to:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;https://api.pulumi.com/api/openapi/pulumi-spec.json
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;No authentication is required. The response is the OpenAPI 3.0 document for the Pulumi Cloud API, describing the supported, documented API surface.&lt;/p&gt;
&lt;h2 id="source-of-truth-and-stability"&gt;Source of truth and stability&lt;/h2&gt;
&lt;p&gt;We do not hand-write the OpenAPI spec. We generate it from the same API definition that drives our backend and console code. When we add or change API routes or models, we regenerate the spec so the published document stays in sync with what the service actually implements. That gives you a clear, stable contract for the Pulumi Cloud API.&lt;/p&gt;
&lt;h2 id="what-we-are-building-next"&gt;What we are building next&lt;/h2&gt;
&lt;p&gt;We are using this spec as the foundation for our own tooling, and have plans to continue leveraging the spec in our toolchain long-term.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CLI&lt;/strong&gt;: We plan to drive the Pulumi CLI’s API client from the OpenAPI spec so that CLI and API stay in lockstep.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pulumi Service Provider&lt;/strong&gt;: We are also building towards day 1 updates to the &lt;a href="https://www.pulumi.com/registry/packages/pulumiservice/"&gt;Pulumi Service Provider&lt;/a&gt; so that new and changed API resources are generated from the spec and ship in sync with the service.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Docs Enhancements&lt;/strong&gt;: Although you can load the spec using Swagger UI for your own browsing, we are intent on shipping enhancements to our &lt;a href="https://www.pulumi.com/docs/reference/cloud-rest-api/"&gt;public REST API docs&lt;/a&gt; that will keep them up-to-date according to the OpenAPI spec.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As we ship those updates, you will get a single source of truth from API to CLI to provider.&lt;/p&gt;
&lt;p&gt;If you have questions or feedback about the OpenAPI spec or the Pulumi Cloud API, reach out in our &lt;a href="https://slack.pulumi.com/"&gt;Community Slack&lt;/a&gt; or open an issue in the &lt;a href="https://github.com/pulumi/pulumi"&gt;Pulumi repository&lt;/a&gt;. We&amp;rsquo;re excited to see what you build with it.&lt;/p&gt;</description><author>Davide Massarenti</author><author>Claire Gaestel</author><author>Devon Grove</author><author>Arun Loganathan</author><author>Zac Cook</author><category>features</category><category>pulumi-cloud</category><category>api</category></item><item><title>Introducing ESC Connect: Integrate Any Secret Source with Pulumi ESC</title><link>https://www.pulumi.com/blog/esc-connect/</link><pubDate>Mon, 01 Dec 2025 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/esc-connect/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/esc-connect/index.png" /&gt;
&lt;p&gt;We&amp;rsquo;re excited to announce ESC Connect — a new capability that lets you integrate any secret source with &lt;a href="https://www.pulumi.com/product/secrets-management/"&gt;Pulumi ESC&lt;/a&gt; by building simple HTTPS adapter services. If you&amp;rsquo;ve ever needed to pull secrets from a proprietary system, a legacy tool, or a third-party service that doesn&amp;rsquo;t have native ESC support, you no longer have to wait for us to build a provider. You can build your own adapter in an afternoon and start using it immediately.&lt;/p&gt;
&lt;p&gt;Pulumi ESC has &lt;a href="https://www.pulumi.com/docs/esc/integrations/"&gt;native integrations&lt;/a&gt; with popular secret management systems like &lt;a href="https://www.pulumi.com/docs/esc/providers/secrets/aws-secrets/"&gt;AWS Secrets Manager&lt;/a&gt;, &lt;a href="https://www.pulumi.com/docs/esc/providers/secrets/azure-secrets/"&gt;Azure KeyVault&lt;/a&gt;, &lt;a href="https://www.pulumi.com/docs/esc/providers/secrets/vault-secrets/"&gt;HashiCorp Vault&lt;/a&gt;, &lt;a href="https://www.pulumi.com/docs/esc/providers/secrets/1password-secrets/"&gt;1Password&lt;/a&gt;, and others. But in real-world infrastructure, you often need to work with systems that fall outside this list. Maybe you built a custom secret management system years ago and it&amp;rsquo;s still running in production. Maybe you&amp;rsquo;re using a niche third-party service. Maybe your secrets are locked behind a firewall in a legacy system that predates modern APIs.&lt;/p&gt;
&lt;p&gt;ESC Connect changes this by letting you build simple HTTPS adapter services using the &lt;a href="https://www.pulumi.com/docs/esc/providers/secrets/external/"&gt;&lt;code&gt;external&lt;/code&gt; provider&lt;/a&gt;. Your adapter handles requests from ESC, fetches secrets from your custom source, and returns them. ESC handles authentication with signed JWT tokens, so you get fine-grained control over access without building a complete security infrastructure.&lt;/p&gt;
&lt;h2 id="building-an-adapter"&gt;Building an adapter&lt;/h2&gt;
&lt;p&gt;Here&amp;rsquo;s an &lt;a href="https://www.pulumi.com/docs/esc/concepts/environments/"&gt;ESC environment&lt;/a&gt; configuration that uses ESC Connect:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;customSecrets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;fn::open::external&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;https://my-adapter.example.com/fetch-secrets&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;secretName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;DATABASE_PASSWORD&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;When you open this environment, ESC makes an authenticated POST request to your adapter. Your adapter validates the JWT token, fetches the secret from your source, and returns it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// 1. Validate JWT from Authorization header
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;claims&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;validateJWT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// 2. Verify audience and body hash for security
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;verifyAudience&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aud&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;verifyBodyHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body_hash&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// 3. Fetch secret from your source
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetchFromYourSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;secretName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// 4. Return the secret
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;statusCode&lt;/span&gt;: &lt;span class="kt"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;: &lt;span class="kt"&gt;JSON.stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;a href="#try-it-out"&gt;example reference implementation&lt;/a&gt; includes an &lt;code&gt;ESCRequestValidator&lt;/code&gt; class that handles JWT verification and request integrity checking for you. See the &lt;a href="https://www.pulumi.com/docs/esc/providers/secrets/external/"&gt;documentation&lt;/a&gt; for detailed security requirements and examples in other languages.&lt;/p&gt;
&lt;h2 id="automated-rotation"&gt;Automated rotation&lt;/h2&gt;
&lt;p&gt;ESC Connect also supports automated secret rotation through &lt;code&gt;fn::rotate::external&lt;/code&gt;. Your rotation adapter receives the current credential state, generates new credentials, updates your target system, and returns the new state. ESC handles scheduling and maintains both current and previous credentials during rotation transitions for zero-downtime rotation.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;rotatedCredentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;fn::rotate::external&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;https://my-adapter.example.com/rotate&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;database&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;production&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Learn more about &lt;a href="https://www.pulumi.com/docs/esc/concepts/rotators/"&gt;secret rotation in Pulumi ESC&lt;/a&gt; and the &lt;a href="https://www.pulumi.com/docs/esc/providers/rotators/external/"&gt;external rotator implementation patterns&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="try-it-out"&gt;Try it out&lt;/h2&gt;
&lt;p&gt;ESC Connect is available now in Pulumi ESC. We&amp;rsquo;ve created a &lt;a href="https://github.com/pulumi/examples/tree/master/aws-ts-esc-external-adapter-lambda"&gt;deployable reference adapter implementation&lt;/a&gt; on AWS Lambda that demonstrates secure request validation:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://app.pulumi.com/new?template=https://github.com/pulumi/examples/blob/master/aws-ts-esc-external-adapter-lambda/README.md"&gt;&lt;img src="https://get.pulumi.com/new/button.svg" alt="Deploy this example with Pulumi"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Check out the documentation for the &lt;a href="https://www.pulumi.com/docs/esc/providers/secrets/external/"&gt;external provider&lt;/a&gt; and &lt;a href="https://www.pulumi.com/docs/esc/providers/rotators/external/"&gt;external rotator&lt;/a&gt; to learn more about building production adapters.&lt;/p&gt;
&lt;p&gt;To learn more about Pulumi ESC, explore the &lt;a href="https://www.pulumi.com/docs/esc/"&gt;ESC documentation&lt;/a&gt; or &lt;a href="https://www.pulumi.com/docs/esc/get-started/"&gt;get started for free&lt;/a&gt;. If you build an adapter for a system that others might find useful, share it in the &lt;a href="https://slack.pulumi.com"&gt;Pulumi Community Slack&lt;/a&gt; — we&amp;rsquo;d love to see what you build.&lt;/p&gt;</description><author>Claire Gaestel</author><category>esc</category><category>secrets</category><category>features</category></item><item><title>Pulumi ESC: Open Approvals</title><link>https://www.pulumi.com/blog/esc-open-approvals/</link><pubDate>Mon, 13 Oct 2025 16:00:00 -0300</pubDate><guid>https://www.pulumi.com/blog/esc-open-approvals/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/esc-open-approvals/index.png" /&gt;
&lt;p&gt;Many teams live with the fear that a production environment &lt;strong&gt;might be accidentally opened, exposing credentials or sensitive systems before anyone even notices&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;We’re excited to announce a new feature for Pulumi ESC: &lt;a href="https://www.pulumi.com/docs/esc/concepts/approvals/#open-approvals"&gt;Open approvals&lt;/a&gt;. A &lt;strong&gt;governance capability that lets organizations require review and sign-off before an environment is opened&lt;/strong&gt; (i.e. activated or exposed)&lt;/p&gt;
&lt;h2 id="expanding-on-our-mission-to-enforce-compliance-and-security-without-slowing-teams-down"&gt;Expanding on our mission to enforce compliance and security without slowing teams down&lt;/h2&gt;
&lt;p&gt;You may recall that earlier this year we introduced &lt;a href="https://www.pulumi.com/blog/approvals-in-pulumi-esc"&gt;Approvals for updates&lt;/a&gt;, which allows teams to require review and sign-off before applying modifications to environment configurations. We are now extending Pulumi ESC auditing and governance capabilities to enable Just in time (JIT) access control.&lt;/p&gt;
&lt;p&gt;Many organizations require more robust guardrails around when an environment becomes active or accessible. Open Approvals introduces a gate before environment activation, enforcing that every environment open is intentional, reviewed, and governed.&lt;/p&gt;
&lt;p&gt;Open Approvals introduces a gate before environment activation, enforcing that every environment open is intentional, reviewed, and governed.&lt;/p&gt;
&lt;h2 id="how-it-works"&gt;How it works&lt;/h2&gt;
&lt;p&gt;When configuring Approvals gates for your environments under &lt;strong&gt;Settings → Approval Rulesets&lt;/strong&gt;, you can now gate &lt;em&gt;Open&lt;/em&gt; actions in addition to Updates.
From this very same UI, you can define the approvals requirements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Number of required reviewers&lt;/li&gt;
&lt;li&gt;Specific teams or individuals allowed to approve&lt;/li&gt;
&lt;li&gt;Whether self-approval is permitted&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://www.pulumi.com/blog/esc-open-approvals/open-approvals-1.png" alt="Settings page for Approvals in ESC"&gt;&lt;/p&gt;
&lt;p&gt;Once a ruleset is defined, all open operations in the corresponding environment will require to be approved before proceeding.&lt;/p&gt;
&lt;p&gt;To create an open request, users need to specify the approval duration, how long they need access after opening the environment, and a description explaining why access is needed.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.pulumi.com/blog/esc-open-approvals/open-approvals-2.png" alt="Request open access to environment modal"&gt;&lt;/p&gt;
&lt;p&gt;Approvals can be later review and approved from the Approvals tab.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.pulumi.com/blog/esc-open-approvals/open-approvals-3.png" alt="Pending approvals page"&gt;&lt;/p&gt;
&lt;p&gt;A request access can also be created from the CLI via the newly introduced &lt;code&gt;pulumi env open-request&lt;/code&gt; command.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ pulumi env open-request org/project/env --access-duration-seconds&lt;span class="o"&gt;=&lt;/span&gt;2h --grant-expiration-seconds&lt;span class="o"&gt;=&lt;/span&gt;1h
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="closing-notes"&gt;Closing notes&lt;/h2&gt;
&lt;p&gt;Open approvals expands on Pulumi ESC support for compliance and governance capability while we stay true to our goal: empower teams to move fast while staying secure and compliant: no matter where or how changes happen.&lt;/p&gt;
&lt;p&gt;We’re excited about what’s ahead and look forward to building it together with your feedback.&lt;/p&gt;
&lt;p&gt;You can learn more about &lt;a href="https://www.pulumi.com/docs/esc/concepts/approvals/"&gt;Approvals in Pulumi ESC Docs&lt;/a&gt;.&lt;/p&gt;</description><author>Pablo Terradillos</author><author>Claire Gaestel</author><author>Robert Harris</author><category>esc</category></item><item><title>Introducing Approvals in Pulumi ESC</title><link>https://www.pulumi.com/blog/approvals-in-pulumi-esc/</link><pubDate>Mon, 04 Aug 2025 09:00:00 -0300</pubDate><guid>https://www.pulumi.com/blog/approvals-in-pulumi-esc/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/approvals-in-pulumi-esc/index.png" /&gt;
&lt;p&gt;Did you know that &lt;strong&gt;80% of unplanned outages aren’t caused by hardware failures or cyberattacks, but by the very changes we make to improve our systems?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Pulumi ESC already enables safer change management with &lt;a href="https://www.youtube.com/watch?v=HQN5KOY4asE"&gt;our innovative versioning capability&lt;/a&gt; which &lt;strong&gt;allows users to track and roll back environment revisions.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Building on this foundation, we’re excited to announce the release of &lt;a href="https://www.pulumi.com/docs/esc/concepts/approvals/"&gt;&lt;strong&gt;Approvals&lt;/strong&gt; in Pulumi ESC&lt;/a&gt;—a new feature that enables organizations to &lt;strong&gt;bring governance and oversight directly into their environment configuration workflows.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;With Approvals, teams can require explicit review and sign-off before applying changes to ESC-managed environments, bringing the same rigor to configuration as they already have with infrastructure-as-code and application development.&lt;/p&gt;
&lt;h2 id="enforce-change-management-without-slowing-teams-down"&gt;Enforce Change Management Without Slowing Teams Down&lt;/h2&gt;
&lt;p&gt;Pulumi ESC helps teams manage environment configurations—such as secrets and application settings—across services and environments, from development through production.&lt;/p&gt;
&lt;p&gt;In fast-moving teams, managing these settings safely and consistently is critical. But as the number of contributors grows, so does the risk of accidental or unreviewed changes making it into critical environments.&lt;/p&gt;
&lt;p&gt;That’s where Approvals come in.&lt;/p&gt;
&lt;p&gt;With Approvals, any proposed change to an ESC environment—whether through the Pulumi Console or CLI—must go through a structured review process before it’s applied. Similar to submitting a Pull Request, contributors can propose changes that are reviewed and approved by designated team members.&lt;/p&gt;
&lt;p&gt;This gives you a native, consistent workflow for gating configuration updates without needing external tools or manual oversight.&lt;/p&gt;
&lt;h2 id="why-use-approvals"&gt;Why use Approvals?&lt;/h2&gt;
&lt;p&gt;Teams can use Pulumi ESC Approvals to apply the same rigor of code review workflows to environment configurations and secrets management. Approvals help organizations enforce governance policies, meet compliance requirements, and reduce the risk of misconfigurations by requiring explicit review and sign‑off before applying changes. Each request creates a clear, auditable record of who approved what, when, and why, which is especially valuable for regulated industries or teams with strict change‑management processes.&lt;/p&gt;
&lt;p&gt;By introducing a controlled review process, Approvals let developers propose updates while ensuring only authorized reviewers can approve and apply them. This balances collaboration with security and accountability, and works seamlessly in both the Pulumi Cloud console and the Pulumi CLI.&lt;/p&gt;
&lt;h2 id="how-it-works"&gt;How It Works&lt;/h2&gt;
&lt;p&gt;To get started, navigate to your environment’s &lt;strong&gt;Settings → Approval Rulesets&lt;/strong&gt;, where you can define approval requirements such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Number of required reviewers&lt;/li&gt;
&lt;li&gt;Specific teams or individuals allowed to approve&lt;/li&gt;
&lt;li&gt;Whether self-approval is permitted&lt;/li&gt;
&lt;li&gt;Whether changes require reapproval if modified&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="approvals-ruleset.png" alt="Pulumi Ruleset configuration"&gt;&lt;/p&gt;
&lt;p&gt;Once a ruleset is enabled, any environment update must go through a &lt;strong&gt;change request&lt;/strong&gt; workflow. Instead of directly saving changes, contributors create a draft, which then must be reviewed and approved before being applied.&lt;/p&gt;
&lt;p&gt;&lt;img src="approvals-workflow.png" alt="Pulumi ESC Approvals Workflows"&gt;&lt;/p&gt;
&lt;p&gt;Changes pending approval are clearly visible to your team, and reviewers can inspect the diff, leave feedback, or revise the request before approving.&lt;/p&gt;
&lt;p&gt;Approvals are also fully supported in the &lt;a href="https://www.pulumi.com/docs/iac/cli/commands/pulumi_env/"&gt;Pulumi CLI&lt;/a&gt;, using the &lt;code&gt;--draft&lt;/code&gt; flag:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ pulumi env &lt;span class="nb"&gt;set&lt;/span&gt; --draft org/project/env FEATURE_X_ENABLED &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In addition to the CLI, Approvals can be used directly from the &lt;a href="https://github.com/pulumi/esc-sdk"&gt;ESC SDK&lt;/a&gt;—making it easy to integrate change‑management workflows into your own applications or automation scripts.&lt;/p&gt;
&lt;p&gt;They are also available through the &lt;a href="https://marketplace.visualstudio.com/items?itemName=pulumi.pulumi-vscode-tools"&gt;Pulumi Visual Studio Code Extension&lt;/a&gt;, allowing developers to review, approve, and apply configuration changes without leaving their editor.&lt;/p&gt;
&lt;p&gt;Finally, the Pulumi Service Provider supports managing approval rules as code. For example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="kr"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;service&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;@pulumi/pulumiservice&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="kr"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;pulumi&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;@pulumi/pulumi&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;approvalRule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ApprovalRule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;rule-test&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;My rule!&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;enabled&lt;/span&gt;: &lt;span class="kt"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;targetActionTypes&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;update&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;environmentIdentifier&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;organization&lt;/span&gt;: &lt;span class="kt"&gt;environment.organization&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;project&lt;/span&gt;: &lt;span class="kt"&gt;environment.project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;name&lt;/span&gt;: &lt;span class="kt"&gt;environment.name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;approvalRuleConfig&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;numApprovalsRequired&lt;/span&gt;: &lt;span class="kt"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;allowSelfApproval&lt;/span&gt;: &lt;span class="kt"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;requireReapprovalOnChange&lt;/span&gt;: &lt;span class="kt"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;eligibleApprovers&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;rbacPermission&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;environment:write&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;pulumi-bot&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;IaroslavTitov&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can find more examples for different languages at the &lt;a href="https://github.com/pulumi/pulumi-pulumiservice/tree/main/examples"&gt;Pulumi Service Provider Github repository&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="built-for-collaboration-and-compliance"&gt;Built for Collaboration and Compliance&lt;/h2&gt;
&lt;p&gt;Whether you’re enforcing separation of duties, complying with industry standards or regulations, or simply want better visibility into changes, Approvals helps bring process to how configuration flows through your systems.&lt;/p&gt;
&lt;p&gt;It also integrates seamlessly into existing workflows—no need to reinvent how teams work. Just add governance where it matters most.&lt;/p&gt;
&lt;h2 id="whats-next"&gt;What’s Next&lt;/h2&gt;
&lt;p&gt;Approvals in ESC is just the beginning. We&amp;rsquo;re exploring how approval workflows can be extended to other areas of the Pulumi ecosystem to provide a consistent, governed experience across the entire software delivery lifecycle.&lt;/p&gt;
&lt;p&gt;Our goal is to empower teams to move fast while staying secure and compliant—no matter where or how changes happen. We’re excited about what’s ahead and look forward to building it together with your feedback.&lt;/p&gt;
&lt;p&gt;Happy building!&lt;/p&gt;</description><author>Pablo Terradillos</author><author>Claire Gaestel</author><category>esc</category></item><item><title>Announcing Snowflake Dynamic and Rotated Credentials with Pulumi ESC</title><link>https://www.pulumi.com/blog/esc-snowflake-providers-launch/</link><pubDate>Thu, 01 May 2025 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/esc-snowflake-providers-launch/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/esc-snowflake-providers-launch/index.png" /&gt;
&lt;p&gt;Snowflake is the data cloud powerhouse for countless businesses, critical for everything from customer dashboards to billing pipelines. The stakes are immense: this data must be strictly secured and always available. But managing this with static credentials or manual key rotation creates persistent security vulnerabilities and introduces operational instability, risking disruptions during clumsy updates. &lt;a href="https://www.pulumi.com/product/secrets-management/"&gt;Pulumi ESC&lt;/a&gt; eliminates this dilemma with two purpose-built Snowflake integrations:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://www.pulumi.com/docs/esc/integrations/dynamic-login-credentials/snowflake-login/"&gt;&lt;code&gt;snowflake-login&lt;/code&gt;&lt;/a&gt;:&lt;/strong&gt; Provides dynamic, short-lived OIDC tokens for temporary authentication &lt;em&gt;to&lt;/em&gt; Snowflake.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://www.pulumi.com/docs/esc/integrations/rotated-secrets/snowflake-user/"&gt;&lt;code&gt;snowflake-user&lt;/code&gt;&lt;/a&gt;:&lt;/strong&gt; Automates the rotation of RSA keypair secrets &lt;em&gt;for&lt;/em&gt; Snowflake users, essential for secure key-pair authentication.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Pulumi ESC allows you to securely manage and consume these Snowflake credentials in your applications, development workflows through &lt;a href="https://www.pulumi.com/docs/esc/development/languages-sdks/"&gt;ESC SDK&lt;/a&gt;, &lt;a href="https://www.pulumi.com/docs/iac/cli/commands/pulumi_env/"&gt;Pulumi CLI&lt;/a&gt;, &lt;a href="https://www.pulumi.com/docs/esc/integrations/kubernetes/"&gt;Kubernetes integrations&lt;/a&gt;, and more!&lt;/p&gt;
&lt;h2 id="snowflake-login-dynamic-oidc-authentication-for-temporary-access"&gt;snowflake-login: Dynamic OIDC Authentication for Temporary Access&lt;/h2&gt;
&lt;p&gt;Use the &lt;code&gt;snowflake-login&lt;/code&gt; provider when you need temporary, just-in-time credentials to interact with Snowflake, such as from local development environments, CI/CD pipelines, or scripts. It leverages OIDC to mint short-lived access tokens, eliminating the need to store static tokens.&lt;/p&gt;
&lt;p&gt;This approach significantly reduces the attack surface by removing long-lived credentials required for &lt;em&gt;authentication&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Setup:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Configure OIDC in Snowflake:&lt;/strong&gt; Create a Security Integration in Snowflake to trust the Pulumi OIDC provider (&lt;code&gt;https://api.pulumi.com/oidc&lt;/code&gt;), map the appropriate user claims and create a Snowflake user. Check out the &lt;a href="https://www.pulumi.com/docs/esc/integrations/dynamic-login-credentials/snowflake-login/#configuring-oidc-for-snowflake"&gt;docs&lt;/a&gt;] for more details&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Configure ESC Environment:&lt;/strong&gt; Define the &lt;code&gt;snowflake-login&lt;/code&gt; provider in your ESC environment, specifying the Snowflake account and the user configured for OIDC.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# my-org/logins/snowflake&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;snowflake&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;login&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;fn::open::snowflake-login&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;oidc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;account&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;myorganization-account&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ESC_LOGIN_USER&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ESC_ROLE &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Optional&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;When this environment is opened, ESC securely handles the OIDC flow and makes the temporary token available under &lt;code&gt;snowflake.login.token&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="snowflake-user-automated-rotation-for-rsa-key-secrets"&gt;snowflake-user: Automated Rotation for RSA Key Secrets&lt;/h2&gt;
&lt;p&gt;For applications or services using Snowflake&amp;rsquo;s key-pair authentication, maintaining the security of those RSA keys is crucial. The &lt;code&gt;snowflake-user&lt;/code&gt; rotator automates the lifecycle management of these keys as &lt;strong&gt;rotated secrets&lt;/strong&gt; within ESC. This rotater uses the same two-secret rotation strategy we use in rest of ESC&amp;rsquo;s &lt;a href="https://www.pulumi.com/docs/esc/integrations/rotated-secrets/"&gt;rotated secrets&lt;/a&gt; providers. This eliminates manual rotation toil and ensures keys are regularly refreshed according to policy, enhancing the security of the &lt;em&gt;user secret&lt;/em&gt; itself. Check out the rotated secrets &lt;a href="https://www.pulumi.com/blog/esc-rotated-secrets-launch/#introducing-esc-rotated-secrets"&gt;blog post&lt;/a&gt; to learn more about its benefits.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Setup:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Prepare Snowflake:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Create the &lt;a href="https://www.pulumi.com/docs/esc/integrations/rotated-secrets/snowflake-user/#step-1-create-the-target-user"&gt;target user&lt;/a&gt; (e.g., &lt;code&gt;MY_APP_SNOWFLAKE_USER&lt;/code&gt;) whose keys need rotation.&lt;/li&gt;
&lt;li&gt;Set up a dedicated &lt;a href="https://www.pulumi.com/docs/esc/integrations/rotated-secrets/snowflake-user/#step-2-create-a-rotator-role"&gt;rotation role&lt;/a&gt; with &lt;code&gt;OWNERSHIP&lt;/code&gt; permission over the users you wish to rotate, so it can modify their keys.&lt;/li&gt;
&lt;li&gt;Create a &lt;a href="https://www.pulumi.com/docs/esc/integrations/rotated-secrets/snowflake-user/#step-3-create-a-rotation-service-user"&gt;service user&lt;/a&gt; using the rotation role, and &lt;a href="https://www.pulumi.com/docs/esc/integrations/rotated-secrets/snowflake-user/#step-4-set-up-oidc-for-the-rotation-service-user"&gt;setup OIDC&lt;/a&gt; to allow ESC to assume the role for rotation.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rotation Environment:&lt;/strong&gt; Define the rotation, importing the managing credentials and specifying the target user.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Environment: my-org/rotators/snowflake-app-key&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;rotatedKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;fn::rotate::snowflake-user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;login&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${environments.logins.snowflake.snowflake.login}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;#reference credentials created using `snowflake-login`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;targetUser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ESC_ROTATION_DEMO_USER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# User whose keys rotate&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After setup, use the triple-dot menu -&amp;gt; “Rotate Secrets” in the Pulumi Cloud UI and ensure rotation happens. Once you rotate a couple of time and ensured everything works well, navigate to the “Secret Rotation” tab for your Rotation environment in the Pulumi Cloud console and define the rotation schedule.&lt;/p&gt;
&lt;h2 id="when-to-use-dynamic-login-vs-rotated-secrets"&gt;When to Use Dynamic Login vs. Rotated Secrets&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: left"&gt;&lt;strong&gt;Feature&lt;/strong&gt;&lt;/th&gt;
&lt;th style="text-align: left"&gt;&lt;strong&gt;&lt;code&gt;snowflake-login&lt;/code&gt; (Dynamic OIDC Login)&lt;/strong&gt;&lt;/th&gt;
&lt;th style="text-align: left"&gt;&lt;strong&gt;&lt;code&gt;snowflake-user&lt;/code&gt; (Rotated Secrets)&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;&lt;strong&gt;Credential Type&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;Short-lived OIDC access token&lt;/td&gt;
&lt;td style="text-align: left"&gt;Long-lived RSA Private Key secret (managed by ESC) + Public Keys (in Snowflake)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;&lt;strong&gt;Primary Use Case&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;Temporary authentication &lt;em&gt;to&lt;/em&gt; Snowflake (CLI, scripts, dev, CI/CD).&lt;/td&gt;
&lt;td style="text-align: left"&gt;Key-pair authentication &lt;em&gt;for&lt;/em&gt; long-lived applications/services connecting to Snowflake.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;&lt;strong&gt;Ideal Scenario&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;Short-lived tasks; frequent, temporary access needs.&lt;/td&gt;
&lt;td style="text-align: left"&gt;Applications needing persistent connections; keys required at deploy time; apps sensitive to auth blips.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;&lt;strong&gt;Lifecycle&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;Generated on-demand, expires quickly (minutes/hours).&lt;/td&gt;
&lt;td style="text-align: left"&gt;Rotated automatically on a schedule (e.g., days/months).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;&lt;strong&gt;Connection Impact&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;Requires re-authentication or new token fetch when token expires.&lt;/td&gt;
&lt;td style="text-align: left"&gt;Rotation is typically seamless; two-key strategy avoids connection drops if apps update keys promptly.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;&lt;strong&gt;Benefit Focus&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;Eliminates static &lt;em&gt;authentication&lt;/em&gt; tokens/credentials.&lt;/td&gt;
&lt;td style="text-align: left"&gt;Automates lifecycle management of user &lt;em&gt;key-pair secrets&lt;/em&gt;, ensures key freshness.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Pulumi ESC&amp;rsquo;s new Snowflake integrations provide robust solutions for modern credential management challenges. Use &lt;code&gt;snowflake-login&lt;/code&gt; for secure, temporary OIDC-based access and &lt;code&gt;snowflake-user&lt;/code&gt; to automate the rotation of critical RSA key secrets. By adopting these capabilities, you can significantly enhance your Snowflake security posture, reduce operational overhead, and simplify compliance.&lt;/p&gt;
&lt;p&gt;Explore the detailed documentation for &lt;a href="https://www.pulumi.com/docs/esc/integrations/dynamic-login-credentials/snowflake-login/"&gt;&lt;code&gt;snowflake-login&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://www.pulumi.com/docs/esc/integrations/rotated-secrets/snowflake-user/"&gt;&lt;code&gt;snowflake-user&lt;/code&gt;&lt;/a&gt;, and see how Pulumi ESC can centralize and secure your configuration and secrets across all your environments. Join the conversation in the &lt;a href="https://slack.pulumi.com/"&gt;Pulumi Community Slack&lt;/a&gt; or our &lt;a href="https://github.com/pulumi/esc"&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;</description><author>Claire Gaestel</author><author>Arun Loganathan</author><category>esc</category><category>secrets</category><category>features</category><category>snowflake</category><category>rotation</category><category>dynamic-login</category><category>oidc</category><category>security</category><category>configuration-management</category></item><item><title>Introducing Rotated Secrets in Pulumi ESC</title><link>https://www.pulumi.com/blog/esc-rotated-secrets-launch/</link><pubDate>Wed, 19 Feb 2025 00:00:00 -0400</pubDate><guid>https://www.pulumi.com/blog/esc-rotated-secrets-launch/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/esc-rotated-secrets-launch/index.png" /&gt;
&lt;p&gt;Managing secrets effectively is no longer a &amp;ldquo;nice-to-have&amp;rdquo;—it&amp;rsquo;s a must-have for any organization building and scaling applications in the cloud. Static, long-lived credentials like database passwords, API keys, and IAM user credentials are a major security vulnerability. They&amp;rsquo;re often overexposed, residing in source code, configuration files, or other easily accessible locations. Manual rotation processes are tedious, error-prone, and infrequent, leaving a wide window of opportunity for potential breaches. Today, we&amp;rsquo;re thrilled to announce a powerful new capability in &lt;a href="https://www.pulumi.com/product/secrets-management/"&gt;Pulumi ESC&lt;/a&gt; that directly addresses this challenge: Rotated Secrets.&lt;/p&gt;
&lt;h2 id="the-challenge-with-static-secrets-a-ticking-time-bomb"&gt;The Challenge with static secrets: A ticking time bomb&lt;/h2&gt;
&lt;p&gt;Traditional secrets management often relies on static, long-lived credentials. This approach creates several critical problems:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Increased attack surface&lt;/strong&gt;: The longer a secret remains unchanged, the greater the chance it will be compromised through accidental exposure, phishing attacks, or insider threats.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Manual, error-prone rotation&lt;/strong&gt;: Manual rotation processes are time-consuming and require significant coordination across teams. A single mistake can lead to application downtime or even data breaches.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Inconsistent practices&lt;/strong&gt;: Ad-hoc scripts and manual procedures often lead to inconsistent rotation policies, making it difficult to enforce security best practices across the organization.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Compliance headaches&lt;/strong&gt;: Many compliance regulations (like SOC 2, GDPR, and HIPAA) require regular secret rotation. Manual processes make it difficult to demonstrate compliance and can lead to costly penalties.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These challenges are amplified in complex, multi-cloud, hybrid, or on-premises environments where secrets are distributed across numerous systems. While Pulumi ESC&amp;rsquo;s &lt;a href="https://www.pulumi.com/docs/esc/providers/login/"&gt;Dynamic Secrets&lt;/a&gt; addresses all of these challenges and makes secret management much easier, static secrets remain a reality for many organizations. Legacy applications, compliance mandates, or integration limitations often prevent a full switch to dynamic credentials. In such cases, Rotated Secrets provide a second-best option—automating credential rotation to minimize risk while integrating seamlessly with existing workflows.&lt;/p&gt;
&lt;h2 id="introducing-esc-rotated-secrets"&gt;Introducing ESC Rotated Secrets&lt;/h2&gt;
&lt;p&gt;Pulumi ESC&amp;rsquo;s new Rotated Secrets feature provides an elegant solution to the challenges of secret lifecycle management. It enables automated, seamless rotation of credentials, ensuring that your secrets are always up-to-date and secure. Here&amp;rsquo;s how it solves the problems outlined above and more:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Automated rotation schedules&lt;/strong&gt;: Define flexible rotation schedules tailored to your security and operational requirements. This eliminates manual effort and ensures consistent, frequent rotations.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;On-demand rotation&lt;/strong&gt;: Need to rotate a secret immediately due to a suspected breach or policy change? ESC allows you to trigger rotations on demand, giving you immediate control.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Seamless integration with ESC Environments&lt;/strong&gt;: Rotated secrets are seamlessly integrated into your existing ESC environment definitions. This allows you to leverage the power of ESC&amp;rsquo;s &lt;a href="https://www.pulumi.com/docs/esc/concepts/imports/"&gt;composability&lt;/a&gt;, allowing you to manage rotated secrets alongside your other configuration values.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Two-secret strategy&lt;/strong&gt;: ESC Rotated secrets uses a two-secret strategy in which two secrets are active and valid at any point in time. This is especially useful when multiple instances of an application share a credential but not all instances pull in the latest credential at the same time, allowing you to rotate a secret without worrying about some instances being unavailable due to invalid credentials.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Auditing and tracking&lt;/strong&gt;: Gain complete visibility into credential usage, including a full history of generated credentials, active credentials, and the principals accessing them. This enhances governance and simplifies compliance.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Admin and creator control&lt;/strong&gt;: Admins and creators/writers of rotated secrets can configure rotations using privileged user credentials and keep those credentials private while ensuring the consumers can still consume the resulting rotated secrets. This provides a clear separation of concern for administration and individual usage.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Configure &lt;a href="https://www.pulumi.com/docs/esc/concepts/webhooks/"&gt;webhooks&lt;/a&gt; for automation&lt;/strong&gt;: Notify teams, trigger custom workflows, or trigger a &lt;a href="https://www.pulumi.com/docs/deployments/concepts/"&gt;Pulumi Deployment&lt;/a&gt; to update your &lt;a href="https://www.pulumi.com/docs/iac/concepts/stacks/"&gt;IaC stacks&lt;/a&gt; whenever rotations occur, ensuring dependent teams and systems are updated on a timely basis&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With today’s launch, we support AWS IAM user credential rotation with many more databases and cloud integrations on the way. Upvote on our &lt;a href="https://github.com/pulumi/esc/issues?q=is%3Aissue%20state%3Aopen%20rotated%20secrets"&gt;GitHub issues&lt;/a&gt; to get support for new integrations.&lt;/p&gt;
&lt;h2 id="how-to-use-esc-rotated-secrets"&gt;How to Use ESC Rotated Secrets&lt;/h2&gt;
&lt;p&gt;Below are the steps to configure &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_users.html"&gt;AWS IAM user&lt;/a&gt; rotated secrets using best practices. You can also configure this via Pulumi IaC. Check out the &lt;a href="https://github.com/pulumi/esc-examples/tree/esc-native-example/rotate/example-native"&gt;example program&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="1-provision-an-aws-iam-user-on-your-aws-console"&gt;1. Provision an AWS IAM User on your AWS console&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_users_create.html"&gt;Create an AWS IAM user&lt;/a&gt; whose access keys you want to rotate.&lt;/p&gt;
&lt;h3 id="2-create-a-new-environment-with-managing-user-credentials"&gt;2. Create a new environment with managing user credentials&lt;/h3&gt;
&lt;p&gt;In the example environment &lt;code&gt;credentials/aws-creds&lt;/code&gt; below, we use the ESC&amp;rsquo;s &lt;a href="https://www.pulumi.com/docs/esc/providers/login/aws-login/"&gt;aws-login provider&lt;/a&gt; using OIDC. This credential was configured with the privileged access to be able to rotate the IAM user credentials.&lt;/p&gt;
&lt;p&gt;The minimal permissions the managing credential requires are IAM List, Create, and DeleteAccessKeys over the IAM user you created.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# credentials/aws-creds&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;login&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;fn::open::aws-login&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;oidc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;1h&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;roleArn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;arn:aws:iam::12345689:role/test-managing-role&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;sessionName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;pulumi-environments-session&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;subjectAttributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;currentEnvironment.name&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="3-create-a-new-environment-with-the-iam-user-rotation-configuration"&gt;3. Create a new environment with the IAM user rotation configuration&lt;/h3&gt;
&lt;p&gt;Define the rotation using rotated secrets aws-iam provider. Below is an example definition. Notice how the managing user credentials created in step 2 is referenced in the rotation provider &lt;code&gt;input&lt;/code&gt;. This credential will only be used during rotation and never be exposed to the consumers of the rotated IAM user credential.&lt;/p&gt;
&lt;p&gt;Providing current and previous credentials is optional. If you already have IAM user access keys that are actively used, you can specify them in the &lt;code&gt;state&lt;/code&gt; field as &lt;code&gt;current&lt;/code&gt; and &lt;code&gt;previous&lt;/code&gt;. If not, you can leave the state empty.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;rotated-creds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;fn::rotate::aws-iam&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;us-west-1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;login&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${environments.credentials.aws-creds.aws.login}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;userArn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;arn:aws:iam::{accountId}:user/{username}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# state section below is optional.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;current&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;accessKeyId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;AKIA...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;secretAccessKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;fn::secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ASIA....&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;previous&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;accessKeyId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;AKIA...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;secretAccessKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;fn::secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ASIA....&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="4-test-the-setup-with-a-manual-rotation"&gt;4. Test the setup with a manual rotation&lt;/h3&gt;
&lt;p&gt;In the Environment Editor screen, open the triple-dot menu and select Rotate Secrets. Alternatively, you can perform the rotation from the ‘secrets rotation’ tab or use the rotate CLI command. Every rotation creates a new &lt;a href="https://www.pulumi.com/docs/esc/concepts/versioning/"&gt;revision&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you did not provide current or precious credentials in step 3, rotation will generate new credentials automatically.&lt;/p&gt;
&lt;h3 id="5-monitor-rotations"&gt;5. Monitor rotations&lt;/h3&gt;
&lt;p&gt;Perform multiple rotations to observe how access keys move from current → previous → deleted. Use the Environment Revision History to track changes.&lt;/p&gt;
&lt;h3 id="6-schedule-rotations"&gt;6. Schedule rotations&lt;/h3&gt;
&lt;p&gt;Now that you&amp;rsquo;ve correctly configured your rotated secrets, set up an automated rotation schedule from the Secrets Rotation tab.&lt;/p&gt;
&lt;div class="my-4"&gt;
&lt;video class="flex outline-none rounded-lg w-full" title="Pulumi ESC Rotated Secrets Demo"
autoplay muted playsinline
loop &gt;
&lt;source src="rotated-secrets-demo.mp4" /&gt;
&lt;/video&gt;
&lt;/div&gt;
&lt;h2 id="rotated-secrets-vs-dynamic-credentials-choosing-the-right-tool"&gt;Rotated Secrets vs. Dynamic Credentials: Choosing the right tool&lt;/h2&gt;
&lt;p&gt;Pulumi ESC offers both &lt;a href="https://www.pulumi.com/docs/esc/providers/login/"&gt;Dynamic Secrets&lt;/a&gt; and Rotated Secrets. While both enhance security, they serve different purposes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Dynamic secrets&lt;/strong&gt;: These are short-lived, ephemeral credentials with a Time-To-Live (TTL) typically ranging from 1 to 8 hours. A new set of credentials is generated every time the environment is opened. This is ideal for minimizing the impact of compromised credentials, as they quickly become invalid.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rotated secrets&lt;/strong&gt;: These are longer-lived credentials, potentially lasting for days, weeks, or even months. The same latest credential is returned every time the environment is opened until the next scheduled rotation occurs. This provides stability and predictability for applications that need consistent credentials over a longer period.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here are scenarios where rotated secrets are the preferred choice:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Legacy applications&lt;/strong&gt;: Many legacy applications were built before modern secret management practices existed and cannot easily be rewritten to support dynamic secrets. For these applications, Rotated Secrets offer a practical way to enhance security without requiring a full application redesign or re-architecture.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Applications that can only update credentials during restarts and redeployments&lt;/strong&gt;: Some applications require credentials to be available at startup and cannot refresh them dynamically. Rotated Secrets works well for this scenario, providing rotations and ensuring applications always have a valid credential when restarted.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Applications requiring persistent connections&lt;/strong&gt;: High-performance applications or applications that have a long-lived database connections cannot afford to fetch fresh credentials every few hours. Rotated Secrets ensure such applications continue to function as needed while boosting security.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Third-Party API keys (Support coming soon)&lt;/strong&gt;: Some external services cannot provision dynamic short-term credentials. Rotated Secrets provide a way to comply with periodic key rotation policies without requiring real-time credential generation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Databases in private networks (Support coming soon)&lt;/strong&gt;: Many Enterprise databases are not connected to the internet, thereby requiring a proxy or an agent running in their internal network to perform the rotation. Rotated Secrets offer stability in such environments by reducing the number of external calls, even when routed through a proxy.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="whats-next"&gt;What&amp;rsquo;s next?&lt;/h2&gt;
&lt;p&gt;This is just the beginning! We&amp;rsquo;re committed to expanding the capabilities of Rotated Secrets. Here&amp;rsquo;s a glimpse of what&amp;rsquo;s on the roadmap:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Database Secrets Rotation: Automated secrets rotation support for a wide range of databases including &lt;strong&gt;PostgreSQL, Snowflake, MySQL&lt;/strong&gt;, and more!&lt;/li&gt;
&lt;li&gt;Secret rotations for databases running in private networks&lt;/li&gt;
&lt;li&gt;3rd party API key rotation (example: &lt;strong&gt;Twilio&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;Other cloud provider support: &lt;strong&gt;Azure, GCP&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Upvote integrations &lt;a href="https://github.com/pulumi/esc/issues?q=is%3Aissue%20state%3Aopen%20rotated%20secrets"&gt;here&lt;/a&gt;. Want an integration not mentioned? Click &lt;a href="https://github.com/pulumi/esc/issues"&gt;here&lt;/a&gt; to raise a request.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Pulumi ESC&amp;rsquo;s Rotated Secrets feature represents a significant advancement in modern secrets management. By automating a traditionally manual and error-prone process, ESC reduces operational risks, strengthens your security posture, and helps you achieve compliance – all without slowing down development.&lt;/p&gt;
&lt;p&gt;We encourage you to explore the &lt;a href="https://www.pulumi.com/docs/esc/concepts/rotators/"&gt;docs&lt;/a&gt; and try out Rotated Secrets. Share any feedback or open new issues in our &lt;a href="https://github.com/pulumi/esc/issues"&gt;GitHub repository&lt;/a&gt;. We&amp;rsquo;re excited to see how you use this powerful new feature to build more secure and resilient applications!&lt;/p&gt;</description><author>Claire Gaestel</author><author>Arun Loganathan</author><category>esc</category><category>secrets</category><category>features</category></item><item><title>Secret Rotation with Pulumi ESC</title><link>https://www.pulumi.com/blog/esc-secret-rotation-with-iac/</link><pubDate>Tue, 10 Dec 2024 15:43:02 -0800</pubDate><guid>https://www.pulumi.com/blog/esc-secret-rotation-with-iac/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/esc-secret-rotation-with-iac/index.png" /&gt;
&lt;div class="note note-info"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.fd29ca76b1ea49dbd3f6703cc46ae6138b0ed98cabf8d2c60a23a474880f964d.svg#p-info-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;Pulumi ESC now natively supports secrets rotation that makes secrets lifecycle management much easier. Check out the &lt;a href="https://www.pulumi.com/blog/esc-rotated-secrets-launch/"&gt;launch blogpost&lt;/a&gt; and &lt;a href="https://www.pulumi.com/docs/esc/concepts/rotators/"&gt;docs&lt;/a&gt;.&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;Static secrets, like database passwords or API keys, &lt;a href="https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html#272-rotation"&gt;should be rotated regularly to maintain security&lt;/a&gt;, 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.&lt;/p&gt;
&lt;p&gt;In this post, we&amp;rsquo;ll explore an approach for automating static secret rotation using &lt;a href="https://www.pulumi.com/docs/esc/"&gt;Pulumi ESC&lt;/a&gt; combined with &lt;a href="https://www.pulumi.com/docs/iac/"&gt;Pulumi IaC&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="a-pattern-for-extending-esc"&gt;A pattern for extending ESC&lt;/h2&gt;
&lt;p&gt;We can take advantage of ESC’s integration with &lt;a href="https://www.pulumi.com/docs/pulumi-cloud/deployments/"&gt;Pulumi Deployments&lt;/a&gt; to create an ergonomic way of managing rotation schedules ourselves.&lt;/p&gt;
&lt;p&gt;We’ll start by defining a custom declarative configuration format for managing a rotation schedule:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# we&amp;#39;ll create our own &amp;#34;extended&amp;#34; fn config for ESC&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;xfn::pulumi-scheduled-update&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# stack reference to a pulumi program that manages a rotated secret&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;example-rotator-stack-reference/dev&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# schedule that will drive an automatic scheduled deployment&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;scheduleCron&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;0 0 * * 0&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# trigger a manual unscheduled rotation whenever this value changes&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;trigger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;break-glass&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;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 &lt;code&gt;fn::open::pulumi-stacks&lt;/code&gt; provider to read the current secret value from the rotator stack to provide to our application.&lt;/p&gt;
&lt;p&gt;Here is how the solution fits together:
&lt;img src="architecture.svg" alt="Architecture"&gt;
Let&amp;rsquo;s break down each component and see how they work together to solve our rotation challenges.&lt;/p&gt;
&lt;h3 id="the-rotator-managing-credential-lifecycles"&gt;The Rotator: Managing Credential Lifecycles&lt;/h3&gt;
&lt;p&gt;At the heart of our solution is a &lt;a href="https://github.com/pulumi/esc-examples/blob/f8a0c47da556aebb74d3ac2d6491f897271bfa27/rotate/example/rotator/rotator.ts"&gt;generic &lt;code&gt;Rotator&lt;/code&gt; component&lt;/a&gt; that manages credential pairs. It&amp;rsquo;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 &lt;em&gt;remains valid&lt;/em&gt;, which allows consuming services enough time to switch over to the new secret.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;creds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Rotator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;rotating-creds&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// rotate credentials on every deployment.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;trigger&lt;/span&gt;: &lt;span class="kt"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// create a set of equivalent credentials that will be rotated between.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// a particular credential should be replaced whenever `trigger` is updated.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;construct&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;trigger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// in this example we&amp;#39;re just creating passwords,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// but these could be mysql users or anything else.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RandomPassword&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;length&lt;/span&gt;: &lt;span class="kt"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;keepers&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;trigger&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// export when the last rotation happened
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lastUpdate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastUpdate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// export the currently active credential, which will be imported by the downstream ESC environment.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="the-scheduler-orchestrating-rotations"&gt;The Scheduler: Orchestrating Rotations&lt;/h3&gt;
&lt;p&gt;The &lt;a href="https://github.com/pulumi/esc-examples/blob/f8a0c47da556aebb74d3ac2d6491f897271bfa27/rotate/example/scheduler/index.ts"&gt;scheduler component&lt;/a&gt; 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 &lt;code&gt;xfn::pulumi-scheduled-update&lt;/code&gt; configuration blocks. Based on these schedule configurations, it creates &lt;a href="https://www.pulumi.com/docs/pulumi-cloud/deployments/schedules/"&gt;scheduled deployments&lt;/a&gt; for the referenced rotator stacks automatically.&lt;/p&gt;
&lt;h3 id="bringing-it-together-environment-configuration"&gt;Bringing It Together: Environment Configuration&lt;/h3&gt;
&lt;p&gt;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:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;db-credentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;database-credential-rotator/dev&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;xfn::pulumi-scheduled-update&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${db-credentials.stack}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;scheduleCron&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;0 0 * * 0&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# weekly rotation&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;trigger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;break-glass&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# manual trigger&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;secrets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;fn::open::pulumi-stacks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;stacks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${db-credentials.stack}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;api-keys&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;api-key-rotator/dev&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;xfn::pulumi-scheduled-update&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${api-keys.stack}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;scheduleCron&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;0 0 1 * *&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# monthly rotation&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;secrets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;fn::open::pulumi-stacks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;stacks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${api-keys.stack}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;environmentVariables&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;DB_CONNECTION_STRING&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${db-credentials.secrets.credentials.current}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;API_KEY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${api-keys.secrets.keys.current}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;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.&lt;/p&gt;
&lt;h2 id="trying-it-out"&gt;Trying it out&lt;/h2&gt;
&lt;p&gt;Open the example environment and take note of the current secrets:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;❯ pulumi env open esc-rotation-demo/test db-credentials.secrets
{
&amp;#34;credentials&amp;#34;: {
&amp;#34;current&amp;#34;: &amp;#34;OBA=wxS:VT&amp;#34;,
&amp;#34;lastUpdate&amp;#34;: &amp;#34;2024-11-26T17:49:03.000Z&amp;#34;,
&amp;#34;previous&amp;#34;: &amp;#34;MUgPXkJ+kE&amp;#34;
}
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now lets force a rotation by changing the manual trigger:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;❯ pulumi env set esc-rotation-demo/test \
db-credentials.schedule.xfn::pulumi-scheduled-update.trigger \
break-glass2
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;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.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ pulumi env open esc-rotation-demo/test db-credentials.secrets
{
&amp;#34;credentials&amp;#34;: {
&amp;#34;current&amp;#34;: &amp;#34;X2WSmF3+29&amp;#34;,
&amp;#34;lastUpdate&amp;#34;: &amp;#34;2024-11-26T17:57:05.000Z&amp;#34;,
&amp;#34;previous&amp;#34;: &amp;#34;OBA=wxS:VT&amp;#34; &amp;lt;---- old &amp;#34;current&amp;#34; has been demoted
}
}
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Secret rotation doesn&amp;rsquo;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:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Automated: Set-and-forget secret rotation&lt;/li&gt;
&lt;li&gt;Zero-downtime: Smooth transitions between credentials&lt;/li&gt;
&lt;li&gt;Flexible: Works with any type of static secret&lt;/li&gt;
&lt;li&gt;Declarative: Configuration through ESC environments&lt;/li&gt;
&lt;li&gt;Auditable: Clear tracking of rotation history&lt;/li&gt;
&lt;li&gt;Scalable: Easy to manage across multiple environments&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href="https://github.com/pulumi/esc-examples/tree/main/rotate/example"&gt;The complete example is available for your perusal here.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;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 🙂&lt;/p&gt;</description><author>Claire Gaestel</author><category>esc</category><category>secrets</category></item></channel></rss>