<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>CSI on TechBlog about OpenShift/Ansible/Satellite and much more</title><link>https://blog.stderr.at/tags/csi/</link><description>TechBlog about OpenShift/Ansible/Satellite and much more</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><copyright>Toni Schmidbauer &amp; Thomas Jungbauer</copyright><lastBuildDate>Mon, 03 Aug 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.stderr.at/tags/csi/index.xml" rel="self" type="application/rss+xml"/><item><title>The Guide to OpenBao - Kubernetes Integration - Part 10</title><link>https://blog.stderr.at/openshift-platform/security/secrets-management/openbao/2026-08-03-openbao-part-10-kubernetes-integration/</link><pubDate>Mon, 03 Aug 2026 00:00:00 +0000</pubDate><guid>https://blog.stderr.at/openshift-platform/security/secrets-management/openbao/2026-08-03-openbao-part-10-kubernetes-integration/</guid><description>&lt;div class="paragraph"&gt;
&lt;p&gt;With authentication and secrets engines configured, the next step is enabling applications to consume secrets. This article covers three integration patterns on OpenShift:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;the &lt;strong&gt;Agent Injector&lt;/strong&gt; for sidecar injection&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;the &lt;strong&gt;CSI Provider&lt;/strong&gt; for volume mounts&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;the &lt;strong&gt;External Secrets Operator (ESO)&lt;/strong&gt; for syncing to native Kubernetes Secrets.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_introduction"&gt;Introduction&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;There are three main patterns for Kubernetes workloads to consume OpenBao secrets:&lt;/p&gt;
&lt;/div&gt;
&lt;table class="tableblock frame-all grid-all stretch"&gt;
&lt;colgroup&gt;
&lt;col style="width: 14.2857%;"/&gt;
&lt;col style="width: 28.5714%;"/&gt;
&lt;col style="width: 28.5714%;"/&gt;
&lt;col style="width: 28.5715%;"/&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class="tableblock halign-left valign-top"&gt;Method&lt;/th&gt;
&lt;th class="tableblock halign-left valign-top"&gt;How It Works&lt;/th&gt;
&lt;th class="tableblock halign-left valign-top"&gt;Pros&lt;/th&gt;
&lt;th class="tableblock halign-left valign-top"&gt;Cons&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Agent Injector&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Sidecar injects secrets as files under &lt;code&gt;/vault/secrets/&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Automatic renewal, templates, dynamic secrets&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Extra container per pod&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;CSI Provider&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Mounts secrets as volumes (optional sync to a Secret)&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;No sidecar needed&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;No continuous auto-renewal (refresh on pod restart)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;External Secrets Operator&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Syncs to native Kubernetes Secrets&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;GitOps-friendly, standard &lt;code&gt;secretKeyRef&lt;/code&gt; / volumes&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Additional operator + NetworkPolicy considerations on OpenShift&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
OpenBao keeps a Vault-compatible API. Agent Injector annotations still use the &lt;code&gt;vault.hashicorp.com/&lt;/code&gt; prefix, and ESO uses its Vault provider. The CSI provider, however, uses &lt;code&gt;provider: openbao&lt;/code&gt; and &lt;code&gt;baoAddress&lt;/code&gt; / &lt;code&gt;baoCACertPath&lt;/code&gt; parameters. Still, it should be easy to understand the differences and map everything to HashiCorp Vault.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_lab_assumptions"&gt;Lab assumptions&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;These examples assume:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;OpenBao is installed in namespace &lt;code&gt;openbao&lt;/code&gt; with TLS (see the series articles on OpenShift deployment and TLS, and test case &lt;a href="https://tests.stdin.at/tests/TC-OPENBAO-OCP-001?product=openbao" rel="noopener" target="_blank"&gt;TC-OPENBAO-OCP-001&lt;/a&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Kubernetes auth is enabled and configured (&lt;a href="https://tests.stdin.at/tests/TC-OPENBAO-AUTH-001?product=openbao" rel="noopener" target="_blank"&gt;TC-OPENBAO-AUTH-001&lt;/a&gt; / &lt;a href="https://blog.stderr.at/openshift-platform/security/secrets-management/openbao/2026-03-09-openbao-part-7-authentication-methods/" rel="noopener" target="_blank"&gt;Part 7&lt;/a&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A KV v2 mount exists at &lt;code&gt;secret/&lt;/code&gt; (&lt;a href="https://blog.stderr.at/openshift-platform/security/secrets-management/openbao/2026-03-25-openbao-part-8-secrets-engines-kv/" rel="noopener" target="_blank"&gt;Part 8&lt;/a&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Policy &lt;code&gt;myapp&lt;/code&gt; allows &lt;code&gt;read&lt;/code&gt; on &lt;code&gt;secret/data/myapp/*&lt;/code&gt; (and metadata list as needed).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Related &lt;strong&gt;Test Cases&lt;/strong&gt; are:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://tests.stdin.at/tests/TC-OPENBAO-KV-001?product=openbao" rel="noopener" target="_blank"&gt;TC-OPENBAO-KV-001&lt;/a&gt; — Enable and Use KV v2 Secrets Engine&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://tests.stdin.at/tests/TC-OPENBAO-KV-003?product=openbao" rel="noopener" target="_blank"&gt;TC-OPENBAO-KV-003&lt;/a&gt; — KV Secret Rotation Pattern with CAS&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://tests.stdin.at/tests/TC-OPENBAO-UC-003?product=openbao" rel="noopener" target="_blank"&gt;TC-OPENBAO-UC-003&lt;/a&gt; — Multi-Tenant Secret Isolation&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://tests.stdin.at/tests/TC-OPENBAO-DB-001?product=openbao" rel="noopener" target="_blank"&gt;TC-OPENBAO-DB-001&lt;/a&gt; — Dynamic PostgreSQL Credentials&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;These and other test cases are available on &lt;a href="https://tests.stdin.at/" rel="noopener" target="_blank"&gt;tests.stdin.at&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Create the lab namespace and ServiceAccount. This namespace will be reused for all three patterns below:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;oc new-project myapp
oc create serviceaccount myapp-sa -n myapp&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Store sample secrets and bind the auth role to &lt;strong&gt;this&lt;/strong&gt; namespace:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock important"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-important" title="Important"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;OpenBao must be installed and you need proper access to the API.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;# Brief reminder:
export BAO_CACERT=&amp;#34;$HOME/openbao-tests/openbao-ca.crt&amp;#34;
export BAO_ADDR=&amp;#39;https://127.0.0.1:8200&amp;#39;
export BAO_TOKEN=$(jq -r &amp;#39;.root_token&amp;#39; openbao-init.json)
bao status&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;# Allow the myapp identity to read KV v2 data (and list metadata) under secret/myapp/
bao policy write myapp - &amp;lt;&amp;lt;&amp;#39;EOF&amp;#39;
path &amp;#34;secret/data/myapp/*&amp;#34; {
capabilities = [&amp;#34;read&amp;#34;, &amp;#34;list&amp;#34;]
}
path &amp;#34;secret/metadata/myapp/*&amp;#34; {
capabilities = [&amp;#34;list&amp;#34;]
}
EOF
# Map ServiceAccount myapp-sa in namespace myapp to that policy via Kubernetes auth
bao write auth/kubernetes/role/myapp \
bound_service_account_names=myapp-sa \
bound_service_account_namespaces=myapp \
policies=myapp \
ttl=1h
# Seed sample secrets for Injector / CSI / ESO examples later in this article
bao kv put secret/myapp/config db_host=pg.example.com db_password=s3cret
bao kv put secret/myapp/api key=api-key-123&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="admonitionblock important"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-important" title="Important"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
If &lt;code&gt;cas_required&lt;/code&gt; is enabled on the KV mount (&lt;a href="https://tests.stdin.at/tests/TC-OPENBAO-KV-002?product=openbao" rel="noopener" target="_blank"&gt;TC-OPENBAO-KV-002&lt;/a&gt;), every write must include &lt;code&gt;-cas=&amp;lt;current_version&amp;gt;&lt;/code&gt;. When updating values that ESO or CSI consume, rewrite &lt;strong&gt;all&lt;/strong&gt; properties the consumer expects — a partial put replaces the whole version and drops missing keys.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_pattern_1_agent_injector"&gt;Pattern 1: Agent Injector&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The Agent Injector uses a mutating webhook to &lt;strong&gt;inject an init container&lt;/strong&gt; and an optional sidecar that authenticates to OpenBao and renders secrets under &lt;code&gt;/vault/secrets/&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_how_it_works"&gt;How It Works&lt;/h3&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-none hljs"&gt;1. Pod created with vault.hashicorp.com/* annotations
2. Mutating webhook intercepts admission
3. vault-agent-init (and optionally vault-agent sidecar) injected
4. Agent authenticates with the pod ServiceAccount JWT (Kubernetes auth)
5. Agent retrieves secrets and writes templated files
6. Application reads from /vault/secrets/
7. Sidecar renews tokens / leases and re-renders as needed&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_prerequisites"&gt;Prerequisites&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;On the OpenShift Helm deployment used in this series, the injector is enabled with the chart (typically two replicas) as part of &lt;a href="https://tests.stdin.at/tests/TC-OPENBAO-OCP-001?product=openbao" rel="noopener" target="_blank"&gt;TC-OPENBAO-OCP-001&lt;/a&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;injector:
enabled: true
replicas: 2&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Verify it is running:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;oc get pods -n openbao -l app.kubernetes.io/name=openbao-agent-injector
# Check webhook
oc get mutatingwebhookconfigurations | grep openbao&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_tls_trust_in_the_application_namespace"&gt;TLS trust in the application namespace&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;With a TLS listener, the Agent must trust the OpenBao CA. Copy the CA into the app namespace and reference it from annotations (&lt;code&gt;tls-secret&lt;/code&gt; / &lt;code&gt;ca-cert&lt;/code&gt;):&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;oc create secret generic openbao-ca -n myapp \
--from-literal=ca.crt=&amp;#34;$(oc get secret openbao-ca-secret -n openbao -o jsonpath=&amp;#39;{.data.ca\.crt}&amp;#39; | base64 -d)&amp;#34; \
--dry-run=client -o yaml | oc apply -f -&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_deploy_an_application_with_annotations"&gt;Deploy an application with annotations&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The Deployment below follows &lt;a href="https://tests.stdin.at/tests/TC-OPENBAO-INT-003?product=openbao" rel="noopener" target="_blank"&gt;TC-OPENBAO-INT-003&lt;/a&gt; (OpenBao Agent Sidecar Injector). That test case validates the full path: mutating webhook injection, Kubernetes auth as ServiceAccount &lt;code&gt;myapp-sa&lt;/code&gt;, OpenBao role &lt;code&gt;myapp&lt;/code&gt;, TLS trust via the &lt;code&gt;openbao-ca&lt;/code&gt; secret copied earlier, and rendered files under &lt;code&gt;/vault/secrets/&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;In practical terms, the annotations tell the Agent to:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;authenticate with role &lt;code&gt;myapp&lt;/code&gt; (bound to &lt;code&gt;myapp-sa&lt;/code&gt; in namespace &lt;code&gt;myapp&lt;/code&gt;);&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;fetch KV v2 paths &lt;code&gt;secret/data/myapp/config&lt;/code&gt; and &lt;code&gt;secret/data/myapp/api&lt;/code&gt;;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;write templated output to &lt;code&gt;/vault/secrets/config.txt&lt;/code&gt; and &lt;code&gt;/vault/secrets/api-key&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;cat &amp;lt;&amp;lt;EOF | oc apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
namespace: myapp
spec:
replicas: 1
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
annotations:
vault.hashicorp.com/agent-inject: &amp;#34;true&amp;#34; &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
vault.hashicorp.com/role: &amp;#34;myapp&amp;#34; &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
vault.hashicorp.com/tls-secret: &amp;#34;openbao-ca&amp;#34; &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
vault.hashicorp.com/ca-cert: &amp;#34;/vault/tls/ca.crt&amp;#34;
vault.hashicorp.com/agent-inject-secret-config.txt: &amp;#34;secret/data/myapp/config&amp;#34; &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
vault.hashicorp.com/agent-inject-template-config.txt: |
{{- with secret &amp;#34;secret/data/myapp/config&amp;#34; -}}
DB_HOST={{ .Data.data.db_host }}
DB_PASSWORD={{ .Data.data.db_password }}
{{- end }}
vault.hashicorp.com/agent-inject-secret-api-key: &amp;#34;secret/data/myapp/api&amp;#34;
vault.hashicorp.com/agent-inject-template-api-key: |
{{- with secret &amp;#34;secret/data/myapp/api&amp;#34; -}}
{{ .Data.data.key }}
{{- end }}
spec:
serviceAccountName: myapp-sa &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: curlimages/curl:latest
command: [&amp;#34;sleep&amp;#34;, &amp;#34;infinity&amp;#34;]
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: [&amp;#34;ALL&amp;#34;]
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
EOF&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Enable injection (API-compatible &lt;code&gt;vault.hashicorp.com&lt;/code&gt; annotations).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Kubernetes auth role name in OpenBao.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Secret in the app namespace that holds the OpenBao CA.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;KV v2 path includes the &lt;code&gt;/data/&lt;/code&gt; segment for the Agent template.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;5&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Must match &lt;code&gt;bound_service_account_names&lt;/code&gt; / &lt;code&gt;namespaces&lt;/code&gt; on the role.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_verify_injection"&gt;Verify injection&lt;/h3&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;oc get pods -n myapp
# Expect READY 2/2 (app + vault-agent) when the sidecar is enabled
oc get pods -n myapp -o jsonpath=&amp;#39;{.items[0].spec.containers[*].name}{&amp;#34;\n&amp;#34;}&amp;#39;
# Expected: app vault-agent
oc get pods -n myapp -o jsonpath=&amp;#39;{.items[0].spec.initContainers[*].name}{&amp;#34;\n&amp;#34;}&amp;#39;
# Expected: vault-agent-init
oc exec -it deploy/myapp -c app -n myapp -- cat /vault/secrets/config.txt
# Expected: DB_HOST=pg.example.com
# DB_PASSWORD=s3cret
oc exec -it deploy/myapp -c app -n myapp -- cat /vault/secrets/api-key
# Expected: api-key-123&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_common_annotations"&gt;Common annotations&lt;/h3&gt;
&lt;table class="tableblock frame-all grid-all stretch"&gt;
&lt;colgroup&gt;
&lt;col style="width: 40%;"/&gt;
&lt;col style="width: 60%;"/&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class="tableblock halign-left valign-top"&gt;Annotation&lt;/th&gt;
&lt;th class="tableblock halign-left valign-top"&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;vault.hashicorp.com/agent-inject&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Enable/disable injection (&lt;code&gt;&amp;#34;true&amp;#34;&lt;/code&gt; / &lt;code&gt;&amp;#34;false&amp;#34;&lt;/code&gt;)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;vault.hashicorp.com/role&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;OpenBao Kubernetes auth role&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;vault.hashicorp.com/tls-secret&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Secret containing the CA used to verify OpenBao TLS&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;vault.hashicorp.com/ca-cert&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Path to the CA file inside the Agent (for example &lt;code&gt;/vault/tls/ca.crt&lt;/code&gt;)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;vault.hashicorp.com/agent-inject-secret-&amp;lt;name&amp;gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Secret path; creates &lt;code&gt;/vault/secrets/&amp;lt;name&amp;gt;&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;vault.hashicorp.com/agent-inject-template-&amp;lt;name&amp;gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Consul Template syntax for rendering the file&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;vault.hashicorp.com/agent-inject-status&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Set to &lt;code&gt;&amp;#34;update&amp;#34;&lt;/code&gt; to force re-injection&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;vault.hashicorp.com/agent-pre-populate-only&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;&lt;code&gt;&amp;#34;true&amp;#34;&lt;/code&gt; = init container only, no sidecar&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;vault.hashicorp.com/agent-revoke-on-shutdown&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;&lt;code&gt;&amp;#34;true&amp;#34;&lt;/code&gt; = revoke the token when the pod terminates&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;vault.hashicorp.com/tls-skip-verify&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Skip TLS verification (lab only — prefer a real CA)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_init_container_only_mode"&gt;Init-container-only mode&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;By default the injector adds both &lt;code&gt;vault-agent-init&lt;/code&gt; and a long-running &lt;code&gt;vault-agent&lt;/code&gt; sidecar. Set &lt;code&gt;agent-pre-populate-only: &amp;#34;true&amp;#34;&lt;/code&gt; when the application only needs secrets &lt;strong&gt;during the init phase&lt;/strong&gt; (for example: static KV, config files, API keys). The init container still authenticates, renders files under &lt;code&gt;/vault/secrets/&lt;/code&gt;, then exits; the sidecar is omitted, so the pod stays &lt;code&gt;1/1&lt;/code&gt; Ready and uses less CPU and memory.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Trade-off: there is &lt;strong&gt;no&lt;/strong&gt; continuous lease renewal or re-render. If OpenBao rotates a secret or a dynamic credential expires, the application will not see the update until the pod restarts.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Minimal annotations for this mode:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;annotations:
vault.hashicorp.com/agent-inject: &amp;#34;true&amp;#34; &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
vault.hashicorp.com/agent-pre-populate-only: &amp;#34;true&amp;#34; &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
vault.hashicorp.com/role: &amp;#34;myapp&amp;#34; &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
vault.hashicorp.com/tls-secret: &amp;#34;openbao-ca&amp;#34; &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
vault.hashicorp.com/ca-cert: &amp;#34;/vault/tls/ca.crt&amp;#34;
vault.hashicorp.com/agent-inject-secret-config.txt: &amp;#34;secret/data/myapp/config&amp;#34; &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Enable the mutating webhook for this pod.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Init container only — skip the sidecar.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Kubernetes auth role (must match SA name/namespace bindings).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;CA Secret in the app namespace so the Agent trusts OpenBao TLS.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;5&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;KV v2 path to render as &lt;code&gt;/vault/secrets/config.txt&lt;/code&gt; (add further &lt;code&gt;agent-inject-secret-*&lt;/code&gt; / template annotations as needed).&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="admonitionblock tip"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-tip" title="Tip"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
For dynamic database credentials with lease renewal, prefer the full sidecar mode, see &lt;a href="https://tests.stdin.at/tests/TC-OPENBAO-UC-002?product=openbao" rel="noopener" target="_blank"&gt;TC-OPENBAO-UC-002&lt;/a&gt; (Agent sidecar + PostgreSQL role).
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_pattern_2_csi_provider"&gt;Pattern 2: CSI Provider&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The Secrets Store CSI Driver mounts secrets as files without an application sidecar. On OpenShift the lab flow is: install the driver (OperatorHub/Software Catalog preferred), manage &lt;code&gt;ClusterCSIDriver&lt;/code&gt;, grant the provider SCC, enable the OpenBao CSI DaemonSet on the existing Helm release, then create a &lt;code&gt;SecretProviderClass&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This section aligns with &lt;a href="https://tests.stdin.at/tests/TC-OPENBAO-INT-002?product=openbao" rel="noopener" target="_blank"&gt;TC-OPENBAO-INT-002&lt;/a&gt; and the &lt;a href="https://openbao.org/docs/platform/k8s/csi/" rel="noopener" target="_blank"&gt;OpenBao CSI documentation&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_installing_the_secrets_store_csi_driver"&gt;Installing the Secrets Store CSI Driver&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Prefer the OpenShift Operator:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Install &lt;strong&gt;Secrets Store CSI Driver for Red Hat OpenShift&lt;/strong&gt; from OperatorHub. Search for &amp;#34;Secrets Store CSI Driver for Red Hat OpenShift&amp;#34; and install the operator with the default settings.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/openshift-platform/security/secrets-management/openbao/images/part10_CSIStorageOperator.png?width=840px" alt="OperatorHub/Software Catalog — Secrets Store CSI Driver for Red Hat OpenShift"/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;On OpenShift, each CSI driver is switched on with a cluster-scoped &lt;code&gt;ClusterCSIDriver&lt;/code&gt; whose &lt;code&gt;metadata.name&lt;/code&gt; must match the driver identifier — here it is &lt;code&gt;secrets-store.csi.k8s.io&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Create that object next:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;apiVersion: operator.openshift.io/v1
kind: ClusterCSIDriver
metadata:
name: secrets-store.csi.k8s.io &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
spec:
managementState: Managed&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Must equal the Secrets Store CSI driver name&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_openbao_csi_provider_on_openshift"&gt;OpenBao CSI provider on OpenShift&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The provider uses &lt;code&gt;hostPath&lt;/code&gt; and needs the privileged SCC:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;oc adm policy add-scc-to-user privileged -z openbao-csi-provider -n openbao&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Enable CSI on the existing OpenBao Helm release (chart &lt;strong&gt;0.18.0+&lt;/strong&gt;), mounting the OpenBao CA into the DaemonSet — same steps (Step 4) as &lt;a href="https://tests.stdin.at/tests/TC-OPENBAO-INT-002?product=openbao" rel="noopener" target="_blank"&gt;TC-OPENBAO-INT-002&lt;/a&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
If this was done already, you can skip this step. The important part here is to enable the CSI part in OpenBao.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;helm upgrade --install openbao openbao/openbao -n openbao --reuse-values \
--set csi.enabled=true \
--set csi.daemonSet.securityContext.container.privileged=true \
--set &amp;#39;csi.volumes[0].name=openbao-ca&amp;#39; \
--set &amp;#39;csi.volumes[0].secret.secretName=openbao-ca-secret&amp;#39; \
--set &amp;#39;csi.volumeMounts[0].name=openbao-ca&amp;#39; \
--set &amp;#39;csi.volumeMounts[0].mountPath=/openbao/tls&amp;#39; \
--set &amp;#39;csi.volumeMounts[0].readOnly=true&amp;#39; \
--set &amp;#39;csi.agent.extraArgs[0]=-ca-path=/openbao/tls/ca.crt&amp;#39;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Verify:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;oc get ds -n openbao openbao-csi-provider
# Desired/Ready should match the node count&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_creating_a_secretproviderclass"&gt;Creating a SecretProviderClass&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;A &lt;code&gt;SecretProviderClass&lt;/code&gt; tells the Secrets Store CSI driver &lt;strong&gt;which provider&lt;/strong&gt; to call and &lt;strong&gt;which secrets to mount&lt;/strong&gt;. The CSI node plugin talks to the OpenBao CSI provider DaemonSet over gRPC; that provider then authenticates to OpenBao (Kubernetes auth role &lt;code&gt;myapp&lt;/code&gt; in our test case) and fetches the listed objects.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Unlike the Agent Injector annotations and ESO’s Vault provider, the CSI parameters are OpenBao-specific. The CA file path must match the volume mounted into the provider DaemonSet in the Helm step above (&lt;code&gt;/openbao/tls/ca.crt&lt;/code&gt;), so TLS verification succeeds.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: openbao-secrets
namespace: myapp
spec:
provider: openbao &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
parameters:
baoAddress: &amp;#34;https://openbao.openbao.svc:8200&amp;#34; &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
baoCACertPath: &amp;#34;/openbao/tls/ca.crt&amp;#34; &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
roleName: &amp;#34;myapp&amp;#34; &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
objects: |
- objectName: &amp;#34;db_password&amp;#34;
secretPath: &amp;#34;secret/data/myapp/config&amp;#34;
secretKey: &amp;#34;db_password&amp;#34;
- objectName: &amp;#34;api_key&amp;#34;
secretPath: &amp;#34;secret/data/myapp/api&amp;#34;
secretKey: &amp;#34;key&amp;#34;
secretObjects: &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
- secretName: myapp-synced
type: Opaque
data:
- objectName: db_password
key: db_password
- objectName: api_key
key: api_key&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;OpenBao CSI provider name — do not use &lt;code&gt;vault&lt;/code&gt; here.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;In-cluster Service DNS and HTTPS listener; avoid the public Route for node-local mounts.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;CA path &lt;strong&gt;inside the CSI provider DaemonSet&lt;/strong&gt; (from the Helm &lt;code&gt;csi.volumeMounts&lt;/code&gt;), not a path in the app pod.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Kubernetes auth role; the mounting pod’s ServiceAccount must match the role bindings.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;5&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Optional: also sync mounted files into a native Secret (&lt;code&gt;myapp-synced&lt;/code&gt;) for &lt;code&gt;secretKeyRef&lt;/code&gt; / env vars.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_using_csi_in_a_pod"&gt;Using CSI in a Pod&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The application pod does &lt;strong&gt;not&lt;/strong&gt; talk to OpenBao itself. It mounts an ephemeral CSI volume that references the &lt;code&gt;SecretProviderClass&lt;/code&gt;. On start, kubelet asks the Secrets Store CSI driver; the driver calls the OpenBao provider, which logs in with the pod’s ServiceAccount JWT and writes the secret objects into the volume (and, if configured, into the synced Secret).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Use the same ServiceAccount (&lt;code&gt;myapp-sa&lt;/code&gt;) that the OpenBao role allows. Unlike the Agent Injector, there is no sidecar — the pod stays a single container, and secret files appear under the mount path you choose (here &lt;code&gt;/mnt/secrets&lt;/code&gt;).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This time a simple Pod …​ because I am too lazy to create a Deployment.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
namespace: myapp
spec:
serviceAccountName: myapp-sa &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: curlimages/curl:latest
command: [&amp;#34;sleep&amp;#34;, &amp;#34;infinity&amp;#34;]
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: [&amp;#34;ALL&amp;#34;]
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
volumeMounts:
- name: secrets
mountPath: /mnt/secrets &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
readOnly: true
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: myapp-synced &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
key: db_password
volumes:
- name: secrets
csi:
driver: secrets-store.csi.k8s.io &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
readOnly: true
volumeAttributes:
secretProviderClass: openbao-secrets &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Must match &lt;code&gt;bound_service_account_names&lt;/code&gt; / &lt;code&gt;namespaces&lt;/code&gt; on role &lt;code&gt;myapp&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Files such as &lt;code&gt;db_password&lt;/code&gt; and &lt;code&gt;api_key&lt;/code&gt; appear here (&lt;code&gt;objectName&lt;/code&gt; from the SecretProviderClass).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Optional env consumption via the synced Secret from &lt;code&gt;secretObjects&lt;/code&gt; — only works if that block was defined.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Driver registered by the &lt;code&gt;ClusterCSIDriver&lt;/code&gt; earlier in this section.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;5&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Links this volume to the &lt;code&gt;SecretProviderClass&lt;/code&gt; in the same namespace.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Verify:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;oc exec -it myapp-pod -n myapp -- ls -la /mnt/secrets/
oc exec -it myapp-pod -n myapp -- cat /mnt/secrets/db_password&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_pattern_3_external_secrets_operator_eso"&gt;Pattern 3: External Secrets Operator (ESO)&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The External Secrets Operator (ESO) synchronises secrets from OpenBao into native Kubernetes Secrets. It is a controller that talks to an external secrets backend (OpenBao here) and creates a native Secret in the namespace for the application to consume. The OpenShift lab path is &lt;a href="https://tests.stdin.at/tests/TC-OPENBAO-INT-001?product=openbao" rel="noopener" target="_blank"&gt;TC-OPENBAO-INT-001&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_installing_external_secrets_operator"&gt;Installing External Secrets Operator&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;On OpenShift, install &lt;strong&gt;External Secrets Operator for Red Hat OpenShift&lt;/strong&gt; from OperatorHub/Software Catalog. After the Operator is installed, create an &lt;code&gt;ExternalSecretsConfig&lt;/code&gt; named &lt;code&gt;cluster&lt;/code&gt;. This is a cluster-scoped object and will start the controller.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock important"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-important" title="Important"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
The default NetworkPolicies for the Red Hat ESO operand deny egress. Without an allow rule to OpenBao on TCP/8200, SecretStore login fails with &lt;code&gt;context deadline exceeded&lt;/code&gt;.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;apiVersion: operator.openshift.io/v1alpha1
kind: ExternalSecretsConfig
metadata:
name: cluster
spec:
controllerConfig:
networkPolicies:
- name: allow-openbao-egress
componentName: ExternalSecretsCoreController
egress:
- ports:
- protocol: TCP
port: 8200
to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: openbao&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Verify the deployment:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;oc get pods -n external-secrets&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_creating_a_secretstore"&gt;Creating a SecretStore&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;A &lt;code&gt;SecretStore&lt;/code&gt; is a &lt;strong&gt;namespaced&lt;/strong&gt; object that defines the connection to OpenBao: it tells ESO how to reach OpenBao and how to authenticate. It does not create application Secrets by itself — that is the job of an &lt;code&gt;ExternalSecret&lt;/code&gt; in the next step.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Because the lab uses TLS, first fetch the CA certificate from the OpenBao CA Secret and use it for the &lt;strong&gt;SecretStore&lt;/strong&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;# Base64 CA PEM (one line) for caBundle
oc get secret openbao-ca-secret -n openbao -o jsonpath=&amp;#39;{.data.ca\.crt}{&amp;#34;\n&amp;#34;}&amp;#39;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;ESO has no separate “OpenBao” provider. Use the &lt;strong&gt;Vault&lt;/strong&gt; provider against OpenBao’s compatible API, with Kubernetes auth and the same &lt;code&gt;myapp&lt;/code&gt; / &lt;code&gt;myapp-sa&lt;/code&gt; bindings as Injector and CSI:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock important"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-important" title="Important"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Replace the &lt;code&gt;&amp;lt;BASE64_CA_PEM&amp;gt;&lt;/code&gt; with the base64 encoded CA certificate.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;apiVersion: external-secrets.io/v1
kind: SecretStore
metadata:
name: openbao-backend
namespace: myapp
spec:
provider:
vault: &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
server: &amp;#34;https://openbao.openbao.svc:8200&amp;#34; &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
path: &amp;#34;secret&amp;#34; &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
version: &amp;#34;v2&amp;#34;
caBundle: &amp;#34;&amp;lt;BASE64_CA_PEM&amp;gt;&amp;#34; &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
auth:
kubernetes:
mountPath: &amp;#34;kubernetes&amp;#34;
role: &amp;#34;myapp&amp;#34; &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
serviceAccountRef:
name: myapp-sa&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Vault provider → OpenBao’s compatible HTTP API (there is no &lt;code&gt;openbao:&lt;/code&gt; block in ESO yet).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;In-cluster Service over HTTPS; the ESO controller must be allowed to reach it (NetworkPolicy above).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;KV mount path &lt;strong&gt;without trailing slash&lt;/strong&gt;; version.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Paste the base64 value from &lt;code&gt;openbao-ca-secret&lt;/code&gt; (do not wrap or re-encode).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;5&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Role and SA must match OpenBao’s Kubernetes auth bindings for namespace &lt;code&gt;myapp&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;oc get secretstore openbao-backend -n myapp
# STATUS should indicate Ready / Valid&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="admonitionblock warning"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-warning" title="Warning"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
If the store stays Not Ready, check NetworkPolicy egress to OpenBao, the &lt;code&gt;caBundle&lt;/code&gt;, and that role &lt;code&gt;myapp&lt;/code&gt; still allows &lt;code&gt;myapp-sa&lt;/code&gt; in &lt;code&gt;myapp&lt;/code&gt;.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
For cluster-wide access, use a &lt;code&gt;ClusterSecretStore&lt;/code&gt; with a ServiceAccount that OpenBao trusts (role + policy bound to that SA/namespace).
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_creating_an_externalsecret"&gt;Creating an ExternalSecret&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;An &lt;code&gt;ExternalSecret&lt;/code&gt; maps remote OpenBao keys onto a &lt;strong&gt;native&lt;/strong&gt; Kubernetes Secret. ESO periodically authenticates via the &lt;code&gt;SecretStore&lt;/code&gt;, reads the listed properties, and writes (or updates) the target Secret. Applications then use ordinary &lt;code&gt;secretKeyRef&lt;/code&gt; or volume mounts — they never call OpenBao directly. This keeps applications secret-agnostic so developers can keep using ordinary Kubernetes Secret consumption.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: myapp-secrets
namespace: myapp
spec:
refreshInterval: &amp;#34;5m&amp;#34; &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
secretStoreRef:
name: openbao-backend
kind: SecretStore
target:
name: myapp-secrets &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
creationPolicy: Owner
template:
type: Opaque
data:
- secretKey: db_host &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
remoteRef:
key: myapp/config &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
property: db_host
- secretKey: db_password
remoteRef:
key: myapp/config
property: db_password
- secretKey: api_key
remoteRef:
key: myapp/api
property: key&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;How often ESO re-reads OpenBao; use &lt;code&gt;force-sync&lt;/code&gt; (below) for an immediate refresh in the lab.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Name of the Kubernetes Secret that ESO creates or owns in this namespace.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Key name &lt;strong&gt;inside&lt;/strong&gt; the Kubernetes Secret (what the app will reference).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Path relative to the store’s &lt;code&gt;path: secret&lt;/code&gt; — &lt;strong&gt;without&lt;/strong&gt; the KV v2 &lt;code&gt;/data/&lt;/code&gt; segment.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
For KV v2, ESO’s &lt;code&gt;remoteRef.key&lt;/code&gt; is typically &lt;code&gt;myapp/config&lt;/code&gt;, while Agent/CSI templates use &lt;code&gt;secret/data/myapp/config&lt;/code&gt;. Mixing the two path styles is a common source of sync errors.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Verify:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The first command will show the status of the sync process. The second command will fetch the actual generated secret and show the keys and values.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;oc get ExternalSecret myapp-secrets -n myapp
# STATUS should be SecretSynced
oc get secret myapp-secrets -n myapp -o jsonpath=&amp;#39;{.data}&amp;#39; | \
jq &amp;#39;to_entries[] | {key: .key, value: (.value | @base64d)}&amp;#39;
# Should show db_host, db_password, api_key&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_fetching_all_keys_from_a_specific_path"&gt;Fetching all keys from a specific path&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;When every field under one OpenBao secret should become a Kubernetes Secret key, use &lt;code&gt;dataFrom.extract&lt;/code&gt; instead of listing each &lt;code&gt;property&lt;/code&gt;. Here all keys from &lt;code&gt;secret/myapp/config&lt;/code&gt; (for example &lt;code&gt;db_host&lt;/code&gt;, &lt;code&gt;db_password&lt;/code&gt;) are copied into &lt;code&gt;myapp-all-config&lt;/code&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: myapp-all-config
namespace: myapp
spec:
refreshInterval: 1h
secretStoreRef:
name: openbao-backend
kind: SecretStore
target:
name: myapp-all-config
dataFrom:
- extract:
key: myapp/config&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_using_the_generated_secret"&gt;Using the generated Secret&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Once the ExternalSecret is synced, treat &lt;code&gt;myapp-secrets&lt;/code&gt; like any other Opaque Secret: environment variables via &lt;code&gt;secretKeyRef&lt;/code&gt;, or files via a &lt;code&gt;secret&lt;/code&gt; volume. No Injector annotations or CSI &lt;code&gt;SecretProviderClass&lt;/code&gt; are required on the workload.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-eso
namespace: myapp
spec:
replicas: 1
selector:
matchLabels:
app: myapp-eso
template:
metadata:
labels:
app: myapp-eso
spec:
containers:
- name: app
image: registry.access.redhat.com/ubi9/ubi-minimal:latest
command: [&amp;#34;sleep&amp;#34;, &amp;#34;infinity&amp;#34;] &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
env:
- name: DB_HOST
valueFrom:
secretKeyRef:
name: myapp-secrets &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
key: db_host
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: myapp-secrets
key: db_password
volumeMounts:
- name: config
mountPath: /etc/config
readOnly: true
volumes:
- name: config
secret:
secretName: myapp-secrets &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Keeps the lab Pod running so &lt;code&gt;oc exec&lt;/code&gt; verification works; replace with the real application entrypoint in production.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Keys match &lt;code&gt;secretKey&lt;/code&gt; values from the ExternalSecret &lt;code&gt;data&lt;/code&gt; list.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Same Secret mounted as files under &lt;code&gt;/etc/config&lt;/code&gt; (one file per key).&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Verify:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;# Environment variables from secretKeyRef
oc exec -n myapp deploy/myapp-eso -- printenv DB_HOST DB_PASSWORD
# Expect the decoded values from myapp-secrets
# Files from the secret volume (one file per key)
oc exec -n myapp deploy/myapp-eso -- ls -l /etc/config
# Expect db_host, db_password, api_key&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_refresh_after_an_openbao_update"&gt;Refresh after an OpenBao update&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;KV v2 writes replace the &lt;strong&gt;entire&lt;/strong&gt; version. When you rotate a password, rewrite every property the ExternalSecret still expects, or those keys disappear from the synced Secret. If &lt;code&gt;cas_required&lt;/code&gt; is enabled, include &lt;code&gt;-cas=&amp;lt;current_version&amp;gt;&lt;/code&gt; (see &lt;a href="https://tests.stdin.at/tests/TC-OPENBAO-KV-002?product=openbao" rel="noopener" target="_blank"&gt;TC-OPENBAO-KV-002&lt;/a&gt;).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;# Rewrite all fields the ExternalSecret reads (KV v2 put replaces the version)
bao kv put --cas=&amp;lt;latest_version&amp;gt; secret/myapp/config \
db_host=pg.example.com \
db_password=newP@ssw0rd
# Force an immediate sync (do not wait for refreshInterval)
oc annotate externalsecret myapp-secrets -n myapp force-sync=$(date +%s) --overwrite&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_verifying_externalsecret_status"&gt;Verifying ExternalSecret status&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Confirm both the ExternalSecret condition and the materialised Secret contents:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;oc get externalsecret -n myapp
# Expect SecretSynced / Ready
oc get secret myapp-secrets -n myapp -o jsonpath=&amp;#39;{.data}&amp;#39; | \
jq &amp;#39;to_entries[] | {key: .key, value: (.value | @base64d)}&amp;#39;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_comparing_integration_methods"&gt;Comparing integration methods&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;table class="tableblock frame-all grid-all stretch"&gt;
&lt;colgroup&gt;
&lt;col style="width: 25%;"/&gt;
&lt;col style="width: 25%;"/&gt;
&lt;col style="width: 25%;"/&gt;
&lt;col style="width: 25%;"/&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class="tableblock halign-left valign-top"&gt;Feature&lt;/th&gt;
&lt;th class="tableblock halign-left valign-top"&gt;Agent Injector&lt;/th&gt;
&lt;th class="tableblock halign-left valign-top"&gt;CSI Provider&lt;/th&gt;
&lt;th class="tableblock halign-left valign-top"&gt;ESO&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Auto-renewal&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Yes&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;No (pod lifecycle)&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Yes (refresh interval / force-sync)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Sidecar needed&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Yes&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;No&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;No&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;GitOps-friendly&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Annotations on workloads&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;SecretProviderClass + pod volume&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Yes (ExternalSecret CRs)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Standard K8s Secret&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;No (files; optional app logic)&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Optional via &lt;code&gt;secretObjects&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Yes&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Dynamic secrets&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Yes&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Limited&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Limited&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Template support&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Yes (Agent templates)&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;No&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Yes (ESO templates)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;OpenShift extras&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Injector CA secret&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;ClusterCSIDriver, SCC, Helm CSI&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;ExternalSecretsConfig + NetworkPolicy&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_best_practices"&gt;Best practices&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_choose_the_right_pattern"&gt;Choose the right pattern&lt;/h3&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Agent Injector&lt;/strong&gt;: auto-renewal, dynamic secrets, or Consul Template-style files (&lt;a href="https://tests.stdin.at/tests/TC-OPENBAO-INT-003?product=openbao" rel="noopener" target="_blank"&gt;TC-OPENBAO-INT-003&lt;/a&gt;, &lt;a href="https://tests.stdin.at/tests/TC-OPENBAO-UC-002?product=openbao" rel="noopener" target="_blank"&gt;TC-OPENBAO-UC-002&lt;/a&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;CSI Provider&lt;/strong&gt;: simple file mounts or synced Secrets without a sidecar (&lt;a href="https://tests.stdin.at/tests/TC-OPENBAO-INT-002?product=openbao" rel="noopener" target="_blank"&gt;TC-OPENBAO-INT-002&lt;/a&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;ESO&lt;/strong&gt;: GitOps and native &lt;code&gt;Secret&lt;/code&gt; consumption (&lt;a href="https://tests.stdin.at/tests/TC-OPENBAO-INT-001?product=openbao" rel="noopener" target="_blank"&gt;TC-OPENBAO-INT-001&lt;/a&gt;); combine with Sealed Secrets migration patterns in &lt;a href="https://tests.stdin.at/tests/TC-OPENBAO-INT-004?product=openbao" rel="noopener" target="_blank"&gt;TC-OPENBAO-INT-004&lt;/a&gt; if you are moving off Bitnami Sealed Secrets.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_security_considerations"&gt;Security considerations&lt;/h3&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;# Bind roles to a single namespace and SA
bao write auth/kubernetes/role/myapp \
bound_service_account_names=myapp-sa \
bound_service_account_namespaces=myapp \
policies=myapp \
ttl=1h
# Prefer short TTLs for dynamic database roles
bao write database/roles/myapp-readonly \
default_ttl=1h \
max_ttl=4h&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Always trust OpenBao TLS with a real CA (&lt;code&gt;caBundle&lt;/code&gt;, &lt;code&gt;baoCACertPath&lt;/code&gt;, or &lt;code&gt;tls-secret&lt;/code&gt;) instead of &lt;code&gt;tls-skip-verify&lt;/code&gt; outside throwaway labs.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This article covered three patterns for integrating OpenBao with Kubernetes and OpenShift:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Agent Injector&lt;/strong&gt;: dynamic secrets and auto-renewal via annotated pods&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;CSI Provider&lt;/strong&gt;: volume mounts (and optional synced Secrets) with the OpenBao CSI provider&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;External Secrets Operator&lt;/strong&gt;: GitOps-friendly native Secrets, including OpenShift NetworkPolicy and TLS details&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Choose the pattern that fits each workload, or combine them. Step-by-step validation lives in the OpenBao plan on &lt;a href="https://tests.stdin.at/?product=openbao" rel="noopener" target="_blank"&gt;tests.stdin.at&lt;/a&gt;: &lt;a href="https://tests.stdin.at/tests/TC-OPENBAO-INT-001?product=openbao" rel="noopener" target="_blank"&gt;TC-OPENBAO-INT-001 — ESO&lt;/a&gt;, &lt;a href="https://tests.stdin.at/tests/TC-OPENBAO-INT-002?product=openbao" rel="noopener" target="_blank"&gt;TC-OPENBAO-INT-002 — CSI&lt;/a&gt;, &lt;a href="https://tests.stdin.at/tests/TC-OPENBAO-INT-003?product=openbao" rel="noopener" target="_blank"&gt;TC-OPENBAO-INT-003 — Injector&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_resources"&gt;Resources&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://openbao.org/docs/platform/k8s/injector" rel="noopener" target="_blank"&gt;Agent Injector documentation&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://openbao.org/docs/platform/k8s/csi" rel="noopener" target="_blank"&gt;CSI Provider documentation&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://external-secrets.io/latest/provider/hashicorp-vault/" rel="noopener" target="_blank"&gt;External Secrets Operator — Vault provider&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://tests.stdin.at/tests/TC-OPENBAO-INT-001?product=openbao" rel="noopener" target="_blank"&gt;TC-OPENBAO-INT-001 — ESO&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://tests.stdin.at/tests/TC-OPENBAO-INT-002?product=openbao" rel="noopener" target="_blank"&gt;TC-OPENBAO-INT-002 — CSI&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://tests.stdin.at/tests/TC-OPENBAO-INT-003?product=openbao" rel="noopener" target="_blank"&gt;TC-OPENBAO-INT-003 — Injector&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item></channel></rss>