<?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 (@riptidesio).</description>
    <link>https://dev.to/riptidesio</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%2Forganization%2Fprofile_image%2F12259%2F2e261897-1176-4a13-b559-1a989bd3cefa.jpeg</url>
      <title>DEV Community: Riptides</title>
      <link>https://dev.to/riptidesio</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/riptidesio"/>
    <language>en</language>
    <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>
