<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Riptides</title>
    <description>The latest articles on DEV Community by Riptides (@riptides).</description>
    <link>https://dev.to/riptides</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3714919%2F173256be-9227-4649-9d1a-2e938aa2ae1d.jpeg</url>
      <title>DEV Community: Riptides</title>
      <link>https://dev.to/riptides</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/riptides"/>
    <language>en</language>
    <item>
      <title>Secretless AI-powered development flow</title>
      <dc:creator>Riptides</dc:creator>
      <pubDate>Mon, 16 Feb 2026 17:20:44 +0000</pubDate>
      <link>https://dev.to/riptides/secretless-ai-powered-development-flow-306p</link>
      <guid>https://dev.to/riptides/secretless-ai-powered-development-flow-306p</guid>
      <description>&lt;p&gt;Modern development increasingly happens inside different environments: local machine, containers, VMs, remote dev servers. At the same time, AI coding assistants like &lt;strong&gt;GitHub Copilot&lt;/strong&gt; have supercharged developer productivity, but they've also amplified a critical security problem.&lt;/p&gt;

&lt;p&gt;When you're using AI to generate infrastructure code, write &lt;code&gt;boto3&lt;/code&gt; scripts, or build &lt;code&gt;Terraform&lt;/code&gt; configs at 10x speed, &lt;strong&gt;credential management is your primary failure point and the biggest security risk&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;How do you securely give a VM or container access to cloud APIs &lt;strong&gt;without mounting credential files, passing secrets as environment variables, or copying keys into the environment&lt;/strong&gt;, especially when AI assistants are generating code that needs those credentials?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And here's the real concern&lt;/strong&gt;: With AI generates code you didn't write line-by-line, how confident are you that it won't accidentally log secrets, echo environment variables, or write credentials to temp files? Running AI-generated code in an environment with &lt;strong&gt;zero access to credentials&lt;/strong&gt; means you can sleep better at night.&lt;/p&gt;

&lt;p&gt;The conventional answer is &lt;strong&gt;still based on secrets&lt;/strong&gt;: copy your &lt;code&gt;~/.aws/credentials&lt;/code&gt; file, mount it as a volume, set &lt;code&gt;AWS_ACCESS_KEY_ID&lt;/code&gt; in your shell, or use instance profiles if you're lucky enough to be running in the cloud already.&lt;/p&gt;

&lt;p&gt;But this approach has serious flaws:&lt;/p&gt;

&lt;p&gt;Credential material tends to &lt;strong&gt;proliferate&lt;/strong&gt; (copied into VM images, accidentally committed, or left behind in shared environments), the &lt;strong&gt;blast radius expands&lt;/strong&gt; (every developer/VM/container with access can leak it), &lt;strong&gt;rotation becomes manual and painful&lt;/strong&gt; (you have to chase down every place the secret landed), and you still get poor &lt;strong&gt;auditability&lt;/strong&gt; (it’s hard to answer “who used which credentials, when?” and “which process touched what API?”).&lt;/p&gt;

&lt;p&gt;This post shows a different approach: &lt;strong&gt;secretless development environments&lt;/strong&gt; powered by Riptides. We'll use &lt;strong&gt;Lima VMs on macOS with GitHub Copilot&lt;/strong&gt; as a practical example, but the same pattern applies to Docker containers, remote SSH sessions, cloud dev environments, or any workspace where your AI-generated code runs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why sandboxed environments matter for AI-generated code&lt;/strong&gt;: Treat AI-generated code as untrusted by default, run it in a &lt;strong&gt;sandboxed VM or container&lt;/strong&gt; with &lt;strong&gt;zero credential access&lt;/strong&gt; so even accidental logs, debug outputs, or file writes can't leak credentials.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with development credentials
&lt;/h2&gt;

&lt;p&gt;Let's say you're a macOS developer building cloud-native applications. You want a clean Linux development environment, so you use &lt;a href="https://github.com/lima-vm/lima" rel="noopener noreferrer"&gt;Lima&lt;/a&gt; to run a lightweight Ubuntu VM. Now you need to run AWS CLI commands, test Terraform configs, or call AWS APIs from Python scripts inside that VM.&lt;/p&gt;

&lt;h3&gt;
  
  
  The standard approach: Copy your credentials
&lt;/h3&gt;

&lt;p&gt;The typical workflow looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Export AWS credentials on your Mac&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;AKIA...
   &lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Pass them to the VM via environment or mount&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;   &lt;span class="c1"&gt;# Lima config&lt;/span&gt;
   &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
     &lt;span class="na"&gt;AWS_REGION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;us-west-2&lt;/span&gt;
     &lt;span class="na"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AKIA...&lt;/span&gt;
     &lt;span class="na"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Or mount your credentials directory&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;   &lt;span class="na"&gt;mounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
     &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;~/.aws"&lt;/span&gt;
       &lt;span class="na"&gt;writable&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Run AWS commands inside the VM&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   limactl shell default
   aws ec2 describe-instances
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works. But you've just &lt;strong&gt;copied possibly long-lived secrets into an isolated environment where they're harder to rotate, easier to leak, and completely unaudited&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What can go wrong?
&lt;/h3&gt;

&lt;p&gt;Credential files are &lt;strong&gt;persistent&lt;/strong&gt; (they remain on the VM filesystem until manually deleted), and VM snapshots or “golden images” can quietly &lt;strong&gt;capture secrets&lt;/strong&gt; as a side effect of reproducibility. Shared VM templates often turn into &lt;strong&gt;shared secrets&lt;/strong&gt;, any process in the VM may be able to read credential files, and while AWS CloudTrail can tell you which IAM principal made a call, it usually won’t tell you &lt;em&gt;which process&lt;/em&gt; or &lt;em&gt;which workload&lt;/em&gt; inside your dev VM did it. When credentials expire or are compromised, &lt;strong&gt;rotation&lt;/strong&gt; becomes a manual update process across every VM/container/config you touched.&lt;/p&gt;

&lt;p&gt;Even if you use temporary credentials via AWS STS, &lt;strong&gt;you're still handling and storing secrets&lt;/strong&gt;, they just happen to be short-lived secrets.&lt;/p&gt;

&lt;h3&gt;
  
  
  The AI coding assistant paradox
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;GitHub Copilot in VS Code&lt;/strong&gt; promises 10x developer productivity. It generates infrastructure code, writes AWS automation scripts, and builds Terraform configurations in seconds.&lt;/p&gt;

&lt;p&gt;But this productivity gain is &lt;strong&gt;bottlenecked by credential management&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;Copilot can generate Terraform code, or a deployment script in seconds—yet you still end up stopping to configure AWS credentials, copy secrets into the VM, or manage API keys and access tokens.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The faster you code with AI, the more you're fighting with credentials.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And worse: the more code AI generates, the more opportunities for credential leakage. AI-generated scripts that echo variables, log debug output, or write to temporary files can accidentally expose secrets that weren't there before.&lt;/p&gt;

&lt;h3&gt;
  
  
  The security benefit: AI code + sandboxed environments + zero credentials
&lt;/h3&gt;

&lt;p&gt;With that setup, you get multiple layers of protection:&lt;/p&gt;

&lt;p&gt;AI-generated code runs in an isolated VM/container rather than on your host; there are &lt;strong&gt;no credentials on disk&lt;/strong&gt; to leak even if the code behaves unexpectedly; and Riptides can tie credential injection and telemetry to the &lt;strong&gt;calling process&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This means you can trust AI to generate code at high speed without worrying about accidentally creating security vulnerabilities.&lt;/strong&gt; Run it, test it, iterate, knowing that even if something goes wrong, credentials were never in play.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Riptides solves the problem
&lt;/h2&gt;

&lt;p&gt;Riptides eliminates stored secrets by injecting credentials &lt;strong&gt;dynamically at runtime, directly into the network requests that need them&lt;/strong&gt;. The workload never stores, reads, or manages AWS credentials directly.&lt;/p&gt;

&lt;p&gt;Here's how it works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Every process gets a kernel-enforced identity&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Riptides assigns a &lt;a href="https://blog.riptides.io/rethinking-workload-identity-at-the-kernel-level" rel="noopener noreferrer"&gt;SPIFFE-based workload identity&lt;/a&gt; to each process, verified and enforced at the kernel level.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Credentials are injected on-the-wire&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
When a process (like the AWS CLI) makes an HTTPS request to AWS APIs, Riptides intercepts the connection in kernel space, fetches short-lived credentials, and injects them directly into the request using &lt;a href="https://blog.riptides.io/introducing-libsigv4-aws-sigv4-signatures-in-portable-c-with-kernel-compatibility" rel="noopener noreferrer"&gt;libsigv4&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No secrets on disk, ever&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
The AWS CLI runs normally, but it never sees credentials. No &lt;code&gt;~/.aws/credentials&lt;/code&gt; file. No environment variables. No mounted volumes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Automatic rotation and refresh&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Credentials are scoped to a single request or short time window. They expire quickly and are automatically refreshed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Full auditability&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Every credential usage is logged: which workload, which process, which API call, when.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you're familiar with our earlier credential injection posts, this is the same approach applied to development environments:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://blog.riptides.io/on-the-wire-credential-injection-secretless-aws-bedrock-access-example" rel="noopener noreferrer"&gt;On-the-Wire Credential Injection: Secretless AWS Bedrock Access&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.riptides.io/secretless-oci-authentication-with-spiffe-based-workload-identity" rel="noopener noreferrer"&gt;Secretless OCI Authentication with SPIFFE-based workload identity&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.riptides.io/on-demand-credentials-secretless-ai-assistant-example-on-gcp" rel="noopener noreferrer"&gt;On-demand credentials: Secretless AI assistant example on GCP&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The difference is that here we're applying it to a &lt;strong&gt;local development environment on macOS&lt;/strong&gt;, making secure, secretless workflows practical for everyday AI-assisted coding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters for GitHub Copilot (and other AI coding tools)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;GitHub Copilot&lt;/strong&gt; excels at generating cloud infrastructure code. But it can't solve the credential problem—it just makes it worse:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Workflow&lt;/th&gt;
&lt;th&gt;Without Riptides&lt;/th&gt;
&lt;th&gt;With Riptides&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Copilot generates AWS code&lt;/td&gt;
&lt;td&gt;You manually configure credentials&lt;/td&gt;
&lt;td&gt;Credentials are injected automatically when the code runs in a jailed environment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Copilot writes Terraform&lt;/td&gt;
&lt;td&gt;You mount &lt;code&gt;~/.aws&lt;/code&gt; into your VM&lt;/td&gt;
&lt;td&gt;No credential configuration needed; run in an isolated VM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Copilot builds deployment scripts&lt;/td&gt;
&lt;td&gt;You copy secrets into environment variables&lt;/td&gt;
&lt;td&gt;Scripts execute securely in jail without ever seeing secrets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Result&lt;/td&gt;
&lt;td&gt;Slow, manual, error-prone credential management that negates AI gains&lt;/td&gt;
&lt;td&gt;AI-accelerated development with defense in depth and near-zero credential overhead&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You get the productivity of AI code generation &lt;strong&gt;without the security risks of credential sprawl or running untrusted code with full system access&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: While this post focuses on GitHub Copilot in VS Code, the same pattern should work with other AI coding environments like &lt;strong&gt;Cursor&lt;/strong&gt;, &lt;strong&gt;Continue&lt;/strong&gt;, &lt;strong&gt;Cody&lt;/strong&gt;, and similar tools that support remote development or SSH workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Lima VMs?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/lima-vm/lima" rel="noopener noreferrer"&gt;Lima&lt;/a&gt; is a lightweight Linux VM manager for macOS. It provides:&lt;/p&gt;

&lt;p&gt;Lima gives you fast, native-ish integration (file sharing, port forwarding, and shell access) while still running a full Linux kernel—so Riptides can load its kernel module (unlike many desktop container setups). It’s also relatively lightweight and automation-friendly thanks to declarative YAML configuration and CLI tooling.&lt;/p&gt;

&lt;p&gt;Lima is ideal for running Riptides in development because &lt;strong&gt;Riptides operates at the kernel level&lt;/strong&gt;, requiring a Linux environment where kernel modules can be loaded. On macOS, Lima provides exactly that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture overview
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd6xn372zyujnktasoem1.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd6xn372zyujnktasoem1.jpg" alt="Architecture overview: macOS host (VS Code + Copilot via Remote-SSH) into a Lima VM running jailed AWS CLI/Terraform with Riptides agent + kernel module injecting credentials" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How requests flow:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;VS Code on macOS connects to the Lima VM via Remote-SSH&lt;/li&gt;
&lt;li&gt;You use Copilot to generate code/commands, and run tools like AWS CLI and Terraform inside the VM&lt;/li&gt;
&lt;li&gt;AWS CLI / Terraform attempts an HTTPS request to AWS APIs (for example &lt;code&gt;ec2.amazonaws.com&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;The Riptides kernel module intercepts the connection&lt;/li&gt;
&lt;li&gt;The Riptides agent:

&lt;ul&gt;
&lt;li&gt;Verifies the process identity&lt;/li&gt;
&lt;li&gt;Fetches short-lived AWS credentials (via federation or local provider)&lt;/li&gt;
&lt;li&gt;Passes credential material to the kernel module&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The Riptides kernel module:

&lt;ul&gt;
&lt;li&gt;Signs the HTTP request using &lt;a href="https://blog.riptides.io/introducing-libsigv4-aws-sigv4-signatures-in-portable-c-with-kernel-compatibility" rel="noopener noreferrer"&gt;libsigv4&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Injects the signed headers into the request&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The request proceeds to AWS, fully authenticated&lt;/li&gt;
&lt;li&gt;AWS CLI / Terraform receives a successful response — without ever seeing credentials&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Setting up Lima with Riptides
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;You’ll need macOS with Homebrew, Lima installed (&lt;code&gt;brew install lima&lt;/code&gt;), and access to the &lt;strong&gt;Riptides kernel module + agent&lt;/strong&gt; (currently closed source; contact &lt;a href="mailto:info@riptides.io"&gt;Riptides Labs&lt;/a&gt; to request access—open source release planned for later in 2026).&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Start a Lima VM
&lt;/h3&gt;

&lt;p&gt;Create a Lima VM configuration file &lt;code&gt;riptides.yaml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;vmType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vz"&lt;/span&gt;
&lt;span class="na"&gt;os&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Linux"&lt;/span&gt;
&lt;span class="na"&gt;arch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;default"&lt;/span&gt;

&lt;span class="na"&gt;images&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://cloud-images.ubuntu.com/releases/24.04/release/ubuntu-24.04-server-cloudimg-amd64.img"&lt;/span&gt;
    &lt;span class="na"&gt;arch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x86_64"&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://cloud-images.ubuntu.com/releases/24.04/release/ubuntu-24.04-server-cloudimg-arm64.img"&lt;/span&gt;
    &lt;span class="na"&gt;arch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aarch64"&lt;/span&gt;

&lt;span class="na"&gt;cpus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;
&lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8GiB"&lt;/span&gt;
&lt;span class="na"&gt;disk&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;100GiB"&lt;/span&gt;

&lt;span class="na"&gt;mounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;~"&lt;/span&gt;
    &lt;span class="na"&gt;writable&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/tmp/lima"&lt;/span&gt;
    &lt;span class="na"&gt;writable&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="na"&gt;provision&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;system&lt;/span&gt;
    &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;#!/bin/bash&lt;/span&gt;
      &lt;span class="s"&gt;apt-get update&lt;/span&gt;
      &lt;span class="s"&gt;apt-get install -y build-essential curl git awscli&lt;/span&gt;
      &lt;span class="s"&gt;snap install terraform --classic&lt;/span&gt;

&lt;span class="na"&gt;containerd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;system&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start the VM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;limactl start riptides.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Install Riptides
&lt;/h3&gt;

&lt;p&gt;Once you have access to the Riptides repository, install via apt inside the Lima VM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;limactl shell riptides

&lt;span class="c"&gt;# Add Riptides repository (provided after contacting Riptides Labs)&lt;/span&gt;
&lt;span class="c"&gt;# Install Riptides (includes kernel module, agent, and systemd service)&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install &lt;/span&gt;riptides

&lt;span class="c"&gt;# Verify the kernel module is loaded&lt;/span&gt;
lsmod | &lt;span class="nb"&gt;grep &lt;/span&gt;riptides
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Connect VS Code Remote-SSH
&lt;/h3&gt;

&lt;p&gt;To use GitHub Copilot in the Lima VM, connect via VS Code Remote-SSH. Lima VMs integrate seamlessly with VS Code Remote-SSH—see the &lt;a href="https://lima-vm.io/docs/examples/vscode/" rel="noopener noreferrer"&gt;Lima VS Code integration guide&lt;/a&gt; for more details.&lt;/p&gt;

&lt;p&gt;Add the SSH config to your &lt;code&gt;~/.ssh/config&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;limactl show-ssh riptides &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.ssh/config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then connect via the VS Code Command Palette (Remote-SSH: Connect to Host).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9flmwiwqi7jniegqacj7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9flmwiwqi7jniegqacj7.png" alt="Connecting to Lima VM via VS Code Remote-SSH" width="756" height="918"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For other AI coding tools&lt;/strong&gt;: Environments like &lt;strong&gt;Cursor&lt;/strong&gt;, &lt;strong&gt;Continue&lt;/strong&gt;, and &lt;strong&gt;Cody&lt;/strong&gt; that support SSH remote development should work with the same configuration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Configure Riptides workload identities
&lt;/h3&gt;

&lt;p&gt;To enable secretless AWS access, configure Riptides to recognize AWS CLI and Terraform processes and inject credentials automatically.&lt;/p&gt;

&lt;h4&gt;
  
  
  Define the AWS service
&lt;/h4&gt;

&lt;p&gt;Create a Service resource that matches all AWS API endpoints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;core.riptides.io/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;amazonaws&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;riptides-system&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;addresses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*.amazonaws.com"&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;443&lt;/span&gt;
  &lt;span class="na"&gt;external&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;svc:name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;amazonaws&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells Riptides to intercept HTTPS connections to any &lt;code&gt;*.amazonaws.com&lt;/code&gt; domain.&lt;/p&gt;

&lt;h4&gt;
  
  
  Configure workload identity for AWS CLI
&lt;/h4&gt;

&lt;p&gt;Create a WorkloadIdentity for the AWS CLI process:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;core.riptides.io/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;WorkloadIdentity&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;awscli&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;riptides-system&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;workloadID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;awscli&lt;/span&gt;
  &lt;span class="na"&gt;connection&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;tls&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;intercept&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;scope&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;agent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;riptides/agent/local-copilot-lima&lt;/span&gt;
  &lt;span class="na"&gt;selectors&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;process:name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This configuration:&lt;/p&gt;

&lt;p&gt;This assigns identity to any process named &lt;code&gt;aws&lt;/code&gt;, enables TLS interception so Riptides can inject credentials into HTTPS requests, and scopes the rule to the Lima VM agent (&lt;code&gt;local-copilot-lima&lt;/code&gt;).&lt;/p&gt;

&lt;h4&gt;
  
  
  Configure workload identity for Terraform
&lt;/h4&gt;

&lt;p&gt;Similarly, create a WorkloadIdentity for Terraform:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;core.riptides.io/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;WorkloadIdentity&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;riptides-system&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;workloadID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform&lt;/span&gt;
  &lt;span class="na"&gt;connection&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;tls&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;intercept&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;scope&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;agent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;riptides/agent/local-copilot-lima&lt;/span&gt;
  &lt;span class="na"&gt;selectors&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;process:name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With these workload identities in place, both AWS CLI and Terraform processes receive:&lt;/p&gt;

&lt;p&gt;With these workload identities in place, both AWS CLI and Terraform gain identity-scoped credential injection, per-process connection telemetry (domain/port/protocol), automatic SigV4 signing, and better audit context (Riptides records workload identity + process details while CloudTrail records the resulting AWS API calls).&lt;/p&gt;

&lt;h4&gt;
  
  
  Complete the credential binding
&lt;/h4&gt;

&lt;p&gt;After defining these resources, create an AWS &lt;code&gt;CredentialSource&lt;/code&gt; and &lt;code&gt;WorkloadCredential&lt;/code&gt;, then reference that credential from the &lt;code&gt;WorkloadIdentity&lt;/code&gt; egress rule (as shown in our &lt;a href="https://blog.riptides.io/on-the-wire-credential-injection-secretless-aws-bedrock-access-example" rel="noopener noreferrer"&gt;AWS credential injection guide&lt;/a&gt;). For AWS IAM trust relationship setup (OIDC + subject mapping), see &lt;a href="https://blog.riptides.io/federating-non-human-identities-with-external-idps-using-id-tokens-in-aws-gcp-and-azure" rel="noopener noreferrer"&gt;Federating non-human identities with external IdPs&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Configure AWS CLI for TLS interception
&lt;/h4&gt;

&lt;p&gt;Since Riptides intercepts TLS connections to inject credentials, you need to configure the AWS CLI to trust Riptides' CA certificate. Create or update &lt;code&gt;~/.aws/config&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[default]&lt;/span&gt;
&lt;span class="py"&gt;output&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;json&lt;/span&gt;
&lt;span class="py"&gt;region&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;eu-west-1&lt;/span&gt;
&lt;span class="py"&gt;ca_bundle&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/sys/module/riptides/certs/ca-certificates.crt&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;ca_bundle&lt;/code&gt; points to the CA certificate that Riptides uses for TLS interception. (Depending on install/version, you may instead set &lt;code&gt;AWS_CA_BUNDLE&lt;/code&gt; to a path like &lt;code&gt;/sys/kernel/riptides/ca-certificates.crt&lt;/code&gt;.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Using secretless AWS access in the Lima VM
&lt;/h2&gt;

&lt;p&gt;Once Riptides is running, AWS CLI commands work &lt;strong&gt;without any credential configuration&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# No ~/.aws/credentials file&lt;/span&gt;
&lt;span class="c"&gt;# No AWS_ACCESS_KEY_ID env var&lt;/span&gt;
&lt;span class="c"&gt;# Just run AWS commands&lt;/span&gt;

aws ec2 describe-instances
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Behind the scenes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Riptides detects the &lt;code&gt;aws&lt;/code&gt; process execution&lt;/li&gt;
&lt;li&gt;The agent matches it against configured rules&lt;/li&gt;
&lt;li&gt;The process is assigned a workload identity based on its runtime attributes&lt;/li&gt;
&lt;li&gt;As the AWS CLI makes HTTPS requests, Riptides intercepts them&lt;/li&gt;
&lt;li&gt;Short-lived credentials are injected into the request at the kernel level&lt;/li&gt;
&lt;li&gt;The request proceeds to AWS, fully authenticated&lt;/li&gt;
&lt;li&gt;The AWS CLI receives the response — never knowing credentials were involved&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Real-world GitHub Copilot workflows with Riptides
&lt;/h2&gt;

&lt;p&gt;These examples show how GitHub Copilot works seamlessly with Riptides—generating code that executes securely in a jailed environment without credential configuration.&lt;/p&gt;

&lt;p&gt;(Here, “jailed” refers to running inside the Lima VM; Riptides’ core contribution is secretless, identity-scoped credential injection and per-process visibility.)&lt;/p&gt;

&lt;h3&gt;
  
  
  Copilot-generated Terraform workflow
&lt;/h3&gt;

&lt;p&gt;Ask GitHub Copilot: &lt;em&gt;"Help me query my EC2 instances with Terraform"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Copilot can help generate the AWS CLI command to list your instances:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffbsgpm2tn2mr3f6h611o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffbsgpm2tn2mr3f6h611o.png" alt="GitHub Copilot generating EC2 list command" width="800" height="410"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First, list your existing EC2 instances:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foxd98fuhk6xdr5gp1vzv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foxd98fuhk6xdr5gp1vzv.png" alt="EC2 instances listed without any credential configuration" width="800" height="667"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Copilot can help generate the Terraform configuration. Create &lt;code&gt;main.tf&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0go9tto9tlwd177qq9yj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0go9tto9tlwd177qq9yj.png" alt="Copilot generating Terraform configuration" width="594" height="758"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# No credentials configured - Riptides injects them automatically&lt;/span&gt;
&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;sts_region&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Query all running EC2 instances&lt;/span&gt;
&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_instances"&lt;/span&gt; &lt;span class="s2"&gt;"running"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;filter&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="s2"&gt;"instance-state-name"&lt;/span&gt;
    &lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"running"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Get details for each instance&lt;/span&gt;
&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_instance"&lt;/span&gt; &lt;span class="s2"&gt;"details"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;for_each&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;toset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_instances&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;running&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;instance_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Output instance information&lt;/span&gt;
&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"instances"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;instance&lt;/span&gt; &lt;span class="nx"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;details&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;id&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
      &lt;span class="nx"&gt;instance_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance_type&lt;/span&gt;
      &lt;span class="nx"&gt;ami&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ami&lt;/span&gt;
      &lt;span class="nx"&gt;private_ip&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private_ip&lt;/span&gt;
      &lt;span class="nx"&gt;public_ip&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public_ip&lt;/span&gt;
      &lt;span class="nx"&gt;tags&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run Terraform:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform init
terraform plan
terraform apply

&lt;span class="c"&gt;# View the discovered instances&lt;/span&gt;
terraform output &lt;span class="nt"&gt;-json&lt;/span&gt; instances
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After running the Terraform workflow, the Riptides UI shows exactly how many times credentials were injected for Terraform. In this example, you can see that the UI displayed &lt;strong&gt;9 credential injections&lt;/strong&gt; for the Terraform process:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3823iq3dxz7igpchrr5p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3823iq3dxz7igpchrr5p.png" alt="Riptides UI showing 9 credential injections for Terraform" width="800" height="272"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key point&lt;/strong&gt;: Notice there's &lt;strong&gt;no credential configuration anywhere&lt;/strong&gt; in the Terraform code or environment:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key point&lt;/strong&gt;: Notice there's &lt;strong&gt;no credential configuration anywhere&lt;/strong&gt; in the Terraform code or environment—no &lt;code&gt;~/.aws/credentials&lt;/code&gt; file, no &lt;code&gt;AWS_ACCESS_KEY_ID&lt;/code&gt; environment variable, and no credential blocks in the provider configuration. It’s just a plain provider with a region.&lt;/p&gt;

&lt;p&gt;Terraform executes successfully because &lt;strong&gt;Riptides intercepts AWS SDK calls&lt;/strong&gt; from the Terraform binary and injects credentials transparently at the kernel level.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The key difference&lt;/strong&gt;: With Riptides, you can use Copilot to generate infrastructure code at full speed, then run it immediately in a jailed VM—no credential setup interrupting your flow, and no risk of the generated code accessing credentials it shouldn't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You can confidently run AI-generated code knowing&lt;/strong&gt; the VM jail restricts filesystem access, no credentials exist to leak, and even if Copilot generated code with unintended side effects, the blast radius is minimal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key benefits for AI-powered development
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Security without friction, at AI speed
&lt;/h3&gt;

&lt;p&gt;Riptides removes credential management from the loop: you never copy/mount/configure AWS credentials (even for Copilot-generated code), and you don’t need application changes—CLI commands, scripts, and Terraform configs just work. Protection is automatic for configured AWS-calling processes, so the flow becomes &lt;em&gt;generate → run in jail → iterate&lt;/em&gt; without pausing to wire up secrets. The payoff is peace of mind: AI-generated code runs in an isolated environment with &lt;strong&gt;zero credential access&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Better collaboration
&lt;/h3&gt;

&lt;p&gt;With no embedded secrets, VM templates can be shared, versioned in git, and distributed safely. Onboarding gets faster (less “here’s how to set up your AWS credentials” documentation), and teams get more consistent dev environments.&lt;/p&gt;

&lt;h3&gt;
  
  
  Improved auditability
&lt;/h3&gt;

&lt;p&gt;Riptides logs which process made which call and makes it practical to correlate workload/process context with CloudTrail. Unexpected processes reaching for AWS APIs become visible, logged, and easier to investigate.&lt;/p&gt;

&lt;h3&gt;
  
  
  Complete network observability
&lt;/h3&gt;

&lt;p&gt;Riptides provides real-time visibility into every network connection from your development environment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6p2o3pzx58yvzmqfg0m4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6p2o3pzx58yvzmqfg0m4.png" alt="Riptides UI showing network connections" width="800" height="410"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;See exactly which processes connect where: GitHub Copilot's Node.js runtime to &lt;code&gt;api.individual.githubcopilot.com:443&lt;/code&gt;, AWS CLI to AWS service endpoints, Terraform to AWS APIs—all in real-time with full process-level detail. Essential for verifying AI-generated code behavior before trusting it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Beyond Lima: Apply this pattern anywhere
&lt;/h2&gt;

&lt;p&gt;While this post uses Lima VMs on macOS with GitHub Copilot as a concrete example, &lt;strong&gt;the same pattern applies to&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;Docker containers (no more &lt;code&gt;-v ~/.aws:/root/.aws&lt;/code&gt; mounts), remote dev servers over SSH, CI/CD pipelines, Kubernetes dev clusters, and cloud IDEs like Codespaces/Cloud9/Gitpod can all benefit from the same idea. In practice, it should also work with other AI coding environments (Cursor, Continue, Cody, etc.) as long as they support SSH or remote development.&lt;/p&gt;

&lt;p&gt;The core principle remains the same: &lt;strong&gt;jailed environments + workload identity + zero credentials = AI code you can run confidently&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Development environments are often the weakest link in cloud security. Credentials get copied, mounted, committed to git, or left lying around in VM snapshots.&lt;/p&gt;

&lt;p&gt;Riptides eliminates this problem by bringing the same secretless, identity-based security model that we've demonstrated for production workloads (&lt;a href="https://blog.riptides.io/on-the-wire-credential-injection-secretless-aws-bedrock-access-example" rel="noopener noreferrer"&gt;AWS&lt;/a&gt;, &lt;a href="https://blog.riptides.io/on-demand-credentials-secretless-ai-assistant-example-on-gcp" rel="noopener noreferrer"&gt;GCP&lt;/a&gt;, &lt;a href="https://blog.riptides.io/secretless-oci-authentication-with-spiffe-based-workload-identity" rel="noopener noreferrer"&gt;OCI&lt;/a&gt;) to local development environments.&lt;/p&gt;

&lt;p&gt;Key takeaways:&lt;/p&gt;

&lt;p&gt;Key takeaways: AWS CLI, Terraform and other tools work without credential configuration—whether you wrote them or Copilot did. Isolation and credential injection are enforced at the OS/kernel layer (not in application code), the generated scripts/configs run unchanged in jailed environments, and credential usage becomes auditable with process-level detail. Most importantly, Copilot’s productivity isn’t throttled by credential setup, and you can run AI-generated code with confidence because credentials never exist in the environment.&lt;/p&gt;

&lt;p&gt;Whether you're using Lima VMs, Docker containers, remote SSH sessions, or cloud-based development environments, Riptides provides a path to &lt;strong&gt;secretless development&lt;/strong&gt; that doesn't compromise productivity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Learn more
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://blog.riptides.io/rethinking-workload-identity-at-the-kernel-level" rel="noopener noreferrer"&gt;Rethinking Workload Identity at the Kernel Level&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.riptides.io/introducing-libsigv4-aws-sigv4-signatures-in-portable-c-with-kernel-compatibility" rel="noopener noreferrer"&gt;Introducing libsigv4: AWS SigV4 Signatures in Portable C&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.riptides.io/workload-attestation-and-metadata-gathering-building-trust-from-the-ground-up" rel="noopener noreferrer"&gt;Workload Attestation and Metadata Gathering&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://lima-vm.io/" rel="noopener noreferrer"&gt;Lima Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>security</category>
      <category>programming</category>
    </item>
    <item>
      <title>SPIFFE Identity Federation: Extending Trust Across Boundaries</title>
      <dc:creator>Riptides</dc:creator>
      <pubDate>Mon, 09 Feb 2026 13:03:37 +0000</pubDate>
      <link>https://dev.to/riptides/spiffe-identity-federation-extending-trust-across-boundaries-35el</link>
      <guid>https://dev.to/riptides/spiffe-identity-federation-extending-trust-across-boundaries-35el</guid>
      <description>&lt;p&gt;Workload identity has become a foundational primitive in modern infrastructure. Static credentials do not scale, degrade over time, and rarely explain why access should be allowed. The SPIFFE specification addresses this by defining a standard way to issue short-lived, cryptographically verifiable identities to workloads.&lt;/p&gt;

&lt;p&gt;From the beginning, SPIFFE was designed with multiple administrative boundaries in mind. Real systems span clusters, cloud accounts, regions, organizations, and partners. Identity must cross those boundaries without collapsing everything into a single control plane.&lt;/p&gt;

&lt;p&gt;SPIFFE identity federation exists to solve exactly this problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trust domains as the foundation
&lt;/h2&gt;

&lt;p&gt;SPIFFE organizes identity around trust domains.&lt;/p&gt;

&lt;p&gt;A trust domain represents an administrative boundary with authority over:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;its SPIFFE ID namespace&lt;/li&gt;
&lt;li&gt;its trust anchors&lt;/li&gt;
&lt;li&gt;the issuance of workload identities within that namespace&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A workload identity is expressed as a SPIFFE ID, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;spiffe://prod.acme.corp/frontend
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That identity is meaningful only because the verifier trusts the authority that governs the &lt;strong&gt;prod.acme.corp&lt;/strong&gt; trust domain.&lt;/p&gt;

&lt;p&gt;Within a single trust domain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;workloads receive SVIDs&lt;/li&gt;
&lt;li&gt;SVIDs chain to the domain’s trust anchors&lt;/li&gt;
&lt;li&gt;peers verify identities using those anchors&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Federation begins when workloads must authenticate across trust domains.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why federation is necessary
&lt;/h2&gt;

&lt;p&gt;Consider two independent trust domains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;spiffe://payments.acme.corp
spiffe://inventory.acme.corp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each domain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;operates its own control plane&lt;/li&gt;
&lt;li&gt;issues and rotates its own identities&lt;/li&gt;
&lt;li&gt;enforces its own policies&lt;/li&gt;
&lt;li&gt;maintains its own trust anchors&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If a workload in &lt;strong&gt;payments.acme.corp&lt;/strong&gt; needs to communicate with a workload in &lt;strong&gt;inventory.acme.corp&lt;/strong&gt;, identity must cross an administrative boundary.&lt;/p&gt;

&lt;p&gt;Without federation, teams typically fall back to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;sharing trust anchors globally&lt;/li&gt;
&lt;li&gt;terminating identity at gateways and re-issuing credentials&lt;/li&gt;
&lt;li&gt;abandoning identity and relying on networks or secrets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of these approaches erase workload identity at the boundary.&lt;/p&gt;

&lt;p&gt;SPIFFE federation exists to avoid that outcome.&lt;/p&gt;

&lt;h2&gt;
  
  
  Federation as defined by the SPIFFE specification
&lt;/h2&gt;

&lt;p&gt;Federation is a first-class concept in the SPIFFE specification.&lt;/p&gt;

&lt;p&gt;The spec defines federation as the ability for a trust domain to verify identities issued by another trust domain without:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;sharing private keys&lt;/li&gt;
&lt;li&gt;merging control planes&lt;/li&gt;
&lt;li&gt;centralizing identity issuance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To support this, the specification defines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;trust domain relationships&lt;/li&gt;
&lt;li&gt;trust bundles&lt;/li&gt;
&lt;li&gt;bundle endpoints&lt;/li&gt;
&lt;li&gt;verification semantics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At the same time, the spec deliberately limits its scope to identity verification.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trust bundles as the unit of trust
&lt;/h2&gt;

&lt;p&gt;The core object used in federation is the SPIFFE trust bundle.&lt;/p&gt;

&lt;p&gt;A trust bundle contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one or more trust anchors for a trust domain&lt;/li&gt;
&lt;li&gt;optional metadata&lt;/li&gt;
&lt;li&gt;no private key material&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Trust bundles are the only artifacts exchanged during federation. No identities are delegated, and no keys are shared.&lt;/p&gt;

&lt;h2&gt;
  
  
  Standardized bundle endpoints
&lt;/h2&gt;

&lt;p&gt;The SPIFFE federation specification defines standardized bundle exchange using SPIFFE bundle endpoints.&lt;/p&gt;

&lt;p&gt;Bundle endpoints are HTTPS endpoints that serve trust bundles. Two authentication profiles are defined:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;https_web, authenticated using Web PKI TLS&lt;/li&gt;
&lt;li&gt;https_spiffe, authenticated using SPIFFE X.509-SVIDs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The specification requires that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;bundle endpoint clients MUST support both profiles&lt;/li&gt;
&lt;li&gt;bundle endpoint servers MUST support at least one profile&lt;/li&gt;
&lt;li&gt;TLS configurations meet defined security requirements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This ensures interoperability while allowing deployment flexibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to establish federation
&lt;/h2&gt;

&lt;p&gt;Assume the following trust domains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;spiffe://payments.acme.corp
spiffe://inventory.acme.corp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To federate:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;payments.acme.corp&lt;/strong&gt; is federated with:

&lt;ul&gt;
&lt;li&gt;the trust domain name &lt;strong&gt;inventory.acme.corp&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;the bundle endpoint URL for that domain&lt;/li&gt;
&lt;li&gt;the expected endpoint authentication profile&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;it periodically fetches the trust bundle from that endpoint&lt;/li&gt;
&lt;li&gt;it validates the bundle according to the specification&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Federation can be symmetric or asymmetric depending on configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  How verification works with federation
&lt;/h2&gt;

&lt;p&gt;Federation does not change how workloads authenticate.&lt;/p&gt;

&lt;p&gt;At runtime:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;a workload presents its SVID&lt;/li&gt;
&lt;li&gt;the verifier extracts the SPIFFE ID&lt;/li&gt;
&lt;li&gt;the trust domain portion of the ID is identified&lt;/li&gt;
&lt;li&gt;the corresponding trust bundle is selected&lt;/li&gt;
&lt;li&gt;certificate chain verification proceeds normally&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Federated identities are verified using the same cryptographic process as local identities.&lt;/p&gt;

&lt;h2&gt;
  
  
  Explicit and scoped trust
&lt;/h2&gt;

&lt;p&gt;SPIFFE federation is explicit and opt-in.&lt;/p&gt;

&lt;p&gt;Trust domains do not implicitly trust each other. Each federation relationship must be configured intentionally, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;which trust domain is federated&lt;/li&gt;
&lt;li&gt;where its bundle endpoint is located&lt;/li&gt;
&lt;li&gt;how its bundle is authenticated&lt;/li&gt;
&lt;li&gt;how often it is refreshed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The spec enables federation but does not force trust expansion.&lt;/p&gt;

&lt;h2&gt;
  
  
  What federation does not define
&lt;/h2&gt;

&lt;p&gt;Even with standardized bundle exchange, the SPIFFE federation spec intentionally does not define:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;authorization semantics&lt;/li&gt;
&lt;li&gt;policy models&lt;/li&gt;
&lt;li&gt;enforcement location&lt;/li&gt;
&lt;li&gt;federation topology&lt;/li&gt;
&lt;li&gt;governance or lifecycle rules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Federation answers one question only:&lt;/p&gt;

&lt;p&gt;Can this workload identity be cryptographically verified as originating from the stated trust domain?&lt;/p&gt;

&lt;p&gt;What that identity is allowed to do remains a separate concern.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common misconceptions
&lt;/h2&gt;

&lt;p&gt;Federation is often misunderstood.&lt;/p&gt;

&lt;p&gt;It is not:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a global root CA&lt;/li&gt;
&lt;li&gt;transitive by default&lt;/li&gt;
&lt;li&gt;workload-level delegation&lt;/li&gt;
&lt;li&gt;dynamic trust based on behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Federation operates strictly at the trust domain level and only affects identity verification.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why federation alone is often insufficient
&lt;/h2&gt;

&lt;p&gt;In theory, SPIFFE federation is clean and well-defined.&lt;/p&gt;

&lt;p&gt;In practice, teams encounter issues when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;trust relationships grow faster than policy&lt;/li&gt;
&lt;li&gt;bundles become stale or poorly governed&lt;/li&gt;
&lt;li&gt;verification is decoupled from enforcement&lt;/li&gt;
&lt;li&gt;identity context disappears after the handshake&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are not flaws in the specification. They are consequences of how identity is applied in real systems.&lt;/p&gt;

&lt;p&gt;Federation is most effective when verification and enforcement remain tightly coupled.&lt;/p&gt;

&lt;h2&gt;
  
  
  Looking ahead
&lt;/h2&gt;

&lt;p&gt;The SPIFFE federation specification provides a precise, interoperable foundation for extending workload identity across administrative boundaries.&lt;/p&gt;

&lt;p&gt;What it intentionally leaves open is how federation is enforced, constrained, and observed at runtime.&lt;/p&gt;

&lt;p&gt;In a follow up post, we will explore how Riptides approaches federation, enforces it below the application layer, and how runtime context changes the security properties of federated workload identity.&lt;/p&gt;




&lt;p&gt;If you enjoyed this post, follow us on &lt;a href="https://www.linkedin.com/company/riptidesio/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; and &lt;a href="https://x.com/riptidesio" rel="noopener noreferrer"&gt;X&lt;/a&gt; for more updates.&lt;/p&gt;

&lt;p&gt;If you'd like to see Riptides in action, &lt;a href="https://riptides.io/request-a-demo" rel="noopener noreferrer"&gt;get in touch with us for a demo&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>cybersecurity</category>
      <category>spiffe</category>
      <category>nhi</category>
      <category>devops</category>
    </item>
    <item>
      <title>Zero-Touch Secrets: On-The-Wire Injection of Vault-Sourced Credentials</title>
      <dc:creator>Riptides</dc:creator>
      <pubDate>Mon, 02 Feb 2026 14:37:31 +0000</pubDate>
      <link>https://dev.to/riptides/zero-touch-secrets-on-the-wire-injection-of-vault-sourced-credentials-10if</link>
      <guid>https://dev.to/riptides/zero-touch-secrets-on-the-wire-injection-of-vault-sourced-credentials-10if</guid>
      <description>&lt;p&gt;In the previous post, &lt;a href="https://blog.riptides.io/ritptides-openai-apikeys" rel="noopener noreferrer"&gt;Secure OpenAI API Key delivery with Riptides&lt;/a&gt;, we demonstrated how Riptides securely delivers short-lived OpenAI API keys sourced from Vault/OpenBao to AI agents using kernel-enforced &lt;code&gt;sysfs&lt;/code&gt; files. That approach already eliminates static secrets, reduces blast radius, and removes the need for Vault clients or tokens in workloads.&lt;/p&gt;

&lt;p&gt;This model provides several key advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A dramatically reduced blast radius compared to static keys
&lt;/li&gt;
&lt;li&gt;A clean separation between identity, access policy, and application logic
&lt;/li&gt;
&lt;li&gt;Strong isolation between co-located workloads, enforced at the Linux kernel level
&lt;/li&gt;
&lt;li&gt;A familiar consumption model for developers (read a credential, use it)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This post goes further. Instead of delivering credentials to workloads, &lt;strong&gt;Riptides injects Vault-sourced credentials directly into outbound requests in kernel space at the moment they are sent&lt;/strong&gt;. API keys and cloud credentials never appear in the application user space; not in files nor in configuration.&lt;/p&gt;

&lt;p&gt;By combining Vault/OpenBao as the system of record for short-lived credentials with on-the-wire injection model of Riptides, organizations can enforce &lt;strong&gt;identity-based access to OpenAI and cloud APIs&lt;/strong&gt; with zero secret handling in workloads, while preserving compatibility with existing applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Existing Riptides capability: secretless cloud credential injection
&lt;/h2&gt;

&lt;p&gt;Before introducing Vault, Riptides already supported &lt;strong&gt;on-the-wire injection of temporary cloud credentials&lt;/strong&gt; that it provisions itself, based on workload identity.&lt;/p&gt;

&lt;p&gt;These credentials are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Short-lived
&lt;/li&gt;
&lt;li&gt;Issued on demand &lt;/li&gt;
&lt;li&gt;Never stored or managed by developers or operators&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can learn more about this capability in these posts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://blog.riptides.io/secretless-oci-authentication-with-spiffe-based-workload-identity" rel="noopener noreferrer"&gt;OCI authentication using SPIFFE-based workload identity&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://blog.riptides.io/on-demand-credentials-secretless-ai-assistant-example-on-gcp" rel="noopener noreferrer"&gt;On-demand credentials for AI assistants on GCP&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://blog.riptides.io/on-the-wire-credential-injection-secretless-aws-bedrock-access-example" rel="noopener noreferrer"&gt;On-the-wire injection for secretless AWS Bedrock access&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In these scenarios, &lt;strong&gt;Riptides itself provisions temporary cloud credentials&lt;/strong&gt; and injects them directly into outbound requests at write time, in kernel space.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No secrets.
&lt;/li&gt;
&lt;li&gt;No SDK changes.
&lt;/li&gt;
&lt;li&gt;No sidecars.
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Vault / OpenBao changes the equation
&lt;/h2&gt;

&lt;p&gt;The injection of Vault/OpenBao-sourced credentials does &lt;strong&gt;not replace existing cloud credential injection Riptides capability&lt;/strong&gt;; it complements it.&lt;/p&gt;

&lt;p&gt;Many organizations already rely on &lt;strong&gt;Vault or OpenBao&lt;/strong&gt; as their central system for issuing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Short-lived &lt;strong&gt;cloud provider credentials&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database credentials&lt;/strong&gt; (both static and dynamic)&lt;/li&gt;
&lt;li&gt;API keys for internal and external services&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For these teams, replacing Vault is neither desirable nor realistic. This is where the approach presented in this post comes in.&lt;/p&gt;

&lt;p&gt;By integrating Vault/OpenBao with Riptides via &lt;a href="https://github.com/riptideslabs/tokenex" rel="noopener noreferrer"&gt;tokenex&lt;/a&gt;, organizations can now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep &lt;strong&gt;Vault/OpenBao as the credential authority&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Continue using existing Vault policies, TTLs, and audit logs&lt;/li&gt;
&lt;li&gt;Eliminate secret distribution and handling on the infrastructure where workloads run&lt;/li&gt;
&lt;li&gt;Apply &lt;strong&gt;the same on-the-wire injection model&lt;/strong&gt; to credentials sourced from Vault&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In other words, if you already use Vault to issue credentials, you can now &lt;strong&gt;consume those credentials without ever exposing them to workloads&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  From secure delivery to full elimination of secrets in workloads
&lt;/h2&gt;

&lt;p&gt;The sysfs-based approach presented previously already offers strong guarantees. But it still involves &lt;strong&gt;materializing credentials in user space&lt;/strong&gt;, even if briefly and under kernel control.&lt;br&gt;
On-the-wire injection takes the final step.&lt;/p&gt;

&lt;p&gt;With this model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Credentials are fetched from Vault/OpenBao&lt;/li&gt;
&lt;li&gt;Authentication to Vault/OpenBao is &lt;strong&gt;JWT-based and tokenless&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Credentials are injected &lt;strong&gt;directly into outbound requests in kernel space on demand&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;The workload never sees, stores, or processes the credential&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We refer to this as &lt;strong&gt;on-the-write credential injection&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  How it works at a high level
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;A workload initiates an outbound request&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An AI agent calling OpenAI&lt;/li&gt;
&lt;li&gt;A service accessing AWS APIs&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Riptides intercepts the request in kernel space&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
No application changes. No SDK wrapping.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Credentials are requested from Vault/OpenBao&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No Vault tokens are stored or distributed&lt;/li&gt;
&lt;li&gt;Vault policies remain fully in control&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Credentials are injected into the request at write time&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OpenAI API keys are added to HTTP headers&lt;/li&gt;
&lt;li&gt;Cloud credentials are injected in the appropriate protocol-specific form&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The request leaves the node authenticated&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
The workload itself remains entirely unaware of the credential.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  What we’ll show next
&lt;/h2&gt;

&lt;p&gt;In the remainder of this post, we’ll demonstrate how:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;OpenAI API keys issued by Vault/OpenBao&lt;/strong&gt; can be injected on the wire using Riptides&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS credentials sourced from Vault/OpenBao&lt;/strong&gt; can be injected into cloud API requests using Riptides&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Prerequisites&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
This guide assumes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Vault or OpenBao is already running&lt;/li&gt;
&lt;li&gt;JWT authentication is configured in Vault/OpenBao to trust the Riptides control plane as an OIDC issuer&lt;/li&gt;
&lt;li&gt;The OpenAI secrets engine plugin is enabled and configured&lt;/li&gt;
&lt;li&gt;The AWS secrets engine plugin is enabled and configured&lt;/li&gt;
&lt;li&gt;Roles for generating OpenAI API keys and AWS credentials are already defined&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  On-the-wire injection of OpenAI API keys sourced from Vault/OpenBao
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Define the external OpenAI service in Riptides
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;core.riptides.io/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;openai-api&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;riptides-system&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;addresses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api.openai.com&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;443&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;openai-api&lt;/span&gt;
  &lt;span class="na"&gt;external&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This Riptides &lt;code&gt;Service&lt;/code&gt; object declares OpenAI as an &lt;strong&gt;external dependency&lt;/strong&gt;. It enables Riptides to apply identity-aware egress policies when workloads communicate with the OpenAI API.&lt;/p&gt;
&lt;h3&gt;
  
  
  Configure Vault/OpenBao as a credential source
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;core.riptides.io/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CredentialSource&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;vault-openai-apikeys&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;riptides-system&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;vault&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://localhost:8200&lt;/span&gt;
    &lt;span class="na"&gt;jwtAuthMethodPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jwt&lt;/span&gt;
    &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;JWT-AUTH-ROLE&amp;gt;&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;openai/creds/my-role&lt;/span&gt;
    &lt;span class="na"&gt;audience&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vault"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api_key&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This &lt;code&gt;CredentialSource&lt;/code&gt; tells Riptides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which Vault/OpenBao instance to connect to&lt;/li&gt;
&lt;li&gt;Which JWT authentication role to use&lt;/li&gt;
&lt;li&gt;Which secret path to read&lt;/li&gt;
&lt;li&gt;That the credential should be treated as a &lt;strong&gt;bearer token&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The OpenAI API key behaves as a bearer token and must be sent in the HTTP request header as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Authorization: Bearer &amp;lt;API_KEY&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By setting the credential &lt;code&gt;type&lt;/code&gt; to &lt;code&gt;token&lt;/code&gt;, Riptides activates its built-in bearer token injection mechanism. The &lt;code&gt;source&lt;/code&gt; field specifies which attribute in the Vault/OpenBao response contains the token value.&lt;/p&gt;

&lt;p&gt;Riptides exchanges a workload identity JWT for a short-lived OpenAI API key, without requiring Vault tokens or SDKs inside the workload.&lt;/p&gt;

&lt;h3&gt;
  
  
  Define the workload identity for the AI agent
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;core.riptides.io/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;WorkloadIdentity&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ai-agent&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;riptides-system&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;scope&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;agent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;AGENT_WORKLOAD_ID&amp;gt;&lt;/span&gt;
  &lt;span class="na"&gt;workloadID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ai-agent&lt;/span&gt;
  &lt;span class="na"&gt;selectors&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;process:name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;AI-AGENT-PROCESS-NAME&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This object binds a &lt;strong&gt;SPIFFE-based workload identity&lt;/strong&gt; to a specific process. Only the matching process is treated as the AI agent and is allowed to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authenticate to Vault/OpenBao&lt;/li&gt;
&lt;li&gt;Receive OpenAI API keys&lt;/li&gt;
&lt;li&gt;Use those credentials when calling the OpenAI API&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Bind the credential source to the workload
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;core.riptides.io/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CredentialBinding&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;vault-openai-apikeys-binding&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;riptides-system&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;workloadID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ai-agent&lt;/span&gt;
  &lt;span class="na"&gt;credentialSource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;vault-openai-apikeys&lt;/span&gt;
  &lt;span class="na"&gt;propagation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;injection&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;selectors&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;openai-api&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This &lt;code&gt;CredentialBinding&lt;/code&gt; connects the AI agent identity with the Vault credential source and defines &lt;strong&gt;how the credential is applied&lt;/strong&gt;. In this case, Riptides injects the OpenAI API key directly into outbound requests sent to the OpenAI API.&lt;/p&gt;

&lt;h2&gt;
  
  
  On-the-wire injection of AWS credentials sourced from Vault/OpenBao
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Define the external AWS service in Riptides
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;core.riptides.io/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-api&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;riptides-system&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;addresses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt;&lt;span class="s"&gt;.amazonaws.com&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;443&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-api&lt;/span&gt;
  &lt;span class="na"&gt;external&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This &lt;code&gt;Service&lt;/code&gt; object declares the AWS API as an &lt;strong&gt;external dependency&lt;/strong&gt;, allowing Riptides to apply identity-aware egress controls to AWS API calls.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configure Vault/OpenBao as an AWS credential source
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;core.riptides.io/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CredentialSource&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;vault-aws-creds&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;riptides-system&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;vault&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://localhost:8200&lt;/span&gt;
    &lt;span class="na"&gt;jwtAuthMethodPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jwt&lt;/span&gt;
    &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;JWT-AUTH-ROLE&amp;gt;&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws/creds/my-role&lt;/span&gt;
    &lt;span class="na"&gt;audience&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vault"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;aws&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By setting the credential &lt;code&gt;type&lt;/code&gt; to &lt;code&gt;aws&lt;/code&gt;, Riptides activates its built-in AWS credential injection mechanism. Under the hood, this uses the&lt;br&gt;
&lt;a href="https://github.com/riptideslabs/libsigv4" rel="noopener noreferrer"&gt;&lt;code&gt;libsigv4&lt;/code&gt;&lt;/a&gt; library to sign AWS API requests in kernel space.&lt;/p&gt;

&lt;h3&gt;
  
  
  Define the workload identity for the AWS client
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;core.riptides.io/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;WorkloadIdentity&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-cli&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;riptides-system&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;scope&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;agent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;AGENT_WORKLOAD_ID&amp;gt;&lt;/span&gt;
  &lt;span class="na"&gt;workloadID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-cli&lt;/span&gt;
  &lt;span class="na"&gt;selectors&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;process:name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This workload identity applies specifically to the AWS CLI process.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bind the AWS credential source to the workload
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;core.riptides.io/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CredentialBinding&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;vault-aws-creds-binding&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;riptides-system&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;workloadID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-cli&lt;/span&gt;
  &lt;span class="na"&gt;credentialSource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;vault-aws-creds&lt;/span&gt;
  &lt;span class="na"&gt;propagation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;injection&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;selectors&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-api&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this binding in place, Riptides injects Vault-issued AWS credentials directly into outbound AWS API requests without exposing credentials to user space, environment variables, or configuration files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key takeaways
&lt;/h2&gt;

&lt;p&gt;Riptides supports &lt;strong&gt;two complementary models&lt;/strong&gt; for secretless access:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Riptides-provisioned temporary credentials injected on the wire&lt;/strong&gt;
Ideal when you want Riptides to directly issue and inject cloud credentials.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vault/OpenBao-sourced credentials injected on the wire&lt;/strong&gt;
Ideal when Vault is already your source of truth for cloud, database, or API credentials.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both approaches share the same core properties:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No long-lived secrets&lt;/li&gt;
&lt;li&gt;Kernel-level enforcement&lt;/li&gt;
&lt;li&gt;Strong workload isolation&lt;/li&gt;
&lt;li&gt;Minimal operational overhead&lt;/li&gt;
&lt;li&gt;Clear auditability and policy control&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On-the-wire injection of credentials represents the &lt;strong&gt;most secure end state&lt;/strong&gt;: credentials never exist in workload user space at all.&lt;/p&gt;

&lt;p&gt;And when that model isn’t feasible for business or technical reasons, the &lt;strong&gt;sysfs-based delivery approach&lt;/strong&gt; remains available, still far more secure than traditional methods, and fully managed by Riptides at the kernel level.&lt;/p&gt;

&lt;p&gt;Together, these options give organizations a &lt;strong&gt;practical, incremental path&lt;/strong&gt; toward eliminating secrets from modern AI and cloud-native systems.&lt;/p&gt;

&lt;p&gt;With these takeaways in mind, the next question is how this model fits into your existing security architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who should use which model?
&lt;/h2&gt;

&lt;p&gt;Both credential injection models serve different organizational needs and maturity levels.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use Riptides provisioned temporary credentials if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You want the simplest path to secretless access for cloud and AI APIs&lt;/li&gt;
&lt;li&gt;You are not already standardized on Vault/OpenBao&lt;/li&gt;
&lt;li&gt;You prefer Riptides to handle the credential lifecycle end-to-end&lt;/li&gt;
&lt;li&gt;You want an immediate reduction in secret sprawl with minimal platform changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This model is ideal for teams adopting secretless infrastructure for the first time or for greenfield platforms.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use Vault/OpenBao-sourced credentials if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Vault/OpenBao is already your system of record for credentials&lt;/li&gt;
&lt;li&gt;You issue short-lived cloud, database, or API credentials via Vault/OpenBao today&lt;/li&gt;
&lt;li&gt;You want to preserve existing policies, audit trails, and governance controls&lt;/li&gt;
&lt;li&gt;You need to consume credentials that Riptides cannot provision natively, but that Vault/OpenBao supports via a secrets engine plugin&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This model is ideal for enterprises with mature security programs that want to &lt;strong&gt;eliminate secret handling at runtime without disrupting existing Vault/OpenBao workflows&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Both approaches share the same core guarantees: identity-based access, short-lived credentials, kernel-level enforcement, and zero secret distribution to developers or operators.&lt;/p&gt;




&lt;p&gt;If you enjoyed this post, follow us on &lt;a href="https://www.linkedin.com/company/riptidesio/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; and &lt;a href="https://x.com/riptidesio" rel="noopener noreferrer"&gt;X&lt;/a&gt; for more updates.&lt;/p&gt;

&lt;p&gt;If you'd like to see Riptides in action, &lt;a href="https://riptides.io/request-a-demo" rel="noopener noreferrer"&gt;get in touch with us for a demo&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>vault</category>
      <category>devops</category>
      <category>cybersecurity</category>
    </item>
    <item>
      <title>tokenex adds Vault &amp; OpenBao support: Exchanging ID tokens (JWTs) for secrets without static credentials</title>
      <dc:creator>Riptides</dc:creator>
      <pubDate>Mon, 26 Jan 2026 13:53:54 +0000</pubDate>
      <link>https://dev.to/riptidesio/tokenex-adds-vault-openbao-support-exchanging-id-tokens-jwts-for-secrets-without-static-hpa</link>
      <guid>https://dev.to/riptidesio/tokenex-adds-vault-openbao-support-exchanging-id-tokens-jwts-for-secrets-without-static-hpa</guid>
      <description>&lt;h2&gt;
  
  
  Introducing Vault &amp;amp; OpenBao support in &lt;strong&gt;&lt;a href="https://github.com/riptideslabs/tokenex" rel="noopener noreferrer"&gt;tokenex&lt;/a&gt;&lt;/strong&gt; open source library
&lt;/h2&gt;

&lt;p&gt;Since its first release, &lt;strong&gt;&lt;a href="https://github.com/riptideslabs/tokenex" rel="noopener noreferrer"&gt;tokenex&lt;/a&gt;&lt;/strong&gt; has focused on &lt;strong&gt;identity-first credential acquisition&lt;/strong&gt;  exchanging short-lived identity tokens (JWT) for cloud credentials just-in-time, without baking secrets into code, files, or images.&lt;/p&gt;

&lt;p&gt;Today, we’re extending that model with &lt;strong&gt;native support for HashiCorp Vault and OpenBao as credential providers in &lt;a href="https://github.com/riptideslabs/tokenex" rel="noopener noreferrer"&gt;tokenex&lt;/a&gt;&lt;/strong&gt; beyond cloud IAM.&lt;/p&gt;

&lt;p&gt;With this new capability, &lt;strong&gt;&lt;a href="https://github.com/riptideslabs/tokenex" rel="noopener noreferrer"&gt;tokenex&lt;/a&gt;&lt;/strong&gt; can exchange &lt;strong&gt;ID tokens (JWTs)&lt;/strong&gt; for secrets stored in &lt;strong&gt;Vault or OpenBao&lt;/strong&gt;, using their built-in &lt;strong&gt;JWT authentication&lt;/strong&gt; flows; no static Vault tokens, no long-lived credentials, and no manual secret distribution.&lt;/p&gt;

&lt;p&gt;This addition makes Vault and OpenBao &lt;strong&gt;first-class participants in tokenex’s identity-driven workflow&lt;/strong&gt;, allowing applications to retrieve both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;cloud-native credentials (AWS, GCP, Azure, OCI), and&lt;/li&gt;
&lt;li&gt;infrastructure or application secrets (databases, APIs, internal services)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;using the &lt;strong&gt;same identity-based access pattern&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In the next section, we’ll explain &lt;strong&gt;why this integration matters&lt;/strong&gt;, how it complements tokenex’s existing capabilities, and what it unlocks for teams building secure, scalable systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we built this feature
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/riptideslabs/tokenex" rel="noopener noreferrer"&gt;tokenex&lt;/a&gt;&lt;/strong&gt; was originally created to provide a &lt;strong&gt;unified, consistent interface for obtaining and refreshing cloud credentials&lt;/strong&gt; across multiple providers including &lt;strong&gt;AWS, GCP, Azure, and OCI — by exchanging identity tokens for temporary credentials&lt;/strong&gt; and streaming those credentials over a channel so applications never have to implement bespoke refresh logic themselves.&lt;/p&gt;

&lt;p&gt;It already supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AWS&lt;/strong&gt;: Exchanging ID tokens for temporary session credentials via Workload Identity Federation
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GCP&lt;/strong&gt;: Exchanging ID tokens for access tokens via federated identity
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Azure&lt;/strong&gt;: Exchanging ID tokens for access tokens using OAuth and Entra ID
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OCI&lt;/strong&gt;: Exchanging ID tokens for User Principal Session Tokens (UPST)
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generic token passthrough&lt;/strong&gt; and Kubernetes secret watching for flexible integrations
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This model enables developers to write &lt;strong&gt;single credential consumption logic&lt;/strong&gt;, regardless of provider, while &lt;strong&gt;&lt;a href="https://github.com/riptideslabs/tokenex" rel="noopener noreferrer"&gt;tokenex&lt;/a&gt;&lt;/strong&gt; handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Identity token exchange&lt;/li&gt;
&lt;li&gt;Credential refresh and rotation&lt;/li&gt;
&lt;li&gt;Streaming updates to applications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While this already removed much of the complexity around cloud authentication, there was still a clear gap: &lt;strong&gt;enterprise secrets management&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Many real‑world systems don’t only need cloud API credentials. They also rely on &lt;strong&gt;database credentials, API keys, and other sensitive configuration&lt;/strong&gt;, which are typically managed by platforms like &lt;strong&gt;HashiCorp Vault&lt;/strong&gt; or &lt;strong&gt;OpenBao&lt;/strong&gt;. These systems excel at issuing &lt;strong&gt;dynamic, short‑lived secrets&lt;/strong&gt;, enforcing least privilege, and providing strong auditability, but consuming those secrets safely still requires glue code and identity plumbing.&lt;/p&gt;

&lt;p&gt;By adding &lt;strong&gt;Vault/OpenBao as a first‑class credentials provider&lt;/strong&gt;, &lt;strong&gt;&lt;a href="https://github.com/riptideslabs/tokenex" rel="noopener noreferrer"&gt;tokenex&lt;/a&gt;&lt;/strong&gt; now allows workloads to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Exchange a &lt;strong&gt;JWT (ID token)&lt;/strong&gt; for secrets using &lt;strong&gt;Vault/OpenBao JWT authentication&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Retrieve &lt;strong&gt;static or dynamic secrets&lt;/strong&gt; (for example, database credentials) without embedding Vault‑specific logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With this addition, &lt;strong&gt;&lt;a href="https://github.com/riptideslabs/tokenex" rel="noopener noreferrer"&gt;tokenex&lt;/a&gt;&lt;/strong&gt; evolves from a cloud‑credential helper into a &lt;strong&gt;general identity‑to‑secret exchange layer&lt;/strong&gt;. Applications authenticate once using identity, and &lt;strong&gt;&lt;a href="https://github.com/riptideslabs/tokenex" rel="noopener noreferrer"&gt;tokenex&lt;/a&gt;&lt;/strong&gt; handles the rest, regardless of whether the target is a cloud API or a centralized secrets manager.&lt;/p&gt;

&lt;p&gt;This feature is a natural extension of tokenex’s core philosophy:&lt;br&gt;&lt;br&gt;
&lt;strong&gt;minimize credential handling in applications, centralize trust in identity, and let platforms issue short‑lived secrets on demand.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo: Using &lt;a href="https://github.com/riptideslabs/tokenex" rel="noopener noreferrer"&gt;tokenex&lt;/a&gt; with OpenBao to publish PostgreSQL credentials
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. Deploy OpenBao and PostgreSQL
&lt;/h3&gt;

&lt;p&gt;In this step, we deploy &lt;strong&gt;OpenBao&lt;/strong&gt; and &lt;strong&gt;PostgreSQL&lt;/strong&gt; as containers using &lt;strong&gt;Docker Compose&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OpenBao configuration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Create a file named &lt;code&gt;vault.hcl&lt;/code&gt; with the following contents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;ui&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="nx"&gt;storage&lt;/span&gt; &lt;span class="s2"&gt;"file"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/vault/data"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;listener&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;address&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"0.0.0.0:8200"&lt;/span&gt;
  &lt;span class="nx"&gt;tls_disable&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;api_addr&lt;/span&gt;     &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http://127.0.0.1:8200"&lt;/span&gt;
&lt;span class="nx"&gt;cluster_addr&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http://127.0.0.1:8201"&lt;/span&gt;

&lt;span class="nx"&gt;plugin_auto_register&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="nx"&gt;plugin_auto_download&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="nx"&gt;plugin_download_behavior&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"fail"&lt;/span&gt;

&lt;span class="nx"&gt;plugin_directory&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/opt/openbao/plugins"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;:&lt;br&gt;
For simplicity, TLS is disabled and file-based storage is used.&lt;br&gt;
This configuration is suitable for demos and local development only.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Docker Compose setup&lt;/strong&gt;&lt;br&gt;
Create a &lt;code&gt;docker-compose.yml&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;openbao&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;openbao/openbao:2.4&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;openbao&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8200:8200"&lt;/span&gt;
    &lt;span class="na"&gt;cap_add&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;IPC_LOCK&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./vault.hcl:/vault/config/vault.hcl:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;vault-data:/vault/data&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;VAULT_ADDR&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://127.0.0.1:8200"&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;vault server -log-level=debug -config=/vault/config/vault.hcl&lt;/span&gt;

  &lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:18&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;shm_size&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;128mb&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mysecretpassword&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5432:5432"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pg-data:/var/lib/postgresql&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;vault-data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

  &lt;span class="na"&gt;pg-data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Initialize and unseal OpenBAO
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;exec &lt;/span&gt;openbao bao operator init &lt;span class="nt"&gt;-format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;json &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; vault-init.json

docker &lt;span class="nb"&gt;exec &lt;/span&gt;openbao bao operator unseal &lt;span class="si"&gt;$(&lt;/span&gt;jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.unseal_keys_b64[0]'&lt;/span&gt; vault-init.json&lt;span class="si"&gt;)&lt;/span&gt;
docker &lt;span class="nb"&gt;exec &lt;/span&gt;openbao bao operator unseal &lt;span class="si"&gt;$(&lt;/span&gt;jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.unseal_keys_b64[1]'&lt;/span&gt; vault-init.json&lt;span class="si"&gt;)&lt;/span&gt;
docker &lt;span class="nb"&gt;exec &lt;/span&gt;openbao bao operator unseal &lt;span class="si"&gt;$(&lt;/span&gt;jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.unseal_keys_b64[2]'&lt;/span&gt; vault-init.json&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Configure JWT authentication in OpenBao
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Enable JWT authentication method&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;VAULT_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.root_token'&lt;/span&gt; vault-init.json&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  openbao bao auth &lt;span class="nb"&gt;enable &lt;/span&gt;jwt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Configure the JWT authentication method&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;VAULT_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.root_token'&lt;/span&gt; vault-init.json&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  openbao bao write auth/jwt/config &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;bound_issuer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;OIDC_ISSUER_URL&amp;gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;oidc_discovery_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;OIDC_DISCOVERY_URL&amp;gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;oidc_client_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;oidc_client_secret&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;bound_issuer&lt;/code&gt;: &lt;code&gt;&amp;lt;OIDC_ISSUER_URL&amp;gt;&lt;/code&gt; – Issuer URL of your OIDC provider&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;oidc_discovery_url&lt;/code&gt;: &lt;code&gt;&amp;lt;OIDC_DISCOVERY_URL&amp;gt;&lt;/code&gt; – URL for OIDC metadata discovery (keys, endpoints)
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create a named role 'secret-reader' that authorizes JWTs with specific subject and audience claims&lt;/span&gt;
&lt;span class="c"&gt;# Assign the 'read-secrets' policy to this role&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;VAULT_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.root_token'&lt;/span&gt; vault-init.json&lt;span class="si"&gt;)&lt;/span&gt; openbao &lt;span class="se"&gt;\&lt;/span&gt;
  bao write auth/jwt/role/secret-reader &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;user_claim&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"sub"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;bound_audiences&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;JWT_AUDIENCE&amp;gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;bound_subject&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;JWT_SUB_CLAIM&amp;gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;token_policies&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"read-secrets"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;token_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"service"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;expiration_leeway&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;150 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;not_before_leeway&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;150 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;role_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"jwt"&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;bound_audiences&lt;/code&gt;: &lt;code&gt;&amp;lt;JWT_AUDIENCE&amp;gt;&lt;/code&gt; – Expected &lt;code&gt;aud&lt;/code&gt; claim in JWT tokens&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;bound_subject&lt;/code&gt;: &lt;code&gt;&amp;lt;JWT_SUB_CLAIM&amp;gt;&lt;/code&gt; – Expected &lt;code&gt;sub&lt;/code&gt; claim in JWT tokens
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create the 'read-secrets' policy&lt;/span&gt;
&lt;span class="c"&gt;# Grants read access to the 'pg-dyn-dbuser' dynamic DB credential and the 'pg-dbuser1' static DB credential&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;VAULT_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.root_token'&lt;/span&gt; vault-init.json&lt;span class="si"&gt;)&lt;/span&gt; openbao &lt;span class="se"&gt;\&lt;/span&gt;
  bao policy write read-secrets -&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
path "database/creds/pg-dyn-dbuser" {
  capabilities = ["read"]
}

path "database/static-creds/pg-dbuser1" {
  capabilities = ["read"]
}
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Configure database secrets engine
&lt;/h3&gt;

&lt;p&gt;In this step, we enable the &lt;strong&gt;database secrets engine&lt;/strong&gt; in OpenBAO and configure it to manage credentials for PostgreSQL, both dynamic (temporary) and static users.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Enable database secrets engine&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;VAULT_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.root_token'&lt;/span&gt; vault-init.json&lt;span class="si"&gt;)&lt;/span&gt; openbao &lt;span class="se"&gt;\&lt;/span&gt;
  bao secrets &lt;span class="nb"&gt;enable &lt;/span&gt;database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Configure the connection to PostgreSQL&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Configure connection to PostgreSQL&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;VAULT_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.root_token'&lt;/span&gt; vault-init.json&lt;span class="si"&gt;)&lt;/span&gt; openbao &lt;span class="se"&gt;\&lt;/span&gt;
  bao write database/config/my-postgresql-database &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;plugin_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"postgresql-database-plugin"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;allowed_roles&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"pg-dyn-dbuser, pg-dbuser1"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;connection_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"postgresql://{{username}}:{{password}}@postgres:5432"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;username&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"postgres"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;password&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"mysecretpassword"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Configure a dynamic role for temporary PostgreSQL users&lt;/strong&gt;&lt;br&gt;
Dynamic roles allow OpenBAO to generate short-lived database credentials automatically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Configure a role that maps a name in OpenBAO to an SQL statement to execute to create the database credential&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;VAULT_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.root_token'&lt;/span&gt; vault-init.json&lt;span class="si"&gt;)&lt;/span&gt; openbao &lt;span class="se"&gt;\&lt;/span&gt;
  bao write database/roles/pg-dyn-dbuser &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;db_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"my-postgresql-database"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;creation_statements&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"CREATE ROLE &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;{{name}}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
        GRANT SELECT ON ALL TABLES IN SCHEMA public TO &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;{{name}}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;default_ttl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"1h"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;max_ttl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"24h"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Note&lt;/strong&gt; that &lt;code&gt;{{name}}&lt;/code&gt;, &lt;code&gt;{{password}}&lt;/code&gt;, and &lt;code&gt;{{expiration}}&lt;/code&gt; are dynamically populated by OpenBAO.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Create a static PostgreSQL user&lt;/strong&gt;&lt;br&gt;
This user will be referenced by a static role in OpenBAO.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create a database user named 'dbuser1' in the PostgreSQL&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;PGPASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mysecretpassword  postgres psql &lt;span class="nt"&gt;-U&lt;/span&gt; postgres &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"CREATE USER &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;dbuser1&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; WITH PASSWORD 'pwd1'; GRANT SELECT ON ALL TABLES IN SCHEMA public TO &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;dbuser1&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Configure a static role in OpenBAO&lt;/strong&gt;&lt;br&gt;
Static roles allow OpenBAO to manage existing database users without creating new ones:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Configure a static role that creates a link to a user named 'dbuser1' in the PostgreSQL database&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;VAULT_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.root_token'&lt;/span&gt; vault-init.json&lt;span class="si"&gt;)&lt;/span&gt; openbao &lt;span class="se"&gt;\&lt;/span&gt;
  bao write database/static-roles/pg-dbuser1 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;db_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"my-postgresql-database"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;username&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"dbuser1"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;rotation_period&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"1d"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Note&lt;/strong&gt; - &lt;code&gt;pg-dbuser1&lt;/code&gt; maps to the existing PostgreSQL user &lt;code&gt;dbuser1&lt;/code&gt; and &lt;code&gt;rotation_period&lt;/code&gt; defines how often the password of the database user should be rotated&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. Demo application
&lt;/h3&gt;

&lt;p&gt;This demo application logs &lt;strong&gt;both dynamic and static database credentials&lt;/strong&gt; to stdout as they are issued and refreshed by OpenBAO.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"context"&lt;/span&gt;
    &lt;span class="s"&gt;"log"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;
    &lt;span class="s"&gt;"os/signal"&lt;/span&gt;
    &lt;span class="s"&gt;"sync"&lt;/span&gt;
    &lt;span class="s"&gt;"syscall"&lt;/span&gt;

    &lt;span class="s"&gt;"emperror.dev/errors"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/go-logr/logr"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/iand/logfmtr"&lt;/span&gt;

    &lt;span class="s"&gt;"go.riptides.io/tokenex/pkg/credential"&lt;/span&gt;
    &lt;span class="s"&gt;"go.riptides.io/tokenex/pkg/token"&lt;/span&gt;
    &lt;span class="s"&gt;"go.riptides.io/tokenex/pkg/vault"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Create a cancellable context&lt;/span&gt;
    &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancel&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithCancel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c"&gt;// Create a logger&lt;/span&gt;
    &lt;span class="n"&gt;opts&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;logfmtr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultOptions&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Humanize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
    &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Colorize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
    &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddCaller&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
    &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CallerSkip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;

    &lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;logfmtr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewWithOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;logfmtr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetVerbosity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// Create a wait group to track goroutines&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;wg&lt;/span&gt; &lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WaitGroup&lt;/span&gt;

    &lt;span class="c"&gt;// Set up graceful shutdown&lt;/span&gt;
    &lt;span class="n"&gt;signalChan&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Signal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signalChan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SIGINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SIGTERM&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Press Ctrl+C to stop..."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;sig&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;signalChan&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Received signal, shutting down..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"sig"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}()&lt;/span&gt;

    &lt;span class="c"&gt;// Create an identity token provider&lt;/span&gt;
    &lt;span class="c"&gt;// under the hood the credential provider uses OCI workload identity federation to fetch user principal session tokens from OCI&lt;/span&gt;
    &lt;span class="c"&gt;// the credential provider exchanges an input ID token for an OCI user principal session token&lt;/span&gt;
    &lt;span class="c"&gt;// the input ID token can be obtained from any OIDC compliant IDP (e.g. Google, Microsoft, Auth0, Okta, etc.)&lt;/span&gt;

    &lt;span class="c"&gt;// for this example, we use a static ID token provider that returns a hardcoded ID token issued by an OIDC compliant IDP&lt;/span&gt;
    &lt;span class="c"&gt;// in a real application, you would implement the `token.IdentityTokenProvider` interface to create a dynamic ID token provider that fetches the ID token from an OIDC compliant IDP&lt;/span&gt;
    &lt;span class="n"&gt;idTokenProvider&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewStaticIdentityTokenProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&amp;lt;id-token-issued-by-idp&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// Create the Vault credentials provider&lt;/span&gt;
    &lt;span class="n"&gt;vaultProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;vault&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewCredentialsProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"http://localhost:8200"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Failed to create Vault credentials provider"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;dynDBCredsChan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;vaultProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetCredentials&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;idTokenProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;vault&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithJWTAuthMethodPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"jwt"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;vault&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithJWTAuthRoleName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"secret-reader"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;vault&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithSecretFullPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"database/creds/pg-dyn-dbuser"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Failed to get dynamic database credentials"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Process dynamic database credentials&lt;/span&gt;
    &lt;span class="n"&gt;wg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;credentialsConsumer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"secret_engine"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"database"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"credential_type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"dynamic"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"secret_path"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"/creds/pg-dyn-dbuser"&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;wg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dynDBCredsChan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;logDBCreds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;staticDBCredsChan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;vaultProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetCredentials&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;idTokenProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;vault&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithJWTAuthMethodPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"jwt"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;vault&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithJWTAuthRoleName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"secret-reader"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;vault&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithSecretFullPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"database/static-creds/pg-dbuser1"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Failed to get static database credentials"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Process static database credentials&lt;/span&gt;
    &lt;span class="n"&gt;wg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;credentialsConsumer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"secret_engine"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"database"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"credential_type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"static"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"secret_path"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"/static-creds/pg-dbuser1"&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;wg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;staticDBCredsChan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;logDBCreds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// Wait for all goroutines to finish&lt;/span&gt;
    &lt;span class="n"&gt;wg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Wait&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;credentialsConsumer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;wg&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WaitGroup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;credsChan&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="n"&gt;credential&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;credsLogger&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;credential&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VaultSecret&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;wg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;logr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FromContextOrDiscard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;credsChan&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"credentials channel closed"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

                    &lt;span class="k"&gt;return&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Error receiving credentials"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetDetails&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

                    &lt;span class="k"&gt;return&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;

                &lt;span class="n"&gt;dbSecret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Credential&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;credential&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VaultSecret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"invalid credential type"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"expected *credential.VaultSecret"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

                    &lt;span class="k"&gt;return&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="n"&gt;credsLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dbSecret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Context cancelled, shutting down credentials handler"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

                &lt;span class="k"&gt;return&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;logDBCreds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="n"&gt;logr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dbSecret&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;credential&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VaultSecret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Database secrets typically contain username and password&lt;/span&gt;
    &lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;dbSecret&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"username"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;dbSecret&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"credential"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"username"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Sample output&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;0 info  | 11:00:29.398131 | Press Ctrl+C to stop...        &lt;span class="nb"&gt;caller&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;main.go:41
0 info  | 11:00:29.428805 | credential                     &lt;span class="nb"&gt;caller&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;main.go:220 &lt;span class="nv"&gt;secret_engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;database &lt;span class="nv"&gt;credential_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;static &lt;span class="nv"&gt;secret_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/static-creds/pg-dbuser1 &lt;span class="nv"&gt;username&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dbuser1 &lt;span class="nv"&gt;password&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Qj-6l6OWghtonpucH6Vd
2 info  | 11:00:29.428858 | Published Vault secret         &lt;span class="nv"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;vault_credentials &lt;span class="nb"&gt;caller&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;creds.go:245 &lt;span class="nv"&gt;secret_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;database/static-creds/pg-dbuser1 &lt;span class="nv"&gt;expiresAt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"2026-01-15 13:51:17.428782 +0100 CET m=+6648.032605334"&lt;/span&gt;
2 info  | 11:00:29.428879 | Using RefreshOn &lt;span class="nb"&gt;time &lt;/span&gt;from credentials &lt;span class="nv"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;vault_credentials &lt;span class="nb"&gt;caller&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;creds.go:252 &lt;span class="nv"&gt;refreshOn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"2026-01-15 13:51:22.428782 +0100 CET m=+6653.032605334"&lt;/span&gt;
1 info  | 11:00:29.428885 | Scheduling credential refresh  &lt;span class="nv"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;vault_credentials &lt;span class="nb"&gt;caller&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;creds.go:260 &lt;span class="nv"&gt;refreshIn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1h50m52.999899041s &lt;span class="nv"&gt;refreshBuffer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0s &lt;span class="nv"&gt;secret_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;database/static-creds/pg-dbuser1
2 info  | 11:00:29.446979 | Published Vault secret         &lt;span class="nv"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;vault_credentials &lt;span class="nb"&gt;caller&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;creds.go:245 &lt;span class="nv"&gt;secret_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;database/creds/pg-dyn-dbuser &lt;span class="nv"&gt;expiresAt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"2026-01-15 12:15:29.446969 +0100 CET m=+900.050791918"&lt;/span&gt;
0 info  | 11:00:29.446989 | credential                     &lt;span class="nb"&gt;caller&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;main.go:220 &lt;span class="nv"&gt;secret_engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;database &lt;span class="nv"&gt;credential_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dynamic &lt;span class="nv"&gt;secret_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/creds/pg-dyn-dbuser &lt;span class="nv"&gt;username&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;v-jwt-ript-pg-dyn-d-QKRDQydUME1zlQtgdyY6-1768474829 &lt;span class="nv"&gt;password&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;t7gZd5h-7BwZr2lB0iFO
1 info  | 11:00:29.447002 | Scheduling credential refresh  &lt;span class="nv"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;vault_credentials &lt;span class="nb"&gt;caller&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;creds.go:260 &lt;span class="nv"&gt;refreshIn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;11m57.937258345s &lt;span class="nv"&gt;refreshBuffer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3m2.06274128s &lt;span class="nv"&gt;secret_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;database/creds/pg-dyn-dbuser
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What we see in the output&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For the &lt;strong&gt;static PostgreSQL user&lt;/strong&gt; &lt;code&gt;dbuser1&lt;/code&gt;, the password is managed and rotated by the OpenBAO database secrets engine.
The user was originally created with the password &lt;code&gt;pwd1&lt;/code&gt;. OpenBAO rotates this password &lt;strong&gt;only when the rotation time is reached&lt;/strong&gt;, at &lt;code&gt;expiresAt="2026-01-15 13:51:17.428782 +0100 CET"&lt;/code&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because this is a static role, &lt;strong&gt;&lt;a href="https://github.com/riptideslabs/tokenex" rel="noopener noreferrer"&gt;tokenex&lt;/a&gt; cannot retrieve a new password before the rotation occurs&lt;/strong&gt;. It must wait until OpenBAO performs the rotation.&lt;br&gt;&lt;br&gt;
  Once the password has been rotated, Tokenex re-fetches and publishes the updated credentials shortly after, at &lt;code&gt;refreshOn="2026-01-15 13:51:22.428782 +0100 CET"&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For the &lt;strong&gt;dynamic PostgreSQL user&lt;/strong&gt; &lt;code&gt;v-jwt-ript-pg-dyn-d-QKRDQydUME1zlQtgdyY6-1768474829&lt;/code&gt;, OpenBAO creates a brand-new database user with a unique password.
This credential is short-lived and expires in approximately 15 minutes.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Unlike static credentials, &lt;strong&gt;&lt;a href="https://github.com/riptideslabs/tokenex" rel="noopener noreferrer"&gt;tokenex&lt;/a&gt; can proactively request a new dynamic credential before the current one expires&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
  In this example, a refresh is scheduled at &lt;code&gt;refreshIn=11m57.937258345s&lt;/code&gt; with a &lt;code&gt;refreshBuffer=3m2.06274128s&lt;/code&gt;, ensuring continuous access without relying on password rotation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example use cases
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Zero-Trust service &amp;amp; database access
&lt;/h3&gt;

&lt;p&gt;Workloads authenticate using a JWT and exchange it for secrets at runtime:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Short-lived database credentials (e.g. PostgreSQL)&lt;/li&gt;
&lt;li&gt;Service-specific API keys or tokens&lt;/li&gt;
&lt;li&gt;Automatically rotated by Vault/OpenBao&lt;/li&gt;
&lt;li&gt;Fully auditable and identity-scoped&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No secrets in config files, CI pipelines, or environment variables.&lt;br&gt;
No shared credentials between services.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Trusted secret orchestrators
&lt;/h3&gt;

&lt;p&gt;A trusted orchestrator (Kubernetes controller, job runner, workflow engine) can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authenticate using its own workload identity&lt;/li&gt;
&lt;li&gt;Use Tokenex to fetch secrets on behalf of workloads&lt;/li&gt;
&lt;li&gt;Enforce centralized policy, intent, and approval flows&lt;/li&gt;
&lt;li&gt;Act as a controlled trust boundary in regulated environments&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;By combining:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Identity-based authentication&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Centralized secrets management&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Short-lived credentials&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Explicit, auditable token exchange&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;you end up with systems that are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Easier to reason about
&lt;/li&gt;
&lt;li&gt;Harder to misuse
&lt;/li&gt;
&lt;li&gt;Safer by default
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/riptideslabs/tokenex" rel="noopener noreferrer"&gt;tokenex&lt;/a&gt;&lt;/strong&gt; doesn’t replace Vault or OpenBao — it &lt;strong&gt;connects them seamlessly to modern identity systems&lt;/strong&gt;, allowing secrets to be accessed only when and where identity has been verified.&lt;/p&gt;

&lt;p&gt;If you enjoyed this post, follow us on &lt;a href="https://www.linkedin.com/company/riptidesio/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; and &lt;a href="https://x.com/riptidesio" rel="noopener noreferrer"&gt;X&lt;/a&gt; for more updates.&lt;/p&gt;

&lt;p&gt;If you’d like to see Riptides in action, &lt;a href="https://riptides.io/request-a-demo" rel="noopener noreferrer"&gt;get in touch with us for a demo&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>security</category>
      <category>nhi</category>
      <category>vault</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
