<?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>Security on TechBlog about OpenShift/Ansible/Satellite and much more</title><link>https://blog.stderr.at/categories/security/</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>Wed, 10 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.stderr.at/categories/security/index.xml" rel="self" type="application/rss+xml"/><item><title>Creating an auditd rule to monitor Ansible Automation Platform static secrets</title><link>https://blog.stderr.at/ansible/2026/06/creating-an-auditd-rule-to-monitor-ansible-automation-platform-static-secrets/</link><pubDate>Tue, 09 Jun 2026 00:00:00 +0000</pubDate><guid>https://blog.stderr.at/ansible/2026/06/creating-an-auditd-rule-to-monitor-ansible-automation-platform-static-secrets/</guid><description>&lt;div class="paragraph"&gt;
&lt;p&gt;Ansible Automation Platform (AAP) comes with an elaborate credential
management solution. All credentials within AAP are stored encrypted
in a PostgreSQL database. But the keys used for encrypting database
fields are currently stored on the machine(s) running AAP. In this
blog post, we show we created an auditd rule to get at least some kind
of monitoring if those keys are accessed.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_ansible_automation_platform_local_secret_key"&gt;Ansible Automation Platform local SECRET_KEY&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;AAP comes with a
&lt;a href="https://docs.redhat.com/en/documentation/red_hat_ansible_automation_platform/2.7/secure-con_controller_how_credentials_work"&gt;credential
management solution&lt;/a&gt;. Credentials are used to&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;access remote machines&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;provide configuration data for automation, like API keys&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;connect AAP to source code repositories and inventories&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;and much more. You can even integrate external credential management
solutions like &lt;a href="https://www.hashicorp.com/products/vault"&gt;HashiCorp
Vault&lt;/a&gt; or &lt;a href="https://openbao.org/"&gt;OpenBao&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;All credentials stored within AAP are saved as encrypted database
fields in a PostgreSQL database. There is a main key and AAP creates
for every encrypted database field a derived AES-256
encryption key. For more details see the
&lt;a href="https://docs.redhat.com/en/documentation/red_hat_ansible_automation_platform/2.7/secure-con_controller_secret_handling#controller-secret-handling-operational-use"&gt;documentation&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;But there is one caveat: the main secret key is stored on the local
file system. There is currently no supported option to integrate
something like a HSM (High Security Module) for storing the main
secret securely.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To mitigate this issue we deployed a Linux audit rule that monitors
read/write and attribute changes to the main secret.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_configuring_linux_auditd"&gt;Configuring Linux auditd&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;We use the containerized version of AAP 2.6. In the containerized
version of AAP those secrets are stored as &lt;em&gt;podman&lt;/em&gt; secrets as the
user running the AAP containers.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To list AAP podman secrets execute&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-terminal hljs" data-lang="terminal"&gt;$ podman secret list
ID NAME DRIVER CREATED UPDATED
02a2eb397dc5cedefde6ae411 hub_resource_server file 2 hours ago 2 hours ago
0dd84dc7b808917c4efb6ea9f controller_channels file 2 hours ago 2 hours ago
85fb73ff57729250c019976ed hub_database_fields file 2 hours ago 2 hours ago
c482ff7c07f908e443d327293 controller_postgres file 2 hours ago 2 hours ago
e6750121a8997cce8185072bf gateway_admin_password file 2 hours ago 2 hours ago
2731b2d03806f88f35809de66 eda_admin_password file 2 hours ago 2 hours ago
31ca44577b4c5b61dd3f0a0b6 eda_secret_key file 2 hours ago 2 hours ago
856473626bad9ff37b1b979d4 eda_resource_server file 2 hours ago 2 hours ago
b20ec0897fc2b6533544c4441 gateway_redis_url file 2 hours ago 2 hours ago
1dfdd812847ed4c1c01b194e2 gateway_db_password file 2 hours ago 2 hours ago
32d04575881dd781be192ef49 eda_db_password file 2 hours ago 2 hours ago
5102f69350717c15177b80821 hub_secret_key file 2 hours ago 2 hours ago
d1faf92922182604cfa746318 controller_resource_server file 2 hours ago 2 hours ago
d29f0d8c2fc709655dd918f2c postgresql_admin_password file 2 hours ago 2 hours ago
1b3ee24261bdff50d2c70e3f0 gateway_secret_key file 2 hours ago 2 hours ago
ae6c9e78e91dea180d22db6ee hub_settings file 2 hours ago 2 hours ago
b348d89bfb7d3b18f33a67d07 controller_secret_key file 2 hours ago 2 hours ago &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&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;the controller secret key&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;If we want to know where those secrets are stored we can execute&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-terminal hljs" data-lang="terminal"&gt;$ podman secret inspect controller_secret_key
[
{
&amp;#34;ID&amp;#34;: &amp;#34;b348d89bfb7d3b18f33a67d07&amp;#34;,
&amp;#34;CreatedAt&amp;#34;: &amp;#34;2026-06-09T10:57:28.278138715Z&amp;#34;,
&amp;#34;UpdatedAt&amp;#34;: &amp;#34;2026-06-09T10:57:28.278138715Z&amp;#34;,
&amp;#34;Spec&amp;#34;: {
&amp;#34;Name&amp;#34;: &amp;#34;controller_secret_key&amp;#34;,
&amp;#34;Driver&amp;#34;: {
&amp;#34;Name&amp;#34;: &amp;#34;file&amp;#34;,
&amp;#34;Options&amp;#34;: {
&amp;#34;path&amp;#34;: &amp;#34;/home/admin/.local/share/containers/storage/secrets/filedriver&amp;#34;
}
},
&amp;#34;Labels&amp;#34;: {}
}
}
]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Let’s see what’s in the filedriver directory:&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-terminal hljs" data-lang="terminal"&gt;$ ls /home/admin/.local/share/containers/storage/secrets/filedriver
secretsdata.json secretsdata.lock&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&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;we are mainly interested in the &lt;em&gt;secretsdata.json&lt;/em&gt; file&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;So the &lt;em&gt;secretsdata.json&lt;/em&gt; file stores all podman secrets that are
using the &lt;em&gt;filedriver&lt;/em&gt;. For more information see the section
&lt;a href="https://docs.podman.io/en/latest/markdown/podman-secret-create.1.html#secret-drivers"&gt;Secret
Drivers&lt;/a&gt; in the podman-secrets-create(1) manpage.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Let’s create a Linux auditd rule to monitor access to this file. As we
are using Red Hat Enterprise Linux 10 a more detailed introduction to
the Linux audit system is available
&lt;a href="https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/10/html/risk_reduction_and_recovery_operations/auditing-the-system"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;We created the file &lt;em&gt;/etc/audit/rules.d/aap_secrets.rules&lt;/em&gt; with the following content:&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-terminal hljs" data-lang="terminal"&gt;-w /home/admin/.local/share/containers/storage/secrets/filedriver/secretsdata.json -p rwa -k aap_secrets &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&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;-p rwa means monitor read/write/attribute changes, -k tells the audit system log events with an additional key &amp;#34;aap_secrets&amp;#34;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To activate the rule we need to load the audit rule via:&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-terminal hljs" data-lang="terminal"&gt;# auditctl -R /etc/audit/rules.d/aap_secrets.rules&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;You can list loaded audit rules via&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-terminal hljs" data-lang="terminal"&gt;# auditctl -l
-w /home/admin/.local/share/containers/storage/secrets/filedriver/secretsdata.json -p rwa -k aap_secrets&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;As a test we can cat the secrets file and check if auditd logged an event:&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-terminal hljs" data-lang="terminal"&gt;# cat /home/admin/.local/share/containers/storage/secrets/filedriver/secretsdata.json
...
# grep aap_secrets /var/log/audit/audit.log
type=SYSCALL msg=audit(1781009319.993:204): arch=c000003e syscall=257 success=yes exit=3 a0=ffffff9c a1=7ffc5276b65c a2=0 a3=0 items=1 ppid=6186 pid=6695 auid=1000 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=pts2 ses=3 comm=&amp;#34;cat&amp;#34; exe=&amp;#34;/usr/bin/cat&amp;#34; subj=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 key=&amp;#34;aap_secrets&amp;#34;ARCH=x86_64 SYSCALL=openat AUID=&amp;#34;admin&amp;#34; UID=&amp;#34;root&amp;#34; GID=&amp;#34;root&amp;#34; EUID=&amp;#34;root&amp;#34; SUID=&amp;#34;root&amp;#34; FSUID=&amp;#34;root&amp;#34; EGID=&amp;#34;root&amp;#34; SGID=&amp;#34;root&amp;#34; FSGID=&amp;#34;root&amp;#34; &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&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;We can see that the &lt;em&gt;cat&lt;/em&gt; command (comm=cat) was used by the root user to display the file contents&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_summary"&gt;Summary&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;In this blog post we demonstrated how to configure the Linux audit
system to log events if the AAP database secret key is accessed. The
audit subsystem can also monitor system calls, for a detailed
introduction see
&lt;a href="https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/10/html/risk_reduction_and_recovery_operations/auditing-the-system#audit-system-architecture"&gt;Audit system architecture&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item><item><title>The Guide to OpenBao - Secrets Engines KV - Part 8</title><link>https://blog.stderr.at/openshift-platform/security/secrets-management/openbao/2026-03-25-openbao-part-8-secrets-engines-kv/</link><pubDate>Wed, 25 Mar 2026 00:00:00 +0000</pubDate><guid>https://blog.stderr.at/openshift-platform/security/secrets-management/openbao/2026-03-25-openbao-part-8-secrets-engines-kv/</guid><description>&lt;div class="paragraph"&gt;
&lt;p&gt;Secrets engines are the heart of OpenBao’s functionality. They store, generate, or encrypt data. This article covers the most commonly used secrets engine: KV for static secrets. It will demonstrate how to use the KV secrets engine to store and retrieve secrets. Upcoming articles will cover the PKI secrets engine and the integration with &lt;strong&gt;cert-manager&lt;/strong&gt;.&lt;/p&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;Secrets engines are components which store, generate, or encrypt data. Some engines simply store and read data, others connect to other services and other engines provide encryption as a service.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;They are enabled at a path in OpenBao (as everything is done as a path in OpenBao). This means each secrets engine defines its very own path including properties.&lt;/p&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;
The path is case-sensitive. For example, the KV secrets engine enabled at kv/ and KV/ are treated as two distinct instances of KV secrets engine.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The management of the secret engines is done via the CLI or API or, if enabled, the UI. Engines can be enabled, disabled, moved or tuned.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Enable - enables a secrets engine at the given path.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Disable - disables an existing secrets engine. All existing secrets are revoked and the data is deleted.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Move - moves the path for an existing secrets engine.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Tune - tunes global configuration for the secrets engine such as the TTLs.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_list_of_secrets_engines"&gt;List of Secrets Engines&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To list all enabled secrets engines, we can use the following command:&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;bao secrets list&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Which will output something like:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock nocopy"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;Path Type Accessor Description
---- ---- -------- -----------
cubbyhole/ cubbyhole cubbyhole_f8b1e784 per-token private secret storage
identity/ identity identity_76940581 identity store
secret/ kv kv_ccb47c73 n/a
sys/ system system_cb0d64b7 system endpoints used for control, policy and debugging&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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;
As a reminder: To login to the OpenBao CLI, we can use the following command (assuming that you are using HTTPS and have the CA certificate ready):
&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;export BAO_ADDR=&amp;#39;https://127.0.0.1:8200&amp;#39;
export BAO_CACERT=&amp;#34;$PWD/openbao-ca.crt&amp;#34;
bao login&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="sect-kv"&gt;KV (Key-Value) Secrets Engine&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The &lt;strong&gt;KV&lt;/strong&gt; secrets engine is probably the most commonly used engine. It is a generic key-value store.
When you work with KV secrets engine you will note very soon that there are two versions of the engine: &lt;strong&gt;KV v1&lt;/strong&gt; and &lt;strong&gt;KV v2&lt;/strong&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_kv_version_1"&gt;KV Version 1&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;KV v1 is the older version of the KV secrets engine. It does not support versioning and only stores the recently written value for a key. Requests to this engine will be more performant because the storage requests will be fewer and no locking is required. The storage as such will be much smaller, since it does not need to retain any history.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_kv_version_2"&gt;KV Version 2&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;KV v2 on the other hand supports versioning of secrets. When a version is deleted, the underlying data is not removed, instead you must destroy it intentionally. Storing the history of secrets will increase the required storage space. However, I would recommend using KV v2 in most cases as the benefit of versioning is too great to ignore.&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;
The following example will use KV v2.
&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="_kv_version_1_vs_version_2"&gt;KV Version 1 vs Version 2&lt;/h3&gt;
&lt;table class="tableblock frame-all grid-all stretch"&gt;
&lt;colgroup&gt;
&lt;col style="width: 20%;"/&gt;
&lt;col style="width: 40%;"/&gt;
&lt;col style="width: 40%;"/&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;Version 1&lt;/th&gt;
&lt;th class="tableblock halign-left valign-top"&gt;Version 2&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;Versioning&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 (up to 10 versions by default)&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;Check-and-Set&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 (prevents concurrent writes)&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;Soft delete&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 (recoverable)&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;Metadata&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 (custom metadata)&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;
KV v1 can be upgraded to KV v2 by using the following command as described in the documentation: &lt;a href="https://openbao.org/docs/secrets/kv/kv-v2/#upgrading-from-version-1" target="_blank" rel="noopener"&gt;Upgrading from version 1&lt;/a&gt;
&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="_enabling_kv_engine"&gt;Enabling KV Engine&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Let’s enable the KV v2 engine at the path &lt;strong&gt;secret/&lt;/strong&gt; and give it the description &amp;#34;Application secrets&amp;#34;.&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;
This path was used in &lt;a href="https://blog.stderr.at/openshift-platform/security/secrets-management/openbao/2026-03-09-openbao-part-7-authentication-methods/"&gt;Part 7 of this series&lt;/a&gt; already. So you might have to remove or rename the engine first if you have already enabled it.
&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;bao secrets enable \
-path=secret \ &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
-version=2 \ &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
-description=&amp;#34;Application secrets&amp;#34; \
kv &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;Path where the secrets will be stored&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;Version of the engine in case of KV&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;Name of the engine&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="_storing_secrets"&gt;Storing Secrets&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;With the enabled engine, we can now store secrets at the path:&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;# Put a secret (KV v2)
bao kv put secret/myapp/database \ &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
username=&amp;#34;dbadmin&amp;#34; \ &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
password=&amp;#34;supersecret123&amp;#34; \
host=&amp;#34;db.example.com&amp;#34; \
port=&amp;#34;5432&amp;#34;&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;Path where the secret will be stored&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;Secret data&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Which will output something like:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock nocopy"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;======= Secret Path =======
secret/data/myapp/database &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
======= Metadata =======
Key Value
--- -----
created_time 2026-03-13T05:56:52.670296216Z
custom_metadata &amp;lt;nil&amp;gt;
deletion_time n/a
destroyed false
version 1 &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&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;Path where the secret will be stored&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;Version of the secret&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="_reading_secrets"&gt;Reading Secrets&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To read the secret, we can use one of the following commands:&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;# Read a secret
bao kv get secret/myapp/database&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Which will output something like:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock nocopy"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;======= Secret Path =======
secret/data/myapp/database &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
[... more metadata ...]
====== Data ====== &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
Key Value
--- -----
host db.example.com
password supersecret123
port 5432
username dbadmin&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;Path where the secret will be stored&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;Secret data&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;As an alternative, we can use the following command to get the secret as JSON:&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;# Get as JSON
bao kv get -format=json secret/myapp/database&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;We can also read a specific existing version of the secret by using the following command:&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;# Read specific version
bao kv get -version=2 secret/myapp/database&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Or simply, a specific field:&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;# Get specific field
bao kv get -field=password secret/myapp/database&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;If the UI is enabled, the same can be achieved via the browser:&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/part8_secretsengine_kv.png" alt="OpenBao Secret Retrieval"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 1. OpenBao Secret Retrieval&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_managing_versions"&gt;Managing Versions&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;KV v2 keeps a version history. This is a big advantage over KV v1. To try version management, ensure the secret has at least two versions. Create a second version by writing to the same path again (e.g. with updated 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;bao kv put secret/myapp/database username=dbadmin password=newpassword456 port=5432 &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&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;Updating an existing secret with a new password, which will create a new version in KV v2.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Now you can list versions, soft-delete, undelete, or destroy a specific version:&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;
I will only paste the output of the first command. The rest will be similar …​ you’ll get the idea.
&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;# List all versions and metadata
bao kv metadata get secret/myapp/database&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Which will output something like:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock nocopy"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;====== Version 1 ====== &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
Key Value
--- -----
created_time 2026-03-13T05:56:52.670296216Z
deletion_time n/a
destroyed false
====== Version 2 ====== &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
Key Value
--- -----
created_time 2026-03-13T06:13:18.539285992Z
deletion_time n/a
destroyed false&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;Version 1&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;Version 2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Specific versions can now be (soft-) deleted, permanently destroyed etc.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock caution"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-caution" title="Caution"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
The last command of the list will completely delete all versions and metadata for a given path aka the secret is gone.
&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;# Delete specific version (soft delete)
bao kv delete -versions=2 secret/myapp/database
# Undelete a version
bao kv undelete -versions=2 secret/myapp/database
# Permanently destroy a version
bao kv destroy -versions=2 secret/myapp/database
# Delete all versions and metadata
bao kv metadata delete secret/myapp/database&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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;
When you define a secret you can set the maximum number of versions to keep using the flag &lt;strong&gt;-max-versions=5&lt;/strong&gt;. This is the number of versions that will be kept in the history.
&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="_custom_metadata"&gt;Custom Metadata&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;A secret’s key metadata can contain multiple custom metadata that can be used to describe the secret. The data will be stored as key-value pairs. Imagine them like labels in Kubernetes.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Let’s extend our secret with some custom metadata:&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;# Add custom metadata to a secret
bao kv metadata put \
-custom-metadata=&amp;#34;owner=team-a&amp;#34; \ &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
-custom-metadata=&amp;#34;environment=production&amp;#34; \ &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
secret/myapp/database&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;First custom metadata to set an owner label&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;Second custom metadata to set an environment label&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="_policies_for_kv_v2"&gt;Policies for KV v2&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;OpenBao policies control which authenticated clients may access which paths and with what actions. For the KV v2 secrets engine, any read or write via the API is checked against the token’s policies; without a policy granting the right capabilities, requests return 403. This section shows example policy rules you can attach to roles (e.g. Kubernetes auth roles) or tokens so that applications and users have the minimum access they need.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;KV v2 exposes two logical path trees under the mount path (here &lt;code&gt;secret/&lt;/code&gt;):&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;secret/data/&amp;lt;path&amp;gt;&lt;/code&gt;&lt;/strong&gt; — Secret values. Use capabilities such as &lt;code&gt;read&lt;/code&gt;, &lt;code&gt;create&lt;/code&gt;, &lt;code&gt;update&lt;/code&gt;, and &lt;code&gt;delete&lt;/code&gt; for reading and writing secret data.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;secret/metadata/&amp;lt;path&amp;gt;&lt;/code&gt;&lt;/strong&gt; — Version and metadata. Use &lt;code&gt;list&lt;/code&gt; to enumerate keys, and &lt;code&gt;read&lt;/code&gt;/&lt;code&gt;delete&lt;/code&gt; for version metadata and destroying versions.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The following example grants read-only access to all secrets under &lt;code&gt;myapp/&lt;/code&gt;, plus full read/write (including create, update, delete) for the single path &lt;code&gt;myapp/database&lt;/code&gt;. Replace &lt;code&gt;myapp&lt;/code&gt; with your application or team prefix as needed.&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-hcl hljs" data-lang="hcl"&gt;# Note: KV v2 uses /data/ for values and /metadata/ for metadata
# Read secret values under myapp/
path &amp;#34;secret/data/myapp/*&amp;#34; {
capabilities = [&amp;#34;read&amp;#34;]
}
# List secret keys under myapp/ (required for listing, e.g. bao kv list secret/myapp)
path &amp;#34;secret/metadata/myapp/*&amp;#34; {
capabilities = [&amp;#34;list&amp;#34;]
}
# Full access to a specific secret (create, read, update, delete)
path &amp;#34;secret/data/myapp/database&amp;#34; {
capabilities = [&amp;#34;create&amp;#34;, &amp;#34;read&amp;#34;, &amp;#34;update&amp;#34;, &amp;#34;delete&amp;#34;]
}
# Read and delete version metadata for this secret (e.g. destroy version)
path &amp;#34;secret/metadata/myapp/database&amp;#34; {
capabilities = [&amp;#34;read&amp;#34;, &amp;#34;list&amp;#34;, &amp;#34;delete&amp;#34;]
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;After writing the policy to OpenBao (e.g. &lt;code&gt;bao policy write myapp-kv policy.hcl&lt;/code&gt;), attach it to the appropriate auth method role or token so that clients receive a token with this policy. For token renewal, ensure the role or token also has &lt;code&gt;update&lt;/code&gt; on &lt;code&gt;auth/token/renew-self&lt;/code&gt; if you use renewable tokens.&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;
&lt;a href="https://blog.stderr.at/openshift-platform/security/secrets-management/openbao/2026-03-09-openbao-part-7-authentication-methods/"&gt;Part 7&lt;/a&gt; of this series (Authentication Methods) explains the authentication methods and policies in more detail.
&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="_conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This part focused on the &lt;strong&gt;KV&lt;/strong&gt; secrets engine: enabling a mount, storing and reading secrets, version lifecycle (list, soft delete, destroy), custom metadata, and &lt;strong&gt;policies&lt;/strong&gt; that distinguish KV v2’s &lt;code&gt;data/&lt;/code&gt; and &lt;code&gt;metadata/&lt;/code&gt; paths.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Key takeaways:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Prefer &lt;strong&gt;KV v2&lt;/strong&gt; when you need &lt;strong&gt;versioning&lt;/strong&gt;, &lt;strong&gt;check-and-set&lt;/strong&gt;, &lt;strong&gt;soft delete&lt;/strong&gt;, and richer &lt;strong&gt;metadata&lt;/strong&gt;; use KV v1 only when you explicitly want the simpler, lower-overhead path.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Grant &lt;strong&gt;&lt;code&gt;secret/data/…​&lt;/code&gt;&lt;/strong&gt; for secret values and &lt;strong&gt;&lt;code&gt;secret/metadata/…​&lt;/code&gt;&lt;/strong&gt; for listing and version operations—policies must match how the CLI and API resolve KV v2 paths.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Attach policies to &lt;strong&gt;tokens or auth roles&lt;/strong&gt; (for example Kubernetes auth) so applications receive &lt;strong&gt;least-privilege&lt;/strong&gt; access; include &lt;strong&gt;&lt;code&gt;auth/token/renew-self&lt;/code&gt;&lt;/strong&gt; if tokens should be renewable.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The &lt;strong&gt;PKI&lt;/strong&gt; secrets engine and integration with &lt;strong&gt;cert-manager&lt;/strong&gt; are covered in the next article in this series.&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/secrets/kv/kv-v2" target="_blank" rel="noopener"&gt;KV Secrets Engine v2&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item><item><title>The Guide to OpenBao - Secrets Engines PKI - Part 9</title><link>https://blog.stderr.at/openshift-platform/security/secrets-management/openbao/2026-03-26-openbao-part-9-secrets-engines-pki/</link><pubDate>Thu, 26 Mar 2026 00:00:00 +0000</pubDate><guid>https://blog.stderr.at/openshift-platform/security/secrets-management/openbao/2026-03-26-openbao-part-9-secrets-engines-pki/</guid><description>&lt;div class="paragraph"&gt;
&lt;p&gt;Secrets engines are one of the most important concepts in OpenBao. &lt;a href="https://blog.stderr.at/openshift-platform/security/secrets-management/openbao/2026-03-25-openbao-part-8-secrets-engines-kv/"&gt;Part 8&lt;/a&gt; covered the KV secrets engine; this article covers the &lt;strong&gt;PKI&lt;/strong&gt; secrets engine and its integration with &lt;strong&gt;cert-manager&lt;/strong&gt; on Kubernetes and OpenShift.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_quick_navigation"&gt;Quick Navigation&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Navigate directly to the main sections of this article:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#sect-pki"&gt;PKI secrets engine&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="#sect-cert-manager"&gt;Integration with cert-manager&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;hr/&gt;
&lt;/div&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;&lt;a href="https://blog.stderr.at/openshift-platform/security/secrets-management/openbao/2026-03-25-openbao-part-8-secrets-engines-kv/"&gt;Part 8&lt;/a&gt; of this series explained the basics of secrets engines and the KV secrets engine. This article focuses on the PKI secrets engine and on integrating OpenBao with &lt;strong&gt;cert-manager&lt;/strong&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;
As a reminder: to &lt;strong&gt;log in&lt;/strong&gt; to the OpenBao CLI, use the following (assuming HTTPS and a CA certificate available locally):
&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;export BAO_ADDR=&amp;#39;https://127.0.0.1:8200&amp;#39;
export BAO_CACERT=&amp;#34;$PWD/openbao-ca.crt&amp;#34;
bao login&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="sect-pki"&gt;PKI Secrets Engine&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The PKI engine dynamically generates X.509 certificates, acting as a &lt;strong&gt;Certificate Authority (CA)&lt;/strong&gt;. You can obtain a certificate without manually generating a private key and CSR on the client: OpenBao provisions the key material and signed certificate according to the role.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The time to live (TTL) of each generated certificate can be kept short to reduce reliance on revocation. It is possible to store the certificates in memory during the startup of an application and discard them when the application shuts down. This way, a certificate is only valid during the application runtime, is kept in memory and is never written to disk.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_setting_up_pki_secrets_engine_root_ca"&gt;Setting Up PKI Secrets Engine (Root CA)&lt;/h3&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;Before we can use the PKI secrets engine, we need to enable and configure it. To enable it, we can simply use the following command:&lt;/p&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;bao secrets enable -path=pki pki&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This will enable the PKI secrets engine at the path &lt;strong&gt;pki/&lt;/strong&gt;. If the path parameter is not provided, the engine will use the name of the engine as path.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The default value of the TTL is 30 days. To increase the maximum lease time for certificates issued under this mount, use &lt;strong&gt;&lt;code&gt;-max-lease-ttl=87600h&lt;/code&gt;&lt;/strong&gt; (or another duration).&lt;/p&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&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;em&gt;Note that individual roles can restrict this value to be shorter on a per-certificate basis. This just configures the global maximum for this secrets engine.&lt;/em&gt; Source: &lt;a href="https://openbao.org/docs/secrets/pki/setup/" target="_blank" rel="noopener"&gt;PKI secrets engine - setup and usage&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;Next we need to provide a CA certificate and a private key. OpenBao can either generate its own self-signed root certificate, or can use an existing one. Using an existing one is recommended for production environments. This is managed outside of OpenBao and OpenBao is provided with a signed intermediate CA.&lt;/p&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;We can simply generate a self-signed root certificate by using the following command:&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;bao write pki/root/generate/internal \
common_name=&amp;#34;Example Root CA&amp;#34; \ &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
issuer_name=&amp;#34;root-ca&amp;#34; \ &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
ttl=8760h \ &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
key_bits=4096 \ &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&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;Common name of the root certificate&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;Issuer name of the root certificate&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;Time to live of the root certificate, here 8760 hours (365 days)&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;Key bits of the root certificate, here 4096 bits (default is 2048 bits)
&lt;div class="paragraph"&gt;
&lt;p&gt;The response will look something like:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock nocopy"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;Key Value
--- -----
certificate -----BEGIN CERTIFICATE-----...
expiration 1536807433
issuing_ca -----BEGIN CERTIFICATE-----...
serial_number 7c:f1:fb:2c:6e:4d:99:0e:82:1b:08:0a:81:ed:61:3e:1d:fa:f5:29&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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;
The certificate key is stored in OpenBao. The returned certificates are purely informational.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set URL configuration&lt;/p&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The location of the Certificate Revocation List (CRL) and the issuing certificate are stored in the configuration of the PKI secrets engine and must be updated manually.&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;# Configure CA and CRL URLs
bao write pki/config/urls \
issuing_certificates=&amp;#34;https://openbao.example.com/v1/pki/ca&amp;#34; \
crl_distribution_points=&amp;#34;https://openbao.example.com/v1/pki/crl&amp;#34;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Configure a Role&lt;/p&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Now we need to define a role for the certificates. Roles define the allowed domains and the maximum time to live for the certificates. The following example creates a role for server certificates. It allows us to issue certificates for the domains &lt;code&gt;example.com&lt;/code&gt; and &lt;code&gt;internal.example.com&lt;/code&gt; with a maximum time to live of 720 hours (30 days) and a key size of 2048 bits.&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;bao write pki/roles/server-cert \
allowed_domains=&amp;#34;example.com,internal.example.com&amp;#34; \ &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
allow_subdomains=true \ &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
max_ttl=720h \
key_bits=2048 \ &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
key_type=rsa \ &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
require_cn=false \
allow_any_name=false&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;Allowed domains&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;Allow subdomains&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 size&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;Key type, typically rsa or ec (elliptic curve)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Issuing Certificates&lt;/p&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;OpenBao is now ready to issue certificates. We will use the &lt;code&gt;issue&lt;/code&gt; endpoint to request a new certificate.
The following example requests a new certificate for the domain &lt;code&gt;myapp.example.com&lt;/code&gt; and the subdomain &lt;code&gt;myapp.internal.example.com&lt;/code&gt; with a maximum time to live of 168 hours (7 days) and a key size of 2048 bits.&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;bao write pki/issue/server-cert \
common_name=&amp;#34;myapp.example.com&amp;#34; \
alt_names=&amp;#34;myapp.internal.example.com&amp;#34; \
ttl=168h \&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This will return the full certificate chain including the root CA certificate and the key.
For better readability, the output was shortened to show the certificate only:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock nocopy"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;Key Value
--- -----
certificate -----BEGIN CERTIFICATE-----
MIIE7zCCAtegAwIBAgIUH0eTVpXCP7iu0Y75dWAvjktbyxcwDQYJKoZIhvcNAQEL
BQAwGjEYMBYGA1UEAxMPRXhhbXBsZSBSb290IENBMB4XDTI2MDMyMjA1MjEyNVoX
DTI2MDMyOTA1MjE1NFowHDEaMBgGA1UEAxMRbXlhcHAuZXhhbXBsZS5jb20wggEi
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDliY0K3qb8TwNBXOHdfBygs5+Y
Pwh83vtS0Nph6ZsPdI9JTuvI6AbzEz6M283aY7OeJ3m3G2fRwt7ZdgsAcyt+jnL8
Izx/MS/4ztsdZJ/wxs3M42/iFehSofqihgorY1+SGaTdveCFg9H7Egjn1eJFnGB/
iqANzxg5NohE4gf8zpAKDjl4CIPDSmNJX2xQ+3wXJgx1eKeVOeB0jFnMSYskS0hD
qwM+QyYRKodc/akI6Xu7OqWQLMW7qmNS+6TBHsGOopve9EP6ym65Jss6de4X9zIP
BYeWQ0gvG18i22eJrkISRWlO2uw+Fbho0tBhqfRfjsVpxrUrTKwIgxg+F0+5AgMB
AAGjggEpMIIBJTAOBgNVHQ8BAf8EBAMCA6gwHQYDVR0lBBYwFAYIKwYBBQUHAwEG
CCsGAQUFBwMCMB0GA1UdDgQWBBRbFm9oqRl//tc+Z0ert4DKp9KjkDAfBgNVHSME
GDAWgBSL03nIIcEY+IAz/1g2q5NBbufx9DBBBggrBgEFBQcBAQQ1MDMwMQYIKwYB
BQUHMAKGJWh0dHBzOi8vb3BlbmJhby5leGFtcGxlLmNvbS92MS9wa2kvY2EwOAYD
VR0RBDEwL4IRbXlhcHAuZXhhbXBsZS5jb22CGm15YXBwLmludGVybmFsLmV4YW1w
bGUuY29tMDcGA1UdHwQwMC4wLKAqoCiGJmh0dHBzOi8vb3BlbmJhby5leGFtcGxl
LmNvbS92MS9wa2kvY3JsMA0GCSqGSIb3DQEBCwUAA4ICAQBUoLwWgpTaodRKpL3c
zp/yPgHNagZA8SQRLqKttywbHzfPNUSsC56XqfZnOHaq/gobNKrmirUAHPnXRUjO
Jd2+wXBO9aKqAhiy5is2ueNRo+C8IdPQpFNQ+5MW+G4FXJqSkvDUlT47mAoRkKUJ
nsjoV/xzHYKgw/YF7QZtmL8moM9W9Bhg3dB6KEaUvscpNn/9USfPquVLXOuQ/8Bs
6zQGijJUhb5gpRgGjmJhs0Oo/MkABxBUq2jFmvGWP0NklcJO7smtetx/RSrtyFZ0
X7zgMlD1yUy4DaHX13LhszAfercTFNodV/mAfEaJUrW+U9I97MzVg8QJvn9/AsaM
mWjfKHczhd9AzupOlogX8L3cdZxx9hkdD4NcR8tRb4QR5sOqzoBN7NfNpQ699b0F
kxESrHIysqBcx4dYjXLFsUqzRGm2sGZ4j1N8w1OZY/s0kfsG7RXpXJjwtQ2RwPo1
iSjbJ/VLjji2pz5BtVRtHiXnkoVtSD8QBpjCYqyHekXUArJCVkaDPzkB2MWNZXpk
2koi2fYa0U4x4ITqDsKeMpLvGAl2vqkp20lphXXqRwaAdNuEFd69YZ6qXzw/6GdI
TC/Ki7zApSvO0kJvMs9NILcHgDjpndCA/sMpG9iv4wFMpgYet9wc+dzPMXnHcj4o
PGbyb/WJomcxQMDuV1x1S1X2iA==
-----END CERTIFICATE-----
expiration 1774761714
issuing_ca -----BEGIN CERTIFICATE-----
MIIFJTCCAw2gAwIBAgIUJde0eccC1TpbufxVsUBhZhq9hWwwDQYJKoZIhvcNAQEL
BQAwGjEYMBYGA1UEAxMPRXhhbXBsZSBSb290IENBMB4XDTI2MDMyMjA0NDI1OVoX
DTI2MDQyMzA0NDMyOVowGjEYMBYGA1UEAxMPRXhhbXBsZSBSb290IENBMIICIjAN
BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA1XGanJKZZYySc9+WCXuGp9MLMo/O
MCRAR0u+BQbmxaSBcFLsxfNdkB8Jcunx7RnaGZIJwYXNIy+tNejqwQfiah7pTwJq
SYnXcYqZAETJUFvdYygjs4MCeaebvogmBQcZRzxJNNPzUblYpVOhmSGLvZZ7Vs5F
zY9wsKUEc3c3fMhwbxe/n/KQzvT/VzXSrLOw54LTsG7GNnUIhkuKwNqkhXuVItDy
ps+3z7RG/hOAWN9vktqLybi/E/uTmUqpsMrHSg7EMjqJ2jP8FMyH9EARLRL9gUG3
XFFNhFzjjDKjFrMsTUyVr+9tp4ucCZaakP9ZhjN9R/6FCXXR1K/XTVhTgYkawPHd
+PJaXn2T7NOie+tChiZx5Sii2XZtDm7WMFfwb4xhOBV67pWCSWSjkKl/xIDSrdRj
qLtzVfP/wiSL4sL9fy7Pya2OBtF2d/bdXXQ54JbbRlw3eRP+y+ej63UD+OP602EE
bbzrGB+B7ozwnfCmNNBnF23GgVYtJPWfDvgjXicprvbgjpmQAjeFohA8IIC322w5
sFsYuDL46KEs/KREoHjQNo1pxHKnmITU5GIMmWWoPyidTZuNNc4qWM6cH5RAbJup
IBaJWcWAr4eZ8p7pZd1dMkWEld4tieLdoJv2ENK77rDhwrq8kz66Xw9/xDJ/Nuio
sw5sd3py6Ayz+ekCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF
MAMBAf8wHQYDVR0OBBYEFIvTecghwRj4gDP/WDark0Fu5/H0MB8GA1UdIwQYMBaA
FIvTecghwRj4gDP/WDark0Fu5/H0MA0GCSqGSIb3DQEBCwUAA4ICAQAJuRiReD46
H85vhc1wzg7JUy6k+q5aUA9iKOupGyYBtkvzniQzhouZGEP47AcWkbPNGqo/3yl+
SCUItkBfPjunBmzcAhhZqyxJJ1q/SgY21DBf3W7WB0yzPx+saCwEi74rf44HbslY
Gp9W9pv0NFDT1+lQGerp978ErGQZh2M8rrGFwhcHmx71/umZ9ju6xyaGRaoHba+Y
8bxhR11j/Zx/UGRQBpjdAe2+VuAHDGpHjd9ieQ45YgoyJYkjBbecXJFttcVnShwu
7rxu8HNV3fMjoa4M6TDaY4z8KDgzHChzQSRB085TJIZzcG+/4zpJredzLe+jEq0P
RAuFEx4FjpRqYQu08HUmepO0Vd8a2ut7WyIBOC5b+pM+55LaGDfDF6rt8H7g6G84
w/RYFP/w/elxp3T1cFgDXsuZ60mfK6SX5ZbH3ZGPN0+xF/VahncUWKSfxSaSjzRI
Eml/Aac22kgtbbtxf8Bn4tgtY30sxqQRVnldHKi02FxS+DNg3SZB1hIpw6myyUo0
XH/ZtyumDLkcCvKzz/KvQGDOWDBmaPfPOPfMgiFXryB+YFMfy75Bhm0+aE+GM8fP
sextO8kawYI62aCHiOClkm2qIBiFtbsNG2HMTL4HKaNDOh9vxesPD5EteVzxTvg4
Oyk8YPB4kGtloH569v2Fj2U24D6KRyqxvQ==
-----END CERTIFICATE-----
not_before 1774156885&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;That is it. We have just created our first certificate.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_setting_up_an_intermediate_ca"&gt;Setting Up an Intermediate CA&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The test above demonstrated the usage of the PKI secrets engine to issue a certificate. However, the certificate was issued by the root CA. This is not practical or recommended for production environments. Instead, we should use an intermediate CA. The setup is very similar to the root CA, but with a few key differences: Instead of directly signing with the root CA, we will create a CSR (Certificate Signing Request) and sign it with the root CA. This can then be used to issue certificates with the intermediate CA.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;First we enable the PKI secrets engine for the intermediate CA and tune it a little bit.&lt;/p&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;# Enable PKI engine for intermediate CA
bao secrets enable -path=pki_int pki
# Configure maximum lease time
bao secrets tune -max-lease-ttl=43800h pki_int&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;We set the maximum TTL of the intermediate CA to 43800 hours (5 years). It should be equal to or less than the root CA.&lt;/p&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Generate a CSR (Certificate Signing Request)&lt;/p&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;# Generate intermediate CSR
bao write -format=json pki_int/intermediate/generate/internal \
common_name=&amp;#34;Example Intermediate CA&amp;#34; \
issuer_name=&amp;#34;intermediate-ca&amp;#34; \
key_bits=4096 \
| jq -r &amp;#39;.data.csr&amp;#39; &amp;gt; pki_int.csr&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This will create the CSR and store it at &lt;code&gt;pki_int.csr&lt;/code&gt;, which can be signed by the root CA and then used to issue certificates with the intermediate CA. As an example, the following CSR is generated:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock nocopy"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;-----BEGIN CERTIFICATE REQUEST-----
MIIEZzCCAk8CAQAwIjEgMB4GA1UEAxMXRXhhbXBsZSBJbnRlcm1lZGlhdGUgQ0Ew
ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC/dLEhNgiBeMiCIwrAq9S8
9m7SjqH59JaYPTBEpdAKCs1gsEBNROOaI+zftVaVJEGw/yi1gFRijTbjvBAbwAbc
YDV5VYLXqaoetkSE2hhIbsX92xwwch++J3BkGrgLt5y6uK0gLfODLp0hbxTdGhO0
RvIBKa2X1ctSLgEscgAr8PrlIfDlzahcxDgxpQQsN/1CASW2X5i/DEj160rbODzG
v10FEsVEcOLaJEee9qQ9yvwySiPLfH7KjEFV8qlQd/uY4gnGE8WW6nHhZEW+0Lsj
h1iO0Y4GA2Yw60FtTxsGAQlTCE3xCZTajInwc0XgDIYBtj/PEJhQUNU7mamchqng
jg+MJb9jzoqYmTw7dLvxTFTobu8s8y+7mjKQYevg7+FRNWQ467PmxGqaD++HQEw9
Ef1kaQ1Aw0L5ZuTZ2QaEzbJe2Rka5slHAja6rHxX2rNk6YkdbE2MfCUPqSVpqHBp
I+ptF8b73EgjgKINpWRXKvqH4QX2f6zUpVbr+5AikbS9TmPLQRqHHKtO0uDFEQzn
38w9PntU/N29BrYhzkeCzdGNFZTcL+Yijnp7fKrHl+iYasIUQD+MGWR3sUqaOODc
JmKvfl8H9UNulV4I7JXn0MBUUIT3uhTtsJ4Q62xr9pltZH5ijgR8Zqrl/q2kh+jf
pCoA7CpUKZXSiSou0zG3lwIDAQABoAAwDQYJKoZIhvcNAQELBQADggIBAGBAEsLK
QdeEejkFxP06xmEKypvGcy4GGw2g/ODp9lZQeNyaObwiABDjdJfj7CBgFGsPcMbq
DAFq/Kt/f+BmQwRulQf6ers9OIC8+4HAgHF9olgYJSn+lsrNXT9M4m02orIAodey
+xFxIcuUCHygQiXMIzb4QLzyvzW0/CqdsCysfbgHNN8P5deOR2/PseirYZKYFRQI
/ZxQrjB2Uj9gBND1Q5ew97Gm+mVvhqnb1T/Y1aIS+iEdRXADjxIvdweOPBXj3pDY
h8P+NgrP49pAKEvPuRFgfWMrkMrg4dQYPtVElcjN5XFMBXY2wZDketTu0rqroSlk
tcEhcwHdFFi+ILq6UHBDtMhZBkFtJG/82FgjcnR1G+1UOwKGSFOJ0HGiZpYGOXtc
hJM07eSZ0JzFCrg+2/geHM5c8X33yShe+4DRc3fi2KpsQbOAvvtH7Kb6AYwNu953
yBoOSpzI3Q9XjiFaAl1U8JDIr5DRfHGkUt1ozg4nf3ZHjPQC/NQNTq4XNEnjWgnJ
kiC+Ss4YZ6QnfvQU3aIwU97WKG6PIb5x9v+5mBwQe6AgDn2T1mmfyumsFfu/7aTB
87BUt3lWesvuW67JvE95LKs8NukNTW8FETR/x3Kn8TSYMulvXJsv7yb8g+Mm8k5w
f+LiteH/fEMVMYsoIdNL6MYJhiZNxStFDC2V
-----END CERTIFICATE REQUEST-----&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Sign the CSR with the root CA&lt;/p&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The CSR must now be signed using the root CA that was generated previously. Use:&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;bao write -format=json pki/root/sign-intermediate \
csr=@pki_int.csr \ &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
format=pem_bundle \
ttl=43800h \
| jq -r &amp;#39;.data.certificate&amp;#39; &amp;gt; cert_bundle.pem &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&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;The CSR file created in the previous step&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;The signed certificate is stored in the file &lt;code&gt;cert_bundle.pem&lt;/code&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This will sign the CSR with the root CA and store the signed certificate at &lt;code&gt;cert_bundle.pem&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Configure the set-signed certificate&lt;/p&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;We need to configure the intermediate CA with the signed certificate, so we can issue new certificates:&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;bao write pki_int/intermediate/set-signed \
certificate=@cert_bundle.pem&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Creating Certificate Roles&lt;/p&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Before we can issue certificates, we need to create roles. The following defines a role for server certificates and a role for client certificates, including allowed domains, maximum TTL, and key type.&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;# Create a role for issuing server certificates
bao write pki_int/roles/server-cert \
allowed_domains=&amp;#34;example.com,internal.example.com&amp;#34; \ &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
allow_subdomains=true \ &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
max_ttl=720h \
key_bits=2048 \ &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
key_type=rsa \
require_cn=false \
allow_any_name=false
# Create a role for client certificates
bao write pki_int/roles/client-cert \
allowed_domains=&amp;#34;example.com&amp;#34; \ &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
allow_subdomains=true \
max_ttl=168h \
key_usage=&amp;#34;DigitalSignature,KeyEncipherment&amp;#34; \
ext_key_usage=&amp;#34;ClientAuth&amp;#34;&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;The allowed domains, comma separated list&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;Allow subdomains, true or false&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;The key bits, 2048 or 4096
&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;
Check out the official documentation for more details of possible options: &lt;a href="https://openbao.org/docs/" target="_blank" rel="noopener"&gt;https://openbao.org/docs/&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Issuing Certificates&lt;/p&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Finally we can issue certificates. The example below issues a server certificate via the intermediate mount (and the article’s role setup supports a separate client role if you need mutual TLS).&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;# Issue a server certificate
bao write pki_int/issue/server-cert \
common_name=&amp;#34;myapp.example.com&amp;#34; \ &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
alt_names=&amp;#34;myapp.internal.example.com&amp;#34; \ &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
ttl=168h&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;The common name, the subdomain under example.com is allowed.&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;The alternative names
&lt;div class="paragraph"&gt;
&lt;p&gt;This will print a large amount of detail. Typical fields include:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;certificate&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;issuing_ca&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;private_key&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;serial_number&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The output contains the full chain (intermediate and root CA), the leaf certificate, and the private key, which you can use to configure TLS in an application.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;+
TIP: It can also be printed out in JSON format which is easier to process later.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;+
The following example creates the JSON file with all the components.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&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;bao write -format=json pki_int/issue/server-cert \
common_name=&amp;#34;api.example.com&amp;#34; \
ttl=168h &amp;gt; api-cert.json&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;+
This can then be extracted to the individual components using the &lt;code&gt;jq&lt;/code&gt; command.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&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;cat /tmp/api-cert.json | jq -r &amp;#39;.data.certificate&amp;#39;
cat /tmp/api-cert.json | jq -r &amp;#39;.data.private_key&amp;#39;
cat /tmp/api-cert.json | jq -r &amp;#39;.data.ca_chain[]&amp;#39;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="pki-policies"&gt;PKI Policies&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Every OpenBao request is checked against the token’s policies; without an explicit allow, paths are &lt;strong&gt;denied&lt;/strong&gt;.
PKI is split into several API paths (&lt;code&gt;issue&lt;/code&gt;, &lt;code&gt;sign&lt;/code&gt;, &lt;code&gt;ca&lt;/code&gt;, &lt;code&gt;crl&lt;/code&gt;, …), so you grant only what each client needs.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;For example: operators using &lt;code&gt;pki_int/issue/…​&lt;/code&gt; need the &lt;strong&gt;issue&lt;/strong&gt; path; cert-manager sending a CSR needs the &lt;strong&gt;sign&lt;/strong&gt; path; workloads or validators that fetch trust material need &lt;strong&gt;read&lt;/strong&gt; on the CA (and often the CRL) paths etc.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The example below is a single policy you can attach to a role or token; split into narrower policies in production if different actors should not share the same privileges.&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;set -e
cat &amp;lt;&amp;lt;&amp;#39;EOF&amp;#39; | bao policy write pki-int-workload -
# Allow issuing certificates with the server-cert role (CLI / API issue endpoint)
path &amp;#34;pki_int/issue/server-cert&amp;#34; {
capabilities = [&amp;#34;create&amp;#34;, &amp;#34;update&amp;#34;]
}
# Allow cert-manager (Vault issuer) to sign CSRs via the sign endpoint for the same role
path &amp;#34;pki_int/sign/server-cert&amp;#34; {
capabilities = [&amp;#34;create&amp;#34;, &amp;#34;update&amp;#34;]
}
# Allow reading CA certificate
path &amp;#34;pki_int/ca/pem&amp;#34; {
capabilities = [&amp;#34;read&amp;#34;]
}
# Allow reading CRL
path &amp;#34;pki_int/crl/pem&amp;#34; {
capabilities = [&amp;#34;read&amp;#34;]
}
EOF
bao policy read pki-int-workload&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="sect-cert-manager"&gt;Integration with cert-manager&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;a href="https://cert-manager.io/" target="_blank" rel="noopener"&gt;cert-manager&lt;/a&gt; requests TLS certificates inside Kubernetes and stores them in Secrets.
It includes a &lt;strong&gt;Vault&lt;/strong&gt; issuer that talks to HashiCorp Vault’s HTTP API; OpenBao exposes the same API for PKI and auth, so you can point that issuer at OpenBao instead of Vault.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;cert-manager generates a private key and a CSR in the cluster, then calls the PKI &lt;strong&gt;sign&lt;/strong&gt; endpoint for your role (not the &lt;strong&gt;issue&lt;/strong&gt; endpoint you used from the CLI earlier).
The path in the issuer must therefore follow &lt;code&gt;mount/sign/role-name&lt;/code&gt;, for example &lt;code&gt;pki_int/sign/server-cert&lt;/code&gt;, matching the intermediate mount and role from this article.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Before the &lt;code&gt;ClusterIssuer&lt;/code&gt; can log in to OpenBao, you need &lt;strong&gt;Kubernetes auth&lt;/strong&gt; enabled, a &lt;strong&gt;policy&lt;/strong&gt; that allows signing on &lt;code&gt;pki_int&lt;/code&gt; (see &lt;a href="#pki-policies"&gt;PKI policies&lt;/a&gt;), and a &lt;strong&gt;role&lt;/strong&gt; that binds the cert-manager controller’s service account to that policy.
&lt;a href="https://blog.stderr.at/openshift-platform/security/secrets-management/openbao/2026-03-09-openbao-part-7-authentication-methods/"&gt;Part 7 of this series&lt;/a&gt; walks through Kubernetes authentication in detail; the commands below are a minimal summary for cert-manager.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_enable_the_kubernetes_auth_method"&gt;Enable the Kubernetes auth method&lt;/h3&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;
Skip this step if &lt;code&gt;kubernetes&lt;/code&gt; is already listed under &lt;code&gt;bao auth list&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;bao auth enable --description=&amp;#34;Kubernetes/OpenShift service account auth&amp;#34; kubernetes&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_configure_the_kubernetes_auth_method"&gt;Configure the Kubernetes auth method&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;OpenBao’s server validates incoming service-account JWTs by calling the Kubernetes TokenReview API.
If OpenBao runs &lt;strong&gt;inside&lt;/strong&gt; the cluster (the setup assumed here), configure &lt;code&gt;auth/kubernetes&lt;/code&gt; with:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;kubernetes_host&lt;/code&gt;&lt;/strong&gt; — API URL reachable from the OpenBao pods (typically &lt;code&gt;&lt;a href="https://kubernetes.default.svc:443" class="bare"&gt;https://kubernetes.default.svc:443&lt;/a&gt;&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;kubernetes_ca_cert&lt;/code&gt;&lt;/strong&gt; — PEM of the cluster CA that signs the API server certificate (same material as in the service-account token Secret).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;token_reviewer_jwt&lt;/code&gt;&lt;/strong&gt; — long-lived JWT for a dedicated service account that may create TokenReview objects (via &lt;code&gt;system:auth-delegator&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;If OpenBao runs &lt;strong&gt;outside&lt;/strong&gt; the cluster, use your public API URL for &lt;code&gt;kubernetes_host&lt;/code&gt; (for example &lt;code&gt;$(oc whoami --show-server)&lt;/code&gt;), the same CA and reviewer JWT extracted from the cluster, and ensure the OpenBao process can reach that URL on the network.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect3"&gt;
&lt;h4 id="_in_cluster_example_reviewer_service_account_and_secret"&gt;In-cluster example: reviewer service account and Secret&lt;/h4&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Create a service account in the OpenBao namespace (here openbao), bind &lt;strong&gt;system:auth-delegator&lt;/strong&gt;, and request a &lt;strong&gt;long-lived token&lt;/strong&gt; Secret (required from Kubernetes 1.24 onward unless you use another token source).&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: ServiceAccount
metadata:
name: openbao-kube-auth
namespace: openbao &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: openbao-kube-auth-delegator
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:auth-delegator &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
subjects:
- kind: ServiceAccount
name: openbao-kube-auth
namespace: openbao
---
apiVersion: v1
kind: Secret
metadata:
name: openbao-kube-auth-token
namespace: openbao
annotations:
kubernetes.io/service-account.name: openbao-kube-auth
type: kubernetes.io/service-account-token &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;The namespace of the OpenBao service account&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;The name of the ClusterRole that binds the service account to the system:auth-delegator role&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;The type of the Secret, which is a long-lived token Secret&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Save the YAML manifest to a file (for example openbao-kube-auth.yaml), apply it, then wait until the Secret contains a token (the control plane fills &lt;code&gt;data.token&lt;/code&gt;—often within a few seconds).&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 apply -n openbao -f openbao-kube-auth.yaml
until oc get secret openbao-kube-auth-token -n openbao -o jsonpath=&amp;#39;{.data.token}&amp;#39; | grep -q .
do
sleep 2
done&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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;
You can manage the reviewer service account, &lt;code&gt;ClusterRoleBinding&lt;/code&gt;, and token Secret through GitOps so the configuration stays reproducible.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect3"&gt;
&lt;h4 id="_configure_authkubernetesconfig"&gt;Configure auth/kubernetes/config&lt;/h4&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Dump the reviewer JWT and CA to files and configure the Kubernetes auth backend.
Use the &lt;strong&gt;in-cluster&lt;/strong&gt; API URL so OpenBao pods reach the Kubernetes API without relying on the external load balancer.&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;TMP=$(mktemp -d) &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
trap &amp;#39;rm -rf &amp;#34;$TMP&amp;#34;&amp;#39; EXIT
oc get secret openbao-kube-auth-token -n openbao -o jsonpath=&amp;#39;{.data.token}&amp;#39; | base64 -d &amp;gt; &amp;#34;$TMP/reviewer.jwt&amp;#34; &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
oc get secret openbao-kube-auth-token -n openbao -o jsonpath=&amp;#39;{.data.ca\.crt}&amp;#39; | base64 -d &amp;gt; &amp;#34;$TMP/k8s-ca.crt&amp;#34; &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
bao write auth/kubernetes/config \ &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
kubernetes_host=&amp;#34;https://kubernetes.default.svc:443&amp;#34; \
kubernetes_ca_cert=@&amp;#34;$TMP/k8s-ca.crt&amp;#34; \
token_reviewer_jwt=@&amp;#34;$TMP/reviewer.jwt&amp;#34;&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;Create a temporary directory to store the reviewer JWT and CA and remove it after the script exits&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;Get the reviewer JWT from the Secret&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;Get the CA from the Secret&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;Write the configuration to the auth/kubernetes/config path using the internal API URL of the Kubernetes cluster.&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;
On plain Kubernetes, replace &lt;code&gt;oc&lt;/code&gt; with &lt;code&gt;kubectl&lt;/code&gt; and the same Secret paths.
&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;
If logins fail after cluster upgrades or with projected service-account tokens, check whether you must set &lt;code&gt;issuer&lt;/code&gt; or related options; see &lt;a href="https://openbao.org/docs/auth/kubernetes/" target="_blank" rel="noopener"&gt;OpenBao Kubernetes auth&lt;/a&gt; and &lt;a href="https://blog.stderr.at/openshift-platform/security/secrets-management/openbao/2026-03-09-openbao-part-7-authentication-methods/"&gt;Part 7&lt;/a&gt;.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To inspect the stored Kubernetes auth backend configuration:&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;bao read auth/kubernetes/config&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;You should see &lt;code&gt;kubernetes_host&lt;/code&gt; and &lt;code&gt;kubernetes_ca_cert&lt;/code&gt;; &lt;code&gt;token_reviewer_jwt&lt;/code&gt; is often omitted or redacted on read.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_policy_and_role_for_cert_manager"&gt;Policy and role for cert-manager&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Attach a policy (here &lt;code&gt;cert-manager-pki&lt;/code&gt;) that matches the PKI paths cert-manager uses—aligned with &lt;a href="#pki-policies"&gt;PKI policies&lt;/a&gt;—then map the controller’s service account to that policy.&lt;/p&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;
Adjust &lt;code&gt;bound_service_account_names&lt;/code&gt; and &lt;code&gt;bound_service_account_namespaces&lt;/code&gt; if your cert-manager installation does not use the &lt;code&gt;cert-manager&lt;/code&gt; service account in the &lt;code&gt;cert-manager&lt;/code&gt; namespace.
&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;bao policy write cert-manager-pki - &amp;lt;&amp;lt;&amp;#39;EOF&amp;#39;
path &amp;#34;pki_int/sign/server-cert&amp;#34; {
capabilities = [&amp;#34;create&amp;#34;, &amp;#34;update&amp;#34;]
}
path &amp;#34;pki_int/ca/pem&amp;#34; {
capabilities = [&amp;#34;read&amp;#34;]
}
path &amp;#34;pki_int/crl/pem&amp;#34; {
capabilities = [&amp;#34;read&amp;#34;]
}
path &amp;#34;auth/token/renew-self&amp;#34; {
capabilities = [&amp;#34;update&amp;#34;]
}
EOF
bao write auth/kubernetes/role/cert-manager \
bound_service_account_names=cert-manager \
bound_service_account_namespaces=cert-manager \
policies=cert-manager-pki \
ttl=1h \
max_ttl=24h&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect3"&gt;
&lt;h4 id="_verify_with_a_kubernetes_login_optional"&gt;Verify with a Kubernetes login (optional)&lt;/h4&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;After the cert-manager auth role exists, prove that OpenBao accepts the same service-account identity cert-manager will use.
Request a short-lived projected token for that service account, then log in to the &lt;strong&gt;Kubernetes&lt;/strong&gt; auth method:&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;JWT=$(oc create token cert-manager -n cert-manager --duration=15m)
bao write -format=json auth/kubernetes/login role=cert-manager jwt=&amp;#34;$JWT&amp;#34; \
| jq -r &amp;#39;.auth.client_token&amp;#39;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;A printed &lt;strong&gt;client token&lt;/strong&gt; means TokenReview and role binding succeeded.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_configure_the_clusterissuer_for_cert_manager"&gt;Configure the (Cluster)Issuer for cert-manager&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To integrate cert-manager with OpenBao we need to configure either a ClusterIssuer (cluster-wide) or an Issuer (namespace-scoped). The setup is very similar.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The ClusterIssuer manifest below must use the same OpenBao role name in &lt;code&gt;spec.vault.auth.kubernetes.role&lt;/code&gt; (here &lt;code&gt;cert-manager&lt;/code&gt;).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The server URL in the issuer must be reachable &lt;strong&gt;from the cert-manager pods&lt;/strong&gt; (in-cluster Service DNS is typical), for example &lt;a href="https://openbao.openbao.svc:8200" class="bare"&gt;https://openbao.openbao.svc:8200&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The following is an example of a ClusterIssuer manifest.&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: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: openbao-cluster-issuer
spec:
vault:
path: pki_int/sign/server-cert &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
server: https://openbao.openbao.svc:8200 &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
caBundle: LS0tLS1CRUdJTi... &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
auth:
kubernetes:
role: cert-manager
mountPath: /v1/auth/kubernetes &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
serviceAccountRef:
name: cert-manager &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;PKI sign path for the OpenBao role (mount/sign/role-name).&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;OpenBao Service URL as seen from cert-manager pods.&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;&lt;strong&gt;Required&lt;/strong&gt; for private / self-signed OpenBao TLS: single-line base64 of the PEM CA (see the command block below). Omit only if the server uses a public CA trusted by default in the cert-manager image.&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 mount path in OpenBao (default is &lt;code&gt;/v1/auth/kubernetes&lt;/code&gt;).&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;Name of the cert-manager controller service account only. Do &lt;strong&gt;not&lt;/strong&gt; add &lt;code&gt;namespace&lt;/code&gt; under &lt;code&gt;serviceAccountRef&lt;/code&gt;; it is not part of the cert-manager API.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;If OpenBao’s HTTPS certificate is not signed by a CA already trusted inside the cert-manager pods, you must supply trust material; otherwise TLS verification fails with &lt;code&gt;x509: certificate signed by unknown authority&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Use &lt;strong&gt;&lt;code&gt;caBundle&lt;/code&gt;&lt;/strong&gt; (base64-encoded PEM of the CA that signed OpenBao’s server certificate) or &lt;strong&gt;&lt;code&gt;caBundleSecretRef&lt;/code&gt;&lt;/strong&gt; pointing to a Secret in the &lt;code&gt;cert-manager&lt;/code&gt; namespace.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Fetch the certificate OpenBao is using:&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 secret openbao-server-tls -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="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;
This secret was created in a previous article, when we were setting up the OpenBao server.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The returned string must be added to the &lt;strong&gt;ClusterIssuer&lt;/strong&gt; manifest as the value of the &lt;code&gt;caBundle&lt;/code&gt; field.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Alternatively, copy the CA PEM into a Secret in the &lt;strong&gt;cert-manager&lt;/strong&gt; namespace and reference it (no giant line in the issuer):&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;# spec.vault fragment — use instead of caBundle if you prefer
caBundleSecretRef:
name: openbao-ca
key: ca.crt&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;However, this requires that the Secret exists in the appropriate namespace. For ClusterIssuer, the Secret must be in the &lt;code&gt;cert-manager&lt;/code&gt; namespace.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Once the ClusterIssuer is created and is ready, we can issue a certificate.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_issue_a_certificate"&gt;Issue a Certificate&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;With the ClusterIssuer in place, create a Certificate in the workload namespace (here myapp, this namespace must exist).
cert-manager will create a TLS Secret (myapp-tls below) containing &lt;code&gt;tls.crt&lt;/code&gt; and &lt;code&gt;tls.key&lt;/code&gt;, and renew the cert before it expires according to &lt;code&gt;renewBefore&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: cert-manager.io/v1
kind: Certificate
metadata:
name: myapp-tls
namespace: myapp &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
spec:
secretName: myapp-tls &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
issuerRef: &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
name: openbao-cluster-issuer
kind: ClusterIssuer
commonName: myapp.example.com &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
dnsNames:
- myapp.example.com
duration: 168h &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
renewBefore: 48h &lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;(6)&lt;/b&gt;
privateKey:
rotationPolicy: Always &lt;i class="conum" data-value="7"&gt;&lt;/i&gt;&lt;b&gt;(7)&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;The namespace of the workload&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;The name of the TLS Secret&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;The issuer of the certificate&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;The common name of the certificate&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;The duration of the certificate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;6&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The duration before the certificate is renewed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="7"&gt;&lt;/i&gt;&lt;b&gt;7&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Explicit rotation policy: &lt;code&gt;Always&lt;/code&gt; matches cert-manager ≥ v1.18 default (new key each renewal). Set &lt;code&gt;Never&lt;/code&gt; only if you rely on a stable key across renewals.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="admonitionblock caution"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-caution" title="Caution"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
The values you set in &lt;code&gt;commonName&lt;/code&gt; and &lt;code&gt;dnsNames&lt;/code&gt; must be &lt;strong&gt;allowed by the OpenBao PKI role&lt;/strong&gt; (&lt;code&gt;allowed_domains&lt;/code&gt;, &lt;code&gt;allow_subdomains&lt;/code&gt;, &lt;code&gt;allow_bare_domains&lt;/code&gt;, and related settings).
&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;
If the Certificate never reaches &lt;code&gt;Ready=True&lt;/code&gt;, check &lt;code&gt;kubectl describe certificate&lt;/code&gt; / &lt;code&gt;kubectl describe certificaterequest&lt;/code&gt; (or the same with &lt;code&gt;oc describe&lt;/code&gt; on OpenShift) in that namespace, the cert-manager controller logs, and the OpenBao audit log for &lt;code&gt;403&lt;/code&gt; policy denials or CSR rejection messages from the PKI engine.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;When everything is working, a Secret named &lt;code&gt;myapp-tls&lt;/code&gt; is created in the &lt;code&gt;myapp&lt;/code&gt; namespace containing &lt;code&gt;tls.crt&lt;/code&gt; and &lt;code&gt;tls.key&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock nocopy"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;oc describe secret/myapp-tls -n myapp
[source,bash]
----
Name: myapp-tls
Namespace: myapp
[...output omitted...]
Type: kubernetes.io/tls
Data
====
tls.key: 1679 bytes
ca.crt: 1846 bytes
tls.crt: 3631 bytes&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="_conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This part showed how to turn OpenBao into a &lt;strong&gt;usable CA&lt;/strong&gt; for cluster workloads and how to &lt;strong&gt;automate TLS&lt;/strong&gt; with cert-manager.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;You should now be able to:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Enable the PKI engine, set CA and CRL URLs, define roles, and issue certificates via the &lt;code&gt;issue&lt;/code&gt; API (CLI or automation).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Stand up an intermediate CA signed by the root, and keep day-to-day issuance on the intermediate mount (&lt;code&gt;pki_int&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Express least-privilege with policies that separate &lt;code&gt;issue&lt;/code&gt; from &lt;code&gt;sign&lt;/code&gt;, and allow CA/CRL reads where validators or clients need them.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Wire cert-manager to OpenBao’s Vault-compatible API: Kubernetes auth in-cluster (reviewer JWT and CA), a ClusterIssuer with &lt;code&gt;caBundle&lt;/code&gt; (or &lt;code&gt;caBundleSecretRef&lt;/code&gt;) for private OpenBao TLS, &lt;code&gt;serviceAccountRef.name&lt;/code&gt; only (no &lt;code&gt;namespace&lt;/code&gt; field), and a Certificate with an explicit &lt;code&gt;privateKey.rotationPolicy&lt;/code&gt; for cert-manager ≥ v1.18.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&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/secrets/pki" target="_blank" rel="noopener"&gt;PKI Secrets Engine&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item><item><title>The Guide to OpenBao - Authentication Methods - Part 7</title><link>https://blog.stderr.at/openshift-platform/security/secrets-management/openbao/2026-03-09-openbao-part-7-authentication-methods/</link><pubDate>Mon, 09 Mar 2026 00:00:00 +0000</pubDate><guid>https://blog.stderr.at/openshift-platform/security/secrets-management/openbao/2026-03-09-openbao-part-7-authentication-methods/</guid><description>&lt;div class="paragraph"&gt;
&lt;p&gt;With OpenBao deployed and running, the next critical step is configuring authentication. Ultimately, you want to limit access to only authorised people. This article covers two common authentication methods: Kubernetes for pods and LDAP for enterprise directories (in a simplified example). There are many more methods, but we cannot cover them all in this article.&lt;/p&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;Authentication in OpenBao verifies the identity of clients before granting access to secrets. OpenBao supports multiple authentication methods, each suited for different use cases. Among others it supports:&lt;/p&gt;
&lt;/div&gt;
&lt;table class="tableblock frame-all grid-all stretch"&gt;
&lt;colgroup&gt;
&lt;col style="width: 20%;"/&gt;
&lt;col style="width: 40%;"/&gt;
&lt;col style="width: 40%;"/&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;Best For&lt;/th&gt;
&lt;th class="tableblock halign-left valign-top"&gt;Key Features&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;Kubernetes&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Pods running in K8s/OpenShift&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Service account tokens, automatic&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;OIDC&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Human users, SSO&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;OpenShift OAuth, Keycloak, Azure AD&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;LDAP&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Enterprise directories&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Active Directory, OpenLDAP&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;AppRole&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;CI/CD, automation&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Role ID + Secret ID&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;Token&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Direct access, bootstrap&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Simple but less secure&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;
The full documentation of the authentication methods and the list of available methods (for example Radius) can be found here: &lt;a href="https://openbao.org/docs/auth/" target="_blank" rel="noopener"&gt;OpenBao Authentication Methods&lt;/a&gt;.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_authentication_workflow"&gt;Authentication Workflow&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The authentication workflow in OpenBao from a user perspective is as follows:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;A client wants to authenticate to OpenBao and provides credentials.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;OpenBao validates the credentials against the authentication method. For example: LDAP&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If the authentication method (e.g. LDAP) is successful, it will return the required information about the client.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;OpenBao maps this result to policies that are mapped to the authentication method.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;OpenBao generates a token that is associated with the policies and returns it to the client.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The client can then use this token for further operations.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_prerequisites_for_authentication_methods"&gt;Prerequisites for Authentication Methods&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Before configuring authentication:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;OpenBao is deployed and unsealed (Parts 2-6)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You have admin access to OpenBao (root token)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Access to the required authentication backend:&lt;/p&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;For Kubernetes auth: Access to the OpenShift cluster&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;For LDAP: Access to the LDAP server&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;etc.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Set up your environment:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;You can set up your environment by using either the &lt;strong&gt;CLI&lt;/strong&gt; or the &lt;strong&gt;UI&lt;/strong&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_using_the_cli"&gt;Using the CLI&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;You can use the CLI to interact with OpenBao. In this example we will forward the port locally to the OpenBao server and set the environment variable &lt;code&gt;BAO_ADDR&lt;/code&gt; to the local address. With &lt;strong&gt;boa login&lt;/strong&gt; and the root token you can authenticate against OpenBao with full access.&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;# Port forward (if needed)
oc port-forward svc/openbao 8200:8200 -n openbao &amp;amp;
# Set environment
export BAO_ADDR=&amp;#39;http://127.0.0.1:8200&amp;#39;
# You may need the SSL CA too.
# export BAO_CACERT=&amp;#34;$PWD/openbao-ca.crt&amp;#34;
# Login with root token
bao login
# List all authentication methods
bao auth list&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The last command returns a list of the available authentication methods. Currently there is only one authentication method enabled: &lt;strong&gt;token&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;Path Type Accessor Description Version
---- ---- -------- ----------- -------
token/ token auth_token_1020fa7b token based credentials n/a&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_ui"&gt;Using the UI&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The UI, if enabled, is accessible via the OpenShift Route (if running on OpenShift and if it was created in &lt;a href="https://blog.stderr.at/openshift-platform/security/secrets-management/openbao/2026-02-13-openbao-part-3-openshift-deployment/"&gt;Part 3&lt;/a&gt;). The password/token is the &lt;strong&gt;root token&lt;/strong&gt; from the initialisation.&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/part7_openbao_ui_login_form.png" alt="OpenBao UI Login Form"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 1. OpenBao UI Login Form&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The UI has the advantage that it offers more visibility into the configuration and possibilities.
For example, at the beginning there is one authentication method &lt;strong&gt;token&lt;/strong&gt; enabled. This is required for the root token authentication.&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/part7_openbao_ui_authentication_methods.png?width=840px" alt="OpenBao UI Authentication Methods"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 2. OpenBao UI Authentication Methods&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;We can enable the other authentication methods by clicking on the &lt;strong&gt;Enable new method&lt;/strong&gt; link to see what options are available.&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/part7_openbao_ui_configuring_new_authentication_methods.png" alt="OpenBao UI Configuring New Authentication Method"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 3. OpenBao UI Configuring New Authentication Method&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_understanding_policies"&gt;Understanding Policies&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Before you can actually use an authentication method, it is important to understand what &lt;strong&gt;policies&lt;/strong&gt; do. A policy is a way to declaratively define what (which paths) authenticated users can access or not. All paths are &lt;strong&gt;denied by default&lt;/strong&gt;. A policy is mapped to an authentication method. For example, when we look at LDAP, we could configure something like this:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;em&gt;Every member of group &amp;#34;dev&amp;#34; is mapped to a policy named &amp;#34;dev-policy&amp;#34;. The policy then allows the user to read the secrets under the path &amp;#34;secret/data/dev/*&amp;#34;.&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_basic_policy_structure"&gt;Basic Policy Structure&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;A policy is a set of &lt;strong&gt;path rules&lt;/strong&gt;. Each rule says: “for this path (or path prefix), the bearer of this policy may use these &lt;strong&gt;capabilities&lt;/strong&gt;.” Paths are tied to OpenBao’s internal API: for example, secrets in the KV v2 engine live under &lt;code&gt;secret/data/&amp;lt;mount-path&amp;gt;/&amp;lt;key&amp;gt;&lt;/code&gt;, so a path like &lt;code&gt;secret/data/myapp/*&lt;/code&gt; means “any key under the &lt;code&gt;myapp&lt;/code&gt; prefix in the KV v2 store mounted at &lt;code&gt;secret/&lt;/code&gt;.”&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The example below does two things that are typical for an application policy:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Secrets access:&lt;/strong&gt; It allows &lt;strong&gt;read&lt;/strong&gt; and &lt;strong&gt;list&lt;/strong&gt; on &lt;code&gt;secret/data/myapp/*&lt;/code&gt;.&lt;/p&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;read&lt;/strong&gt; lets the client fetch the value of a secret at a path (e.g. &lt;code&gt;secret/data/myapp/database&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;list&lt;/strong&gt; is required to list keys under a path (e.g. to discover &lt;code&gt;myapp/database&lt;/code&gt;, &lt;code&gt;myapp/api-key&lt;/code&gt;); without it, the client would need to know every path in advance.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Token renewal:&lt;/strong&gt; It allows &lt;strong&gt;update&lt;/strong&gt; on &lt;code&gt;auth/token/renew-self&lt;/code&gt;. Tokens often have a limited lifetime; this path lets the holder extend their own token without needing the root token or extra permissions. Including it avoids applications losing access when the token expires.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&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;
Policies can be written in JSON or HCL.
&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-hcl hljs" data-lang="hcl"&gt;# Example policy: myapp-read-policy.hcl
# Allow reading secrets from specific path (KV v2 engine at mount &amp;#34;secret/&amp;#34;)
path &amp;#34;secret/data/myapp/*&amp;#34; {
capabilities = [&amp;#34;read&amp;#34;, &amp;#34;list&amp;#34;]
}
# Allow renewing own token so the app can extend its token before expiry
path &amp;#34;auth/token/renew-self&amp;#34; {
capabilities = [&amp;#34;update&amp;#34;]
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The path &lt;code&gt;secret/data/myapp/*&lt;/code&gt; assumes you have a KV v2 secrets engine mounted at &lt;code&gt;secret/&lt;/code&gt; and will store application secrets under keys like &lt;code&gt;myapp/database&lt;/code&gt;, &lt;code&gt;myapp/api-key&lt;/code&gt;, etc. Adjust the mount path and prefix to match your setup.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock caution"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-caution" title="Caution"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
The use of globs (*) may result in surprising or unexpected behaviour. Use them with caution.
&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="_policy_capabilities"&gt;Policy Capabilities&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Each path defined in a policy must have at least one capability. This provides control over the allowed or denied operations a user may perform. There are several capabilities available:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;create&lt;/code&gt; - Create new data at the given path&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;read&lt;/code&gt; - Read data&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;update&lt;/code&gt; - Update existing data&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;patch&lt;/code&gt; - Patch existing data (Partial update)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;delete&lt;/code&gt; - Delete data&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;list&lt;/code&gt; - List keys at a path&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;scan&lt;/code&gt; - Scan or browse the path for keys&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;sudo&lt;/code&gt; - Allows access to paths that are &lt;strong&gt;root-protected&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;deny&lt;/code&gt; - Explicitly deny (overrides other grants)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_default_policies"&gt;Default Policies&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;When OpenBao is deployed the first time, there are no additional authentication methods enabled, except for &lt;strong&gt;token&lt;/strong&gt;. This is required for the root access itself. To allow the administrator to access using the root token, there is one default policy called &lt;strong&gt;root&lt;/strong&gt;. This policy is somewhat of a catch-all policy; it allows the administrator to access everything.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;In addition to the root policy, there is one other default policy called &lt;strong&gt;default&lt;/strong&gt;. This policy is used for all authenticated users. It allows them, for example, to look up their own properties and to renew or revoke their own token.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Let’s look at the default policies in the CLI:&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;
A quick reminder of how to log in to OpenBao using the CLI when running it against Kubernetes: first open a port-forward, set the variable &lt;code&gt;BAO_ADDR&lt;/code&gt; to the local address; if you use HTTPS you may need to set the SSL CA certificate, then log in with the root token.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;You need to have the CA certificate in the current directory, which you might fetch with:&lt;/p&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 secret openbao-ca-secret -n openbao -o jsonpath=&amp;#39;{.data.ca\.crt}&amp;#39; | base64 -d &amp;gt; openbao-ca.crt&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Port forward, set the environment variables and login with the root token:&lt;/p&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;# Port forward (if needed)
oc port-forward svc/openbao 8200:8200 -n openbao &amp;amp;
# Set environment
export BAO_ADDR=&amp;#39;https://127.0.0.1:8200&amp;#39;
# You may need the SSL CA too.
export BAO_CACERT=&amp;#34;$PWD/openbao-ca.crt&amp;#34;
# Login with root token
bao login&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Now to list and fetch the current policies you can use the following commands:&lt;/p&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;# List policies
bao policy list
# Returns something like this:
#default
#root
# Read a policy
bao policy read default&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The second command returns the policy content:&lt;/p&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 tokens to look up their own properties
path &amp;#34;auth/token/lookup-self&amp;#34; {
capabilities = [&amp;#34;read&amp;#34;]
}
# Allow tokens to renew themselves
path &amp;#34;auth/token/renew-self&amp;#34; {
capabilities = [&amp;#34;update&amp;#34;]
}
# Allow tokens to revoke themselves
path &amp;#34;auth/token/revoke-self&amp;#34; {
capabilities = [&amp;#34;update&amp;#34;]
}
# Allow a token to look up its own capabilities on a path
path &amp;#34;sys/capabilities-self&amp;#34; {
[...]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Again you will see the same policies in the UI. You can also manage them or create new ones using the UI.&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/part7_openbao_ui_list_policies.png" alt="OpenBao UI List Policies"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 4. OpenBao UI Listing Policies&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_creating_policies_using_the_cli"&gt;Creating Policies using the CLI&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Creating a policy, for example using the CLI, is straightforward. You can create a policy from a file or inline. The syntax is either JSON or HCL.
The example below creates a policy called &amp;#34;myapp-read&amp;#34; from a file called &amp;#34;myapp-read-policy.hcl&amp;#34;. The policy allows reading and listing secrets under the path &amp;#34;secret/data/myapp/*&amp;#34;.&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;# Create a policy from file
bao policy write myapp-read myapp-read-policy.hcl
# Or inline
bao policy write myapp-read - &amp;lt;&amp;lt;EOF
path &amp;#34;secret/data/myapp/*&amp;#34; {
capabilities = [&amp;#34;read&amp;#34;, &amp;#34;list&amp;#34;]
}
EOF
# List policies
bao policy list
# Read a policy
bao policy read myapp-read&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="_kubernetes_authentication_method"&gt;Kubernetes Authentication Method&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;As we work a lot with OpenShift/Kubernetes, this method of authentication will be the first to try.
The Kubernetes auth method can be used to authenticate against OpenBao using Kubernetes Service Account tokens.
It is essential for pods to access secrets.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Let us try a simple example.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_step_1_enable_kubernetes_auth"&gt;Step 1: Enable Kubernetes Auth&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Assuming you are still logged into OpenBao, you can enable the kubernetes auth method with the following command:&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;bao auth enable --description=&amp;#34;Authentication method for Kubernetes/OpenShift cluster&amp;#34; kubernetes&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This simply enabled the authentication method and set a description. However, we need to configure it further.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_step_2_configure_kubernetes_auth"&gt;Step 2: Configure Kubernetes Auth&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To configure the kubernetes auth method, you need to get the Kubernetes API server address. You can get it with the following command (assuming you are logged into the OpenShift cluster):&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;
The command &lt;code&gt;oc&lt;/code&gt; can be replaced by &lt;code&gt;kubectl&lt;/code&gt; if you are not using an OpenShift cluster.
&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;# Get the Kubernetes API server address
KUBERNETES_HOST=$(oc whoami --show-server) &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
# Configure the auth method
bao write auth/kubernetes/config \ &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
kubernetes_host=&amp;#34;$KUBERNETES_HOST&amp;#34;&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;Get the Kubernetes API server address. This will use something like &lt;a href="https://api.cluster.example.com:6443" class="bare"&gt;https://api.cluster.example.com:6443&lt;/a&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;Configure the auth method with the Kubernetes API server address.&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;
When running inside Kubernetes, OpenBao automatically uses the pod’s service account to communicate with the Kubernetes API.
&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="_step_3_create_a_policy_for_the_application"&gt;Step 3: Create a Policy for the Application&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Now we need to associate a policy to the authentication method. We could also assign the default policy but we want to provide access to the path &lt;strong&gt;secret/data/expense/database&lt;/strong&gt; and &lt;strong&gt;secret/metadata/expense/config&lt;/strong&gt; for the application.&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;# Create policy for an example application
bao policy write expense-app - &amp;lt;&amp;lt;EOF
# Read database credentials
path &amp;#34;secret/data/expense/database&amp;#34; {
capabilities = [&amp;#34;read&amp;#34;]
}
# Read application config
path &amp;#34;secret/data/expense/config&amp;#34; {
capabilities = [&amp;#34;read&amp;#34;, &amp;#34;list&amp;#34;]
}
# Allow token renewal
path &amp;#34;auth/token/renew-self&amp;#34; {
capabilities = [&amp;#34;update&amp;#34;]
}
EOF&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_step_4_create_a_kubernetes_auth_role"&gt;Step 4: Create a Kubernetes Auth Role&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;As the next step we map the policy to the authentication method. We will further limit the access to the namespace &lt;strong&gt;expense&lt;/strong&gt; and the service account &lt;strong&gt;expense-app&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;# Create a role that maps Kubernetes service accounts to OpenBao policies
bao write auth/kubernetes/role/expense-app \
bound_service_account_names=expense-app \ &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
bound_service_account_namespaces=expense \ &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
policies=expense-app \ &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
ttl=1h \ &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
max_ttl=24h &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;&lt;code&gt;bound_service_account_names&lt;/code&gt;: K8s service account name(s)&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;&lt;code&gt;bound_service_account_namespaces&lt;/code&gt;: K8s namespace(s)&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;&lt;code&gt;policies&lt;/code&gt;: OpenBao policies to attach&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;&lt;code&gt;ttl&lt;/code&gt;: Token time-to-live&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;&lt;code&gt;max_ttl&lt;/code&gt;: Maximum token lifetime&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;
All of the configuration above, created using the CLI, can also be done using the UI of OpenBao.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="sect3"&gt;
&lt;h4 id="_ensure_the_kv_v2_secrets_engine_is_mounted_at_secret"&gt;Ensure the KV v2 secrets engine is mounted at &lt;code&gt;secret/&lt;/code&gt;&lt;/h4&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;
We will discuss secret engines in more detail in the next part of the guide.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The policy and demo commands in this guide use the path &lt;code&gt;secret/data/expense/database&lt;/code&gt;, which assumes a KV v2 secrets engine mounted at &lt;code&gt;secret/&lt;/code&gt;. To check and enable it:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Check existing mounts:&lt;/strong&gt; List enabled secrets engines and look for &lt;code&gt;secret/&lt;/code&gt; with type &lt;code&gt;kv&lt;/code&gt; (version 2):&lt;/p&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;# List all secrets engine mounts (requires a token with read on sys/mounts)
bao secrets list&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Look for an entry like &lt;code&gt;secret/&lt;/code&gt; with type &lt;code&gt;kv&lt;/code&gt; or &lt;code&gt;kv-v2&lt;/code&gt;. If you see &lt;code&gt;secret/&lt;/code&gt; and it is KV v2, you can skip to Step 5.&lt;/p&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Enable KV v2 at &lt;code&gt;secret/&lt;/code&gt; if missing:&lt;/strong&gt; If &lt;code&gt;secret/&lt;/code&gt; is not listed, or you want to use a fresh mount, enable the KV v2 engine at the path &lt;code&gt;secret&lt;/code&gt;:&lt;/p&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;# Enable KV v2 secrets engine at path &amp;#34;secret&amp;#34; (requires root or policy with sys/mounts capability)
bao secrets enable -path=secret -version=2 kv&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;If &lt;code&gt;secret/&lt;/code&gt; already exists as KV v1, you cannot change it in place to v2; use a different path (e.g. &lt;code&gt;secretv2/&lt;/code&gt;) and update the policy and demo paths accordingly (&lt;code&gt;secretv2/data/expense/database&lt;/code&gt;, etc.).&lt;/p&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_step_5_store_the_database_secret_in_openbao_one_time"&gt;Step 5: Store the database secret in OpenBao (one-time)&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Before the application can read it, store the database password (and any other keys) at the path the policy allows. Run this once from a machine that has OpenBao access (e.g. &lt;code&gt;bao&lt;/code&gt; CLI and a token).&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;
You must use a token that has &lt;strong&gt;create&lt;/strong&gt; and &lt;strong&gt;update&lt;/strong&gt; capability on the KV path. The &lt;code&gt;expense-app&lt;/code&gt; policy is read-only (for the application). Use the &lt;strong&gt;root token&lt;/strong&gt; from OpenBao initialisation, or log in with &lt;code&gt;bao login&lt;/code&gt; and use a token that has a policy granting &lt;code&gt;create&lt;/code&gt; and &lt;code&gt;update&lt;/code&gt; on &lt;code&gt;secret/data/expense/*&lt;/code&gt;. If you use a read-only token (e.g. one that only has the expense-app policy), you will get a 403 &amp;#34;preflight capability check returned 403&amp;#34;.
&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;# Store the database secret at secret/data/expense/database (KV v2 path)
bao kv put secret/expense/database password=&amp;#34;my-super-secret-db-password&amp;#34; username=&amp;#34;expense_db_user&amp;#34;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This creates (or overwrites) the secret at &lt;code&gt;secret/data/expense/database&lt;/code&gt;. The policy grants the &lt;code&gt;expense-app&lt;/code&gt; role &lt;strong&gt;read&lt;/strong&gt; on this path only; the application cannot create or update it.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_step_6_create_the_expense_namespace_and_demo_application"&gt;Step 6: Create the expense namespace and demo application&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Apply the following manifests to create the namespace, service account, optional long-lived token (Kubernetes 1.24+), and a demo deployment that authenticates to OpenBao and fetches the database secret. Adjust the OpenBao URL if your OpenBao is in another namespace or uses HTTPS (e.g. &lt;code&gt;&lt;a href="https://openbao.openbao.svc:8200" class="bare"&gt;https://openbao.openbao.svc:8200&lt;/a&gt;&lt;/code&gt;).&lt;/p&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;
Remember: We have created a policy that allows the service account with the name &lt;strong&gt;expense-app&lt;/strong&gt; in the namespace &lt;strong&gt;expense&lt;/strong&gt; to read the secret at &lt;strong&gt;secret/data/expense/database&lt;/strong&gt;. If you want to use different paths or namespaces, you need to adjust the policy accordingly.
&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;# full-demo-expense-app.yaml
# 1. Namespace
apiVersion: v1
kind: Namespace &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
metadata:
name: expense
---
# 2. Service account used by the demo app to authenticate to OpenBao
apiVersion: v1
kind: ServiceAccount &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
metadata:
name: expense-app
namespace: expense
---
# 3. Long-lived token for Kubernetes 1.24+ (optional; allows pods to use the SA token)
apiVersion: v1
kind: Secret &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
metadata:
name: expense-app-token
namespace: expense
annotations:
kubernetes.io/service-account.name: expense-app
type: kubernetes.io/service-account-token
---
# 4. Demo deployment: authenticates to OpenBao and fetches secret/data/expense/database
apiVersion: apps/v1
kind: Deployment
metadata:
name: expense-demo
namespace: expense
labels:
app: expense-demo
spec:
replicas: 1
selector:
matchLabels:
app: expense-demo
template:
metadata:
labels:
app: expense-demo
spec:
serviceAccountName: expense-app
containers:
- name: demo
image: registry.access.redhat.com/ubi9/ubi-minimal:latest
command:
- /bin/sh
- -c
- |
echo &amp;#34;=== Installing curl ===&amp;#34;
microdnf install -y curl 2&amp;gt;/dev/null || true
echo &amp;#34;=== Fetching database secret from OpenBao ===&amp;#34;
JWT=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
OPENBAO_URL=&amp;#34;${OPENBAO_URL:-http://openbao.openbao:8200}&amp;#34;
RESP=$(curl -s -k -X POST -H &amp;#34;Content-Type: application/json&amp;#34; \ &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
-d &amp;#34;{\&amp;#34;jwt\&amp;#34;: \&amp;#34;$JWT\&amp;#34;, \&amp;#34;role\&amp;#34;: \&amp;#34;expense-app\&amp;#34;}&amp;#34; \
&amp;#34;$OPENBAO_URL/v1/auth/kubernetes/login&amp;#34;)
TOKEN=$(echo &amp;#34;$RESP&amp;#34; | sed -n &amp;#39;s/.*&amp;#34;client_token&amp;#34;:&amp;#34;\([^&amp;#34;]*\)&amp;#34;.*/\1/p&amp;#39;)
if [ -z &amp;#34;$TOKEN&amp;#34; ]; then
echo &amp;#34;ERROR: Failed to get OpenBao token. Check OpenBao URL ($OPENBAO_URL), role expense-app, and network.&amp;#34;
echo &amp;#34;Response: $RESP&amp;#34;
exit 1
fi
echo &amp;#34;OpenBao token obtained. Reading secret/data/expense/database ...&amp;#34;
SECRET=$(curl -s -k -H &amp;#34;X-Vault-Token: $TOKEN&amp;#34; &amp;#34;$OPENBAO_URL/v1/secret/data/expense/database&amp;#34;) &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
echo &amp;#34;$SECRET&amp;#34;
PASSWORD=$(echo &amp;#34;$SECRET&amp;#34; | sed -n &amp;#39;s/.*&amp;#34;password&amp;#34;:&amp;#34;\([^&amp;#34;]*\)&amp;#34;.*/\1/p&amp;#39;)
echo &amp;#34;Database password (data.data.password): ${PASSWORD:-&amp;lt;not set&amp;gt;}&amp;#34;
echo &amp;#34;=== Demo complete. Pod stays running; exec in to run more curl commands. ===&amp;#34;
exec sleep infinity
env:
# Override if OpenBao is in another namespace or uses HTTPS
- name: OPENBAO_URL
value: &amp;#34;https://openbao.openbao:8200&amp;#34;
resources:
requests:
memory: &amp;#34;64Mi&amp;#34;
cpu: &amp;#34;10m&amp;#34;&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;Create the namespace&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;Create the service account&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;Create the secret (long-lived token for Kubernetes 1.24+)&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;Authenticate to OpenBao and get a client token (using the Kubernetes JWT)&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;Fetch the database secret&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&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;
The examples above use &lt;strong&gt;curl -k&lt;/strong&gt; to bypass certificate verification. For production, you should adjust this and mount the CA certificate in the pod (see the note earlier in this section on using the &lt;code&gt;openbao-ca&lt;/code&gt; Secret).
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Apply the manifests and wait for the pod to be ready:&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 apply -f full-demo-expense-app.yaml
# Wait for the demo pod to be running
oc -n expense rollout status deployment/expense-demo
# View the log: you should see the OpenBao token response and the fetched secret (including the password)
oc -n expense logs -l app=expense-demo -f&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The log output shows the application authenticating with the Kubernetes JWT, receiving an OpenBao token, and reading the secret at &lt;code&gt;secret/data/expense/database&lt;/code&gt;. The field &lt;code&gt;data.data.password&lt;/code&gt; is the database password you stored in Step 5.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_step_7_test_kubernetes_authentication_manually"&gt;Step 7: Test Kubernetes authentication manually&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;From any pod that uses the same service account (e.g. the demo pod above), you can run the same steps by hand:&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;# From within a pod using the service account (e.g. oc exec -it deployment/expense-demo -n expense -- /bin/sh)
JWT=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
# 1. Authenticate to OpenBao and get a client token (sed works on UBI minimal; use jq -r .auth.client_token if jq is available)
LOGIN=$(curl -s -k --request POST \
--data &amp;#34;{\&amp;#34;jwt\&amp;#34;: \&amp;#34;$JWT\&amp;#34;, \&amp;#34;role\&amp;#34;: \&amp;#34;expense-app\&amp;#34;}&amp;#34; \
https://openbao.openbao:8200/v1/auth/kubernetes/login)
TOKEN=$(echo &amp;#34;$LOGIN&amp;#34; | sed -n &amp;#39;s/.*&amp;#34;client_token&amp;#34;:&amp;#34;\([^&amp;#34;]*\)&amp;#34;.*/\1/p&amp;#39;)
# 2. Fetch the database secret (same path the policy allows)
curl -s -k -H &amp;#34;X-Vault-Token: $TOKEN&amp;#34; https://openbao.openbao:8200/v1/secret/data/expense/database&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_summary_kubernetes_auth_method"&gt;Summary: Kubernetes auth method&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;A lot of steps were involved to configure the Kubernetes auth method. Let us summarize them:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;In OpenBao:&lt;/strong&gt; Enabled the Kubernetes auth method, configured it with the cluster API server address, created the &lt;code&gt;expense-app&lt;/code&gt; policy (read on &lt;code&gt;secret/data/expense/database&lt;/code&gt; and &lt;code&gt;secret/data/expense/config&lt;/code&gt;, update on &lt;code&gt;auth/token/renew-self&lt;/code&gt;), and created the &lt;code&gt;expense-app&lt;/code&gt; role that binds the Kubernetes service account &lt;code&gt;expense-app&lt;/code&gt; in namespace &lt;code&gt;expense&lt;/code&gt; to that policy.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Secrets engine:&lt;/strong&gt; Ensured the KV v2 engine is mounted at &lt;code&gt;secret/&lt;/code&gt; and stored the database secret at &lt;code&gt;secret/expense/database&lt;/code&gt; (one-time, using the root token).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;In the cluster:&lt;/strong&gt; Created the &lt;code&gt;expense&lt;/code&gt; namespace, the &lt;code&gt;expense-app&lt;/code&gt; service account, an optional long-lived token Secret (for Kubernetes 1.24+), and the &lt;code&gt;expense-demo&lt;/code&gt; deployment that uses that service account to authenticate to OpenBao and fetch the secret. For HTTPS, created the &lt;code&gt;openbao-ca&lt;/code&gt; Secret in &lt;code&gt;expense&lt;/code&gt; with the OpenBao server CA.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Verification:&lt;/strong&gt; Applied the manifests, confirmed the demo pod starts and logs show a successful login and the fetched secret (including the database password). Optionally ran the same login and read steps manually via &lt;code&gt;oc exec&lt;/code&gt; into the pod.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_ldap_authentication_method"&gt;LDAP Authentication Method&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;LDAP auth integrates with enterprise directories like Active Directory.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock caution"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-caution" title="Caution"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
This is a very simplified example. As a prerequisite you need to have an LDAP server and a service account with the necessary permissions to read the users and groups. This is not part of this guide.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_step_1_enable_ldap_auth"&gt;Step 1: Enable LDAP Auth&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To enable LDAP authentication, you need to use the following command:&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;bao auth enable ldap&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_step_2_configure_ldap_active_directory_example"&gt;Step 2: Configure LDAP (Active Directory Example)&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;In this step you need to configure the LDAP server address, the user and group attributes, the bind DN and the bind password.
The example below configures the LDAP server address to &lt;strong&gt;ldaps://ad.example.com:636&lt;/strong&gt;, the user attribute to &lt;strong&gt;sAMAccountName&lt;/strong&gt;, the user DN to &lt;strong&gt;OU=Users,DC=example,DC=com&lt;/strong&gt;, the group DN to &lt;strong&gt;OU=Groups,DC=example,DC=com&lt;/strong&gt;, the group attribute to &lt;strong&gt;cn&lt;/strong&gt;, the group filter to &lt;strong&gt;(&amp;amp;(objectClass=group)(member:1.2.840.113556.1.4.1941:={{.UserDN}}))&lt;/strong&gt;, the bind DN to &lt;strong&gt;CN=openbao-svc,OU=ServiceAccounts,DC=example,DC=com&lt;/strong&gt; and the bind password to &lt;strong&gt;service-account-password&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;bao write auth/ldap/config \
url=&amp;#34;ldaps://ad.example.com:636&amp;#34; \
userattr=&amp;#34;sAMAccountName&amp;#34; \
userdn=&amp;#34;OU=Users,DC=example,DC=com&amp;#34; \
groupdn=&amp;#34;OU=Groups,DC=example,DC=com&amp;#34; \
groupattr=&amp;#34;cn&amp;#34; \
groupfilter=&amp;#34;(&amp;amp;(objectClass=group)(member:1.2.840.113556.1.4.1941:={{.UserDN}}))&amp;#34; \
binddn=&amp;#34;CN=openbao-svc,OU=ServiceAccounts,DC=example,DC=com&amp;#34; \
bindpass=&amp;#34;service-account-password&amp;#34; \
certificate=@/path/to/ldap-ca.pem \
insecure_tls=false \
starttls=false&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_step_3_create_the_policies_used_by_the_group_mapping"&gt;Step 3: Create the policies used by the group mapping&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Before mapping LDAP groups to policy names, create those policies. Below are example definitions: &lt;strong&gt;admin-policy&lt;/strong&gt; grants read/list/update on secrets (e.g. for operators), and &lt;strong&gt;user-policy&lt;/strong&gt; grants read-only access to a limited path.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong&gt;admin-policy&lt;/strong&gt; — for members of &lt;strong&gt;openbao-admins&lt;/strong&gt; (e.g. operators who may read and update secrets)&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong&gt;user-policy&lt;/strong&gt; — for members of &lt;strong&gt;openbao-users&lt;/strong&gt; (e.g. developers with read-only access to a subset of secrets)&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Create them with the CLI (run before the group mapping in Step 4):&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;# Create admin-policy (e.g. from file admin-policy.hcl)
bao policy write admin-policy - &amp;lt;&amp;lt;EOF
path &amp;#34;secret/data/*&amp;#34; {
capabilities = [&amp;#34;read&amp;#34;, &amp;#34;list&amp;#34;, &amp;#34;update&amp;#34;, &amp;#34;create&amp;#34;, &amp;#34;delete&amp;#34;]
}
path &amp;#34;secret/metadata/*&amp;#34; {
capabilities = [&amp;#34;read&amp;#34;, &amp;#34;list&amp;#34;]
}
path &amp;#34;auth/token/renew-self&amp;#34; {
capabilities = [&amp;#34;update&amp;#34;]
}
EOF
# Create user-policy
bao policy write user-policy - &amp;lt;&amp;lt;EOF
path &amp;#34;secret/data/myapp/*&amp;#34; {
capabilities = [&amp;#34;read&amp;#34;, &amp;#34;list&amp;#34;]
}
path &amp;#34;auth/token/renew-self&amp;#34; {
capabilities = [&amp;#34;update&amp;#34;]
}
EOF&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Adjust paths (e.g. &lt;code&gt;secret/data/myapp/&lt;/code&gt;) to match your KV v2 mount and the paths you want each role to access. The &lt;strong&gt;default&lt;/strong&gt; policy is built-in and grants token lookup/renew/revoke-self. Attaching it in addition to &lt;strong&gt;admin-policy&lt;/strong&gt; or &lt;strong&gt;user-policy&lt;/strong&gt; is a typical pattern.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_step_4_map_ldap_groups_to_policies"&gt;Step 4: Map LDAP groups to policies&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Now map the LDAP groups to the policy names. The example below maps the LDAP group &lt;strong&gt;openbao-admins&lt;/strong&gt; to the policies &lt;strong&gt;admin-policy&lt;/strong&gt; and &lt;strong&gt;default&lt;/strong&gt; and the LDAP group &lt;strong&gt;openbao-users&lt;/strong&gt; to the policies &lt;strong&gt;user-policy&lt;/strong&gt; and &lt;strong&gt;default&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;# Map an LDAP group to policies
bao write auth/ldap/groups/openbao-admins \
policies=&amp;#34;admin-policy,default&amp;#34;
bao write auth/ldap/groups/openbao-users \
policies=&amp;#34;user-policy,default&amp;#34;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_step_5_test_ldap_authentication"&gt;Step 5: Test LDAP authentication&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Assuming you have a user &lt;strong&gt;jdoe&lt;/strong&gt; in the LDAP group &lt;strong&gt;openbao-users&lt;/strong&gt;, you can test the LDAP authentication with the following commands:&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;# Login with LDAP credentials
bao login -method=ldap username=jdoe
# Or via API
curl --request POST \
--data &amp;#39;{&amp;#34;password&amp;#34;: &amp;#34;user-password&amp;#34;}&amp;#39; \
$BAO_ADDR/v1/auth/ldap/login/jdoe&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect3"&gt;
&lt;h4 id="_what_jdoe_can_and_cannot_do_user_policy"&gt;What jdoe can and cannot do (user-policy)&lt;/h4&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;jdoe holds &lt;strong&gt;user-policy&lt;/strong&gt; (and &lt;strong&gt;default&lt;/strong&gt;), so their token has only the capabilities defined there.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;What jdoe can do:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Get a secret under &lt;code&gt;secret/data/myapp/*&lt;/code&gt;: after logging in, jdoe can read and list secrets in that path. For example:&lt;/p&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;# After bao login -method=ldap username=jdoe (and entering the password)
bao kv get secret/myapp/database
# Or via API (use the client_token from the login response)
curl -s -H &amp;#34;X-Vault-Token: $TOKEN&amp;#34; $BAO_ADDR/v1/secret/data/myapp/database&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;List keys under &lt;code&gt;secret/data/myapp/&lt;/code&gt; to discover available secrets.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Renew or revoke their own token (from the &lt;strong&gt;default&lt;/strong&gt; policy) and look up their own token properties.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;What jdoe cannot do:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Read secrets outside &lt;code&gt;myapp&lt;/code&gt; — e.g. &lt;code&gt;secret/data/expense/database&lt;/code&gt; or &lt;code&gt;secret/data/other-app/*&lt;/code&gt; returns permission denied.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create, update, or delete any secret — &lt;strong&gt;user-policy&lt;/strong&gt; grants only &lt;code&gt;read&lt;/code&gt; and &lt;code&gt;list&lt;/code&gt; on &lt;code&gt;secret/data/myapp/*&lt;/code&gt;, so write operations are denied.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Access admin or system paths — no access to &lt;code&gt;sys/&lt;strong&gt;&lt;/strong&gt;&lt;/code&gt;&lt;strong&gt;, other auth methods, or policies. Only the paths explicitly granted in *user-policy&lt;/strong&gt; and &lt;strong&gt;default&lt;/strong&gt; are allowed.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_common_practices_for_authentication"&gt;Common Practices for Authentication&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Use the principle of least privilege&lt;/strong&gt; - Do not grant more permissions than necessary.&lt;/p&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-hcl hljs" data-lang="hcl"&gt;# Bad: Too broad
path &amp;#34;secret/*&amp;#34; {
capabilities = [&amp;#34;read&amp;#34;, &amp;#34;list&amp;#34;]
}
# Good: Specific paths
path &amp;#34;secret/data/myapp/database&amp;#34; {
capabilities = [&amp;#34;read&amp;#34;]
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Set Appropriate Token TTLs&lt;/strong&gt; - Set the appropriate TTLs for different use cases.&lt;/p&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;# Short-lived for automated systems
bao write auth/kubernetes/role/batch-job \
ttl=5m \
max_ttl=15m
# Longer for interactive users
bao write auth/oidc/role/human-user \
ttl=8h \
max_ttl=24h&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Enable Audit Logging&lt;/strong&gt; - Enable audit logging to track authentication attempts and other activities.&lt;/p&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;# Enable file audit
bao audit enable file file_path=/var/log/openbao/audit.log
# All authentication attempts are logged&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Regularly Rotate Credentials&lt;/strong&gt; - Rotate the credentials periodically to reduce the risk of compromise.&lt;/p&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;# Rotate AppRole Secret IDs periodically
bao write -f auth/approle/role/cicd-role/secret-id
# Revoke old tokens
bao token revoke &amp;lt;old-token&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_common_authentication_patterns"&gt;Common Authentication Patterns&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Pattern 1: Application Pods&lt;/strong&gt;&lt;/p&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-none hljs"&gt;Pod → Service Account Token → Kubernetes Auth → Policy → Secret&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Pattern 2: Human Users&lt;/strong&gt;&lt;/p&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-none hljs"&gt;User → LDAP/OIDC Login → Group Mapping → Policy → Secret&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Pattern 3: CI/CD Pipeline&lt;/strong&gt;&lt;/p&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-none hljs"&gt;Pipeline → AppRole (Role ID + Secret ID) → Policy → Secret&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_what_is_coming_next"&gt;What is Coming Next?&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;In Part 8, we will explore secrets engines, like:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;KV (Key-Value) for static secrets&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;In upcoming parts, I will try to cover common practices for OpenBao operation and maintenance. If there is time and an opportunity to test other authentication methods, I will do so.&lt;/p&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;Proper authentication configuration is crucial for OpenBao security. Key takeaways:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Use &lt;strong&gt;Kubernetes auth&lt;/strong&gt; for pods&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Use &lt;strong&gt;LDAP/OIDC&lt;/strong&gt; for human users (SSO)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Use &lt;strong&gt;AppRole&lt;/strong&gt; for automation&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create &lt;strong&gt;specific policies&lt;/strong&gt; with least privilege&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;strong&gt;appropriate TTLs&lt;/strong&gt; for different use cases&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Enable audit logging&lt;/strong&gt; for compliance&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&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/auth/kubernetes" target="_blank" rel="noopener"&gt;Kubernetes Auth Method&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://openbao.org/docs/auth/jwt" target="_blank" rel="noopener"&gt;JWT/OIDC Auth Method&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://openbao.org/docs/auth/ldap" target="_blank" rel="noopener"&gt;LDAP Auth Method&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://openbao.org/docs/auth/approle" target="_blank" rel="noopener"&gt;AppRole Auth Method&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://openbao.org/docs/concepts/policies" target="_blank" rel="noopener"&gt;Policies Documentation&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item><item><title>The Guide to OpenBao - Initialisation, Unsealing, and Auto-Unseal - Part 6</title><link>https://blog.stderr.at/openshift-platform/security/secrets-management/openbao/2026-03-05-openbao-part-6-auto-unsealing/</link><pubDate>Thu, 05 Mar 2026 00:00:00 +0000</pubDate><guid>https://blog.stderr.at/openshift-platform/security/secrets-management/openbao/2026-03-05-openbao-part-6-auto-unsealing/</guid><description>&lt;div class="paragraph"&gt;
&lt;p&gt;After deploying OpenBao via GitOps (&lt;a href="https://blog.stderr.at/openshift-platform/security/secrets-management/openbao/2026-02-20-openbao-part-5-gitops-deployment/"&gt;Part 5&lt;/a&gt;), OpenBao must be initialised and then unsealed before it becomes functional. You usually do not want to do this unsealing manually, since this is not scalable especially in bigger, productive environments. This article explains how to handle initialisation and unsealing, and possible options to configure an &lt;strong&gt;auto-unseal&lt;/strong&gt; process so that OpenBao unseals itself on every restart without manual key entry.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_what_is_happening"&gt;What is happening?&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;OpenBao starts in a &lt;strong&gt;sealed&lt;/strong&gt; state. You must:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;Initialise (once): run &lt;code&gt;bao operator init&lt;/code&gt; to generate unseal keys (or recovery keys when using auto-unseal).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Unseal (after each restart): provide a threshold of unseal keys so each node can decrypt the root key. In the previous articles, we used a threshold of 3 (out of 5). This means that you need to run &lt;strong&gt;bao operator unseal&lt;/strong&gt; three times with different keys on every node after every start before the service becomes available.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Unseal workflow (assuming a threshold of 3 and 5 nodes and initialisation already happened):&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-mermaid {align=" center"="" zoom="true" }="" hljs"="" data-lang="mermaid {align=" center"="" zoom="true" }"=""&gt;---
title: &amp;#34;Unseal Workflow&amp;#34;
config:
theme: &amp;#39;dark&amp;#39;
---
flowchart LR
A[&amp;#34;openbao-0 starts → 3× unseal using different keys&amp;#34;]
A --&amp;gt; B[&amp;#34;openbao-1 starts → 3× unseal using different keys&amp;#34;]
B --&amp;gt; C[&amp;#34;openbao-2 starts → 3× unseal using different keys&amp;#34;]
C --&amp;gt; D[OpenBao is ready]&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;
This is a chicken-egg problem: you need to start somewhere, but you cannot start without the keys.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Manual unsealing does not scale very well and for production, &lt;strong&gt;auto-unseal&lt;/strong&gt; is recommended.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This article covers:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Practical approaches to initialisation and unsealing (manual, Jobs, init containers).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Potential auto-unseal options: with AWS KMS, static key, and transit seal.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_handling_initialisation_and_unsealing"&gt;Handling Initialisation and Unsealing&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The challenge with GitOps is that initialisation and unsealing are one-off or stateful operations. Below are several approaches. The problem is always the same: where do you start? You must have the unseal keys available to start the process. Do you keep them locally, do you keep them in a Kubernetes Secret, do you use an external Key Management System (KMS)? The most robust for production in my opinion is auto-unseal using a KMS (see &lt;a href="#_approach_4_auto_unseal_with_kms_recommended_for_production"&gt;[_approach_4_auto_unseal_with_kms_recommended_for_production]&lt;/a&gt; and the following sections). It uses an external Key Management System (KMS) to store the keys. This way, the keys are not stored in the cluster and are not exposed to the risk of being compromised.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Let’s have a look at the different options.&lt;/p&gt;
&lt;/div&gt;
&lt;hr/&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_approach_1_manual_initialisation_simplest"&gt;Approach 1: Manual Initialisation (simplest)&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;While manual initialisation and unsealing is the simplest approach, it is not recommended for production. Still, I would like to mention it here for completeness and at least provide a simple for loop to unseal the OpenBao service (on Kubernetes/OpenShift).&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;
Manual unsealing is still a valid option when you have a small cluster and assume that it is not required often.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;After Argo CD deploys OpenBao, the first pod, openbao-0, will not become ready until it is initialised and unsealed. Once done, the next pod, openbao-1, will try to start and wait until it is unsealed, and then the third pod, and so on.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The following, simplified script will go through the pods and unseal them one by one. It initialises the first pod and stores the unseal keys in the file &lt;strong&gt;openbao-init.json&lt;/strong&gt; locally on your machine. From there it takes three different keys and unseals the OpenBao service.
Since it takes a while until other pods are pulling the image and starting, we will use a sleep timer of 30 seconds between each pod. This may or may not be enough depending on your network and cluster speed. As said, this is a very simplified script:&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;
The CLI tool &lt;strong&gt;oc&lt;/strong&gt; can be replaced by &lt;strong&gt;kubectl&lt;/strong&gt; in case you are using a different cluster. In addition, you will need the jq command line tool to parse the JSON file.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&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 your OpenBao was initialised previously, you can remove the first three commands and start with the unseal commands.
&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;# Initialise the first pod, assuming the Pod openbao-0 is already running.
# You can skip this if OpenBao was initialised previously.
echo &amp;#34;Initialising OpenBao on openbao-0 (keys will be saved to openbao-init.json)...&amp;#34;
oc exec -it openbao-0 -n openbao -- bao operator init \
-key-shares=5 -key-threshold=3 -format=json &amp;gt; openbao-init.json &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
echo &amp;#34;Init complete. Unseal keys and root token are in openbao-init.json.&amp;#34;
# Unseal each pod (three times each with different keys)
echo &amp;#34;Unsealing pods openbao-0, openbao-1, openbao-2 (3 key steps each)...&amp;#34;
for i in 0 1 2; do
sleep_timer=30
SLEEPER_TMP=1
echo &amp;#34;Unsealing openbao-$i...&amp;#34;
for j in 1 2 3; do &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
KEY=$(cat openbao-init.json | jq -r &amp;#34;.unseal_keys_b64[$((j-1))]&amp;#34;)
oc exec -it openbao-$i -n openbao -- bao operator unseal $KEY
done
echo &amp;#34;openbao-$i unsealed. Waiting ${sleep_timer}s for next pod to be ready...&amp;#34;
while [[ $SLEEPER_TMP -le &amp;#34;$sleep_timer&amp;#34; ]]; do &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
if (( SLEEPER_TMP % 10 == 0 )); then
echo -n &amp;#34;$SLEEPER_TMP&amp;#34;
else
echo -n &amp;#34;.&amp;#34;
fi
sleep 1
SLEEPER_TMP=$((SLEEPER_TMP + 1))
done
echo &amp;#34;&amp;#34;
done
echo &amp;#34;All pods unsealed. Store init data securely (e.g. in a password manager).&amp;#34;
# Store init data securely (e.g. in a password manager)&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;Initialise the first pod, assuming the Pod openbao-0 is already running. This will store the keys (including the root token) in the file &lt;strong&gt;openbao-init.json&lt;/strong&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;Unseal each pod (three times each with different keys). The keys are taken from the &lt;strong&gt;openbao-init.json&lt;/strong&gt; file.&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;Wait 30 seconds between each pod to give the other pods time to start and pull the image …​ super-fancy sleep timer.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="admonitionblock caution"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-caution" title="Caution"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
The unseal keys as well as the root token are stored in the &lt;strong&gt;openbao-init.json&lt;/strong&gt; file. This file is stored locally on your machine. Make sure to store it securely and do not share it with anyone.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_approach_2_kubernetes_job_for_initialisation"&gt;Approach 2: Kubernetes Job for initialisation&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;A Job can run after OpenBao is deployed, initialise it if needed, and store the init output (unseal keys and root token) in a Kubernetes Secret. You can then unseal manually or with a second Job that reads from that Secret. This section assumes the init output is stored in a Kubernetes Secret and explains how to create that Secret from the Job and how to use it.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect3"&gt;
&lt;h4 id="_how_the_secret_is_created"&gt;How the Secret is created&lt;/h4&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The init Job runs &lt;code&gt;bao operator init&lt;/code&gt; and then creates the Secret using &lt;code&gt;oc&lt;/code&gt; (or &lt;code&gt;kubectl&lt;/code&gt;). The Secret name and key used here are &lt;code&gt;openbao-init-data&lt;/code&gt; and &lt;code&gt;init.json&lt;/code&gt; (the file contains the JSON output of &lt;code&gt;bao operator init&lt;/code&gt;, including &lt;code&gt;unseal_keys_b64&lt;/code&gt; and &lt;code&gt;root_token&lt;/code&gt;). One advantage of this approach is that you can integrate it into your GitOps pipeline. However, we can argue whether it is a good idea to store the unseal keys and the root token as a Secret in the same cluster where OpenBao is running.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong&gt;Example structure of &lt;code&gt;init.json&lt;/code&gt;&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-json hljs" data-lang="json"&gt;{
&amp;#34;unseal_keys_b64&amp;#34;: [
&amp;#34;key1&amp;#34;,
&amp;#34;key2&amp;#34;,
&amp;#34;key3&amp;#34;,
&amp;#34;key4&amp;#34;,
&amp;#34;key5&amp;#34;
],
&amp;#34;unseal_keys_hex&amp;#34;: [
&amp;#34;hex1&amp;#34;,
&amp;#34;hex2&amp;#34;,
&amp;#34;hex3&amp;#34;,
&amp;#34;hex4&amp;#34;,
&amp;#34;hex5&amp;#34;
],
&amp;#34;unseal_shares&amp;#34;: 5,
&amp;#34;unseal_threshold&amp;#34;: 3,
&amp;#34;recovery_keys_b64&amp;#34;: null,
&amp;#34;recovery_keys_hex&amp;#34;: null,
&amp;#34;recovery_keys_shares&amp;#34;: 0,
&amp;#34;recovery_keys_threshold&amp;#34;: 0,
&amp;#34;root_token&amp;#34;: &amp;#34;root.token&amp;#34;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;You need at least &lt;code&gt;unseal_threshold&lt;/code&gt; keys (e.g. 3) from &lt;code&gt;unseal_keys_b64&lt;/code&gt; to unseal each node. Store this file securely; anyone with access can unseal OpenBao and the &lt;code&gt;root_token&lt;/code&gt; has full admin access.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect3"&gt;
&lt;h4 id="_rbac_configuration"&gt;RBAC Configuration&lt;/h4&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The Job’s service account (&lt;code&gt;openbao-init&lt;/code&gt;) must be allowed to create (and optionally get/update) Secrets in the &lt;code&gt;openbao&lt;/code&gt; namespace. Create a Role and RoleBinding:&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: ServiceAccount
metadata:
name: openbao-init &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
namespace: openbao
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: openbao-init-secret-writer
namespace: openbao
rules: &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
- apiGroups: [&amp;#34;&amp;#34;]
resources: [&amp;#34;secrets&amp;#34;]
verbs: [&amp;#34;create&amp;#34;, &amp;#34;get&amp;#34;, &amp;#34;update&amp;#34;, &amp;#34;patch&amp;#34;]
- apiGroups: [&amp;#34;&amp;#34;] &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
resources: [&amp;#34;pods&amp;#34;]
verbs: [&amp;#34;get&amp;#34;, &amp;#34;list&amp;#34;]
- apiGroups: [&amp;#34;&amp;#34;] &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
resources: [&amp;#34;pods/exec&amp;#34;]
verbs: [&amp;#34;create&amp;#34;]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
metadata:
name: openbao-init-secret-writer
namespace: openbao
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: openbao-init-secret-writer
subjects:
- kind: ServiceAccount
name: openbao-init
namespace: openbao&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;The ServiceAccount is used by the Job(s) to create the Secret and unseal the OpenBao pods.&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;The Role is used by the Job to create the Secret. The rules might be extended to include the permission to execute into the pods to unseal the OpenBao service (see below).&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;Permissions to get and list the Pods, required for the unseal process later&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;Permissions to execute into the Pods, required for the unseal process later&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;The RoleBinding is used by the Job to create the Secret.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The Job must also be able to reach the OpenBao API (e.g. &lt;code&gt;openbao.openbao.svc:8200&lt;/code&gt;). This is usually possible when running in the same namespace as the OpenBao service.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The example below shows a working Job manifest that will perform the initialisation and the creation of the Secret in the &lt;code&gt;openbao&lt;/code&gt; namespace. The manifest is already prepared for Argo CD with useful annotations.
It runs an init container to initialise the OpenBao service and a main container to create the Secret. The init container writes (using the OpenBao CLI) the init.json file to a shared volume and the main container (using the kubectl command) creates the Secret from that file.
In addition, the Secret containing the CA certificate is mounted as a volume and the BAO_CACERT environment variable is set to the path of the CA certificate.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect3"&gt;
&lt;h4 id="_init_job_that_creates_the_secret"&gt;Init Job that creates the Secret&lt;/h4&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: batch/v1
kind: Job
metadata:
name: openbao-init
namespace: openbao
annotations: &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
argocd.argoproj.io/sync-wave: &amp;#34;30&amp;#34;
argocd.argoproj.io/hook: PostSync
argocd.argoproj.io/hook-delete-policy: HookSucceeded
spec:
template:
spec:
serviceAccountName: openbao-init &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
initContainers: &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
- name: openbao-init
image: ghcr.io/openbao/openbao:latest
command: &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
- /bin/sh
- -c
- |
if [ -f /etc/openbao-ca/ca.crt ]; then
export BAO_CACERT=/etc/openbao-ca/ca.crt
export BAO_ADDR=https://openbao:8200
echo &amp;#34;Using TLS CA from /etc/openbao-ca/ca.crt&amp;#34;
else
export BAO_ADDR=http://openbao:8200
echo &amp;#34;Using plain HTTP&amp;#34;
fi
until bao status 2&amp;gt;&amp;amp;1 | grep -q &amp;#34;Initialized&amp;#34;; do
echo &amp;#34;Waiting for OpenBao...&amp;#34;
sleep 5
done
if bao status | grep -q &amp;#34;Initialized.*false&amp;#34;; then
echo &amp;#34;Initialising OpenBao...&amp;#34;
bao operator init -key-shares=5 -key-threshold=3 \
-format=json &amp;gt; /shared/init.json
echo &amp;#34;Init complete; init.json written to shared volume.&amp;#34;
else
echo &amp;#34;OpenBao already initialised (skipping init).&amp;#34;
fi
volumeMounts:
- name: openbao-ca
mountPath: /etc/openbao-ca
readOnly: true
- name: shared
mountPath: /shared
containers: &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
- name: create-secret
image: bitnami/kubectl:latest
command:
- /bin/sh
- -c
- |
if [ -f /shared/init.json ]; then
echo &amp;#34;Creating Secret openbao-init-data from init.json...&amp;#34;
kubectl create secret generic openbao-init-data \
--from-file=init.json=/shared/init.json \
-n openbao --dry-run=client -o yaml | kubectl apply -f -
echo &amp;#34;Initialisation complete!&amp;#34;
else
echo &amp;#34;No init.json (OpenBao already initialised or init skipped).&amp;#34;
fi
volumeMounts:
- name: shared
mountPath: /shared
readOnly: true
volumes:
- name: openbao-ca
secret:
secretName: openbao-ca-secret
optional: true
- name: shared &lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;(6)&lt;/b&gt;
emptyDir: {}
restartPolicy: Never
backoffLimit: 3&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;The annotations are used by Argo CD to trigger the Job after the OpenBao deployment.&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;The ServiceAccount is used by the Job to initialise the OpenBao service.&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;The init container runs the OpenBao CLI (wait for API, then operator init) and writes init.json to a shared volume.&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;The command is used to initialise the OpenBao service.&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;The main container runs kubectl to create the Secret from that file. This avoids needing a single image that bundles both &lt;code&gt;bao&lt;/code&gt; and &lt;code&gt;kubectl&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;6&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The shared volume is used to store the init.json file.&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;
As a result the secret &lt;code&gt;openbao-init-data&lt;/code&gt; is created in the &lt;code&gt;openbao&lt;/code&gt; namespace. It contains the unseal keys and the root token.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect3"&gt;
&lt;h4 id="_using_the_secret_in_a_job_unseal"&gt;Using the Secret in a Job (unseal)&lt;/h4&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;A second Job can use the Secret &lt;code&gt;openbao-init-data&lt;/code&gt; that was created by the init Job to unseal each OpenBao pod. A lot is happening here:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;The init container extracts the unseal keys from the Secret using &lt;code&gt;jq&lt;/code&gt; and writes them to a shared volume.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The main container is reading the unseal keys from the shared volume and unseals the first pod&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Then the process is waiting for 30 seconds, to give the next pod time to pull the image and start&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The process is repeated for the next pod and so on until all pods are unsealed.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;First of all, the unseal Job needs permission to &lt;code&gt;exec&lt;/code&gt; into the OpenBao pods. Add a Role that grants the Job’s service account &lt;code&gt;create&lt;/code&gt; on &lt;code&gt;pods/exec&lt;/code&gt; in the &lt;code&gt;openbao&lt;/code&gt; namespace.
To make it easier, we can extend the Role &lt;code&gt;openbao-init-secret-writer&lt;/code&gt; that was created in the init Job.&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;
These rules were already applied with the configuration for the init Job, but we will repeat them here for completeness.
&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;rules: &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
- apiGroups: [&amp;#34;&amp;#34;]
resources: [&amp;#34;pods&amp;#34;]
verbs: [&amp;#34;get&amp;#34;, &amp;#34;list&amp;#34;]
- apiGroups: [&amp;#34;&amp;#34;]
resources: [&amp;#34;pods/exec&amp;#34;]
verbs: [&amp;#34;create&amp;#34;]&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;The role &lt;code&gt;openbao-init-secret-writer&lt;/code&gt; must be extended to include the permission to execute into the pods to unseal the OpenBao service.&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: batch/v1
kind: Job
metadata:
name: openbao-unseal
namespace: openbao
annotations:
argocd.argoproj.io/sync-wave: &amp;#34;35&amp;#34;
argocd.argoproj.io/hook: PostSync
argocd.argoproj.io/hook-delete-policy: HookSucceeded
spec:
template:
spec:
serviceAccountName: openbao-init &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
initContainers:
- name: extract-keys
image: quay.io/codefreshplugins/curl-jq
command:
- /bin/sh
- -c
- |
if [ ! -f /secrets/init.json ]; then
echo &amp;#34;Secret not found; run init Job first.&amp;#34;
exit 1
fi
for i in 0 1 2; do
jq -r &amp;#34;.unseal_keys_b64[$i]&amp;#34; /secrets/init.json | tr -d &amp;#39;\n&amp;#39; &amp;gt; &amp;#34;/shared/key$i&amp;#34; &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
done
echo &amp;#34;Unseal keys extracted to shared volume.&amp;#34;
volumeMounts:
- name: init-secret
mountPath: /secrets
readOnly: true
- name: shared
mountPath: /shared
containers: &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
- name: unseal
image: bitnami/kubectl:latest
command: &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
- /bin/sh
- -c
- |
if [ -f /etc/openbao-ca/ca.crt ]; then
export BAO_CACERT=/etc/openbao-ca/ca.crt
export BAO_ADDR=https://openbao:8200
echo &amp;#34;Using TLS CA from /etc/openbao-ca/ca.crt&amp;#34;
else
export BAO_ADDR=http://openbao:8200
echo &amp;#34;Using plain HTTP&amp;#34;
fi
sleep_timer=30
if [ ! -f /shared/key0 ]; then
echo &amp;#34;Keys not found; extract-keys init container may have failed.&amp;#34;
exit 1
fi
echo &amp;#34;Unsealing pods openbao-0, openbao-1, openbao-2 (3 key steps each), ${sleep_timer}s delay between pods...&amp;#34;
for pod in openbao-0 openbao-1 openbao-2; do
echo &amp;#34;Unsealing $pod...&amp;#34;
for i in 0 1 2; do
key=$(cat &amp;#34;/shared/key$i&amp;#34; | tr -d &amp;#39;\n&amp;#39;)
kubectl exec -n openbao &amp;#34;$pod&amp;#34; -- sh -c &amp;#39;bao operator unseal &amp;#34;$1&amp;#34;&amp;#39; _ &amp;#34;$key&amp;#34; || true
done
echo &amp;#34;$pod unsealed.&amp;#34;
if [ &amp;#34;$pod&amp;#34; != &amp;#34;openbao-2&amp;#34; ]; then
echo &amp;#34;Waiting ${sleep_timer}s for next pod to be ready...&amp;#34;
SLEEPER_TMP=1
while [ &amp;#34;$SLEEPER_TMP&amp;#34; -le &amp;#34;$sleep_timer&amp;#34; ]; do
if [ $(( SLEEPER_TMP % 10 )) -eq 0 ]; then
echo -n &amp;#34;$SLEEPER_TMP&amp;#34;
else
echo -n &amp;#34;.&amp;#34;
fi
sleep 1
SLEEPER_TMP=$(( SLEEPER_TMP + 1 ))
done
echo &amp;#34;&amp;#34;
fi
done
echo &amp;#34;Unseal complete.&amp;#34;
volumeMounts:
- name: shared
mountPath: /shared
readOnly: true
- name: openbao-ca
mountPath: /etc/openbao-ca
readOnly: true
volumes: &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
- name: init-secret
secret:
secretName: openbao-init-data
- name: shared
emptyDir: {}
- name: openbao-ca
secret:
secretName: openbao-ca-secret
optional: true
restartPolicy: Never
backoffLimit: 2&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;The ServiceAccount is used by the Job to unseal the OpenBao pods. Here we are using the same ServiceAccount as the one used to initialise the OpenBao service.&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;The init container extracts unseal keys from init.json using jq and writes them to a shared volume.&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;The main container runs kubectl to exec into each OpenBao pod and run &lt;code&gt;bao operator unseal&lt;/code&gt; (the OpenBao image is used inside the target pods).&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;The command is used to unseal the OpenBao service.&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;The volumes that are mounted: init-secret (contains the unseal keys), shared (contains the extracted unseal keys, shared between containers), openbao-ca (contains the CA certificate).&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&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;
Both Jobs are using the same ServiceAccount and therefore the same RBAC rules. It is also possible to use a different ServiceAccount for the unseal Job.
&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;
Store init data securely. Keeping unseal keys in a cluster Secret is convenient but less secure than auto-unseal or an external secrets manager. Restrict access to the &lt;code&gt;openbao-init-data&lt;/code&gt; Secret (e.g. RBAC and network policies) and consider rotating or moving keys to a more secure store after bootstrap.
&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 class="sect2"&gt;
&lt;h3 id="_approach_3_auto_unseal_with_key_management_service_kms_recommended_for_production"&gt;Approach 3: Auto-unseal with Key Management Service KMS (recommended for production)&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Configure a KMS (e.g. AWS KMS, Azure Key Vault, GCP Cloud KMS, Transit etc.) so that OpenBao unseals itself on every restart. You initialise &lt;strong&gt;once&lt;/strong&gt; and thereafter no manual unseal step is needed.&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;
The rest of this article will mention the different options. However, I can only demonstrate a real example using the AWS KMS option.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="sect3"&gt;
&lt;h4 id="_auto_unseal_options_overview"&gt;Auto-unseal options overview&lt;/h4&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Without auto-unseal, you must run &lt;code&gt;bao operator unseal&lt;/code&gt; with a threshold of keys after each restart. With auto-unseal, OpenBao uses a seal backend to protect the root key and unseals itself on startup.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Supported options:&lt;/p&gt;
&lt;/div&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: 50%;"/&gt;
&lt;/colgroup&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Option&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Use case&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Notes&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;1. AWS KMS&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;AWS (EKS, OpenShift on AWS)&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;IRSA or IAM user; no keys in cluster with IRSA.&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;2. Azure Key Vault&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Azure (AKS, OpenShift on Azure)&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Service principal or managed identity.&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;3. Google Cloud KMS&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;GCP (GKE)&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Service account key or Workload Identity.&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;5. Transit&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Existing Vault/OpenBao as root of trust; or a &lt;strong&gt;dedicated seal-only OpenBao&lt;/strong&gt; that exists only to unseal production&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;External transit engine; token in a Secret.&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class="sect3"&gt;
&lt;h4 id="_why_use_openbao_when_the_seal_is_in_kms_why_not_keep_all_secrets_in_kms_and_skip_openbao"&gt;Why use OpenBao when the seal is in KMS? Why not keep all secrets in KMS and skip OpenBao?&lt;/h4&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;When using a cloud KMS (or Key Vault) for auto-unseal, a natural question is: why not store and retrieve &lt;strong&gt;all&lt;/strong&gt; secrets from the KMS and run without OpenBao at all? OpenBao remains useful for operators and applications for several reasons:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;KMS is for keys, not a full secrets platform.&lt;/strong&gt; AWS KMS, Azure Key Vault keys, and GCP Cloud KMS are built to encrypt and decrypt small blobs (e.g. data encryption keys, or OpenBao’s seal blob). They are not a general-purpose secrets store with versioning, path-based access, dynamic credentials, and per-request audit. OpenBao provides that layer: applications request secrets by path, get short-lived tokens, and you get a single place to define who can read what.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Dynamic secrets.&lt;/strong&gt; OpenBao can generate short-lived credentials on demand (e.g. database users, cloud IAM roles, PKI certificates). The KMS does not create or rotate such credentials; it only holds keys. If you need “give this pod a DB password that expires in 1 hour,” that is OpenBao’s domain, not the KMS.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Fine-grained, identity-aware access.&lt;/strong&gt; OpenBao supports auth methods (Kubernetes, OIDC, AppRole, LDAP) and path-based policies. You can say “this service account may read only &lt;code&gt;secret/data/myapp/*&lt;/code&gt;.” The KMS has IAM or Key Vault RBAC, but not the same model of “one API, many identities, many paths” that applications and operators use day to day.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Audit and compliance.&lt;/strong&gt; OpenBao logs every secret read and write with identity and path. That gives a clear audit trail of who accessed which secret and when. KMS audit logs key usage, which is different from “which application read which secret at what time” in a unified way.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Abstraction and portability.&lt;/strong&gt; Applications talk to OpenBao’s API. You can move the seal or the storage backend (or even the cloud) without changing how applications request secrets. If you put everything directly in a cloud KMS, you tie every app to that vendor’s API and limits.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Encryption as a service (Transit).&lt;/strong&gt; OpenBao’s Transit secrets engine lets applications encrypt data with a key without ever seeing the key—useful for application-level encryption and key rotation. KMS can do something similar, but OpenBao integrates that with the same auth and audit as the rest of your secrets.&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;
In short: the KMS is the &lt;strong&gt;root of trust&lt;/strong&gt; for the seal (so OpenBao can unseal itself). OpenBao is the &lt;strong&gt;secrets management platform&lt;/strong&gt; that applications and operators use for storing, retrieving, generating, and auditing secrets. Both layers complement each other.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;General flow (same for all options):&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;Set up the seal backend (KMS key, Key Vault, static key, or transit server).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add the appropriate &lt;code&gt;seal &amp;#34;…​&amp;#34;&lt;/code&gt; stanza to OpenBao configuration (e.g. in Helm &lt;code&gt;server.ha.raft.config&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Deploy OpenBao; pods start and remain sealed until initialised.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Run one-time initialisation: &lt;code&gt;bao operator init -recovery-shares=5 -recovery-threshold=3&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;From then on, restarts are automatically unsealed.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect3"&gt;
&lt;h4 id="_option_aws_kms"&gt;Option: AWS KMS&lt;/h4&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Best for: EKS, OpenShift on AWS (ROSA), or any Kubernetes on AWS.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect4"&gt;
&lt;h5 id="_create_a_kms_key"&gt;Create a KMS key&lt;/h5&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Create a &lt;strong&gt;symmetric&lt;/strong&gt; KMS key used only for the OpenBao seal (do not use for application data).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;AWS WebUI:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;search for: KMS → Create key → Symmetric, Encrypt/Decrypt&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Optionally set an alias (e.g. &lt;code&gt;openbao-unseal&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;OPTIONAL: Define key administrative permissions&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;OPTIONAL: Define key usage permissions&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Edit key policy&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;AWS CLI:&lt;/strong&gt; (requires the AWS CLI to be installed and configured)&lt;/p&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;aws kms create-key \
--description &amp;#34;OpenBao auto-unseal key&amp;#34; \
--key-usage ENCRYPT_DECRYPT
aws kms create-alias \
--alias-name openbao-unseal \
--target-key-id &amp;lt;key-id-from-above&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Note the &lt;strong&gt;Key ID&lt;/strong&gt; or &lt;strong&gt;Alias&lt;/strong&gt; and the &lt;strong&gt;region&lt;/strong&gt; (e.g. &lt;code&gt;us-west-1&lt;/code&gt;) from the output of the command.&lt;/p&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect4"&gt;
&lt;h5 id="_key_policy_for_openbao"&gt;Key policy for OpenBao&lt;/h5&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The OpenBao process needs permission to use the key. Create an IAM policy. In the UI you can configure this directly when creating the key. When using the CLI, create a &lt;code&gt;policy.json&lt;/code&gt; file with the following content:&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-json hljs" data-lang="json"&gt;{
&amp;#34;Version&amp;#34;: &amp;#34;2012-10-17&amp;#34;,
&amp;#34;Statement&amp;#34;: [
{
&amp;#34;Effect&amp;#34;: &amp;#34;Allow&amp;#34;,
&amp;#34;Action&amp;#34;: [
&amp;#34;kms:Encrypt&amp;#34;,
&amp;#34;kms:Decrypt&amp;#34;,
&amp;#34;kms:DescribeKey&amp;#34;
],
&amp;#34;Resource&amp;#34;: &amp;#34;arn:aws:kms:&amp;lt;REGION&amp;gt;:&amp;lt;ACCOUNT_ID&amp;gt;:key/&amp;lt;KEY_ID&amp;gt;&amp;#34; &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&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;Replace &lt;code&gt;&amp;lt;REGION&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;ACCOUNT_ID&amp;gt;&lt;/code&gt;, and &lt;code&gt;&amp;lt;KEY_ID&amp;gt;&lt;/code&gt; with your values (or use the key ARN).&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect4"&gt;
&lt;h5 id="_create_an_iam_user_for_the_seal"&gt;Create an IAM user for the seal&lt;/h5&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;When using static credentials (e.g. on OpenShift or when IRSA is not available), create a dedicated IAM user that has permission only to use the KMS key above. Use that user’s access keys in the Kubernetes Secret.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;AWS WebUI:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;search for: IAM → Users → Create user&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;User name: e.g. &lt;code&gt;openbao-seal&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;DO NOT select &amp;#34;Provide user access to the AWS Management Console&amp;#34; (programmatic access only)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create user&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Open the created user → Permissions → Add permissions → Create inline policy (or Attach policies → Create policy)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In the policy editor, choose JSON and paste the policy from &lt;a href="#_key_policy_for_openbao"&gt;Key policy for OpenBao&lt;/a&gt;, replacing &lt;code&gt;&amp;lt;REGION&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;ACCOUNT_ID&amp;gt;&lt;/code&gt;, and &lt;code&gt;&amp;lt;KEY_ID&amp;gt;&lt;/code&gt; with your KMS key ARN or alias&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Name the policy (e.g. &lt;code&gt;OpenBaoSealKMS&lt;/code&gt;) and attach it to the user&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Security credentials → Create access key → Application running outside AWS (or &amp;#34;Third-party service&amp;#34;) → Create access key&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Save the Access key ID and Secret access key; you will put them in the Kubernetes Secret &lt;code&gt;openbao-aws-credentials&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;AWS CLI:&lt;/strong&gt; (requires the AWS CLI to be installed and configured)&lt;/p&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Create a policy file (e.g. &lt;code&gt;openbao-seal-policy.json&lt;/code&gt;) with the same JSON as in &lt;a href="#_key_policy_for_openbao"&gt;Key policy for OpenBao&lt;/a&gt;, then create the user, attach the policy, and create access keys:&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;# Replace REGION, ACCOUNT_ID, and KEY_ID in the policy (or use alias: arn:aws:kms:REGION:ACCOUNT_ID:alias/openbao-unseal)
aws iam create-policy \
--policy-name OpenBaoSealKMS \
--policy-document file://openbao-seal-policy.json
aws iam create-user --user-name openbao-seal
aws iam attach-user-policy \
--user-name openbao-seal \
--policy-arn arn:aws:iam::ACCOUNT_ID:policy/OpenBaoSealKMS
aws iam create-access-key --user-name openbao-seal&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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;
The last command returns &lt;code&gt;AccessKeyId&lt;/code&gt; and &lt;code&gt;SecretAccessKey&lt;/code&gt;; store them in the Secret &lt;code&gt;openbao-aws-credentials&lt;/code&gt; with key &lt;code&gt;AWS_REGION&lt;/code&gt; set to your region (e.g. &lt;code&gt;us-west-1&lt;/code&gt;).
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect4"&gt;
&lt;h5 id="_create_the_secret"&gt;Create the Secret&lt;/h5&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Create the Secret &lt;code&gt;openbao-aws-credentials&lt;/code&gt; in the &lt;code&gt;openbao&lt;/code&gt; namespace. It contains the access key, secret access key, and region.&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: Secret
metadata:
name: openbao-aws-credentials
namespace: openbao
type: Opaque
stringData: &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
AWS_ACCESS_KEY_ID: &amp;lt;your-access-key&amp;gt;
AWS_SECRET_ACCESS_KEY: &amp;lt;your-secret-key&amp;gt;
AWS_REGION: &amp;lt;your-region&amp;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;Replace &lt;code&gt;&amp;lt;your-access-key&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;your-secret-key&amp;gt;&lt;/code&gt;, and &lt;code&gt;&amp;lt;your-region&amp;gt;&lt;/code&gt; with the values from the previous steps.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect4"&gt;
&lt;h5 id="_helm_argo_cd_configuration_aws"&gt;Helm / Argo CD configuration (AWS)&lt;/h5&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Add the &lt;code&gt;seal &amp;#34;awskms&amp;#34;&lt;/code&gt; block inside the same HCL config that contains &lt;code&gt;listener&lt;/code&gt;, &lt;code&gt;storage &amp;#34;raft&amp;#34;&lt;/code&gt;. In Helm values this is typically under &lt;code&gt;openbao.server.ha.raft.config&lt;/code&gt;.&lt;/p&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;
Be sure to update the region and key ID.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The values file we used for Argo CD can be found at: &lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/blob/main/clusters/management-cluster/openbao/values.yaml" target="_blank" rel="noopener"&gt;OpenBao Argo CD values file&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;openbao:
server:
extraSecretEnvironmentVars: &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
- envName: AWS_ACCESS_KEY_ID
secretName: openbao-aws-credentials
secretKey: AWS_ACCESS_KEY_ID
- envName: AWS_SECRET_ACCESS_KEY
secretName: openbao-aws-credentials
secretKey: AWS_SECRET_ACCESS_KEY
- envName: AWS_REGION
secretName: openbao-aws-credentials
secretKey: AWS_REGION
ha:
raft:
config: |
ui = true
seal &amp;#34;awskms&amp;#34; { &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
region = &amp;#34;us-east-1&amp;#34;
kms_key_id = &amp;#34;alias/openbao-unseal&amp;#34;
}
listener &amp;#34;tcp&amp;#34; {
tls_disable = 0
address = &amp;#34;[::]:8200&amp;#34;
cluster_address = &amp;#34;[::]:8201&amp;#34;
tls_cert_file = &amp;#34;/openbao/tls/openbao-server-tls/tls.crt&amp;#34;
tls_key_file = &amp;#34;/openbao/tls/openbao-server-tls/tls.key&amp;#34;
tls_min_version = &amp;#34;tls12&amp;#34;
}
storage &amp;#34;raft&amp;#34; {
path = &amp;#34;/openbao/data&amp;#34;
}
service_registration &amp;#34;kubernetes&amp;#34; {}&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;If you use static credentials (e.g. for OpenShift), inject them via a Secret.&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;This must be added. Be sure to update your region and key ID.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&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 &lt;code&gt;seal &amp;#34;awskms&amp;#34;&lt;/code&gt; block alone is not enough. OpenBao must have AWS credentials available at runtime. If you see an error such as &lt;code&gt;NoCredentialProviders: no valid providers in chain&lt;/code&gt; or &lt;code&gt;error fetching AWS KMS wrapping key information&lt;/code&gt;, the pod has no credentials. You must either inject static credentials from a Secret (see below) or use IRSA so the pod can assume an IAM 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="sect2"&gt;
&lt;h3 id="_approach_4_init_container_with_external_secret_store"&gt;Approach 4: Init container with external secret store&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Another option is to use an init container that retrieves secrets from an external source. Two use cases can be considered here:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Shamir unseal:&lt;/strong&gt; The init container fetches the unseal keys from an external secret manager and then runs &lt;code&gt;bao operator unseal&lt;/code&gt; (or writes keys to a file used by a sidecar/unseal script). This is the classic “external secret store” idea (e.g. using AWS Secrets Manager, HashiCorp Vault, another Kubernetes cluster).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Transit seal token:&lt;/strong&gt; The init container fetches the &lt;strong&gt;token&lt;/strong&gt; that production OpenBao uses to call the dedicated unseal service (see &lt;a href="#_dedicated_unseal_service_seal_only_openbao"&gt;Dedicated unseal service (seal-only OpenBao)&lt;/a&gt;). That token is written to a file or shared volume so the main OpenBao process can use it in &lt;code&gt;seal &amp;#34;transit&amp;#34;&lt;/code&gt;. The token never lives in a Kubernetes Secret in Git or in plain YAML; it is fetched at pod start from the external store.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Below is a generic placeholder for the init container.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect3"&gt;
&lt;h4 id="_option_init_container_example"&gt;Option: init Container example&lt;/h4&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;
The following options are generic and more theoretical. Unlike the other options, I could not test them in a real environment yet. This may be covered in a future article.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Generic placeholder (implement according to your secret store):&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;# Additional values for the Helm chart
server:
extraInitContainers:
- name: fetch-secrets
image: registry.access.redhat.com/ubi9/ubi-minimal:latest
command:
- /bin/sh
- -c
- |
# Fetch unseal keys OR transit token from external secret manager
echo &amp;#34;Waiting for OpenBao to be ready...&amp;#34;
sleep 30
# Your fetch logic here (e.g. aws secretsmanager get-secret-value, vault kv get, curl to API) &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
env:
- name: EXTERNAL_SECRET_ENDPOINT &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
value: &amp;#34;https://your-external-secret-store.example.com&amp;#34;&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;Replace with your fetch logic.&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;Replace with your external secret store endpoint.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;div class="sect3"&gt;
&lt;h4 id="_option_transit_seal"&gt;Option: Transit seal&lt;/h4&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;When you already have a Vault or OpenBao cluster and want it to act as the root of trust (e.g. central Vault encrypts the seal key), you can use this option.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To prepare everything, you need to do the following:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Enable the &lt;strong&gt;Transit&lt;/strong&gt; secrets engine on the external Vault/OpenBao and create a key (e.g. &lt;code&gt;openbao-unseal&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Grant the token used by this OpenBao permission to encrypt/decrypt with that key.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Store the token in a Kubernetes Secret and inject via &lt;code&gt;extraSecretEnvironmentVars&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="admonitionblock caution"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-caution" title="Caution"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Do not put the token in the config file.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The following HCL configuration is used to configure the OpenBao cluster to use the transit seal.&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-hcl hljs" data-lang="hcl"&gt;seal &amp;#34;transit&amp;#34; {
address = &amp;#34;https://external-vault.example.com:8200&amp;#34;
token = &amp;#34;s.xxx&amp;#34;
disable_renewal = &amp;#34;false&amp;#34;
key_name = &amp;#34;openbao-unseal&amp;#34;
mount_path = &amp;#34;transit/&amp;#34;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;See the &lt;a href="https://openbao.org/docs/configuration/seal/transit/" target="_blank" rel="noopener"&gt;OpenBao transit seal&lt;/a&gt; documentation for further details.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect4"&gt;
&lt;h5 id="_dedicated_unseal_service_seal_only_openbao"&gt;Dedicated unseal service (seal-only OpenBao)&lt;/h5&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;A common and recommended pattern is to run a &lt;strong&gt;separate, standalone OpenBao instance&lt;/strong&gt; whose &lt;strong&gt;only&lt;/strong&gt; role is to provide the seal for your production OpenBao. In other words: the external OpenBao holds the unseal keys—via its Transit secrets engine—and the production OpenBao cluster uses the transit seal to auto-unseal by calling that external instance. No application secrets or other workloads run on the dedicated instance; it exists solely to unseal the production OpenBao.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong&gt;Why use a dedicated unseal service?&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Separation of concerns:&lt;/strong&gt; The root of trust for unsealing lives outside the production cluster. If the production OpenBao is compromised or rebuilt, the seal keys remain in the dedicated instance.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Simpler operations:&lt;/strong&gt; You unseal and manage only one small, locked-down OpenBao (the seal service). The production OpenBao unseals itself automatically via transit.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;No cloud KMS required:&lt;/strong&gt; On-premise or air-gapped environments can use this pattern instead of AWS KMS, Azure Key Vault, or GCP Cloud KMS.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Clear trust boundary:&lt;/strong&gt; The dedicated instance can be hardened, network-isolated, and backed up independently.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong&gt;Architecture (high level):&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Dedicated unseal service:&lt;/strong&gt; A standalone OpenBao (single node is often enough) with the Transit secrets engine enabled and a dedicated transit key (e.g. &lt;code&gt;production-openbao-unseal&lt;/code&gt;). This instance is initialised and unsealed once; you keep its unseal keys and root token very secure. It does not store application secrets.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Production OpenBao:&lt;/strong&gt; Your HA OpenBao cluster (e.g. in Kubernetes) is configured with &lt;code&gt;seal &amp;#34;transit&amp;#34;&lt;/code&gt; pointing at the dedicated service URL and a token that has permission only to use that transit key. On startup, each production node asks the dedicated OpenBao to decrypt its seal blob and thus unseals automatically.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_migration_from_shamir_to_auto_unseal"&gt;Migration from Shamir to auto-unseal&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;You might wonder if you can migrate from Shamir to auto-unseal. The answer is yes, you can.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;If OpenBao was previously initialised with &lt;strong&gt;Shamir&lt;/strong&gt; unseal keys and you want to switch to any auto-unseal backend, you can do the following:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;Plan a short maintenance window. Raft HA will tolerate one node at a time.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add the appropriate &lt;code&gt;seal &amp;#34;…​&amp;#34;&lt;/code&gt; block to the config and deploy (e.g. via Argo CD).&lt;/p&gt;
&lt;div class="admonitionblock caution"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-caution" title="Caution"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Do NOT re-run init.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Unseal the &lt;strong&gt;leader&lt;/strong&gt; with the existing Shamir unseal keys.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Run &lt;strong&gt;seal migration&lt;/strong&gt; on the leader:&lt;/p&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;bao operator unseal -migrate&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Restart the leader. It should auto-unseal via the new seal.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Update and restart standby nodes. They should auto-unseal as well.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&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/concepts/seal/" target="_blank" rel="noopener"&gt;OpenBao seal concepts&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://openbao.org/docs/platform/k8s/" target="_blank" rel="noopener"&gt;OpenBao on Kubernetes&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://openbao.org/docs/configuration/seal/awskms/" target="_blank" rel="noopener"&gt;AWS KMS seal&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://openbao.org/docs/configuration/seal/azurekeyvault/" target="_blank" rel="noopener"&gt;Azure Key Vault seal&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://openbao.org/docs/configuration/seal/gcpckms/" target="_blank" rel="noopener"&gt;Google Cloud KMS seal&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://openbao.org/docs/configuration/seal/static/" target="_blank" rel="noopener"&gt;Static seal&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://openbao.org/docs/configuration/seal/transit/" target="_blank" rel="noopener"&gt;Transit seal&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item><item><title>The Guide to OpenBao - GitOps Deployment with Argo CD - Part 5</title><link>https://blog.stderr.at/openshift-platform/security/secrets-management/openbao/2026-02-20-openbao-part-5-gitops-deployment/</link><pubDate>Fri, 20 Feb 2026 00:00:00 +0000</pubDate><guid>https://blog.stderr.at/openshift-platform/security/secrets-management/openbao/2026-02-20-openbao-part-5-gitops-deployment/</guid><description>&lt;div class="paragraph"&gt;
&lt;p&gt;Following the GitOps mantra &amp;#34;If it is not in Git, it does not exist&amp;#34;, this article demonstrates how to deploy and manage OpenBao using Argo CD. This approach provides version control, audit trails, and declarative management for your secret management infrastructure.&lt;/p&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;Deploying OpenBao via GitOps offers significant advantages:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Version Control&lt;/strong&gt;: All configuration changes are tracked in Git&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Audit Trail&lt;/strong&gt;: Who changed what, when, and why&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Declarative&lt;/strong&gt;: Desired state is defined, not imperative commands&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Reproducible&lt;/strong&gt;: Same deployment process across environments&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Self-healing&lt;/strong&gt;: Argo CD ensures actual state matches desired state&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;However, there are challenges specific to secret management:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Initial unsealing requires manual intervention or automationxw&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Root tokens and unseal keys must be handled carefully&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Chicken-and-egg problem: How to store OpenBao secrets before OpenBao exists?&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;
This article will focus on the deployment of the applicaiotns for the OpenBao deployment. The problem with the automatic unsealing will be addressed in the next article.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_prerequisites"&gt;Prerequisites&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Before you begin, ensure you have:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;OpenShift GitOps (Argo CD) installed and configured&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A Git repository for your cluster configuration&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Understanding of the App-of-Apps pattern (see &lt;a href="https://blog.stderr.at/gitopscollection/2024-04-02-configure_app_of_apps/"&gt;Configure App-of-Apps&lt;/a&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The official OpenBao Helm chart from &lt;a href="https://blog.stderr.at/openshift-platform/security/secrets-management/openbao/2026-02-13-openbao-part-3-openshift-deployment/"&gt;Part 3&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_the_wrapper_helm_chart"&gt;The (Wrapper) Helm Chart&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The official OpenBao Helm Chart works well for deploying OpenBao itself. However, additional settings such as certificates are not covered there. Therefore, I created a (wrapper) Helm Chart that includes the official OpenBao Helm Chart, a &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/cert-manager" target="_blank" rel="noopener"&gt;Cert-Manager Helm Chart&lt;/a&gt; that I created a while ago, and allows you to add additional objects in the templates folder.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;I will follow the same setup as discussed in &lt;a href="https://blog.stderr.at/openshift-platform/security/secrets-management/openbao/2026-02-17-openbao-part-4-enabling-tls"&gt;Part 4&lt;/a&gt;. This means:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;Create Issuer for Cert-Manager&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create CA Certificate for OpenBao&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create Certificate for OpenBao Server&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create Certificate for OpenBao Agent Injector&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install OpenBao Helm Chart&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;However, there is &lt;strong&gt;one significant issue&lt;/strong&gt; with this approach: we need the certificate details (especially the CA certificate) before we can install the OpenBao Helm Chart, since the certificate must be referenced in the values file. This is a chicken-and-egg problem—particularly if you use self-signed CA certificates.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Since you typically create the CA certificate first and only once (or have it already), a separate Application for the CA certificate is a good approach. Once this is synchronised, the OpenBao Application can be updated and deployed.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This means we will need two Helm Charts: one for the CA certificate and one for OpenBao itself.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_helm_chart_for_the_ca_certificate"&gt;Helm Chart for the CA Certificate&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Create a new Helm Chart for the CA certificate. This will use the &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/cert-manager" target="_blank" rel="noopener"&gt;Cert-Manager Helm Chart&lt;/a&gt; as a dependency and will create the:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Issuer for Cert-Manager&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Request the CA certificate from the Issuer&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create the &lt;strong&gt;openbao&lt;/strong&gt; namespace&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The example I am using can be found at &lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/management-cluster/openbao-ca-certificate" target="_blank" rel="noopener"&gt;GitOps openbao-ca-certificate&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;
Please check the &lt;a href="https://blog.stderr.at/gitopscollection/2023-12-28-gitops-repostructure/" target="_blank" rel="noopener"&gt;GitOps Repository Structure&lt;/a&gt; and subsequent articles for more information on how to structure your Git repository.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;In the &lt;strong&gt;Chart.yaml&lt;/strong&gt; file you can see two dependencies:&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;dependencies:
- name: tpl
version: ~1.0.0
repository: https://charts.stderr.at/
- name: cert-manager
version: ~2.0.3 &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
repository: https://charts.stderr.at/
condition: cert-manager.enabled&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;The Cert-Manager Helm Chart version must be at least 2.0.3 to support the namespace parameter for the Issuer.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The &lt;strong&gt;tpl&lt;/strong&gt; dependency is a library that contains templates for the namespace and other shared components (I keep this library in my &lt;a href="https://blog.stderr.at/helm-charts/" target="_blank" rel="noopener"&gt;Helm Charts&lt;/a&gt; repository). The &lt;strong&gt;cert-manager&lt;/strong&gt; dependency is the Cert-Manager Helm Chart.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The &lt;strong&gt;values.yaml&lt;/strong&gt; file below contains the configuration for the Cert-Manager:&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;
All settings that are passed to a subchart (cert-manager) must be prefixed with the name of the subchart. In this case, &lt;code&gt;cert-manager.&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;namespace:
create: true &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
name: &amp;#34;openbao&amp;#34;
description: &amp;#34;OpenBao Namespace&amp;#34;
displayName: &amp;#34;OpenBao Namespace&amp;#34;
additionalLabels:
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/audit-version: latest
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/warn-version: latest
cert-manager: &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
enabled: true &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
issuer:
# Name of issuer
- name: openbao-selfsigned &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
# -- Syncwave to create this issuer
syncwave: 5 &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
# -- Type can be either ClusterIssuer or Issuer
type: Issuer
# -- Enable this issuer.
# @default -- false
enabled: true
# -- Namespace for Issuer (ignored for ClusterIssuer). Defaults to the default namespace when not set.
namespace: openbao &lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;(6)&lt;/b&gt;
# -- Create a selfSigned issuer. The SelfSigned issuer doesn&amp;#39;t represent a certificate authority as such, but instead denotes that certificates will &amp;#34;sign themselves&amp;#34; using a given private key.
selfSigned: true &lt;i class="conum" data-value="7"&gt;&lt;/i&gt;&lt;b&gt;(7)&lt;/b&gt;
certificates: &lt;i class="conum" data-value="8"&gt;&lt;/i&gt;&lt;b&gt;(8)&lt;/b&gt;
enabled: true
# List of certificates
certificate:
- name: openbao-ca
enabled: true
namespace: openbao
syncwave: &amp;#34;10&amp;#34;
secretName: openbao-ca-secret
duration: 87660h
dnsNames:
- openbao-ca
privateKey:
algorithm: ECDSA
size: 256
rotationPolicy: Always
isCA: true
# Reference to the issuer that shall be used.
issuerRef:
name: openbao-selfsigned
kind: Issuer&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;Create the &lt;strong&gt;openbao&lt;/strong&gt; namespace with various settings.&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;Enable the Cert-Manager subchart. All settings below will be passed to the Cert-Manager subchart.&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;Enables Cert-Manager.&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;Name of the issuer.&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;Syncwave for this issuer; it must be lower than the syncwave of the certificate.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;6&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Namespace for the issuer; supported since version 2.0.3 of the Cert-Manager Helm Chart.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="7"&gt;&lt;/i&gt;&lt;b&gt;7&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Creates a self-signed issuer.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="8"&gt;&lt;/i&gt;&lt;b&gt;8&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The certificate section and all the settings. Verify the &lt;a href="https://blog.stderr.at/openshift-platform/security/secrets-management/openbao/2026-02-17-openbao-part-4-enabling-tls/#_step_1_certificate_authority_ca_for_openbao"&gt;Part 4&lt;/a&gt; for more information.&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;
This chart creates the &lt;strong&gt;openbao&lt;/strong&gt; namespace, which is required for the OpenBao deployment.
&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="_helm_chart_for_the_openbao_deployment"&gt;Helm Chart for the OpenBao Deployment&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Create a new Helm Chart for the OpenBao deployment. This will use the official &lt;a href="https://github.com/openbao/openbao-helm" target="_blank" rel="noopener"&gt;OpenBao Helm chart&lt;/a&gt; and the &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/cert-manager" target="_blank" rel="noopener"&gt;Cert-Manager Helm Chart&lt;/a&gt; as dependencies and will create the:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;OpenBao deployment (including Route)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Issuer for the OpenBao server using the CA certificate&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Required OpenBao certificates based on the CA certificate&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;I have added the full values.yaml file below. Please check the &lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/blob/main/clusters/management-cluster/openbao/values.yaml" target="_blank" rel="noopener"&gt;GitOps openbao/values.yaml&lt;/a&gt; to fetch the full chart.&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;
Besides the two certificates added here in the values.yaml file, I made two further changes: fullnameOverride and nameOverride are both set to &lt;strong&gt;openbao&lt;/strong&gt;. This is good practice for setting the name of the deployment when using Argo CD.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="admonitionblock caution"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-caution" title="Caution"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
This values file contains the CA certificate in plain text. Whether that is acceptable is debatable—it is a public certificate, and here it is self-signed and used only in my demo environment.
&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;# Full values.yaml file for the OpenBao deployment
########################################################
# Cert-Manager
########################################################
cert-manager:
enabled: true
issuer:
# Name of issuer
- name: openbao-ca-issuer &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
# -- Syncwave to create this issuer
syncwave: &amp;#39;-1&amp;#39;
# -- Type can be either ClusterIssuer or Issuer
type: Issuer
# -- Enable this issuer.
# @default -- false
enabled: true
# -- Namespace for Issuer (ignored for ClusterIssuer). Defaults to the default namespace when not set.
namespace: openbao
ca:
secretName: openbao-ca-secret
certificates:
enabled: true
# List of certificates
certificate:
########################################################
# OpenBao Server TLS Certificate
########################################################
- name: openbao-server-tls &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
enabled: true
namespace: openbao
syncwave: &amp;#34;0&amp;#34;
secretName: openbao-server-tls
duration: 8760h
renewBefore: 720h
dnsNames:
- openbao.openbao.svc
- openbao.apps.ocp.aws.ispworld.at # Route host (adjust to your domain)
- openbao
- openbao.openbao
- openbao.openbao.svc
- openbao.openbao.svc.cluster.local
- openbao-internal
- openbao-internal.openbao
- openbao-internal.openbao.svc
- openbao-internal.openbao.svc.cluster.local
- openbao-0.openbao-internal
- openbao-0.openbao-internal.openbao
- openbao-0.openbao-internal.openbao.svc
- openbao-0.openbao-internal.openbao.svc.cluster.local
- openbao-1.openbao-internal
- openbao-1.openbao-internal.openbao
- openbao-1.openbao-internal.openbao.svc
- openbao-2.openbao-internal
- openbao-2.openbao-internal.openbao
- openbao-2.openbao-internal.openbao.svc
ipAddresses:
- 127.0.0.1
- &amp;#34;::1&amp;#34;
# Reference to the issuer that shall be used.
issuerRef:
name: openbao-ca-issuer
kind: Issuer
########################################################
# Injector TLS Certificate
########################################################
- name: injector-certificate &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
enabled: true
namespace: openbao
syncwave: &amp;#34;0&amp;#34;
secretName: injector-tls
duration: 24h
renewBefore: 144m
dnsNames:
- openbao-agent-injector-svc
- openbao-agent-injector-svc.openbao
- openbao-agent-injector-svc.openbao.svc
- openbao-agent-injector-svc.openbao.svc.cluster.local
# Reference to the issuer that shall be used.
issuerRef:
name: openbao-ca-issuer
kind: Issuer
########################################################
# OpenBao Deployment
########################################################
openbao: &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
# Override the full name of the deployment via Argo CD
fullnameOverride: openbao
nameOverride: openbao
global:
# Enable OpenShift-specific settings
openshift: true
# -- The namespace to deploy to. Defaults to the `helm` installation namespace.
namespace: openbao
# Required when TLS is enabled: tells the chart to use HTTPS for readiness/liveness
# probes and for in-pod API_ADDR (127.0.0.1:8200). Otherwise you get &amp;#34;client sent
# an HTTP request to an HTTPS server&amp;#34; from the probes.
tlsDisable: false
server:
extraEnvironmentVars:
BAO_CACERT: /openbao/tls/openbao-server-tls/ca.crt
# High Availability configuration
ha:
enabled: true
replicas: 3
# Raft storage configuration
raft:
enabled: true
setNodeId: true
config: |
ui = true
listener &amp;#34;tcp&amp;#34; {
tls_disable = 0
address = &amp;#34;[::]:8200&amp;#34;
cluster_address = &amp;#34;[::]:8201&amp;#34;
tls_cert_file = &amp;#34;/openbao/tls/openbao-server-tls/tls.crt&amp;#34;
tls_key_file = &amp;#34;/openbao/tls/openbao-server-tls/tls.key&amp;#34;
tls_min_version = &amp;#34;tls12&amp;#34;
telemetry {
unauthenticated_metrics_access = &amp;#34;true&amp;#34;
}
}
storage &amp;#34;raft&amp;#34; {
path = &amp;#34;/openbao/data&amp;#34;
retry_join {
leader_api_addr = &amp;#34;https://openbao-0.openbao-internal:8200&amp;#34;
leader_tls_servername = &amp;#34;openbao-0.openbao-internal&amp;#34;
leader_ca_cert_file = &amp;#34;/openbao/tls/openbao-server-tls/ca.crt&amp;#34;
}
retry_join {
leader_api_addr = &amp;#34;https://openbao-1.openbao-internal:8200&amp;#34;
leader_tls_servername = &amp;#34;openbao-1.openbao-internal&amp;#34;
leader_ca_cert_file = &amp;#34;/openbao/tls/openbao-server-tls/ca.crt&amp;#34;
}
retry_join {
leader_api_addr = &amp;#34;https://openbao-2.openbao-internal:8200&amp;#34;
leader_tls_servername = &amp;#34;openbao-2.openbao-internal&amp;#34;
leader_ca_cert_file = &amp;#34;/openbao/tls/openbao-server-tls/ca.crt&amp;#34;
}
}
service_registration &amp;#34;kubernetes&amp;#34; {}
telemetry {
prometheus_retention_time = &amp;#34;30s&amp;#34;
disable_hostname = true
}
route:
enabled: true
host: openbao.apps.ocp.aws.ispworld.at
tls:
# Route terminates client TLS; backend can use reencrypt or passthrough
termination: reencrypt
insecureEdgeTerminationPolicy: Redirect
destinationCACertificate: |
-----BEGIN CERTIFICATE-----
MIIBWzCCAQCgAwIBAgIQNdbg4KIu9oi6dDClE8drmjAKBggqhkjOPQQDAjAAMB4X
DTI2MDIxODA5NDIxNFoXDTM2MDIxODIxNDIxNFowADBZMBMGByqGSM49AgEGCCqG
SM49AwEHA0IABIeYw35/kEHvyctLtOA5xMlyQNxUtXtfBbZMUfPh6AN5MFjIGuNS
cn07a3EpSpfY6/3DaPpu+4wYNFlc+/qDNYajXDBaMA4GA1UdDwEB/wQEAwICpDAP
BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQgyRk5Vv9K0VULcCgga5mRg4O9kTAY
BgNVHREBAf8EDjAMggpvcGVuYmFvLWNhMAoGCCqGSM49BAMCA0kAMEYCIQCwQ7lZ
Q0jzUjJFzpTGkQjU2+OB159LIQMSbSQ7dz8nVQIhAIDa7f87tjQxDxbJio+/vJx2
awFaWnueGOOQpvwCcV/+
-----END CERTIFICATE-----
extraVolumes:
- type: secret
name: openbao-server-tls
path: /openbao/tls
readOnly: true
extraVolumeMounts:
- name: openbao-server-tls
mountPath: /openbao/tls
readOnly: true
# Resource requests and limits
resources:
requests:
memory: 256Mi
cpu: 250m
limits:
memory: 1Gi
cpu: 1000m
# Persistent volume for data
dataStorage:
enabled: true
size: 10Gi
# storageClass: &amp;#34;gp3-csi&amp;#34;
# Injector configuration
injector:
enabled: true
replicas: 2 # HA for the injector too
certs:
secretName: injector-tls
# For a private CA: set caBundle to the CA cert (PEM) so the Kubernetes API server trusts the injector webhook. E.g. oc get secret openbao-ca-secret -n openbao -o jsonpath=&amp;#39;{.data.ca\.crt}&amp;#39; | base64 -d
caBundle: &amp;#34;LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJXekNDQVFDZ0F3SUJBZ0lRTmRiZzRLSXU5b2k2ZERDbEU4ZHJtakFLQmdncWhrak9QUVFEQWpBQU1CNFgKRFRJMk1ESXhPREE1TkRJeE5Gb1hEVE0yTURJeE9ESXhOREl4TkZvd0FEQlpNQk1HQnlxR1NNNDlBZ0VHQ0NxRwpTTTQ5QXdFSEEwSUFCSWVZdzM1L2tFSHZ5Y3RMdE9BNXhNbHlRTnhVdFh0ZkJiWk1VZlBoNkFONU1GaklHdU5TCmNuMDdhM0VwU3BmWTYvM0RhUHB1KzR3WU5GbGMrL3FETllhalhEQmFNQTRHQTFVZER3RUIvd1FFQXdJQ3BEQVAKQmdOVkhSTUJBZjhFQlRBREFRSC9NQjBHQTFVZERnUVdCQlFneVJrNVZ2OUswVlVMY0NnZ2E1bVJnNE85a1RBWQpCZ05WSFJFQkFmOEVEakFNZ2dwdmNHVnVZbUZ2TFdOaE1Bb0dDQ3FHU000OUJBTUNBMGtBTUVZQ0lRQ3dRN2xaClEwanpVakpGenBUR2tRalUyK09CMTU5TElRTVNiU1E3ZHo4blZRSWhBSURhN2Y4N3RqUXhEeGJKaW8rL3ZKeDIKYXdGYVdudWVHT09RcHZ3Q2NWLysKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=&amp;#34;
certName: tls.crt
keyName: tls.key
# UI configuration
ui:
enabled: true&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;Issuer for OpenBao. The syncwave is set to &amp;#39;-1&amp;#39; to create the issuer before the certificate request and the OpenBao deployment.&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;Certificate for the OpenBao server, with all DNS names and IP addresses used to reach it. The syncwave is set to &amp;#39;0&amp;#39; so the certificate is created after the issuer.&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;Certificate for the OpenBao Agent Injector, with all DNS names used to reach it. The syncwave is set to &amp;#39;0&amp;#39; so the certificate is created after the issuer.&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;OpenBao deployment configuration; the two overrides fullnameOverride and nameOverride are both set to &lt;strong&gt;openbao&lt;/strong&gt;. See &lt;a href="https://blog.stderr.at/openshift-platform/security/secrets-management/openbao/2026-02-17-openbao-part-4-enabling-tls/#_step_5_helm_values_for_server_tls"&gt;Part 4&lt;/a&gt; for more information.&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="_creating_argo_cd_applications"&gt;Creating Argo CD Applications&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Whilst the Helm Charts are ready, we need to create the Argo CD Applications for the CA certificate and the OpenBao deployment.
If you read our blog carefully, you will notice that these Application resources are created automatically when I add something to the folder clusters/management-cluster :)
This is done by leveraging ApplicationSet resources and is fully described in the &lt;a href="https://blog.stderr.at/gitopscollection/2023-12-28-gitops-repostructure/" target="_blank" rel="noopener"&gt;GitOps Repository Structure&lt;/a&gt; and subsequent articles.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;For the sake of simplicity, I will show the created Application resources below. The setup is the same for both; they simply target a different path in the Git repository.&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;# Full Application resource for the OpenBao CA Certificate
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: in-cluster-openbao-ca-certificate
namespace: openshift-gitops
spec:
destination:
name: in-cluster &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
namespace: default &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
info:
- name: Description
value: ApplicationSet that Deploys on Management Cluster Configuration (using Git Generator)
project: in-cluster &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
source:
path: clusters/management-cluster/openbao-ca-certificate &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
repoURL: &amp;#39;https://github.com/tjungbauer/openshift-clusterconfig-gitops&amp;#39; &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
targetRevision: main
syncPolicy:
retry:
backoff:
duration: 5s
factor: 2
maxDuration: 3m
limit: 5
---
# Full Application resource for the OpenBao deployment
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: in-cluster-openbao
namespace: openshift-gitops
spec:
destination:
name: in-cluster &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
namespace: openbao &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
info:
- name: Description
value: ApplicationSet that Deploys on Management Cluster Configuration (using Git Generator)
project: in-cluster &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
source:
path: clusters/management-cluster/openbao &lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;(6)&lt;/b&gt;
repoURL: &amp;#39;https://github.com/tjungbauer/openshift-clusterconfig-gitops&amp;#39; &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
targetRevision: main
syncPolicy:
retry:
backoff:
duration: 5s
factor: 2
maxDuration: 3m
limit: 5&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;Target cluster; here, the local cluster&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;Namespace of the target cluster; here, the OpenBao deployment will be installed.&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;Argo CD Project (must exist)&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 to the Git repository for the CA certificate&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;URL of the Git repository&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;6&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Path to the OpenBao deployment&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This will create the following Applications:
- in-cluster-openbao-ca-certificate
- in-cluster-openbao&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;
The ApplicationSet adds the prefix &lt;strong&gt;in-cluster-&lt;/strong&gt; to each Application name so that they remain unique in Argo CD.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&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/part5_openbao_argocd.png" alt="OpenBao Argo CD Applications"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 1. Argo CD: OpenBao Argo CD Applications&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The first Application to synchronise is &lt;strong&gt;in-cluster-openbao-ca-certificate&lt;/strong&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;It creates the following:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Namespace&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;CA Issuer&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;CA Certificate&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Then synchronise &lt;strong&gt;in-cluster-openbao&lt;/strong&gt;. It creates:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;OpenBao deployment&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;OpenBao Agent Injector&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;OpenBao Server TLS Certificate&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;OpenBao Agent Injector TLS Certificate&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_openbao_is_runningwhat_next"&gt;OpenBao is running—what next?&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Deploying OpenBao via GitOps gives you version control and declarative management for your secret management infrastructure.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Whilst OpenBao is running and managed by Argo CD, the next step is to configure it: you will need to handle initialisation (for a new cluster) and unsealing (see &lt;a href="https://blog.stderr.at/openshift-platform/security/secrets-management/openbao/2026-02-13-openbao-part-3-openshift-deployment/"&gt;Part 3&lt;/a&gt; and &lt;a href="https://blog.stderr.at/openshift-platform/security/secrets-management/openbao/2026-02-17-openbao-part-4-enabling-tls/"&gt;Part 4&lt;/a&gt;). That manual approach does not scale. In the next article, I will discuss ways to automate initialisation and unsealing.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Key takeaways:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Use sync waves to control deployment order&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Consider auto-unseal for production (Part 6)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Store initialisation data securely outside Git&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&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://blog.stderr.at/gitopscollection/2024-04-02-configure_app_of_apps/" target="_blank" rel="noopener"&gt;Configure App-of-Apps&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://argo-cd.readthedocs.io/en/stable/user-guide/helm/" target="_blank" rel="noopener"&gt;Argo CD Helm Support&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://argo-cd.readthedocs.io/en/stable/user-guide/sync-waves/" target="_blank" rel="noopener"&gt;Argo CD Sync Waves&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item><item><title>The Guide to OpenBao - Enabling TLS on OpenShift - Part 4</title><link>https://blog.stderr.at/openshift-platform/security/secrets-management/openbao/2026-02-17-openbao-part-4-enabling-tls/</link><pubDate>Tue, 17 Feb 2026 00:00:00 +0000</pubDate><guid>https://blog.stderr.at/openshift-platform/security/secrets-management/openbao/2026-02-17-openbao-part-4-enabling-tls/</guid><description>&lt;div class="paragraph"&gt;
&lt;p&gt;In &lt;a href="https://blog.stderr.at/openshift-platform/security/secrets-management/openbao/2026-02-13-openbao-part-3-openshift-deployment/"&gt;Part 3&lt;/a&gt; we deployed OpenBao on OpenShift in HA mode with TLS disabled: the OpenShift Route terminates TLS at the edge, and traffic from the Route to the pods is plain HTTP. While this is ok for quick tests, for a production-ready deployment, you should consider TLS for the entire journey. This article explains why and how to enable TLS end-to-end using the cert-manager operator, what to consider, and the exact steps to achieve it.&lt;/p&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;Part 3 uses &lt;code&gt;tls_disable = 1&lt;/code&gt; in the OpenBao listener and relies on the OpenShift Route for TLS. That gives encryption between the client and the Route, but &lt;strong&gt;not&lt;/strong&gt; between the Route and the OpenBao pods or between pods (e.g. Raft). Enabling TLS on OpenBao itself adds encryption in transit everywhere and aligns with defense-in-depth and compliance requirements. After all, we are talking about a secrets management system here and should not compromise security.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This part assumes:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;OpenBao is already deployed as in &lt;a href="https://blog.stderr.at/openshift-platform/security/secrets-management/openbao/2026-02-13-openbao-part-3-openshift-deployment/"&gt;Part 3&lt;/a&gt; (HA with Raft, Agent Injector enabled).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;strong&gt;cert-manager operator&lt;/strong&gt; is installed and configured on your OpenShift cluster.&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;
The article &lt;a href="https://your-blog/openshift-platform/security/certificates/ssl-certificate-management/"&gt;SSL Certificate Management for OpenShift&lt;/a&gt; describes the setup and usage of the cert-manager operator as an example.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_why_enable_tls"&gt;Why Enable TLS?&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Enabling TLS for OpenBao makes sense for several reasons:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Encryption in transit&lt;/strong&gt;: Traffic between the Route and the pods, and between OpenBao peers (Raft), is encrypted. Secrets and tokens are never sent in plain text on the cluster network.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Defense in depth&lt;/strong&gt;: Even if the Route or network is misconfigured, backend traffic remains protected.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Compliance&lt;/strong&gt;: Many standards (e.g. PCI-DSS, SOC 2) require encryption in transit for sensitive data; TLS to the application (OpenBao) helps satisfy this.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Agent Injector&lt;/strong&gt;: The injector webhook is called by the Kubernetes API server. Using TLS for the webhook (with a valid certificate) is required for production and when running multiple replicas.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Consistency&lt;/strong&gt;: Using HTTPS everywhere simplifies client configuration and avoids mixing HTTP/HTTPS in the same environment.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_what_must_be_considered"&gt;What Must Be Considered?&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Two TLS contexts&lt;/strong&gt;: Each needs its own certificate and configuration.&lt;/p&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;OpenBao server (API and Raft)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Agent Injector (mutating webhook)&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Certificate SANs&lt;/strong&gt;: Server certificates must include &lt;strong&gt;all names&lt;/strong&gt; used to reach OpenBao: Route host, internal service names (e.g. openbao.openbao.svc, openbao-0.openbao-internal.openbao.svc), 127.0.0.1 and ::1 for in-pod traffic, and any external DNS you use. The injector certificate must match the injector Service DNS name (e.g. openbao-agent-injector-svc.openbao.svc).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;cert-manager&lt;/strong&gt;: Using cert-manager gives automatic issuance and renewal. You need a ClusterIssuer (or an Issuer) in the OpenBao namespace.&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;
In this example, we will use a self-signed CA for the OpenBao server and the Agent Injector. In a production environment, you should use a trusted CA.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;OpenShift Route&lt;/strong&gt;: With backend TLS enabled, you can keep the Route in reencrypt mode: the Route terminates TLS from the client and opens a new TLS connection to the pod. Alternatively, use passthrough if you want end-to-end TLS without re-encryption at the Route.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Raft join&lt;/strong&gt;: After switching to TLS, retry_join and cluster addresses must use https:// and the correct hostnames. Existing unseal keys and root token are unchanged; only the transport is different.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Clients&lt;/strong&gt;: CLI and applications must use https:// for BAO_ADDR and, if you use a private CA, BAO_CACERT (or the system trust store) so that the client trusts the server certificate.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_prerequisites"&gt;Prerequisites&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;OpenBao HA deployment from &lt;a href="https://blog.stderr.at/openshift-platform/security/secrets-management/openbao/2026-02-13-openbao-part-3-openshift-deployment/"&gt;Part 3&lt;/a&gt; (namespace &lt;code&gt;openbao&lt;/code&gt;, Helm release &lt;code&gt;openbao&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;cert-manager operator installed on OpenShift.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Sufficient rights to create Issuers, Certificates, and Secrets in the &lt;code&gt;openbao&lt;/code&gt; namespace.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_overview_of_steps"&gt;Overview of Steps&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;Create a &lt;strong&gt;Certificate Authority (CA)&lt;/strong&gt; in the &lt;code&gt;openbao&lt;/code&gt; namespace (or use an existing ClusterIssuer).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Issue a &lt;strong&gt;Certificate for the OpenBao server&lt;/strong&gt; (API + Raft) and store it in a Secret.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Issue a &lt;strong&gt;Certificate for the Agent Injector&lt;/strong&gt; and reference it in the Helm values.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Update &lt;strong&gt;Helm values&lt;/strong&gt; to mount the server cert and CA, and configure the listener and Raft for TLS.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Upgrade&lt;/strong&gt; the Helm release; initialize and unseal openbao-0 (new cluster) or re-unseal (existing).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Verify&lt;/strong&gt; access via HTTPS and configure clients (&lt;code&gt;BAO_ADDR&lt;/code&gt;, &lt;code&gt;BAO_CACERT&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_step_1_certificate_authority_ca_for_openbao"&gt;Step 1: Certificate Authority (CA) for OpenBao&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;If you already have a ClusterIssuer (e.g. Let’s Encrypt or an enterprise CA), you can use it for the server and injector certificates and skip this step. For a self-signed CA in the OpenBao namespace (typical for internal cluster TLS), create the following.&lt;/p&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;
This self-signed CA is only for testing purposes. In a production environment, you should use a trusted CA, preferably your own.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;Create a self-signed CA Issuer in the openbao namespace:&lt;/p&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;
You might have your own CA or a ClusterIssuer already. You can use them, instead of creating a new one.
&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: cert-manager.io/v1
kind: Issuer
metadata:
name: openbao-selfsigned
namespace: openbao &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
spec:
selfSigned: {} &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&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;The namespace where the OpenBao deployment is running.&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;The self-signed CA Issuer.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a self-signed CA Certificate in the openbao namespace:&lt;/p&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This will now actually request the CA certificate from the self-signed CA Issuer. This process is fully automated by cert-manager because everything is self-signed. The requested certificate will be stored in the secret openbao-ca-secret and is available immediately.&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: cert-manager.io/v1
kind: Certificate
metadata:
name: openbao-ca
namespace: openbao &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
spec:
isCA: true &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
commonName: OpenBao CA
secretName: openbao-ca-secret &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
duration: 87660h &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
privateKey: &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
algorithm: ECDSA
size: 256 &lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;(6)&lt;/b&gt;
rotationPolicy: Always &lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;(6)&lt;/b&gt;
issuerRef:
name: openbao-selfsigned &lt;i class="conum" data-value="7"&gt;&lt;/i&gt;&lt;b&gt;(7)&lt;/b&gt;
kind: Issuer &lt;i class="conum" data-value="8"&gt;&lt;/i&gt;&lt;b&gt;(8)&lt;/b&gt;
group: cert-manager.io&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;The namespace where the OpenBao deployment is running.&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;The certificate is a CA certificate.&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;The name of the secret where the certificate and key will be stored.&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;The duration of the certificate. In this case 10 years.&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;The private key algorithm and size.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;6&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The rotation policy of the private key.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="7"&gt;&lt;/i&gt;&lt;b&gt;7&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The issuer of the certificate.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="8"&gt;&lt;/i&gt;&lt;b&gt;8&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The kind of the issuer. Can be Issuer or ClusterIssuer.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_step_2_create_an_issuer_for_the_openbao_server_and_agent_injector"&gt;Step 2: Create an Issuer for the OpenBao Server and Agent Injector&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Create an Issuer for the OpenBao Server and Agent Injector. This Issuer will reference the CA certificate and key stored in the secret openbao-ca-secret.&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: cert-manager.io/v1
kind: Issuer
metadata:
name: openbao-ca-issuer
namespace: openbao
spec:
ca:
secretName: openbao-ca-secret&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The name of the secret where the CA certificate and key are stored.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_step_3_certificate_for_the_openbao_server"&gt;Step 3: Certificate for the OpenBao Server&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Now it is time to create the certificate for the OpenBao server. The server certificate must include &lt;strong&gt;every hostname&lt;/strong&gt; used to reach OpenBao: the Route host, the headless service, and each Raft member. Adjust the dnsNames and optional uris to match your cluster and Route.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Create the following Certificate object:&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: cert-manager.io/v1
kind: Certificate
metadata:
name: openbao-server-tls
namespace: openbao
spec:
secretName: openbao-server-tls &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
duration: 8760h &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
renewBefore: 720h &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
commonName: openbao.openbao.svc &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
dnsNames:
- openbao.apps.cluster.example.com # Route host (adjust to your domain) &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
- openbao &lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;(6)&lt;/b&gt;
- openbao.openbao
- openbao.openbao.svc
- openbao.openbao.svc.cluster.local
- openbao-internal
- openbao-internal.openbao
- openbao-internal.openbao.svc
- openbao-internal.openbao.svc.cluster.local
- openbao-0.openbao-internal
- openbao-0.openbao-internal.openbao
- openbao-0.openbao-internal.openbao.svc
- openbao-0.openbao-internal.openbao.svc.cluster.local
- openbao-1.openbao-internal
- openbao-1.openbao-internal.openbao
- openbao-1.openbao-internal.openbao.svc
- openbao-2.openbao-internal
- openbao-2.openbao-internal.openbao
- openbao-2.openbao-internal.openbao.svc
ipAddresses: &lt;i class="conum" data-value="7"&gt;&lt;/i&gt;&lt;b&gt;(7)&lt;/b&gt;
- 127.0.0.1
- &amp;#34;::1&amp;#34;
issuerRef:
name: openbao-ca-issuer &lt;i class="conum" data-value="8"&gt;&lt;/i&gt;&lt;b&gt;(8)&lt;/b&gt;
kind: Issuer
group: cert-manager.io&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;The name of the secret where the certificate and key will be stored.&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;The duration of the certificate. In this case 1 year.&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;The duration before the certificate is renewed. In this case 30 days.&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;The common name of the certificate. This is the service name of the OpenBao server.&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;The Route host. Adjust to your domain.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;6&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The headless service names, all should be included.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="7"&gt;&lt;/i&gt;&lt;b&gt;7&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Required for in-pod traffic: Readiness/liveness probes and local bao commands (e.g. raft join, operator unseal) connect to 127.0.0.1:8200. Without these IP SANs you get &amp;#34;tls: bad certificate&amp;#34; or &amp;#34;x509: cannot validate certificate for 127.0.0.1 because it doesn’t contain any IP SANs&amp;#34;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="8"&gt;&lt;/i&gt;&lt;b&gt;8&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The issuer of the certificate, this time openbao-ca-issuer.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;After a few moments the certificate will be ready and the secret will be created. The cert-manager will store the signed certificate and key in the Secret openbao-server-tls with keys tls.crt and tls.key. The Helm chart can mount this secret for the OpenBao listener.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_step_4_certificate_for_the_agent_injector"&gt;Step 4: Certificate for the Agent Injector&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The Agent Injector runs as a webhook; the Kubernetes API server calls it over TLS. The certificate must match the Service DNS name of the injector. Create a Certificate that references the same CA Issuer. In this example we use a short-lived certificate valid for 24 hours, renewed when 10% of the validity period remains.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Save as openbao-injector-cert.yaml:&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: cert-manager.io/v1
kind: Certificate
metadata:
name: injector-certificate
namespace: openbao
spec:
secretName: injector-tls &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
duration: 24h &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
renewBefore: 144m
commonName: Agent Inject Cert &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
dnsNames:
- openbao-agent-injector-svc &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
- openbao-agent-injector-svc.openbao
- openbao-agent-injector-svc.openbao.svc
- openbao-agent-injector-svc.openbao.svc.cluster.local
issuerRef:
name: openbao-ca-issuer
kind: Issuer
group: cert-manager.io&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;The name of the secret where the certificate and key will be stored.&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;The duration of the certificate. In this case 24 hours. (renewal is done 10% before expiry)&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;The injector service name.&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;The injector service name is defined by the Helm chart. If you override the injector service name, adjust dnsNames accordingly.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_step_5_helm_values_for_server_tls"&gt;Step 5: Helm Values for Server TLS&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;With all the certificates created, we can update the Helm values so that OpenBao uses the server certificate and listens with TLS. You need to:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Mount the Secret openbao-server-tls into the OpenBao pods.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set the listener to use tls_cert_file and tls_key_file and disable tls_disable.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Switch Raft retry_join and cluster addresses to https://.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add the environment variable BAO_CACERT to the OpenBao pods.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Before we start, we need to export the CA certificate from the secret openbao-ca-secret and save its value:&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 secret openbao-ca-secret -n openbao -o jsonpath=&amp;#39;{.data.ca\.crt}&amp;#39; | base64 -d&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This will return the certificate like this:&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;-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Save this, you will need it in the next step.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Now we need to create the Helm values file. This time we will enable TLS. Refer to Part 3 of this series to see what the initial values file looks like.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Create or update openbao-ha-values-tls.yaml (building on your Part 3 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-yaml hljs" data-lang="yaml"&gt;global:
# Enable OpenShift-specific settings
openshift: true
# Required when TLS is enabled: tells the chart to use HTTPS for readiness/liveness
# probes and for in-pod API_ADDR (127.0.0.1:8200). Otherwise you get &amp;#34;client sent
# an HTTP request to an HTTPS server&amp;#34; from the probes.
tlsDisable: false &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
server:
extraEnvironmentVars:
BAO_CACERT: /openbao/tls/openbao-server-tls/ca.crt &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
# High Availability configuration
ha:
enabled: true
replicas: 3
# Raft storage configuration
raft:
enabled: true
setNodeId: true
config: |
ui = true
listener &amp;#34;tcp&amp;#34; {
tls_disable = 0 &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
address = &amp;#34;[::]:8200&amp;#34;
cluster_address = &amp;#34;[::]:8201&amp;#34;
tls_cert_file = &amp;#34;/openbao/tls/openbao-server-tls/tls.crt&amp;#34; &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
tls_key_file = &amp;#34;/openbao/tls/openbao-server-tls/tls.key&amp;#34; &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
tls_min_version = &amp;#34;tls12&amp;#34; &lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;(6)&lt;/b&gt;
telemetry {
unauthenticated_metrics_access = &amp;#34;true&amp;#34;
}
}
storage &amp;#34;raft&amp;#34; {
path = &amp;#34;/openbao/data&amp;#34;
retry_join {
leader_api_addr = &amp;#34;https://openbao-0.openbao-internal:8200&amp;#34; &lt;i class="conum" data-value="7"&gt;&lt;/i&gt;&lt;b&gt;(7)&lt;/b&gt;
leader_tls_servername = &amp;#34;openbao-0.openbao-internal&amp;#34;
leader_ca_cert_file = &amp;#34;/openbao/tls/openbao-server-tls/ca.crt&amp;#34; &lt;i class="conum" data-value="8"&gt;&lt;/i&gt;&lt;b&gt;(8)&lt;/b&gt;
}
retry_join {
leader_api_addr = &amp;#34;https://openbao-1.openbao-internal:8200&amp;#34;
leader_tls_servername = &amp;#34;openbao-1.openbao-internal&amp;#34;
leader_ca_cert_file = &amp;#34;/openbao/tls/openbao-server-tls/ca.crt&amp;#34;
}
retry_join {
leader_api_addr = &amp;#34;https://openbao-2.openbao-internal:8200&amp;#34;
leader_tls_servername = &amp;#34;openbao-2.openbao-internal&amp;#34;
leader_ca_cert_file = &amp;#34;/openbao/tls/openbao-server-tls/ca.crt&amp;#34;
}
}
service_registration &amp;#34;kubernetes&amp;#34; {}
telemetry {
prometheus_retention_time = &amp;#34;30s&amp;#34;
disable_hostname = true
}
route:
enabled: true
host: openbao.apps.cluster.example.com
tls: &lt;i class="conum" data-value="9"&gt;&lt;/i&gt;&lt;b&gt;(9)&lt;/b&gt;
# Route terminates client TLS; backend can use reencrypt or passthrough
termination: reencrypt
insecureEdgeTerminationPolicy: Redirect
destinationCACertificate: | &lt;i class="conum" data-value="10"&gt;&lt;/i&gt;&lt;b&gt;(10)&lt;/b&gt;
-----BEGIN CERTIFICATE-----
# CA Certificate
-----END CERTIFICATE-----
extraVolumes: &lt;i class="conum" data-value="11"&gt;&lt;/i&gt;&lt;b&gt;(11)&lt;/b&gt;
- type: secret
name: openbao-server-tls
path: /openbao/tls
readOnly: true
extraVolumeMounts: &lt;i class="conum" data-value="12"&gt;&lt;/i&gt;&lt;b&gt;(12)&lt;/b&gt;
- name: openbao-server-tls
mountPath: /openbao/tls
readOnly: true
# Resource requests and limits
resources:
requests:
memory: 256Mi
cpu: 250m
limits:
memory: 1Gi
cpu: 1000m
# Persistent volume for data
dataStorage:
enabled: true
size: 10Gi
# storageClass: &amp;#34;gp3-csi&amp;#34;
# Injector configuration
injector:
enabled: true
replicas: 2 # HA for the injector too
certs: &lt;i class="conum" data-value="13"&gt;&lt;/i&gt;&lt;b&gt;(13)&lt;/b&gt;
secretName: injector-tls
# For a private CA: set caBundle to the CA cert (PEM) so the Kubernetes API server trusts the injector webhook. E.g. oc get secret openbao-ca-secret -n openbao -o jsonpath=&amp;#39;{.data.ca\.crt}&amp;#39; | base64 -d
caBundle: &amp;#34;BASE64_ENCODED_CA_CERTIFICATE&amp;#34;
certName: tls.crt
keyName: tls.key
# UI configuration
ui:
enabled: true&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;&lt;strong&gt;global.tlsDisable&lt;/strong&gt;: Set to false when the server listener uses TLS. This makes the chart use HTTPS for readiness/liveness probes and for the in-pod API_ADDR env var. If you leave it true (default), probes and local clients will use HTTP and you will see &amp;#34;client sent an HTTP request to an HTTPS server&amp;#34;.&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;The environment variable BAO_CACERT is set to the CA certificate file path. This is helpful to execute the boa command inside the container.&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;The listener tls_disable is set to 0 to enable TLS.&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;The listener tls_cert_file is set to the certificate file path.&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;The listener tls_key_file is set to the key file path.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;6&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The listener tls_min_version is set to tls12.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="7"&gt;&lt;/i&gt;&lt;b&gt;7&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The leader API address is set to the HTTPS address.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="8"&gt;&lt;/i&gt;&lt;b&gt;8&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The leader CA certificate file is set to the CA certificate file path.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="9"&gt;&lt;/i&gt;&lt;b&gt;9&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The Route tls termination is set to reencrypt.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="10"&gt;&lt;/i&gt;&lt;b&gt;10&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The destination CA certificate is set to the CA certificate. Be sure not to add any extra lines or spaces.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="11"&gt;&lt;/i&gt;&lt;b&gt;11&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The extra volumes are mounted at the /openbao/tls path.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="12"&gt;&lt;/i&gt;&lt;b&gt;12&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The extra volume mounts are mounted at the /openbao/tls path.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="13"&gt;&lt;/i&gt;&lt;b&gt;13&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The injector certs are set to the injector TLS secret name. The caBundle must be provided in base64 format.&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;
The secret key names must match what cert-manager writes: tls.crt and tls.key. The OpenBao Helm chart mounts each entry in extraVolumes at path + name (e.g. with path: /openbao/tls and name: openbao-server-tls the secret is mounted at /openbao/tls/openbao-server-tls/). The listener tls_cert_file and tls_key_file must use that full path.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&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;
Be sure to change the Route host in the Helm values to the one you are using. In addition, make sure that the CA certificate is added correctly to the Route. Do not add any extra lines or spaces and do not forget the | after destinationCACertificate: and the -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- lines.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_step_6_upgrade_the_helm_release"&gt;Step 6: Upgrade the Helm Release&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Perform a Helm upgrade so the new volumes and configuration are applied. Pods will restart and pick up TLS; Raft will use HTTPS for join and replication.&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;helm upgrade openbao openbao/openbao \
--namespace openbao \
--values openbao-ha-values-tls.yaml&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Watch the rollout. The first pod (openbao-0) will log &amp;#34;raft retry join initiated&amp;#34; and stay 0/1 Ready until it is initialized and unsealed (new cluster) or until it forms quorum (existing cluster).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_new_cluster_initialize_and_unseal_openbao_0"&gt;New cluster: initialize and unseal openbao-0&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;openbao-0 will not become Ready until it is initialized and unsealed. Fetch the certificate, use port-forward and talk to OpenBao over HTTPS with the CA:&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;# 1. Save the CA cert (for BAO_CACERT)
oc get secret openbao-ca-secret -n openbao -o jsonpath=&amp;#39;{.data.ca\.crt}&amp;#39; | base64 -d &amp;gt; openbao-ca.crt
# 2. Port-forward to openbao-0 (background)
oc port-forward openbao-0 8200:8200 -n openbao &amp;amp;
# 3. Use HTTPS and CA cert
export BAO_ADDR=&amp;#39;https://127.0.0.1:8200&amp;#39;
export BAO_CACERT=&amp;#34;$PWD/openbao-ca.crt&amp;#34;
# 4. Check status, then initialize (only once) and unseal 3 times
bao status
bao operator init -key-shares=5 -key-threshold=3 -format=json &amp;gt; openbao-init.json
bao operator unseal
bao operator unseal
bao operator unseal
bao status&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;After unsealing, openbao-0 becomes leader and goes 1/1 Ready. Then start openbao-1 and openbao-2 and join or unseal them as in Part 3 (use https://).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Perform the following steps for the other pods - join the raft cluster and unseal them (3 times):&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 -ti openbao-1 -- bao operator raft join https://openbao-0.openbao-internal:8200
# 3 times with 3 different unseal keys
oc exec -ti openbao-1 -- bao operator unseal
Unseal Key (will be hidden):&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Repeat the same steps for openbao-2.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_existing_cluster_re_unseal_after_restart"&gt;Existing cluster: re-unseal after restart&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The existing cluster is already initialized. Re-unseal the pods (and re-join the raft cluster if necessary).&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 -w
oc exec -ti openbao-1 -- bao operator raft join https://openbao-0.openbao-internal:8200
oc exec -ti openbao-1 -- bao operator unseal # three times; same for openbao-2&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Repeat the same steps for openbao-2.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_step_7_verify_and_use_https_from_clients"&gt;Step 7: Verify and Use HTTPS from Clients&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Verify server health over HTTPS (from inside the cluster):&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;curl -k https://openbao.apps.cluster.example.com/v1/sys/health | jq&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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;
Be sure to use the Route host in the Helm values.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;From your workstation, use the Route URL with HTTPS. If the Route host is signed by your internal CA, set BAO_CACERT to the CA file:&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;export BAO_ADDR=&amp;#39;https://openbao.apps.cluster.example.com&amp;#39;
export BAO_CACERT=&amp;#39;/path/to/openbao-ca.crt&amp;#39;
bao status&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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;
Be sure to use the Route host in the Helm values.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Agent Injector: New pods that use the injector should start without webhook certificate errors. Check injector logs if you see TLS or certificate errors:&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 logs -n openbao -l app.kubernetes.io/name=openbao-agent-injector -f&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_troubleshooting"&gt;Troubleshooting&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_mutatingwebhookconfiguration_conflict_vault_k8s"&gt;MutatingWebhookConfiguration conflict (vault-k8s)&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;conflict occurred while applying object ... MutatingWebhookConfiguration: Apply failed with 1 conflict: conflict with &amp;#34;vault-k8s&amp;#34; using ... .webhooks[name=&amp;#34;vault.hashicorp.com&amp;#34;].clientConfig.caBundle
this is a server-side apply (SSA) field ownership conflict. The OpenBao chart uses the same webhook name (vault.hashicorp.com) as the HashiCorp Vault agent injector for annotation compatibility. The clientConfig.caBundle field is still owned by a previous manager (e.g. a prior Vault Helm release or the Vault injector), so Helm cannot update it when you change the injector certificate.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong&gt;Fix option 1&lt;/strong&gt; – Delete the webhook and re-upgrade (recommended)&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Remove the MutatingWebhookConfiguration so Helm can recreate it and own all fields. There will be a short window where the injector webhook is missing (new pods requesting injection may fail until the upgrade completes).&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;# Replace RELEASE_NAME with your Helm release name (e.g. openbao)
RELEASE_NAME=openbao
oc delete mutatingwebhookconfiguration ${RELEASE_NAME}-agent-injector-cfg
# Re-run the upgrade
helm upgrade ${RELEASE_NAME} openbao/openbao \
--namespace openbao \
--values openbao-ha-values-tls.yaml&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong&gt;Fix option 2&lt;/strong&gt; – Force takeover of the conflicting field&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;If you cannot delete the webhook (e.g. in production), take over the caBundle field with server-side apply, then run the Helm upgrade again:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Export the MutatingWebhookConfiguration, set webhooks[0].clientConfig.caBundle to your injector CA (base64 PEM from openbao-ca-secret), then re-apply with --server-side --force-conflicts:&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;# Get the current object and the new CA bundle
oc get mutatingwebhookconfiguration openbao-agent-injector-cfg -o yaml &amp;gt; mwc.yaml
CA_BUNDLE=$(oc get secret openbao-ca-secret -n openbao -o jsonpath=&amp;#39;{.data.ca\.crt}&amp;#39;)
# Edit mwc.yaml: set .webhooks[0].clientConfig.caBundle to the value of CA_BUNDLE (no quotes in YAML).
# Then apply with force-conflicts so Helm can later manage it:
oc apply -f mwc.yaml --server-side --force-conflicts
# Re-run the Helm upgrade
helm upgrade openbao openbao/openbao --namespace openbao --values openbao-ha-values-tls.yaml&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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 you still have HashiCorp Vault’s agent injector installed on the same cluster, ensure only one injector is active for a given namespace (e.g. use namespace selectors) or uninstall the Vault injector to avoid two webhooks with the same name in different resources.
&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="_route_ui_tls_bad_record_mac"&gt;Route / UI – tls: bad record MAC&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Pod logs show &lt;code&gt;tls: bad record MAC&lt;/code&gt; from the router IP. The Route is likely using &lt;strong&gt;edge&lt;/strong&gt; termination (HTTP to pod). Fix: Use reencrypt and set &lt;code&gt;destinationCACertificate&lt;/code&gt; (Step 5).&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 route openbao -n openbao -o jsonpath=&amp;#39;{.spec.tls.termination}&amp;#39;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;If the OpenBao UI does not load and the pod logs show:&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;http: TLS handshake error from 10.x.x.x:xxxxx: local error: tls: bad record MAC&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;the traffic is coming from the OpenShift router (the IP is typically a cluster pod IP).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong&gt;Cause&lt;/strong&gt;: The Route is likely using edge termination: the router terminates TLS at the edge and sends plain HTTP to the pod. The pod expects HTTPS, so the TLS layer receives non-TLS data and reports &amp;#34;bad record MAC&amp;#34;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong&gt;Fix&lt;/strong&gt;: Use reencrypt (or passthrough) and set destinationCACertificate so the router talks HTTPS to the pod. See Step 5 above. Quick check:&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 route openbao -n openbao -o jsonpath=&amp;#39;{.spec.tls.termination}&amp;#39;
# Must be &amp;#34;reencrypt&amp;#34; or &amp;#34;passthrough&amp;#34;, not &amp;#34;edge&amp;#34;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;If the output is edge, patch the Route to reencrypt and set spec.tls.destinationCACertificate to the CA PEM (Step 5).&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_tls_handshake_127_0_0_1_certificate_errors"&gt;TLS handshake / 127.0.0.1 certificate errors&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;If you see in the pod logs:&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;remote error: tls: bad certificate (from 127.0.0.1)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;or when 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 exec -ti openbao-0 — bao operator raft join https://…​;: x509: cannot validate certificate for 127.0.0.1 because it doesn’t contain any IP SANs&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The server certificate does not include 127.0.0.1 (and optionally ::1) as Subject Alternative Names. Readiness/liveness probes and in-pod bao commands connect to the listener on 127.0.0.1, so the certificate must include these IP SANs.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong&gt;Fix&lt;/strong&gt;: Add ipAddresses to the OpenBao server Certificate and let cert-manager re-issue:&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;# Edit the Certificate (or re-apply the YAML from Step 3 with ipAddresses added)
oc edit certificate openbao-server-tls -n openbao
# Add under spec:
# ipAddresses:
# - 127.0.0.1
# - &amp;#34;::1&amp;#34;
# cert-manager will issue a new cert; wait until the secret is updated
oc get certificate openbao-server-tls -n openbao
# Restart OpenBao pods so they load the new cert
oc rollout restart statefulset/openbao -n openbao&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="_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/helm/configuration/" target="_blank" rel="noopener"&gt;OpenBao Helm configuration&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://openbao.org/docs/platform/k8s/helm/examples/injector-tls-cert-manager/" target="_blank" rel="noopener"&gt;OpenBao Agent Injector TLS with cert-manager&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://cert-manager.io/docs/" target="_blank" rel="noopener"&gt;cert-manager documentation&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item><item><title>The Guide to OpenBao - OpenShift Deployment with Helm - Part 3</title><link>https://blog.stderr.at/openshift-platform/security/secrets-management/openbao/2026-02-13-openbao-part-3-openshift-deployment/</link><pubDate>Fri, 13 Feb 2026 00:00:00 +0000</pubDate><guid>https://blog.stderr.at/openshift-platform/security/secrets-management/openbao/2026-02-13-openbao-part-3-openshift-deployment/</guid><description>&lt;div class="paragraph"&gt;
&lt;p&gt;After understanding standalone installation in &lt;a href="https://blog.stderr.at/openshift-platform/security/secrets-management/openbao/2026-02-12-openbao-part-2-standalone-installation/" target="_blank" rel="noopener"&gt;Part 2&lt;/a&gt;, it is time to deploy OpenBao on OpenShift/Kubernetes using the official Helm chart. This approach provides high availability, Kubernetes-native management, and seamless integration with the OpenShift ecosystem.&lt;/p&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;Deploying OpenBao on OpenShift/Kubernetes offers several advantages:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;High Availability&lt;/strong&gt;: Multiple replicas with automatic failover&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Kubernetes-native&lt;/strong&gt;: Managed by standard Kubernetes primitives&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Persistent Storage&lt;/strong&gt;: Data survives pod restarts via PVCs&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Integration&lt;/strong&gt;: Works with Kubernetes service accounts for authentication&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Scalability&lt;/strong&gt;: Easy to scale and manage&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The official &lt;a href="https://github.com/openbao/openbao-helm" target="_blank" rel="noopener"&gt;OpenBao Helm chart&lt;/a&gt; supports multiple deployment modes:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Dev&lt;/strong&gt;: Single server, in-memory storage (testing only)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Standalone&lt;/strong&gt;: Single server, persistent storage&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;HA&lt;/strong&gt;: Multiple servers with Raft consensus (recommended)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;External&lt;/strong&gt;: Connect to external OpenBao cluster&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_integrations"&gt;Integrations&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Currently, OpenBao supports the following integrations that help seamlessly load secrets into applications without the need to modify the application code:&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;: A mutating webhook that automatically injects a sidecar container that retrieves and renews secrets.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;CSI Provider&lt;/strong&gt;: A (vendor neutral) CSI driver that mounts secrets as volumes.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_prerequisites"&gt;Prerequisites&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Before deploying, ensure you have:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;OpenShift 4.12+ or Kubernetes 1.30+&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Helm 3.6+&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;oc&lt;/code&gt; or &lt;code&gt;kubectl&lt;/code&gt; CLI configured&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The OpenBao CLI (&lt;code&gt;bao&lt;/code&gt;) for initialization and unsealing (see &lt;a href="https://openbao.org/docs/install/" target="_blank" rel="noopener"&gt;OpenBao installation&lt;/a&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Cluster-admin privileges (for initial setup)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A storage class that supports ReadWriteOnce PVCs&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-bash hljs" data-lang="bash"&gt;# Verify prerequisites
oc version
helm version
# Check available storage classes
oc get storageclass&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_adding_the_helm_repository"&gt;Adding the Helm Repository&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;First, add the OpenBao Helm repository:&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;# Add the OpenBao Helm repository
helm repo add openbao https://openbao.github.io/openbao-helm
# Update repository cache
helm repo update
# Search for available charts
helm search repo openbao
# Expected output:
# NAME CHART VERSION APP VERSION DESCRIPTION
# openbao/openbao 0.x.x 2.x.x Official OpenBao Helm chart&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;
According to the official documentation, the Helm chart is new and under significant development. It should always be run with --dry-run before any install or upgrade to verify changes.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_creating_the_namespace"&gt;Creating the Namespace&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Create a dedicated namespace for OpenBao:&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;
While I am using the &lt;strong&gt;oc&lt;/strong&gt; CLI, you can also use the &lt;strong&gt;kubectl&lt;/strong&gt; CLI as in-place replacement.
&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 new-project openbao&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_deployment_mode_high_availability_recommended"&gt;Deployment Mode: High Availability (Recommended)&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;For production environments, deploy in HA mode with Raft. This is the recommended deployment mode for production and is straightforward to achieve on Kubernetes and OpenShift.
The following values definitions will use OpenShift specific settings. I will mark them in the callouts.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;We will first create a values file, deploy openbao and then discuss what must be done to activate all OpenBao pods.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_create_ha_values_file"&gt;Create HA Values File&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Create &lt;code&gt;openbao-ha-values.yaml&lt;/code&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;
The values file is based on the official &lt;a href="https://github.com/openbao/openbao-helm/blob/main/charts/openbao/values.yaml" target="_blank" rel="noopener"&gt;values file from that chart&lt;/a&gt;, but only modified values or important changes are listed here.
&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;global:
# Enable OpenShift-specific settings
openshift: true &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
server:
# High Availability configuration
ha:
enabled: true &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
replicas: 3 &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
# Raft storage configuration
raft:
enabled: true &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
setNodeId: true
config: | &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
ui = true
listener &amp;#34;tcp&amp;#34; {
tls_disable = 1
address = &amp;#34;[::]:8200&amp;#34;
cluster_address = &amp;#34;[::]:8201&amp;#34;
telemetry {
unauthenticated_metrics_access = &amp;#34;true&amp;#34;
}
}
storage &amp;#34;raft&amp;#34; {
path = &amp;#34;/openbao/data&amp;#34;
retry_join {
leader_api_addr = &amp;#34;http://openbao-0.openbao-internal:8200&amp;#34;
}
retry_join {
leader_api_addr = &amp;#34;http://openbao-1.openbao-internal:8200&amp;#34;
}
retry_join {
leader_api_addr = &amp;#34;http://openbao-2.openbao-internal:8200&amp;#34;
}
}
service_registration &amp;#34;kubernetes&amp;#34; {}
telemetry {
prometheus_retention_time = &amp;#34;30s&amp;#34;
disable_hostname = true
}
route:
enabled: true &lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;(6)&lt;/b&gt;
host: openbao.apps.cluster.example.com &lt;i class="conum" data-value="7"&gt;&lt;/i&gt;&lt;b&gt;(7)&lt;/b&gt;
tls:
termination: edge
# Resource requests and limits
resources:
requests:
memory: 256Mi
cpu: 250m
limits:
memory: 1Gi
cpu: 1000m
# Persistent volume for data
dataStorage: &lt;i class="conum" data-value="8"&gt;&lt;/i&gt;&lt;b&gt;(8)&lt;/b&gt;
enabled: true
size: 10Gi
# storageClass: &amp;#34;gp3-csi&amp;#34;
# Injector configuration
injector:
enabled: true
replicas: 2 # HA for the injector too &lt;i class="conum" data-value="9"&gt;&lt;/i&gt;&lt;b&gt;(9)&lt;/b&gt;
# UI configuration
ui: &lt;i class="conum" data-value="10"&gt;&lt;/i&gt;&lt;b&gt;(10)&lt;/b&gt;
enabled: true&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;&lt;strong&gt;OpenShift specific&lt;/strong&gt;: Activate OpenShift Mode: Critical setting, if you install on OpenShift. It adjusts the Helm chart to use Routes instead of Ingress and modifies RoleBindings to work with OpenShift’s stricter authentication.&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;High Availability (HA): Deploys OpenBao as a StatefulSet rather than a Deployment.&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;Raft Consensus Quorum: Sets the cluster size to 3. Raft requires an &lt;strong&gt;odd number of nodes&lt;/strong&gt; to handle leader elections and avoid split-brain scenarios. This cluster can survive the loss of 1 node.&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;Integrated Raft Storage: Enables the internal Raft storage backend, removing the need for external dependencies like Consul or Etcd.&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;Server Configuration (HCL): The actual OpenBao server configuration file. Note that &lt;strong&gt;tls_disable = 1&lt;/strong&gt; is used because the OpenShift Route handles TLS termination at the edge, passing unencrypted traffic to the pod.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;6&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;OpenShift specific&lt;/strong&gt;: Route: Tells Helm to create an OpenShift Route object automatically.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="7"&gt;&lt;/i&gt;&lt;b&gt;7&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;OpenShift specific&lt;/strong&gt;: Host: The external DNS address where users and applications will access the OpenBao API and UI.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="8"&gt;&lt;/i&gt;&lt;b&gt;8&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Persistent Storage: Allocates a 10Gi Persistent Volume Claim (PVC) for each of the 3 pods to store encrypted data and Raft logs.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="9"&gt;&lt;/i&gt;&lt;b&gt;9&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Injector Redundancy: Runs 2 replicas of the sidecar injector. If the injector service is down, new application pods attempting to start with secrets will fail.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="10"&gt;&lt;/i&gt;&lt;b&gt;10&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Web UI: Enables the graphical dashboard service.&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="_deploy_ha_cluster"&gt;Deploy HA Cluster&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;# Deploy with HA values
helm install openbao openbao/openbao \
--namespace openbao \
--values openbao-ha-values.yaml&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_verify_the_deployment"&gt;Verify the Deployment&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 openbao&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This will result in the following output:&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;NAME READY STATUS RESTARTS AGE
openbao-0 0/1 Running 0 60s &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
openbao-agent-injector-xxx 1/1 Running 0 60s
openbao-agent-injector-yyy 1/1 Running 0 60s&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;Only 1 openbao pod (instead of 3) is running, and the pod is not in &amp;#34;ready&amp;#34; state.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;As you can see, only one OpenBao pod exists so far, and it is not in &amp;#34;ready&amp;#34; state. This is because OpenBao is not yet initialized and unsealed. Once that is done, the other pods will appear and join the cluster.&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;
The OpenBao pods show 0/1 ready because they are sealed and need initialization.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This is also indicated in the logs of the openbao pod:&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;2026-02-13T14:44:52.587Z [ERROR] core: failed to get raft challenge: leader_addr=http://openbao-0.openbao-internal.openbao.svc:8200
error=
| error during raft bootstrap init call: Error making API request.
|
| URL: PUT http://openbao-0.openbao-internal.openbao.svc:8200/v1/sys/storage/raft/bootstrap/challenge
| Code: 503. Errors:
|
| * Vault is sealed &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
2026-02-13T14:44:52.588Z [ERROR] core: failed to get raft challenge: leader_addr=http://openbao-2.openbao-internal.openbao.svc:8200 error=&amp;#34;error during raft bootstrap init call: Put \&amp;#34;http://openbao-2.openbao-internal.openbao.svc:8200/v1/sys/storage/raft/bootstrap/challenge\&amp;#34;: dial tcp: lookup openbao-2.openbao-internal.openbao.svc on 172.30.0.10:53: no such host&amp;#34; &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
2026-02-13T14:44:52.589Z [ERROR] core: failed to get raft challenge: leader_addr=http://openbao-1.openbao-internal.openbao.svc:8200 error=&amp;#34;error during raft bootstrap init call: Put \&amp;#34;http://openbao-1.openbao-internal.openbao.svc:8200/v1/sys/storage/raft/bootstrap/challenge\&amp;#34;: dial tcp: lookup openbao-1.openbao-internal.openbao.svc on 172.30.0.10:53: no such host&amp;#34;
2026-02-13T14:44:52.589Z [ERROR] core: failed to retry join raft cluster: retry=2s err=&amp;#34;failed to get raft challenge&amp;#34;&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 is sealed: This means that the OpenBao service is not yet initialized and unsealed.&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;No such host: This means that the OpenBao service is not yet available.&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="_initializing_and_unsealing_openbao"&gt;Initializing and Unsealing OpenBao&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;After deployment, OpenBao needs to be initialized and unsealed. This is done on the first pod. Once this is done, the other pods will appear and can join the cluster. We will create a local portforwarding to the first pod to initialize it.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_initialize_the_cluster"&gt;Initialize the Cluster&lt;/h3&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;Create a local port forwarding to the first pod&lt;/p&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 port-forward openbao-0 8200:8200 -n openbao &amp;amp;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set environment variable&lt;/p&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;export BAO_ADDR=&amp;#39;http://127.0.0.1:8200&amp;#39;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Check status&lt;/p&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;bao status&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This will result in the following output:&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;Key Value
--- -----
Seal Type shamir
Initialized false &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
Sealed true &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
Total Shares 0
Threshold 0
Unseal Progress 0/0
Unseal Nonce n/a
Version 2.5.0
Build Date 2026-02-04T16:19:33Z
Storage Type raft
HA Enabled true &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;Not yet initialized&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;Vault is sealed&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;High Availability is enabled&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Initialize the cluster with 5 key shares and 3 threshold&lt;/p&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;bao operator init -key-shares=5 -key-threshold=3 -format=json &amp;gt; openbao-init.json&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This will create the file &lt;code&gt;openbao-init.json&lt;/code&gt; with the unseal keys and root token.&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;cat openbao-init.json&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="admonitionblock caution"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-caution" title="Caution"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Take care of the &lt;code&gt;openbao-init.json&lt;/code&gt; file. It contains the unseal keys and root token!
&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="_unseal_pod_openbao_0"&gt;Unseal Pod openbao-0&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;After initialization, we need to unseal the first pod. This is done by providing 3 different unseal keys. (threshold is 3)&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;# Unseal openbao-0 (3 times with different keys)
bao operator unseal # Enter first key
bao operator unseal # Enter second key
bao operator unseal # Enter third key&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This unseals openbao-0, which can be verified with the command &lt;code&gt;bao status&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;Key Value
--- -----
Seal Type shamir
Initialized true
Sealed false &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
Total Shares 5
Threshold 3
Version 2.5.0
Build Date 2026-02-04T16:19:33Z
Storage Type raft
Cluster Name vault-cluster-80c01167
Cluster ID b81ecb85-9751-655a-95b7-69463dd13241
HA Enabled true
HA Cluster https://openbao-0.openbao-internal:8201
HA Mode active
Active Since 2026-02-13T14:46:13.292643992Z
Raft Committed Index 29
Raft Applied Index 29&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 is unsealed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This makes openbao-0 ready and openbao-1 is trying to start:&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
NAME READY STATUS RESTARTS AGE
openbao-0 1/1 Running 0 2m10s &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
openbao-1 0/1 Running 0 14s &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
openbao-agent-injector-98769cf97-r4stk 1/1 Running 0 2m12s
openbao-agent-injector-98769cf97-xldgm 1/1 Running 0 2m12s&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-0 is ready&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;openbao-1 is trying to start and wants to join the Raft cluster&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="_activate_pod_openbao_1"&gt;Activate Pod openbao-1&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Since OpenBao is already initialized, we can skip the initialization step. However, we must join the &lt;strong&gt;Raft cluster&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;oc exec -ti openbao-1 -- bao operator raft join http://openbao-0.openbao-internal:8200&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Once done, we can unseal openbao-1. We need to provide 3 different unseal keys again.
Execute the following command 3 times:&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 -ti openbao-1 -- bao operator unseal
Unseal Key (will be hidden):&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Now &lt;strong&gt;openbao-1&lt;/strong&gt; is ready and openbao-2 is trying to start:&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
NAME READY STATUS RESTARTS AGE
openbao-0 1/1 Running 0 3m48s
openbao-1 1/1 Running 0 112s
openbao-2 0/1 Running 0 18s
openbao-agent-injector-98769cf97-r4stk 1/1 Running 0 3m50s
openbao-agent-injector-98769cf97-xldgm 1/1 Running 0 3m50s&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_activate_pod_openbao_2"&gt;Activate Pod openbao-2&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Now we need to repeat the previous steps for openbao-2.&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 -ti openbao-2 -- bao operator raft join http://openbao-0.openbao-internal:8200
# Run 3 times (enter a different unseal key each time):
oc exec -ti openbao-2 -- bao operator unseal&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_verify_raft_cluster"&gt;Verify Raft Cluster&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;You can verify the Raft cluster by logging in with the root token and then checking the Raft peer list.&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 -ti openbao-0 -- bao login&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Check the peer list:&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;# Check Raft peer list
oc exec -ti openbao-0 -- bao operator raft list-peers
Node Address State Voter
---- ------- ----- -----
openbao-0 openbao-0.openbao-internal:8201 leader true
openbao-1 openbao-1.openbao-internal:8201 follower true
openbao-2 openbao-2.openbao-internal:8201 follower true&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="_accessing_the_ui"&gt;Accessing the UI&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Once unsealed, access the OpenBao UI:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_via_route_production"&gt;Via Route (Production)&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 route openbao -n openbao
# Open browser: https://openbao.apps.cluster.example.com&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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/part3_openbao_login_form.png?width=480px" alt="OpenBao Login Form"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 1. OpenBao Login Form&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Login with the root token from initialization, or with credentials once authentication is configured.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_upgrading_openbao"&gt;Upgrading OpenBao&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Keep your secrets management up to date. To upgrade an existing 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;# Update Helm repository
helm repo update
# Check available versions
helm search repo openbao --versions
# Upgrade with your values file
helm upgrade openbao openbao/openbao \
--namespace openbao \
--values openbao-ha-values.yaml
# Watch the rolling update
oc get pods -n openbao -w&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;
After upgrade, pods may need to be unsealed again if they restart.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_troubleshooting"&gt;Troubleshooting&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_pods_not_starting"&gt;Pods Not Starting&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;# Check pod status
oc describe pod openbao-0 -n openbao
# Check pod logs
oc logs openbao-0 -n openbao
# Check events
oc get events -n openbao --sort-by=&amp;#39;.lastTimestamp&amp;#39;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_pvc_issues"&gt;PVC Issues&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;# Check PVC status
oc get pvc -n openbao
# If pending, check storage class
oc describe pvc data-openbao-0 -n openbao&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_raft_join_failures"&gt;Raft Join Failures&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;If pods cannot join the Raft cluster:&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;# Check internal DNS resolution
oc exec -it openbao-0 -n openbao -- nslookup openbao-internal
# Check connectivity between pods
oc exec -it openbao-0 -n openbao -- wget -O- http://openbao-1.openbao-internal:8200/v1/sys/health&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="_what_should_be_considered_next"&gt;What Should Be Considered Next?&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Securely store the unseal keys and root token&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Configure pod anti-affinity for true HA&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Consider auto-unseal for operational ease (upcoming article)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Put everything into a GitOps pipeline&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&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;You now have OpenBao running on OpenShift in high-availability mode. This deployment:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Survives pod failures and restarts&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Uses Raft for distributed consensus&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Integrates with OpenShift security model&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Is ready for production use (after unsealing automation)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Key points to remember:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Use HA mode for production&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Store unseal keys securely&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Configure pod anti-affinity for true HA&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Consider auto-unseal for operational ease (upcoming article)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&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/helm" target="_blank" rel="noopener"&gt;OpenBao Helm Chart Documentation&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://github.com/openbao/openbao-helm" target="_blank" rel="noopener"&gt;OpenBao Helm Chart GitHub&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://openbao.org/docs/platform/k8s/helm/run" target="_blank" rel="noopener"&gt;Running OpenBao on Kubernetes&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item><item><title>Hosted Control Planes behind a Proxy</title><link>https://blog.stderr.at/openshift-platform/other-topics/2025-12-15-hosted-control-planes-and-proxy/</link><pubDate>Mon, 15 Dec 2025 00:00:00 +0000</pubDate><guid>https://blog.stderr.at/openshift-platform/other-topics/2025-12-15-hosted-control-planes-and-proxy/</guid><description>&lt;div class="paragraph"&gt;
&lt;p&gt;Recently, I encountered a problem deploying a Hosted Control Plane (HCP) at a customer site. The installation started successfully—etcd came up fine—but then it just stopped. The virtual machines were created, but they never joined the cluster. No OVN or Multus pods ever started.
The only meaningful message in the cluster-version-operator pod logs was:&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-console hljs" data-lang="console"&gt;I1204 08:22:35.473783 1 status.go:185] Synchronizing status errs=field.ErrorList(nil) status=&amp;amp;cvo.SyncWorkerStatus{Generation:1, Failure:(*payload.UpdateError)(0xc0006313b0), Done:575, Total:623,&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This message did appear over and over again.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_problem_summary"&gt;Problem Summary&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;etcd started successfully, but the installation stalled&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;API server was running&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;VMs started but never joined the cluster&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;No OVN or Multus pods ever started&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The installation was stuck in a loop&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_troubleshooting"&gt;Troubleshooting&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Troubleshooting was not straightforward. The cluster-version-operator logs provided the only clue.
However, the VMs were already running, so we could log in to them—and there it was, the reason for the stalled installation.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_connect_to_a_vm"&gt;Connect to a VM&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To connect to a VM, use the &lt;code&gt;virtctl&lt;/code&gt; command. This connects you to the machine as the &lt;code&gt;core&lt;/code&gt; user:&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;
You can download &lt;code&gt;virtctl&lt;/code&gt; from the OpenShift Downloads page in the OpenShift web console.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&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;
You need the SSH key for the &lt;code&gt;core&lt;/code&gt; user.
&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;virtctl ssh -n clusters-my-hosted-cluster core@vmi/my-node-pool-dsj7z-sss8w &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&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;Replace &lt;strong&gt;my-hosted-cluster&lt;/strong&gt; with the name of your hosted cluster and &lt;strong&gt;my-node-pool-dsj7z-sss8w&lt;/strong&gt; with the name of your VM.&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_the_problem"&gt;Verify the Problem&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Once logged in, check the &lt;code&gt;journalctl -xf&lt;/code&gt; output. In case of a proxy issue, you’ll see an error like this:&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;&amp;gt; Dec 10 06:23:09 my-node-pool2-gwvjh-rwtx8 sh[2143]: time=&amp;#34;2025-12-10T06:23:09Z&amp;#34; level=warning msg=&amp;#34;Failed, retrying in 1s ... (3/3). Error: initializing source docker://quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:500704de3ef374e61417cc14eda99585450c317d72f454dda0dadd5dda1ba57a: pinging container registry quay.io: Get \&amp;#34;https://quay.io/v2/\&amp;#34;: dial tcp 3.209.93.201:443: i/o timeout&amp;#34;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;So we have a timeout when trying to pull the image from the container registry. This is a classic proxy issue. When you try &lt;code&gt;curl&lt;/code&gt; or manual pulls you will get the same message.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_configuring_the_proxy_for_the_hosted_control_plane"&gt;Configuring the Proxy for the Hosted Control Plane&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The big question is: How do we configure the proxy for the Hosted Control Plane?&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This is not well documented yet—in fact, it’s barely documented at all.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The Hosted Control Plane is managed through a custom resource called &lt;code&gt;HostedCluster&lt;/code&gt;, and that’s exactly where we configure the proxy.
The upstream documentation at &lt;a href="https://hypershift.pages.dev/how-to/configure-ocp-components/#overview" target="_blank" rel="noopener"&gt;HyperShift Documentation&lt;/a&gt; explains that you can add a &lt;code&gt;configuration&lt;/code&gt; section to the resource. Let’s do that:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Update the &lt;code&gt;HostedCluster&lt;/code&gt; resource with the following configuration:&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;spec:
configuration:
proxy:
httpProxy: http://proxy.example.com:8080 &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
httpsProxy: https://proxy.example.com:8080 &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
noProxy: .cluster.local,.svc,10.128.0.0/14,127.0.0.1,localhost &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
trustedCA:
name: user-ca-bundle &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&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;HTTP proxy URL&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;HTTPS proxy URL&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;Comma-separated list of domains, IPs, or CIDRs to exclude from proxy routing&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;OPTIONAL: Name of a ConfigMap containing a custom CA certificate bundle&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="admonitionblock caution"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-caution" title="Caution"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
If you’re using a custom CA, create the ConfigMap beforehand or alongside the &lt;code&gt;HostedCluster&lt;/code&gt; resource. The ConfigMap must contain a key named &lt;code&gt;ca-bundle.crt&lt;/code&gt; with your CA certificate(s).
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_but_wait_there_is_more"&gt;But wait there is more…​&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;If you are using such a proxy, which is injecting it’s own certificate, then you probably saw the following: The &lt;strong&gt;Release Image&lt;/strong&gt; is not available when you try to deploy a new Hosted Control Plane.
The drop down in the UI is empty and you can’t select a release image.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This happens because the Pod that is trying to fetch the image is not able to connect to Github.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;You can test this with the following commands:&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 rsh cluster-image-set-controller-XXXXX -n multicluster-engine &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
curl -v https://github.com/stolostron/acm-hive-openshift-releases.git/info/refs?service=git-upload-pack &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&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;Replace &lt;strong&gt;XXXXX&lt;/strong&gt; with the name of the Pod cluster-image-set-controller in the &lt;strong&gt;multicluster-engine&lt;/strong&gt; namespace&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;Try to execute the curl command to see if you can connect to Github&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;If this command fails with a certificate error, then you need to add the certificate to the Pod/Deployment.
A Configmap called &lt;strong&gt;trusted-ca-bundle&lt;/strong&gt; should already exist in the multicluster-engine namespace. If not, it must be created with the certificate chain of your Proxy (key: ca-bundle.crt)&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The following command will add the ConfigMap to 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 -n multicluster-engine set volume deployment/cluster-image-set-controller --add --type configmap --configmap-name trusted-ca-bundle --name trusted-ca-bundle --mount-path /etc/pki/tls/certs/ --overwrite&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Wait a couple of minutes after the Pods has been restarted. It will try to download the release images from Github again.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;You can check the logs of the Pod to see if the download was successful.&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 logs cluster-image-set-controller-XXXXX -n multicluster-engine &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&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;Replace &lt;strong&gt;XXXXX&lt;/strong&gt; with the name of the Pod cluster-image-set-controller in the &lt;strong&gt;multicluster-engine&lt;/strong&gt; namespace&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;That should do it, you should now be able to select a release image and deploy a new Hosted Control Plane.&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/other-topics/images/HCP-Release-Images.png" alt="Release Images"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 1. Drow Down with Release Images&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_summary"&gt;Summary&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;That’s actually everything you need for proxy configuration with Hosted Control Planes. Hopefully, the official OpenShift documentation will be updated soon to include this information.
red Hat created two tickets to track the progress of this:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://issues.redhat.com/browse/ACM-10151" target="_blank" rel="noopener"&gt;ACM-10151&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://issues.redhat.com/browse/ACM-23664" target="_blank" rel="noopener"&gt;ACM-23664&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item><item><title>The Guide to OpenBao - Standalone Installation - Part 2</title><link>https://blog.stderr.at/openshift-platform/security/secrets-management/openbao/2026-02-12-openbao-part-2-standalone-installation/</link><pubDate>Thu, 12 Feb 2026 00:00:00 +0000</pubDate><guid>https://blog.stderr.at/openshift-platform/security/secrets-management/openbao/2026-02-12-openbao-part-2-standalone-installation/</guid><description>&lt;div class="paragraph"&gt;
&lt;p&gt;In the previous article, we introduced OpenBao and its core concepts. Now it is time to get our hands dirty with a standalone installation. This approach is useful for testing, development environments, edge deployments, or scenarios where Kubernetes is not available.&lt;/p&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;While OpenBao shines in Kubernetes environments, understanding the standalone installation helps you grasp the fundamentals. This knowledge is valuable whether you are:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Learning OpenBao before deploying to production&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Running OpenBao outside of Kubernetes (edge, legacy systems)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Debugging issues in containerized deployments&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Setting up a development environment&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_installation_methods_overview"&gt;Installation Methods Overview&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;OpenBao can be installed through multiple methods:&lt;/p&gt;
&lt;/div&gt;
&lt;table class="tableblock frame-all grid-all stretch"&gt;
&lt;colgroup&gt;
&lt;col style="width: 20%;"/&gt;
&lt;col style="width: 40%;"/&gt;
&lt;col style="width: 40%;"/&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;Best For&lt;/th&gt;
&lt;th class="tableblock halign-left valign-top"&gt;Complexity&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;Package managers (apt, dnf, brew)&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Production Linux/macOS systems&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Low&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;Container images (Podman/Docker)&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Quick testing, isolated environments&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Low&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;Binary download&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Air-gapped environments&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Low&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;Source compilation&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Custom builds, development&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Medium&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;
For this article, we will focus on macOS for local testing with binary and container image (using Podman) and Red Hat Enterprise Linux to set up an example production-ready server.
The deployment on the Kubernetes environment will be discussed in the next article.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_method_1_package_manager_installation"&gt;Method 1: Package Manager Installation&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_rhelfedoracentos"&gt;RHEL/Fedora/CentOS&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;On RHEL or CentOS, before you can install OpenBao you need to install the EPEL repository. To do so, you first need to enable the Code Ready Repository. The following commands will do the trick. Be sure that your system is registered, in case of RHEL.&lt;/p&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 you get the error &amp;#34;Repositories disabled by configuration.&amp;#34; you need to tell the subscription manager that you want to manage the repositories. This can be done permanently or temporarily. You can use the command: &lt;strong&gt;sudo subscription-manager config --rhsm.manage_repos=1&lt;/strong&gt; to do so.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="sect3"&gt;
&lt;h4 id="_rhel"&gt;RHEL&lt;/h4&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;Enable Code Ready Repository&lt;/p&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;sudo subscription-manager repos --enable codeready-builder-for-rhel-9-$(arch)-rpms&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install EPEL Repository&lt;/p&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;sudo dnf install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install OpenBao&lt;/p&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;sudo dnf install -y openbao&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect3"&gt;
&lt;h4 id="_centos"&gt;CentOS&lt;/h4&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;Enable Code Ready Repository on CentOS&lt;/p&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;sudo dnf config-manager --set-enabled crb&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install EPEL Repository&lt;/p&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;sudo dnf install epel-release epel-next-release&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install OpenBao&lt;/p&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;sudo dnf install -y openbao&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_macos_homebrew"&gt;macOS (Homebrew)&lt;/h3&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;Install OpenBao&lt;/p&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;brew install openbao&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_verify_installation"&gt;Verify Installation&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;After installation, verify OpenBao is available:&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;bao version
# Output:
OpenBao v2.5.0 (bcbb6036ec2b747bceb98c7706ce9b974faa1b23), built 2026-02-04T15:57:17Z (cgo)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="admonitionblock caution"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-caution" title="Caution"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
The OpenBao CLI command is &lt;code&gt;bao&lt;/code&gt;, not &lt;code&gt;vault&lt;/code&gt;. This distinguishes it from HashiCorp Vault.
&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="_start_development_server"&gt;Start Development Server&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To start a development environment, you can use the following command.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock caution"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-caution" title="Caution"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
This is NOT suitable for production, it is just a test to evaluate the basic concepts of 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;bao server -dev -dev-root-token-id=&amp;#34;dev-only-token&amp;#34;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This will start the server. The UI is accessible at &lt;a href="http://localhost:8200" class="bare"&gt;http://localhost:8200&lt;/a&gt; where you can login using the root token &lt;code&gt;dev-only-token&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_method_2_container_image"&gt;Method 2: Container Image&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;For quick testing or isolated environments, container images are ideal. Luckily, OpenBao offers several types of containers suitable for any environment. We will use the image hosted on &lt;strong&gt;quay.io&lt;/strong&gt;, which is based on &lt;strong&gt;RHEL UBI&lt;/strong&gt; and can be found at: &lt;a href="https://quay.io/openbao/openbao-ubi" target="_blank" rel="noopener"&gt;quay.io/openbao/openbao-ubi&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_using_podman"&gt;Using Podman&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The following command will fetch the image and start the container.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock caution"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-caution" title="Caution"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
This is NOT suitable for production, it is just a test to evaluate the basic concepts of 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;# Run in dev mode (for testing only!)
podman run --rm -d \
--name openbao-dev \
-p 8200:8200 \
-e &amp;#39;BAO_DEV_ROOT_TOKEN_ID=dev-only-token&amp;#39; \
-e &amp;#39;BAO_DEV_LISTEN_ADDRESS=0.0.0.0:8200&amp;#39; \
quay.io/openbao/openbao-ubi:latest server -dev&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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;# Verify it is running
podman logs -f openbao-dev&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This will start the server. The UI is accessible at &lt;a href="http://localhost:8200" class="bare"&gt;http://localhost:8200&lt;/a&gt; where you can login using the root token &lt;code&gt;dev-only-token&lt;/code&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 you prefer to use Docker, simply replace &lt;code&gt;podman&lt;/code&gt; with &lt;code&gt;docker&lt;/code&gt; in the commands.
&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="_dev_mode_quick_start_for_testing"&gt;Dev Mode: Quick Start for Testing&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Dev mode is the fastest way to start using OpenBao for learning and testing.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The characteristics in this mode are:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;In-memory storage (data lost on restart)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Automatically initialized and unsealed&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Root token printed to stdout&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;TLS disabled&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Single server (no HA)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Let’s create an example secret and try to retrieve it again.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;Authenticate against OpenBao&lt;/p&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;export VAULT_TOKEN=&amp;#34;dev-only-token&amp;#34;
export BAO_ADDR=&amp;#39;http://127.0.0.1:8200&amp;#39;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create Secret&lt;/p&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;curl \
--header &amp;#34;X-Vault-Token: $VAULT_TOKEN&amp;#34; \
--header &amp;#34;Content-Type: application/json&amp;#34; \
--request POST \
--data &amp;#39;{&amp;#34;data&amp;#34;: {&amp;#34;password&amp;#34;: &amp;#34;OpenBao123&amp;#34;}}&amp;#39; \
$BAO_ADDR/v1/secret/data/my-secret-password &amp;amp;&amp;amp;
echo &amp;#34;Secret written successfully.&amp;#34;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Retrieve Secret&lt;/p&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;curl --header &amp;#34;X-Vault-Token: $VAULT_TOKEN&amp;#34; \
$BAO_ADDR/v1/secret/data/my-secret-password | jq &amp;#39;.data.data&amp;#39;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;You should see the password that was created before:&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-json hljs" data-lang="json"&gt;{
&amp;#34;password&amp;#34;: &amp;#34;OpenBao123&amp;#34;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_check_status_of_openbao_server"&gt;Check Status of OpenBao Server&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;bao status&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This will give you the status of your running OpenBao instance.&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;Key Value
--- -----
Seal Type shamir
Initialized true
Sealed false
Total Shares 1
Threshold 1
Version 2.5.0
Build Date 2026-02-04T15:57:17Z
Storage Type inmem
Cluster Name vault-cluster-421b2431
Cluster ID 6d42dcd2-e399-211e-999a-49b1874cc8ce
HA Enabled false&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="_hardening_your_system"&gt;Hardening your System&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;OpenBao does a good job to secure your secrets, however, memory paging (or swap) can undermine the protection. Your OS should either have swap disabled completely or encrypt the swap space.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;As I am testing on macOS, the swap space is encrypted out of the box.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;However, OpenBao has documented what must be done for various operating systems at &lt;a href="https://openbao.org/docs/install/#post-installation-hardening" target="_blank" rel="noopener"&gt;Post-installation hardening&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_production_standalone_setup"&gt;Production Standalone Setup&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;For a proper standalone installation, follow these steps:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_step_1_create_configuration_file"&gt;Step 1: Create Configuration File&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Create &lt;code&gt;/etc/openbao.d/openbao.hcl&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-ini hljs" data-lang="ini"&gt;# Full configuration for standalone OpenBao server
# Cluster name for identification
cluster_name = &amp;#34;openbao-standalone&amp;#34;
# Storage backend using integrated Raft
storage &amp;#34;raft&amp;#34; {
path = &amp;#34;/var/lib/openbao/data&amp;#34;
node_id = &amp;#34;node1&amp;#34;
}
# HTTP listener (for internal communication)
listener &amp;#34;tcp&amp;#34; {
address = &amp;#34;0.0.0.0:8200&amp;#34;
cluster_address = &amp;#34;0.0.0.0:8201&amp;#34;
tls_disable = false
tls_cert_file = &amp;#34;/etc/openbao.d/tls/tls.crt&amp;#34;
tls_key_file = &amp;#34;/etc/openbao.d/tls/tls.key&amp;#34;
}
# API address for clients
api_addr = &amp;#34;https://openbao.example.com:8200&amp;#34;
# Cluster address for raft communication
cluster_addr = &amp;#34;https://openbao.example.com:8201&amp;#34;
# UI enabled
ui = true
# Logging
log_level = &amp;#34;info&amp;#34;
log_file = &amp;#34;/var/log/openbao/openbao.log&amp;#34;
# Disable memory locking (enable in production if possible)
disable_mlock = true
# Telemetry (optional)
telemetry {
prometheus_retention_time = &amp;#34;30s&amp;#34;
disable_hostname = true
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_step_2_generate_tls_certificates"&gt;Step 2: Generate TLS Certificates&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;For production, you should use proper certificates. For testing, create self-signed ones:&lt;/p&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;
This is only for testing purposes. In production, you should use proper certificates.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;First, prepare the TLS directory if it does not exist&lt;/p&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;sudo mkdir -p /etc/openbao.d/tls&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Then create a configuration file for the TLS certificates:&lt;/p&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;# Create TLS config
cat &amp;lt;&amp;lt;EOF &amp;gt; openbao.cnf
[req]
distinguished_name = req_distinguished_name
x509_extensions = v3_req
prompt = no
[req_distinguished_name]
CN = openbao.example.com
[v3_req]
subjectAltName = @alt_names
[alt_names]
DNS.1 = openbao.example.com
IP.1 = 127.0.0.1 &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
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;The IP address of the server. In this case we are using localhost, but you can add your IPs here.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Then generate the certificate:&lt;/p&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;# Generate private key
sudo openssl genrsa -out /etc/openbao.d/tls/tls.key 4096
# Generate certificate signing request
sudo openssl req -new -key /etc/openbao.d/tls/tls.key -out /etc/openbao.d/tls/tls.csr -subj &amp;#34;/CN=openbao.example.com&amp;#34;
# Generate self-signed certificate
sudo openssl x509 -req -days 365 -in /etc/openbao.d/tls/tls.csr -signkey /etc/openbao.d/tls/tls.key -out /etc/openbao.d/tls/tls.crt -extfile openbao.cnf -extensions v3_req
# Set permissions
sudo chown -R openbao:openbao /etc/openbao.d/tls
sudo chmod 600 /etc/openbao.d/tls/tls.key
sudo chmod 755 /etc/openbao.d/tls&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_step_3_create_systemd_service"&gt;Step 3: Create Systemd Service&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Create &lt;code&gt;/etc/systemd/system/openbao.service&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-ini hljs" data-lang="ini"&gt;[Unit]
Description=OpenBao Secret Management
Documentation=https://openbao.org/docs
Requires=network-online.target
After=network-online.target
ConditionFileNotEmpty=/etc/openbao.d/openbao.hcl
[Service]
User=openbao
Group=openbao
ProtectSystem=full
ProtectHome=read-only
PrivateTmp=yes
PrivateDevices=yes
SecureBits=keep-caps
AmbientCapabilities=CAP_IPC_LOCK
CapabilityBoundingSet=CAP_SYSLOG CAP_IPC_LOCK
NoNewPrivileges=yes
ExecStart=/usr/bin/bao server -config=/etc/openbao.d/openbao.hcl
ExecReload=/bin/kill --signal HUP $MAINPID
KillMode=process
KillSignal=SIGINT
Restart=on-failure
RestartSec=5
TimeoutStopSec=30
LimitNOFILE=65536
LimitMEMLOCK=infinity
[Install]
WantedBy=multi-user.target&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_step_4_start_the_service"&gt;Step 4: Start the Service&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;# Reload systemd
sudo systemctl daemon-reload
# Enable and start OpenBao
sudo systemctl enable openbao
sudo systemctl start openbao
# Check status
sudo systemctl status openbao
# View logs
sudo journalctl -u openbao -f&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="_initialize_and_unseal_openbao"&gt;Initialize and Unseal OpenBao&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;After starting OpenBao for the first time, it needs to be initialized and unsealed.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_set_environment_variables"&gt;Set Environment Variables&lt;/h3&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;
We will do the following commands as root user.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&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;
Be sure that the hostname is resolvable. In this case I am using the test domain: openbao.example.com.
&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;# Set the OpenBao address
export BAO_ADDR=&amp;#39;https://openbao.example.com:8200&amp;#39;
# If using self-signed certificates
export BAO_CACERT=&amp;#39;/etc/openbao.d/tls/tls.crt&amp;#39;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_check_status"&gt;Check Status&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;You will see that OpenBao is running but not yet initialized. This will be the next step.&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;bao status
Key Value
--- -----
Seal Type shamir
Initialized false &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
Sealed true
Total Shares 0
Threshold 0
Unseal Progress 0/0
Unseal Nonce n/a
Version 2.4.4-1.el9
Build Date 2025-11-24
Storage Type file
HA Enabled false&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;Not yet initialized&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="_initialize_openbao"&gt;Initialize OpenBao&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Before we use OpenBao, we need to initialize it. This will create the unseal keys and the root token. The unseal keys are used to … well, unseal the OpenBao service.
The root token is used as a master key to authenticate against the OpenBao service. This token has access to ALL secrets. &lt;strong&gt;Treat this root token with care.&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;We will initialize OpenBao with default options of 5 key shares and a threshold of 3. This means that we need 3 different unseal keys to unseal the OpenBao service.&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;bao operator init -key-shares=5 -key-threshold=3 -format=json &amp;gt; /root/openbao-init.json&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="admonitionblock caution"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-caution" title="Caution"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Store the unseal keys and root token securely! Anyone with these can access all secrets.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The output looks like:&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-json hljs" data-lang="json"&gt;{
&amp;#34;unseal_keys_b64&amp;#34;: [
&amp;#34;key1...&amp;#34;,
&amp;#34;key2...&amp;#34;,
&amp;#34;key3...&amp;#34;,
&amp;#34;key4...&amp;#34;,
&amp;#34;key5...&amp;#34;
],
&amp;#34;unseal_keys_hex&amp;#34;: [...],
&amp;#34;unseal_shares&amp;#34;: 5,
&amp;#34;unseal_threshold&amp;#34;: 3,
&amp;#34;recovery_keys_b64&amp;#34;: [],
&amp;#34;recovery_keys_hex&amp;#34;: [],
&amp;#34;root_token&amp;#34;: &amp;#34;sbr.xxxxxxxxxxxx&amp;#34;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_unseal_openbao"&gt;Unseal OpenBao&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Now everything is set up and we can use the unseal keys to unseal the OpenBao service.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;You need to provide 3 (threshold) different unseal keys:&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;# First key
bao operator unseal
# Enter first unseal key
# Second key
bao operator unseal
# Enter second unseal key
# Third key
bao operator unseal
# Enter third unseal key&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;After providing enough keys:&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;bao status
Key Value
--- -----
Seal Type shamir
Initialized true
Sealed false &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
Total Shares 5
Threshold 3
Version 2.4.4-1.el9
Build Date 2025-11-24
Storage Type file
Cluster Name openbao-standalone
Cluster ID d07874ef-df52-e45f-1723-ade333c6d609
HA Enabled false&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;Now unsealed and ready&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Now we can login with the root token to the OpenBao service.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_login_with_root_token"&gt;Login with Root Token&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Since we have not created any users yet, we will use the root token to authenticate.&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;# Enter the root token from initialization
bao login&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="_basic_verification"&gt;Basic Verification&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Let us verify the installation works:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;List enabled secrets engines&lt;/p&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;bao secrets list&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This lists the enabled secrets engines.&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;Path Type Accessor Description
---- ---- -------- -----------
cubbyhole/ cubbyhole cubbyhole_84c3d2a5 per-token private secret storage
identity/ identity identity_61809171 identity store
sys/ system system_034ae772 system endpoints used for control, policy and debugging&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;List enabled auth methods&lt;/p&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;bao auth list&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This lists the enabled authentication methods.&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;Path Type Accessor Description Version
---- ---- -------- ----------- -------
token/ token auth_token_c7feca17 token based credentials n/a&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a test secret&lt;/p&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;bao secrets enable -path=secret kv-v2&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This enables the KV (key/value) secrets engine at the path &lt;code&gt;secret&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;bao kv put secret/test message=&amp;#34;Hello from OpenBao&amp;#34;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This creates a test secret at the path &lt;code&gt;secret/test&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;bao kv get secret/test&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This retrieves the test secret from the path &lt;code&gt;secret/test&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;That’s it for the basic verification. You can now start to use OpenBao in your production environment. Let’s see what we can do in the UI.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_visiting_the_ui"&gt;Visiting the UI&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;If you watched the previous commands closely, you will have noticed that the UI has been enabled in the configuration file:&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-ini hljs" data-lang="ini"&gt;# UI enabled
ui = true&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The UI is accessible at &lt;a href="http://openbao.example.com:8200" class="bare"&gt;http://openbao.example.com:8200&lt;/a&gt;. You can login with the root token from the initialization.&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/part2_openbao_login_form.png?width=480px" alt="OpenBao Login Form"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 1. OpenBao Login Form&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Here you will see several options to explore. For now we are interested in the Secrets Engine section.&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/part2_openbao_secrets_engine.png?width=480px" alt="OpenBao Secrets Engine"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 2. OpenBao Secrets Engine&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;We have created the path &amp;#34;secret&amp;#34; and inside it a secret called &amp;#34;test&amp;#34;.
We can retrieve it now:&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/part2_test_secret_retrieval.png?width=480px" alt="OpenBao Secret Retrieval"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 3. OpenBao Secret Retrieval&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_security_hardening_checklist"&gt;Security Hardening Checklist&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Before using OpenBao in production you should always consider the following checklist:&lt;/p&gt;
&lt;/div&gt;
&lt;table class="tableblock frame-all grid-all stretch"&gt;
&lt;colgroup&gt;
&lt;col style="width: 25%;"/&gt;
&lt;col style="width: 75%;"/&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class="tableblock halign-left valign-top"&gt;Item&lt;/th&gt;
&lt;th class="tableblock halign-left valign-top"&gt;Recommendation&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;TLS&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Always enable TLS with valid certificates&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;Root Token&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Revoke root token after initial setup and create admin users instead&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;Unseal Keys&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Distribute to different people/locations, consider auto-unseal&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;Network&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Restrict access with firewall rules&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;Audit&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Enable audit logging&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;Backups&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Regular Raft snapshots&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;Updates&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Keep OpenBao updated&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="_what_is_coming_next"&gt;What is Coming Next?&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;In Part 3, we will deploy OpenBao on OpenShift/Kubernetes using the official Helm chart. This provides:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;High availability out of the box&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Kubernetes-native management&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Integration with OpenShift security features&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Persistent storage via PVCs&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&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;You now have a working standalone OpenBao installation. This forms the foundation for understanding how OpenBao operates. While standalone mode is useful for testing and edge cases, most production deployments will use Kubernetes, which we will cover next.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Key takeaways:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;OpenBao can run standalone or in containers&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Dev mode is for testing only&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Production requires proper TLS, initialization, and security hardening&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Unseal keys must be stored securely&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&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/install" target="_blank" rel="noopener"&gt;OpenBao Installation Documentation&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://openbao.org/docs/configuration" target="_blank" rel="noopener"&gt;OpenBao Configuration Reference&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://openbao.org/docs/commands" target="_blank" rel="noopener"&gt;OpenBao CLI Reference&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item><item><title>The Guide to OpenBao - Introduction - Part 1</title><link>https://blog.stderr.at/openshift-platform/security/secrets-management/openbao/2026-02-11-openbao-part-1-introduction/</link><pubDate>Wed, 11 Feb 2026 00:00:00 +0000</pubDate><guid>https://blog.stderr.at/openshift-platform/security/secrets-management/openbao/2026-02-11-openbao-part-1-introduction/</guid><description>&lt;div class="paragraph"&gt;
&lt;p&gt;I finally had some time to dig into Secret Management. For my demo environments, SealedSecrets is usually enough to quickly test something. But if you want to deploy a real application with Secret Management, you need to think of a more permanent solution.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This article is the first of a series of articles about &lt;strong&gt;OpenBao&lt;/strong&gt;, a HashiCorp Vault fork. Today, we will explore what OpenBao is, why it was created, and when you should consider using it for your secret management needs. If you are familiar with HashiCorp Vault, you will find many similarities, but also some important differences that we will discuss.&lt;/p&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;In general, sensitive information in any system should be stored in a secure way. When it comes to OpenShift or Kubernetes, this is especially true, since the secrets are stored in the etcd database. Even if etcd is encrypted at rest, anybody can decode a given base64 string which is stored in the Secret.&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;
Base64 is not an encryption format. It is an encoding format.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;For example, the string &lt;code&gt;Thomas&lt;/code&gt; encoded as base64 is &lt;code&gt;VGhvbWFzCg==&lt;/code&gt;. This is simply masked plain text and it is not secure to share these values, especially not on Git. To make your CI/CD pipelines or GitOps process secure, you need to think of a secure way to manage your Secrets.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This is where OpenBao comes in.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_what_is_openbao"&gt;What is OpenBao?&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;a href="https://openbao.org" target="_blank" rel="noopener"&gt;OpenBao&lt;/a&gt; is an identity-based secrets and encryption management system. It is an open-source, community driven fork of HashiCorp Vault and provides secure storage, fine-grained access control, and lifecycle management for secrets such as API or SSH keys, passwords, certificates, and encryption keys.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The core OpenBao workflow consists of four stages:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Authentication&lt;/strong&gt;: Verifying the identity of the client to determine if they are who they say they are. Once authenticated, a token is created and associated with a so-called policy.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Validation&lt;/strong&gt;: Checking if the client has the required permissions&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Authorization&lt;/strong&gt;: Granting access based on policies that provide or deny access to certain paths and operations in OpenBao.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Access&lt;/strong&gt;: Limiting what the client can do with the secret&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;At its core, OpenBao:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Stores and encrypts sensitive information at rest and in transit&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Provides fine-grained access controls (ACL)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Supports dynamic secret generation&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Offers a comprehensive audit trail (must be enabled)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Enables encryption as a service&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_the_history_why_openbao_exists"&gt;The History: Why OpenBao Exists&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;OpenBao is a community-driven, open-source fork of HashiCorp Vault. But why was it created?&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;In August 2023, HashiCorp announced a significant change to the licensing of their products, including Vault. They moved from the Mozilla Public License 2.0 (MPL 2.0) to the Business Source License 1.1 (BSL 1.1).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The BSL 1.1 license includes restrictions on competitive use:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Competitors could no longer use Vault’s code to offer competing services&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Cloud providers and managed service providers faced restrictions&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The open-source community lost the freedom to fork and commercialize&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_the_fork"&gt;The Fork&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;In response to this license change, the Linux Foundation announced the OpenBao project in December 2023. OpenBao is:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;A fork of HashiCorp Vault (from version 1.14.x)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Licensed under the OSI-approved Mozilla Public License 2.0&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Governed by a community-driven model&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Maintained independently of HashiCorp&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Continues to enable innovation without license restrictions&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_openbao_vs_hashicorp_vault_key_differences"&gt;OpenBao vs. HashiCorp Vault: Key Differences&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;While OpenBao shares its heritage with Vault, there are several important differences:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_licensing"&gt;Licensing&lt;/h3&gt;
&lt;table class="tableblock frame-all grid-all stretch"&gt;
&lt;colgroup&gt;
&lt;col style="width: 20%;"/&gt;
&lt;col style="width: 40%;"/&gt;
&lt;col style="width: 40%;"/&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class="tableblock halign-left valign-top"&gt;Aspect&lt;/th&gt;
&lt;th class="tableblock halign-left valign-top"&gt;OpenBao&lt;/th&gt;
&lt;th class="tableblock halign-left valign-top"&gt;HashiCorp Vault&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;License&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;MPL 2.0 (OSI-approved open source)&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;BSL 1.1 (with staged conversion)&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;Governance&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Community-driven (Linux Foundation)&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;HashiCorp/IBM controlled&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;Commercial restrictions&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;None&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Restrictions on competitive use&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;Enterprise Features&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Community-driven additions&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Paid Enterprise tier&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;Support&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Community support&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Paid support available&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;Branding&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;bao CLI, OpenBao naming&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;vault CLI, Vault naming&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="_technical_differences"&gt;Technical Differences&lt;/h3&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Token Format&lt;/strong&gt; - OpenBao uses shorter tokens in the format &lt;code&gt;sbr.&lt;a href="#random"&gt;[random]&lt;/a&gt;&lt;/code&gt;, while Vault uses longer tokens (&lt;code&gt;hvs.&lt;/code&gt;, &lt;code&gt;hvb.&lt;/code&gt;, &lt;code&gt;hvr.&lt;/code&gt; prefixes followed by long random strings). Old Vault tokens are still accepted according to their TTLs, but newly issued tokens follow the OpenBao format.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Plugin Ecosystem&lt;/strong&gt; - OpenBao comes with fewer built-in plugins by default, focusing on OSI-licensed integrations. Proprietary cloud vendor plugins have been moved to external repositories.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Storage Backend&lt;/strong&gt; - OpenBao has simplified its storage options, primarily supporting Raft as the recommended backend.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;API Compatibility&lt;/strong&gt; - OpenBao’s API is designed to be compatible with Vault, meaning existing clients and integrations should work without modification. However, some edge cases may require updates.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_core_concepts"&gt;Core Concepts&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Before diving into installation and configuration in the next articles, it is essential to understand the fundamental concepts of OpenBao:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_secrets_engines"&gt;Secrets Engines&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Secrets engines are components that store, generate, or encrypt data. OpenBao supports multiple types:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;KV (Key-Value)&lt;/strong&gt;: Simple static secret storage with optional versioning&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;PKI&lt;/strong&gt;: Certificate Authority for generating TLS certificates&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Database&lt;/strong&gt;: Dynamic credential generation for databases&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Transit&lt;/strong&gt;: Encryption as a Service (EaaS)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;SSH&lt;/strong&gt;: SSH key signing and OTP generation&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_authentication_methods"&gt;Authentication Methods&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Authentication methods verify client identity before granting access:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Kubernetes&lt;/strong&gt;: Uses Kubernetes service account tokens. Essential for Kubernetes/OpenShift deployments.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;OIDC&lt;/strong&gt;: OpenID Connect for user authentication, like Keycloak, Okta, or Azure AD.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;LDAP&lt;/strong&gt;: Directory service authentication, like Active Directory, OpenLDAP, or Microsoft AD.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;AppRole&lt;/strong&gt;: Machine-oriented authentication for applications. Ideal for CI/CD pipelines.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Token&lt;/strong&gt;: Direct token-based authentication. The root token is created during initialization.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_storage_backend"&gt;Storage Backend&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;OpenBao encrypts all data before writing to storage. Supported backends include:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Integrated Raft&lt;/strong&gt; (recommended): Built-in distributed storage&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Consul&lt;/strong&gt;: HashiCorp’s service discovery and KV store (less common with OpenBao)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;File&lt;/strong&gt;: Single-node deployments only&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;PostgreSQL/MySQL&lt;/strong&gt;: Database-backed storage&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_policies"&gt;Policies&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Policies define what a client can do after authentication. They use path-based access control (deny-by-default 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-hcl hljs" data-lang="hcl"&gt;path &amp;#34;secret/data/myapp/*&amp;#34; {
capabilities = [&amp;#34;read&amp;#34;, &amp;#34;list&amp;#34;]
}
path &amp;#34;pki/issue/my-role&amp;#34; {
capabilities = [&amp;#34;create&amp;#34;, &amp;#34;update&amp;#34;]
}
path &amp;#34;database/creds/myapp-role&amp;#34; {
capabilities = [&amp;#34;read&amp;#34;]
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_tokens"&gt;Tokens&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Tokens are the primary authentication credential in OpenBao. After successful authentication, clients receive a token that:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Has a TTL (Time To Live)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Is associated with one or more policies&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Can be renewed (if renewable)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Can create child tokens&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_sealunseal"&gt;Seal/Unseal&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;OpenBao starts in a &lt;strong&gt;sealed&lt;/strong&gt; state where it cannot access encrypted data. The unseal process requires multiple key shares (using Shamir’s Secret Sharing) or auto-unseal mechanisms to decrypt the master key.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_leases"&gt;Leases&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Most secrets in OpenBao have an associated lease - a duration after which the secret expires. This enables:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Automatic secret rotation&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Revocation of compromised credentials&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Audit trail of secret usage&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_use_cases"&gt;Use Cases&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_dynamic_database_credentials"&gt;Dynamic Database Credentials&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Instead of storing static database passwords:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;Application authenticates to OpenBao&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Requests database credentials&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;OpenBao creates a temporary database user&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Returns credentials with a short TTL&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Credentials automatically expire&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Benefits: No long-lived credentials, automatic rotation, per-application isolation.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_certificate_management_with_pki"&gt;Certificate Management with PKI&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Instead of manually managing TLS certificates:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;Configure OpenBao as an intermediate CA&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Applications request certificates on demand&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Short-lived certificates (hours/days instead of years)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Automatic renewal before expiration&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Benefits: No certificate sprawl, automated rotation, reduced attack surface.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_encryption_as_a_service"&gt;Encryption as a Service&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Instead of implementing encryption in each application:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;Applications send plaintext to OpenBao’s Transit engine&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;OpenBao encrypts with managed keys&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Applications store ciphertext&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Decryption requests go through OpenBao&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Benefits: Centralized key management, separation of duties, key rotation without re-encryption.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_kubernetes_secret_injection"&gt;Kubernetes Secret Injection&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Instead of storing secrets in Kubernetes Secrets:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;Deploy OpenBao with Kubernetes auth&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Configure injector or External Secrets Operator&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Pods automatically receive secrets at startup&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Secrets never stored in etcd&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Benefits: Secrets not in cluster, dynamic injection, centralized management.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_when_to_use_openbao"&gt;When to Use OpenBao&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;OpenBao is an excellent choice when you need:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Centralized Secret Management&lt;/strong&gt; - If you have secrets scattered across configuration files, environment variables, and various secret stores, OpenBao provides a single source of truth.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Dynamic Secrets&lt;/strong&gt; - For use cases where you need short-lived, automatically rotated credentials (e.g., database passwords), OpenBao can generate them on-demand.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Encryption as a Service&lt;/strong&gt; - If applications need encryption capabilities without managing encryption keys, OpenBao’s Transit engine provides this functionality.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Certificate Management&lt;/strong&gt; - OpenBao’s PKI engine can act as a Certificate Authority, issuing and managing TLS certificates.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Compliance Requirements&lt;/strong&gt; - For environments with strict audit requirements, OpenBao provides comprehensive audit logging of all secret access.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_when_not_to_use_openbao"&gt;When NOT to Use OpenBao&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;OpenBao might be overkill for:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Simple, Static Secrets&lt;/strong&gt; - If you only have a few static secrets that rarely change, simpler solutions like Sealed Secrets or External Secrets Operator with a basic backend might suffice.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Small Teams with Limited Resources&lt;/strong&gt; - OpenBao requires operational expertise to maintain. If you do not have the resources to operate it properly, consider managed alternatives.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Single-Application Deployments&lt;/strong&gt; - If you have a single application with minimal secret requirements, the complexity of OpenBao may not be justified.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_architecture_overview"&gt;Architecture Overview&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;A typical OpenBao deployment consists of:&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/part1_openbao_architecture.png" alt="Architecture Overview"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 1. Architecture Overview&lt;/div&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Multiple Nodes&lt;/strong&gt;: For high availability (HA), OpenBao runs as a cluster&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Raft Consensus&lt;/strong&gt;: Leader election and data replication&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Persistent Storage&lt;/strong&gt;: Encrypted data stored on persistent volumes&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Load Balancer&lt;/strong&gt;: Distributes client requests&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&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;OpenBao provides a powerful, open-source solution for secret management that addresses the challenges of modern cloud-native environments. Its fork from HashiCorp Vault means it benefits from years of development while remaining truly open source.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;In the next article, we will start with a standalone installation to understand the fundamentals before moving to Kubernetes deployments.&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" target="_blank" rel="noopener"&gt;OpenBao Official Website&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://openbao.org/docs" target="_blank" rel="noopener"&gt;OpenBao Documentation&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://github.com/openbao/openbao" target="_blank" rel="noopener"&gt;OpenBao GitHub Repository&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.linuxfoundation.org/press/linux-foundation-launches-openbao" target="_blank" rel="noopener"&gt;Linux Foundation OpenBao Announcement&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item><item><title>RBAC Overview — an OpenShift Console plugin</title><link>https://blog.stderr.at/openshift-platform/security/rbac/2026-06-10-rbac-overview-console-plugin/</link><pubDate>Wed, 10 Jun 2026 00:00:00 +0000</pubDate><guid>https://blog.stderr.at/openshift-platform/security/rbac/2026-06-10-rbac-overview-console-plugin/</guid><description>&lt;div class="paragraph"&gt;
&lt;p&gt;Answering the question &lt;strong&gt;&amp;#34;who can do what on this cluster?&amp;#34;&lt;/strong&gt; usually means juggling oc commands, YAML dumps, and several console pages. RoleBindings live in namespaces, ClusterRoleBindings are cluster-scoped, OpenShift adds Users and Groups on top of Kubernetes subjects, and SecurityContextConstraints have their own authorisation model. For a quick audit before a change window, that is a lot of clicking and I always struggled to find a quick and reliable way to get and overview. For example, I always wanted to see immediately which users have cluster-admin rights or which service accounts are on the privileged SCC.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The &lt;strong&gt;RBAC Overview&lt;/strong&gt; is a dynamic &lt;strong&gt;OpenShift Console plugin&lt;/strong&gt; that pulls these views into one place. This article walks through what it does, how to install or test it, and how I use the tabs in practice. This open source plugin is available on &lt;a href="https://github.com/tjungbauer/openshift-console-rbac-overview" target="_blank" rel="noopener"&gt;GitHub&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_why_another_rbac_view"&gt;Why another RBAC view?&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The console already lists RoleBindings, ClusterRoleBindings, Users, and Groups. That is sufficient when you know exactly which resource you need. It is less helpful when you start from a question:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Who has cluster-admin outside the usual system groups?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;What can user alice do in the namespace openshift-gitops?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Which groups may get pods in the namespace default?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Which service accounts are on the privileged SCC?&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The RBAC Overview plugins tries to fix these questions. Each tab uses the Kubernetes and OpenShift authorization APIs (&lt;code&gt;SelfSubjectReview&lt;/code&gt;, &lt;code&gt;ResourceAccessReview&lt;/code&gt;, &lt;code&gt;SubjectAccessReview&lt;/code&gt;, and list permissions) rather than re-implementing policy evaluation in the browser.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_what_you_get"&gt;What you get&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Navigation: The plugin hooks into the &lt;strong&gt;Administrator perspective → User Management → RBAC Overview&lt;/strong&gt; (entry appears above &lt;strong&gt;Users&lt;/strong&gt;).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;As direct URL you can use: &lt;code&gt;&amp;lt;cluster-url&amp;gt;/rbac-overview&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The following tabs are available:&lt;/p&gt;
&lt;/div&gt;
&lt;table class="tableblock frame-all grid-all stretch"&gt;
&lt;colgroup&gt;
&lt;col style="width: 25%;"/&gt;
&lt;col style="width: 75%;"/&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class="tableblock halign-left valign-top"&gt;Tab&lt;/th&gt;
&lt;th class="tableblock halign-left valign-top"&gt;Question it answers&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;&lt;strong&gt;Who can&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Who may perform a certain action or whether one subject is allowed to do certain actions. It basically mirrors the &lt;code&gt;oc adm policy who-can&lt;/code&gt; command and presents the results in a more user-friendly way.
The tab also includes a &lt;strong&gt;Show my access&lt;/strong&gt; button for the logged-in user, which shows what the logged in user can do on the cluster.&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;&lt;strong&gt;Cluster admins&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Cluster-wide and namespace-scoped bindings to sensitive roles (&lt;code&gt;cluster-admin&lt;/code&gt;, &lt;code&gt;admin&lt;/code&gt;, or roles you configure). This helps to see which users have elevated rights and which namespaces are affected.&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;&lt;strong&gt;Namespace access&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;All bindings that affect one namespace — local RoleBindings and applicable ClusterRoleBindings.&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;&lt;strong&gt;Subjects&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Pick a user, group, or service account and inspect its bindings. This helps to inspect a certain subject and see which roles are bound to it.&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;&lt;strong&gt;Role access&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Pick a ClusterRole and list every ClusterRoleBinding and RoleBinding that references it — the inverse of the Subjects tab when you start from a role name instead of a user or group.&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;&lt;strong&gt;SCC access&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;All SCCs with key fields, direct &lt;code&gt;users&lt;/code&gt; / &lt;code&gt;groups&lt;/code&gt; / &lt;code&gt;serviceAccounts&lt;/code&gt; on each SCC, and RBAC grants to &lt;code&gt;use securitycontextconstraints&lt;/code&gt;. This helps to see which users, groups, and service accounts are on which SCCs and which RBAC grants are in place.&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_architecture_in_brief"&gt;Architecture in brief&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The plugin follows the usual dynamic plugin pattern:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;Webpack builds static assets (&lt;code&gt;plugin-manifest.json&lt;/code&gt;, JavaScript bundles, locales).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A container image serves those files over &lt;strong&gt;HTTPS on port 9443&lt;/strong&gt; (required by the &lt;code&gt;ConsolePlugin&lt;/code&gt; API).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Helm installs Deployment, Service, ConsolePlugin, ConfigMap (&lt;code&gt;plugin-config.json&lt;/code&gt;), and optionally a Job that enables the plugin on the console. (Plugins must be enabled by the console operator.)&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The runtime image uses &lt;a href="https://hummingbird-project.io/" target="_blank" rel="noopener"&gt;Project Hummingbird&lt;/a&gt; (&lt;code&gt;registry.access.redhat.com/hi/nginx&lt;/code&gt;). I tried this as they are expected to be super-small distroless images, with the goal to have zero CVEs …​ and the size of the image is really only 19MB.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The nginx that is used in this image is serving &lt;code&gt;dist/&lt;/code&gt; with a serving certificate injected by OpenShift. There is no application logic in the container beyond static files.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Configuration for &lt;strong&gt;sensitive roles&lt;/strong&gt; on the Cluster admins tab is delivered via &lt;code&gt;plugin-config.json&lt;/code&gt; (Helm values → ConfigMap).
Default entries include &lt;code&gt;cluster-admin&lt;/code&gt; and &lt;code&gt;admin&lt;/code&gt;; you can add names or label ClusterRoles with &lt;code&gt;rbac-overview.io/elevated: &amp;#34;true&amp;#34;&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_prerequisites"&gt;Prerequisites&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;OpenShift Container Platform or OKD &lt;strong&gt;4.10+&lt;/strong&gt; (dynamic console plugins)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;For cluster install: Helm 3, &lt;code&gt;oc&lt;/code&gt;, Podman or Docker, and permission to push your image registry&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;For local development: Node.js 22+, npm, and &lt;code&gt;oc login&lt;/code&gt; with a &lt;strong&gt;bearer token&lt;/strong&gt; (not certificate-only kubeconfig)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The plugin is tested against recent 4.20+ consoles. Match the console SDK generation to your cluster (the repository currently targets &lt;code&gt;@openshift-console/dynamic-plugin-sdk@4.21-latest&lt;/code&gt;).&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_install_on_a_cluster"&gt;Install on a cluster&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_install_with_helm"&gt;Install with Helm&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Log in to the cluster, then install the chart. The published and public plugin image is &lt;code&gt;quay.io/tjungbau/openshift-console-rbac-overview:1.0.5&lt;/code&gt; (chart version &lt;strong&gt;1.0.5&lt;/strong&gt;).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;From the &lt;a href="https://github.com/tjungbauer/openshift-console-rbac-overview" target="_blank" rel="noopener"&gt;repository&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;oc login https://&amp;lt;api-url&amp;gt;:6443 -u &amp;lt;user&amp;gt; -p &amp;#39;&amp;lt;password&amp;gt;&amp;#39;
git clone https://github.com/tjungbauer/openshift-console-rbac-overview.git
cd openshift-console-rbac-overview
helm upgrade --install rbac-overview ./chart/rbac-overview \
--namespace rbac-overview \
--create-namespace \
--set namespace.create=false \
--set image.tag=1.0.5 \
--set image.pullPolicy=Always \
--wait&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Or add the published Helm repository (no clone required):&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;helm repo add rbac-overview https://tjungbauer.github.io/openshift-console-rbac-overview
helm repo update
helm upgrade --install rbac-overview rbac-overview/rbac-overview \
--namespace rbac-overview \
--create-namespace \
--set namespace.create=false \
--set image.tag=1.0.5 \
--wait&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The chart defaults to &lt;code&gt;quay.io/tjungbau/openshift-console-rbac-overview&lt;/code&gt;; set &lt;code&gt;image.tag&lt;/code&gt; only when you need a specific release.&lt;/p&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;
Do not combine &lt;code&gt;helm --create-namespace&lt;/code&gt; with &lt;code&gt;--set namespace.create=true&lt;/code&gt;. Helm 4 reports an ownership error on the Namespace object. Use &lt;code&gt;--create-namespace&lt;/code&gt; and &lt;code&gt;namespace.create=false&lt;/code&gt; as shown above. I will clean this up in the future.
&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="_build_your_own_image"&gt;Build your own image&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;If you prefer to create your own image, for example in an air-gapped environment, you are free to do so. The runtime image is a thin nginx layer on top of webpack output — there is no &lt;code&gt;npm&lt;/code&gt; step inside the container. This helps keep the image small and avoids unnecessary dependencies.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;From the repository root:&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;npm install
npm run build # writes dist/ (plugin-manifest.json, bundles, locales)
podman build --platform=linux/amd64 \ &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
-t &amp;lt;your-registry-repository&amp;gt;/:&amp;lt;tag&amp;gt; .
podman login &amp;lt;your-registry-repository&amp;gt; # if your repository is private
podman push &amp;lt;your-registry-repository&amp;gt;/:&amp;lt;tag&amp;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;Use your own registry, repository and version tag.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Then point Helm at your image:&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;helm upgrade --install rbac-overview ./chart/rbac-overview \
--namespace rbac-overview \
--create-namespace \
--set namespace.create=false \
--set image.repository=&amp;lt;your-registry-repository&amp;gt; \ &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
--set image.tag=&amp;lt;tag&amp;gt; \ &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
--set image.pullPolicy=Always \
--wait&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;Use your own registry and repository.&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;Use your own version tag.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The repository also ships &lt;code&gt;scripts/build-install.sh&lt;/code&gt;, which runs &lt;code&gt;npm run build&lt;/code&gt;, builds and pushes the image, installs the chart, and runs verification checks in one pass:&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;chmod +x scripts/build-install.sh
QUAY_USER=&amp;lt;your-quay-user&amp;gt; ./scripts/build-install.sh&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Override &lt;code&gt;VERSION&lt;/code&gt; or &lt;code&gt;PLATFORM&lt;/code&gt; when the tag should differ from &lt;code&gt;package.json&lt;/code&gt; or when you build on Apple Silicon for a &lt;code&gt;linux/amd64&lt;/code&gt; cluster.&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;
The script was created due to my own lazyness and it has quay.io hardcoded.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;After install, confirm the plugin is enabled:&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 console.operator cluster -o jsonpath=&amp;#39;{.spec.plugins}{&amp;#34;\n&amp;#34;}&amp;#39;
oc -n rbac-overview get deploy,consoleplugin&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Open the console, switch to &lt;strong&gt;Administrator&lt;/strong&gt;, and reload &lt;strong&gt;User Management → RBAC Overview&lt;/strong&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_local_development"&gt;Local development&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;If you like to test the plugin locally first, you can do so by running the following commands - no image build required:&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;# terminal 1
npm install
npm run start
# terminal 2
oc login ... # must provide a token: oc whoami --show-token
npm run start-console # console on http://localhost:9000&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Open &lt;a href="http://localhost:9000/rbac-overview" target="_blank" rel="noopener"&gt;http://localhost:9000/rbac-overview&lt;/a&gt;. The console loads the plugin from &lt;code&gt;localhost:9001&lt;/code&gt; while API calls use your &lt;code&gt;oc&lt;/code&gt; session.&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;
On Apple Silicon, set &lt;code&gt;CONSOLE_IMAGE_PLATFORM=linux/amd64&lt;/code&gt; before &lt;code&gt;npm run start-console&lt;/code&gt;.
&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 class="sect1"&gt;
&lt;h2 id="_using_the_plugin"&gt;Using the plugin&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_who_can"&gt;Who can&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The default tab mirrors &lt;code&gt;oc adm policy who-can&lt;/code&gt; for namespaced and cluster-scoped resources.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong&gt;Query mode&lt;/strong&gt; offers:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Who can&lt;/strong&gt; — list subjects allowed to perform the action&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Can subject&lt;/strong&gt; — check one user, group, or service account&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Set &lt;strong&gt;Scope&lt;/strong&gt;, &lt;strong&gt;Verb&lt;/strong&gt;, &lt;strong&gt;Resource&lt;/strong&gt;, optional &lt;strong&gt;API group&lt;/strong&gt;, &lt;strong&gt;Subresource&lt;/strong&gt;, &lt;strong&gt;Resource name&lt;/strong&gt;, and for namespaced queries a real &lt;strong&gt;Namespace&lt;/strong&gt;.&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/RBAC/images/rbac-overview-who-can-results.png?width=900px" alt="Who can results for get pods in default"/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The button &lt;strong&gt;Show my access&lt;/strong&gt; lists groups, visible namespaces, and role bindings for the logged-in user. This becomes handy when someone asks &lt;strong&gt;what can I actually see on this cluster?&lt;/strong&gt;&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/RBAC/images/rbac-overview-my-access.png?width=900px" alt="Show my access panel"/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;From &lt;strong&gt;My access&lt;/strong&gt; you can jump to &lt;strong&gt;Can subject&lt;/strong&gt; with your user pre-filled, or open the &lt;strong&gt;Subjects&lt;/strong&gt; tab on yourself.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_cluster_admins"&gt;Cluster admins&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This tab highlights bindings to roles you treat as &lt;strong&gt;elevated&lt;/strong&gt; (configured in plugin-config.json or via the rbac-overview.io/elevated label on ClusterRoles).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Sub tabs:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Cluster-wide admins&lt;/strong&gt; — ClusterRoleBindings to sensitive ClusterRoles.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Namespace elevated&lt;/strong&gt; — RoleBindings in any namespace that reference sensitive roles.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/openshift-platform/security/RBAC/images/rbac-overview-cluster-admins.png?width=900px" alt="Cluster-wide sensitive bindings"/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Use &lt;strong&gt;View rules&lt;/strong&gt; on a row to expand policy rules below the table.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_namespace_access"&gt;Namespace access&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Select a namespace, then browse:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;In namespace&lt;/strong&gt; — RoleBindings defined in that namespace.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Cluster-wide&lt;/strong&gt; — ClusterRoleBindings whose ClusterRoles apply there.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/openshift-platform/security/RBAC/images/rbac-overview-namespace-access.png?width=900px" alt="Namespace access for openshift-gitops"/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The default filter shows &lt;strong&gt;User&lt;/strong&gt; and &lt;strong&gt;Group&lt;/strong&gt; subjects; add &lt;strong&gt;ServiceAccount&lt;/strong&gt; when you care about workload identities.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_subjects"&gt;Subjects&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;If you are interested to query a specific subject (user, group etc.) you can use this tab. The list merges:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;subjects found in RoleBindings and ClusterRoleBindings across the cluster, and&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Users, Groups, and ServiceAccounts returned by the OpenShift user API (where your account may list them).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/openshift-platform/security/RBAC/images/rbac-overview-subjects.png?width=900px" alt="Subjects tab with user and group filter"/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Select an entry to see binding details on the right, open linked resources, or continue in &lt;strong&gt;Who can → Can subject&lt;/strong&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;
The error message that appears in the screenshot for the selected user is due to the fact that there are no bindings for this user. This is normal and expected.
&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="_role_access"&gt;Role access&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The &lt;strong&gt;Subjects&lt;/strong&gt; tab answers &lt;strong&gt;who has what bound to them?&lt;/strong&gt; &lt;strong&gt;Role access&lt;/strong&gt; flips the question: &lt;strong&gt;who is bound to this ClusterRole?&lt;/strong&gt; That is useful when you audit a built-in or custom ClusterRole (&lt;code&gt;view&lt;/code&gt;, &lt;code&gt;edit&lt;/code&gt;, &lt;code&gt;cluster-admin&lt;/code&gt;, or a role you ship with an operator) and need every ClusterRoleBinding and RoleBinding that references it in one table.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Pick a &lt;strong&gt;ClusterRole&lt;/strong&gt; from the dropdown. The table lists matching bindings with scope, namespace (for namespaced RoleBindings), role kind, subjects, and &lt;strong&gt;View rules&lt;/strong&gt; to expand the ClusterRole policy inline.&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/RBAC/images/rbac-overview-role-access.png?width=900px" alt="Role access for ClusterRole cluster-admin"/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The screenshot shows &lt;code&gt;cluster-admin&lt;/code&gt; with 30 bindings — a mix of human users, the &lt;code&gt;cluster-admins&lt;/code&gt; group, and OpenShift system groups such as &lt;code&gt;system:openshift:operator:kube-apiserver-operator&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_scc_access"&gt;SCC access&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To get a quick overview about SCC and who is on which SCC was always tricky.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;OpenShift SCCs authorise subjects in two ways:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;direct fields on the SCC object&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;RBAC to &lt;code&gt;use securitycontextconstraints&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This tab shows every SCC and their configuration.&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/RBAC/images/rbac-overview-scc-access.png?width=900px" alt="All security context constraints"/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;All SCCs&lt;/strong&gt; — every SCC you can list, with priority, privileged/host settings, run-as-user and SELinux strategy, volumes, and direct subject count.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Direct authorizations&lt;/strong&gt; — users, groups, and service accounts listed on each SCC.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;RBAC use grants&lt;/strong&gt; — ClusterRoleBindings that grant &lt;code&gt;use&lt;/code&gt; on SCC resources.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This tab does not replace a full SCC compliance review, but it saves time when you are tracing why a pod was allowed a privileged context.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_limitations"&gt;Limitations&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Who can&lt;/strong&gt; requires the same elevated create permissions as &lt;code&gt;oc adm policy who-can&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Large clusters: tables paginate, but initial binding scans can take a moment on first tab open.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_summary"&gt;Summary&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;RBAC Overview does not replace &lt;code&gt;oc&lt;/code&gt; or your GitOps RBAC manifests. It gives operators and auditors a single console entry point for the questions that come up every week — who is admin, what does this namespace grant, what is bound to this service account, who can touch this verb/resource, and which identities sit on sensitive SCCs.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The plugin was created with the help of AI (I always want to be open about that), especially the initial layout, some styling elements like dropdowns and buttons and the README …​ which I never want to write.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;If you try it, feedback and issues are welcome on the &lt;a href="https://github.com/tjungbauer/openshift-console-rbac-overview" target="_blank" rel="noopener"&gt;repository&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_references"&gt;References&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://github.com/tjungbauer/openshift-console-rbac-overview" target="_blank" rel="noopener"&gt;openshift-console-rbac-overview on GitHub&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://quay.io/repository/tjungbau/openshift-console-rbac-overview?tab=tags" target="_blank" rel="noopener"&gt;openshift-console-rbac-overview on Quay&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://github.com/openshift/console/tree/main/frontend/packages/console-dynamic-plugin-sdk" target="_blank" rel="noopener"&gt;OpenShift Console dynamic plugin SDK&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://docs.openshift.com/container-platform/latest/web_console/dynamic-plugins-overview.html" target="_blank" rel="noopener"&gt;OpenShift dynamic plugins documentation&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://hummingbird-project.io/" target="_blank" rel="noopener"&gt;Project Hummingbird&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item><item><title>Cert-Manager Policy Approver in OpenShift</title><link>https://blog.stderr.at/openshift-platform/security/certificates/2025-06-03-cert-manager-approver-policy/</link><pubDate>Tue, 03 Jun 2025 00:00:00 +0000</pubDate><guid>https://blog.stderr.at/openshift-platform/security/certificates/2025-06-03-cert-manager-approver-policy/</guid><description>&lt;div class="paragraph"&gt;
&lt;p&gt;One of the most commonly deployed operators in OpenShift environments is the &lt;strong&gt;Cert-Manager Operator&lt;/strong&gt;. It automates the management of TLS certificates for applications running within the cluster, including their issuance and renewal.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The tool supports a variety of certificate issuers by default, including ACME, Vault, and self-signed certificates. Whenever a certificate is needed, Cert-Manager will automatically create a CertificateRequest resource that contains the details of the certificate. This resource is then processed by the appropriate issuer to generate the actual TLS certificate. The approval process in this case is usually fully automated, meaning that the certificate is issued without any manual intervention.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;But what if you want to have more control? What if certificate issuance must follow strict organizational policies, such as requiring a specifc country code or organization name?
This is where the &lt;strong&gt;CertificateRequestPolicy&lt;/strong&gt; resource, a resource provided by the Approver Policy, comes into play.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This article walks through configuring the &lt;strong&gt;Cert-Manager Approver Policy&lt;/strong&gt; in OpenShift to enforce granular policies on certificate requests.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_prerequisites"&gt;Prerequisites&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Before you begin, ensure you have the following:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;OpenShift 4.16 or higher with cluster-admin access&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Cert-Manager Operator installed&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;
The installation of Cert-Manager itself is discussed in the article: &lt;a href="https://blog.stderr.at/gitopscollection/2024-07-04-managing-certificates-with-gitops/"&gt;Managing Certificates using GitOps approach&lt;/a&gt;.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="admonitionblock caution"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-caution" title="Caution"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
The Cert-Manager Operator does not currently support the Approver Policy by default. You need to install the Approver Policy manually using a Helm Chart. There is a feature request to include the Approver Policy in the Cert-Manager Operator in the future.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_adding_approver_policy_chart_as_a_dependency"&gt;Adding Approver Policy Chart as a dependency&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Source: &lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/management-cluster/cert-manager" target="_blank" rel="noopener"&gt;Cert-Manager Deployment&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The above Chart contains the necessary resources to deploy the Cert-Manager itself and Cert-Manager Approver Policy in OpenShift.
To deploy the Approver Policy alongside Cert-Manager, add it as a dependency in your &lt;strong&gt;Chart.yaml&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-yaml hljs" data-lang="yaml"&gt;[...]
- name: cert-manager-approver-policy
version: v0.19.0
repository: https://charts.jetstack.io
[...]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This is the official Helm Chart for the Cert-Manager Approver Policy tool provided by Jetstack. The version used in this example is v0.19.0, but you can use a newer version if available.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_configuration_of_the_helm_chart"&gt;Configuration of the Helm Chart&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The initial &lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/blob/main/clusters/management-cluster/cert-manager/values.yaml" target="_blank" rel="noopener"&gt;values.yaml&lt;/a&gt; file was extended to:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;include the configuration for the Approver Policy Chart&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;the configuration for a &lt;strong&gt;CertificateRequestPolicy&lt;/strong&gt; object&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;with some specific modifications to the CertManager resource itself&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_disabling_cert_managers_auto_approver"&gt;Disabling Cert-Manager’s Auto-Approver&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The first step we need to do is to disable the auto-approver of the Cert-Manager. If this is not done, there will be a race condition between the auto-approver and the Approver Policy, which will lead to unexpected results.
These changes are done by:&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;cert-manager:
certManager:
enable_patch: true &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
unsupportedConfigOverrides:
controller:
args:
- &amp;#39;--controllers=*,-certificaterequests-approver&amp;#39; &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&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;This enables the patching of the CertManager resource, which tells the chart to overwrite (patch) the automatically generated CertManager resource with the custom configuration.&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;This disables the auto-approver Controller of the Cert-Manager.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="admonitionblock caution"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-caution" title="Caution"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
As the name suggests, this is currently an unsupported configuration, but it is necessary to disable the auto-approver for the Cert-Manager. In the future versions of the Cert-Manager, this might change, and the auto-approver might be supported out of the box.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;In my case, the full CertManager resource looks like this:&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/v1alpha1
kind: CertManager
metadata:
annotations:
name: cluster
spec:
controllerConfig:
overrideArgs: &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
- &amp;#39;--dns01-recursive-nameservers-only&amp;#39;
- &amp;#39;--dns01-recursive-nameservers=ns-362.awsdns-45.com:53,ns-930.awsdns-52.net:53&amp;#39;
logLevel: Normal
managementState: Managed
operatorLogLevel: Normal
unsupportedConfigOverrides: &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
controller:
args:
- &amp;#39;--controllers=*,-certificaterequests-approver&amp;#39;&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;Settings to support AWS Nameservers for DNS01 challenges.&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;This disables the auto-approver Controller of the Cert-Manager.&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="_configuration_of_the_approver_policy"&gt;Configuration of the Approver Policy&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The second step is to configure the Approver Policy chart. This chart will deploy the necessary resources, most importantly a Deployment that will start the Pods which will process the CertificateRequestPolicy resources later on.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;My configuration for that chart looks like this (some default values are omitted for brevity):&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;cert-manager-approver-policy:
crds: &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
# This option decides if the CRDs should be installed
# as part of the Helm installation.
enabled: true
# This option makes it so that the &amp;#34;helm.sh/resource-policy&amp;#34;: keep
# annotation is added to the CRD. This will prevent Helm from uninstalling
# the CRD when the Helm release is uninstalled.
# WARNING: when the CRDs are removed, all cert-manager-approver-policy custom resources
# (CertificateRequestPolicy) will be removed too by the garbage collector.
keep: true
# Number of replicas of approver-policy to run.
replicaCount: 1 &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
image: &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
# Target image repository.
repository: quay.io/jetstack/cert-manager-approver-policy
# Kubernetes imagePullPolicy on Deployment.
pullPolicy: IfNotPresent
tag: v0.19.0
app:
# List of signer names that approver-policy will be given permission to
# approve and deny. CertificateRequests referencing these signer names can be
# processed by approver-policy. Defaults to an empty array, allowing approval
# for all signers.
approveSignerNames: &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
- &amp;#39;issuers.cert-manager.io/*&amp;#39;
- &amp;#39;clusterissuers.cert-manager.io/*&amp;#39;&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;This enables the installation of the CRDs that are required for the Approver Policy.&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;The number of replicas of the Approver Policy Deployment. In most cases, one replica is enough.&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;The image configuration for the Approver Policy. The image is pulled from the Jetstack Quay.io repository.&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;The list of signer names that the Approver Policy will be allowed to approve. In this case, it is configured to allow all issuers and clusterissuers.&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;
The approveSignerNames are, if configured, an important setting, especially if you want to add custom (cluster)issuers. In such a case, you need to add the name of the custom issuer to this list. Otherwise the Approver Policy will not be able to approve the CertificateRequests for that issuer.
&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="_creating_a_certificaterequestpolicy_and_rolebinding"&gt;Creating a CertificateRequestPolicy and Role(Binding)&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The final step in our configuration is to define a &lt;strong&gt;CertificateRequestPolicy&lt;/strong&gt; resource that will define the policy for the certificate requests. This resource will be processed by the Approver Policy and will determine if a certificate request is approved or denied based on the defined criteria.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The following example shows a CertificateRequestPolicy that will:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Allow certificate requests with any common name, DNS names, IP addresses, URIs, and email addresses.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Require DNS names to be set.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Require the subject to contain a specific organization (MyOrganization) and country code (AT).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Allow usages for server auth and client auth.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set constraints for the certificate duration (1h-24h) and private key algorithm (RSA) and size (2048-4096).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Allow all issuers by using an empty selector.&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;role: cert-manager-policy:global-approver &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
serviceAccount: cert-manager &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
cert_manager_Namespace: cert-manager &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
policies:
- name: my-approver-policy
enabled: true
allowed:
commonName:
required: false
value: &amp;#34;*&amp;#34;
validations: []
dnsNames: &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
required: true
values:
- &amp;#34;*&amp;#34;
validations: []
ipAddresses:
required: false
values: [&amp;#34;*&amp;#34;]
validations: []
uris:
required: false
values:
- &amp;#34;*&amp;#34;
validations: []
emailAddresses:
required: false
values:
- &amp;#34;*&amp;#34;
validations: []
# isCA: false
subject:
organizations: &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
required: true
values:
- &amp;#34;MyOrganization&amp;#34;
validations:
- rule: self.matches(&amp;#34;MyOrganization&amp;#34;)
message: Organization must be MyOrganization
countries:
required: true
values:
- AT
validations:
- rule: self.matches(&amp;#34;AT&amp;#34;)
message: Country code must be AT
usages:
- &amp;#34;server auth&amp;#34;
- &amp;#34;client auth&amp;#34;
constraints: &lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;(6)&lt;/b&gt;
minDuration: 1h
maxDuration: 24h
privateKey:
algorithm: RSA
minSize: 2048
maxSize: 4096
selector:
issuerRef: {} &lt;i class="conum" data-value="7"&gt;&lt;/i&gt;&lt;b&gt;(7)&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;The role that is used to approve the certificate requests. This role must be created in the OpenShift cluster and must have the necessary permissions to approve certificate requests.&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;The service account that is used by the Approver Policy to process the certificate requests. This service account must have the necessary permissions to access the CertificateRequest resources.&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;The namespace where the Cert-Manager is deployed. This is usually the &lt;code&gt;cert-manager&lt;/code&gt; namespace, but you can change it if you have a different namespace.&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;The DNS names are required to be set for the certificate request.&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;The subject must contain the organization MyOrganization and the country code AT.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;6&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The constraints for the certificate request, such as the minimum and maximum duration, private key algorithm, and size.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="7"&gt;&lt;/i&gt;&lt;b&gt;7&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The selector is empty, which means that the policy applies to all issuers. If you want to limit the policy to specific issuers, you can specify the issuerRef here.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_rendered_certificaterequestpolicy_and_rolebinding"&gt;Rendered CertificateRequestPolicy and Role(Binding)&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The above configuration will create a CertificateRequestPolicy resource that looks like this:
&lt;div class="expand"&gt;
&lt;div class="expand-label" style="cursor: pointer;" onclick="$h = $(this);$h.next('div').slideToggle(100,function () {$h.children('i').attr('class',function () {return $h.next('div').is(':visible') ? 'fas fa-chevron-down' : 'fas fa-chevron-right';});});"&gt;
&lt;i style="font-size:x-small;" class="fas fa-chevron-right"&gt;&lt;/i&gt;
&lt;span&gt;
&lt;a&gt;Expand me...&lt;/a&gt;
&lt;/span&gt;
&lt;/div&gt;
&lt;div class="expand-content" style="display: none;"&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;---
# Source: cert-manager/templates/ClusterRole-Approver-Policy-approving.yaml
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: &amp;#34;cert-manager-policy:global-approver&amp;#34;
labels:
helm.sh/chart: cert-manager-2.0.0
app.kubernetes.io/name: cert-manager
app.kubernetes.io/instance: release-name
app.kubernetes.io/managed-by: Helm
rules:
- verbs:
- use
apiGroups:
- policy.cert-manager.io
resources:
- certificaterequestpolicies
resourceNames:
- my-approver-policy
---
# Source: cert-manager/charts/cert-manager-approver-policy/templates/clusterrolebinding.yaml
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
labels:
app.kubernetes.io/name: cert-manager-approver-policy
helm.sh/chart: cert-manager-approver-policy-v0.19.0
app.kubernetes.io/instance: release-name
app.kubernetes.io/version: &amp;#34;v0.19.0&amp;#34;
app.kubernetes.io/managed-by: Helm
name: cert-manager-approver-policy
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cert-manager-approver-policy
subjects:
- kind: ServiceAccount
name: cert-manager-approver-policy
namespace: default
---
# Source: cert-manager/templates/CertificateRequestPolicy.yaml
apiVersion: policy.cert-manager.io/v1alpha1
kind: CertificateRequestPolicy
metadata:
name: my-approver-policy
annotations:
argocd.argoproj.io/sync-wave: &amp;#34;10&amp;#34;
labels:
helm.sh/chart: cert-manager-2.0.0
app.kubernetes.io/name: cert-manager
app.kubernetes.io/instance: release-name
app.kubernetes.io/managed-by: Helm
spec:
allowed:
commonName:
required: false
value: &amp;#34;*&amp;#34;
validations: []
dnsNames:
required: true
values:
- &amp;#34;*&amp;#34;
validations: []
emailAddresses:
required: false
values:
- &amp;#34;*&amp;#34;
validations: []
ipAddresses:
required: false
values:
- &amp;#34;*&amp;#34;
validations: []
uris:
required: false
values:
- &amp;#34;*&amp;#34;
validations: []
isCA: false
subject:
organizations:
required: true
values:
- &amp;#34;MyOrganization&amp;#34;
validations:
- rule: &amp;#34;self.matches(\&amp;#34;MyOrganization\&amp;#34;)&amp;#34;
message: &amp;#34;Organization must be MyOrganization&amp;#34;
countries:
required: true
values:
- &amp;#34;AT&amp;#34;
validations:
- rule: &amp;#34;self.matches(\&amp;#34;AT\&amp;#34;)&amp;#34;
message: &amp;#34;Country code must be AT&amp;#34;
organizationalUnits:
required: false
values: [&amp;#34;*&amp;#34;]
validations: []
localities:
required: false
values: [&amp;#34;*&amp;#34;]
validations: []
provinces:
required: false
values: [&amp;#34;*&amp;#34;]
validations: []
streetAddresses:
required: false
values: [&amp;#34;*&amp;#34;]
validations: []
postalCodes:
required: false
values: [&amp;#34;*&amp;#34;]
validations: []
serialNumber:
required: false
value: &amp;#34;*&amp;#34;
validations: []
usages:
- &amp;#34;server auth&amp;#34;
- &amp;#34;client auth&amp;#34;
constraints:
minDuration: 1h
maxDuration: 24h
privateKey:
algorithm: RSA
minSize: 2048
maxSize: 4096
selector:
issuerRef: {}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;One important note is about the ClusterRole and ClusterRoleBinding that are created by the Helm Chart. The role looks like the following and is required to allow the Approver Policy to approve certificate requests. This small bit, puzzled me for a while:&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;rules:
- verbs:
- use
apiGroups:
- policy.cert-manager.io
resources:
- certificaterequestpolicies
resourceNames:
- my-approver-policy&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;With the above configuration we are good to go. The Helm Chart can be deployed to the OpenShift cluster (for example, using Argo CD), and the CertificateRequestPolicy will be created automatically.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;A new Pod is running in the &lt;strong&gt;cert-manager&lt;/strong&gt; namespace:&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-console hljs" data-lang="console"&gt;❯ oc get pods -n cert-manager | grep approver
NAME READY STATUS RESTARTS AGE
cert-manager-approver-policy-xxxxx 1/1 Running 0 XXm &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&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;The Pod &lt;code&gt;cert-manager-approver-policy-xxxxx&lt;/code&gt; is the Pod that is responsible for processing the CertificateRequestPolicy resources.&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="_testing_the_policy"&gt;Testing the Policy&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_test_1_valid_certificate_request"&gt;Test 1 - Valid Certificate Request&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Now it is time to test the policy. We need to create a Certificate and monitor the output of our approval pod.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;As a reminder, the policy we created requires the following:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The subject must contain the organization MyOrganization&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The subject must contain the country code AT.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The keysize must be at least 2048 bits. (max 4096 bits)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The duration must be between 1 hour and 24 hours.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The usage must be server auth or client auth.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Let’s create this example Certificate in the &lt;code&gt;myproject&lt;/code&gt; namespace:&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: cert-manager.io/v1
kind: Certificate
metadata:
name: test-certificate1
namespace: myproject
spec:
dnsNames:
- test1.apps.ocp.aws.ispworld.at
duration: 24h
issuerRef:
kind: ClusterIssuer
name: letsencrypt-prod
privateKey:
algorithm: RSA
encoding: PKCS1
rotationPolicy: Always
secretName: test1
subject:
organizations:
- MyOrganization
countries:
- AT
usages:
- server auth&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;In the log of the Approver Policy Pod, we should see the following output:&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-console hljs" data-lang="console"&gt;time=2025-06-03T16:07:58.656Z level=DEBUG+3 msg=&amp;#34;Approved by CertificateRequestPolicy: \&amp;#34;my-approver-policy\&amp;#34;&amp;#34; logger=controller-manager/events type=Normal object=&amp;#34;{Kind:CertificateRequest Namespace:myproject Name:test-certificate1-1 [...]}&amp;#34; reason=Approved&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This indicates that the CertificateRequest was approved by the Approver Policy. The policy was able to validate the subject, keysize, duration, and usage of the certificate request and approved it accordingly.
The certificate has been created successfully in the &lt;strong&gt;myproject&lt;/strong&gt; namespace, and the secret &lt;strong&gt;test1&lt;/strong&gt; contains the TLS certificate and private key.&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;❯ oc get secret test1 -n myproject -o yaml
apiVersion: v1
data:
tls.crt: ...
tls.key: ...
kind: Secret
metadata:
labels:
controller.cert-manager.io/fao: &amp;#34;true&amp;#34;
name: test1
namespace: myproject
type: kubernetes.io/tls&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_test_2_invalid_certificate_request"&gt;Test 2 - Invalid Certificate Request&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;That was easy, but what happens if we create a CertificateRequest that does not meet the policy requirements? Let’s try to create a Certificate without the required organization or a wrong country code:&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: cert-manager.io/v1
kind: Certificate
metadata:
name: test-certificate2
namespace: myproject
spec:
dnsNames:
- test2.apps.ocp.aws.ispworld.at
duration: 24h
issuerRef:
kind: ClusterIssuer
name: letsencrypt-prod
privateKey:
algorithm: RSA
encoding: PKCS1
rotationPolicy: Always
secretName: test2
subject: &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
countries:
- XX
usages:
- server auth&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;The subject does not contain the required organization MyOrganization and the country code is set to XX, which is not allowed by the policy.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This will lead to the following error in the log of the Approver Policy Pod:&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-console hljs" data-lang="console"&gt;time=2025-06-03T16:16:02.233Z level=DEBUG+3 msg=&amp;#34;No policy approved this request: [my-approver-policy: [spec.allowed.subject.organizations.required: Required value: true, spec.allowed.subject.countries.values: Invalid value: []string{\&amp;#34;XX\&amp;#34;}: AT, spec.allowed.subject.countries.validations[0]: Invalid value: \&amp;#34;XX\&amp;#34;: Country code must be AT]]&amp;#34; logger=controller-manager/events type=Warning object=&amp;#34;{Kind:CertificateRequest Namespace:myproject Name:test-certificate2-1 ...&amp;#34; reason=Denied&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;It complains that the subject does not meet the policy requirements and therefore the CertificateRequest was denied.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_ok_we_have_a_policy_but_whats_next"&gt;Ok we have a policy, but whats next?&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The above example shows how the Cert-Manager Approver Policy can be configured and deployed, even if it is not yet supported by the Cert-Manager Operator. However, we only scratched the surface of what is possible with the Approver Policy.
You can create more complex policies that include additional validations, such as checking the validity of the DNS names, IP addresses, or URIs. You can also create policies that require specific email addresses or organizational units in the subject.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;You can even create fine-grained policies that apply to specific issuers or namespaces by using the &lt;code&gt;selector&lt;/code&gt; field in the CertificateRequestPolicy resource. This allows you to create policies that are tailored to your specific requirements and use cases.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The best references can be found here:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Official documentation: &lt;a href="https://cert-manager.io/docs/policy/approval/approver-policy/" target="_blank" rel="noopener"&gt;Cert-Manager Approver Policy&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Example Policies: &lt;a href="https://github.com/cert-manager/approver-policy/tree/main/docs/examples" target="_blank" rel="noopener"&gt;Cert-Manager Approver Policy Examples&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&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;The Cert-Manager Approver Policy is a powerful tool that allows you to implement custom policies for certificate requests in OpenShift. It provides a way to control the issuance of TLS certificates based on specific criteria, such as the subject, key size, duration, and usage of the certificate.
While not yet officially supported by the Cert-Manager Operator, it can be easily integrated into your OpenShift environment using a Helm Chart. Future support is currently being discussed, and it is expected that the Approver Policy will be included in the Cert-Manager Operator in the future.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item><item><title>Single log out from Keycloak and OpenShift</title><link>https://blog.stderr.at/openshift-platform/security/authentication/2025-05-22-keycloak-openshift-singlelogout/</link><pubDate>Thu, 22 May 2025 00:00:00 +0000</pubDate><guid>https://blog.stderr.at/openshift-platform/security/authentication/2025-05-22-keycloak-openshift-singlelogout/</guid><description>&lt;div class="paragraph"&gt;
&lt;p&gt;The following 1-minute article is a follow-up to my &lt;a href="https://blog.stderr.at/openshift/2025/05/step-by-step-using-keycloak-authentication-in-openshift/"&gt;previous article&lt;/a&gt; about how to use Keycloak as an authentication provider for OpenShift. In this article, I will show you how to configure Keycloak and OpenShift for Single Log Out (SLO). This means that when you log out from Keycloak, you will also be logged out from OpenShift automatically. This requires some additional configuration in Keycloak and OpenShift, but it is not too complicated.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_prerequisites"&gt;Prerequisites&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The following prerequisites are required to follow this article:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;OpenShift 4.16 or higher with cluster-admin privileges&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Keycloak installed and configured, as described in the &lt;a href="https://blog.stderr.at/openshift/2025/05/step-by-step-using-keycloak-authentication-in-openshift/"&gt;Step by Step - Using Keycloak Authentication in OpenShift&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_keycloak_configuration"&gt;Keycloak Configuration&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Configure the logout URL in Keycloak. This is done by adding a new &lt;strong&gt;Valid post logout redirect URIs&lt;/strong&gt; to the Keycloak client configuration. In this case, we want to call the OpenShift logout URL.
Which is: &lt;strong&gt;&lt;a href="https://oauth-openshift.apps.&amp;lt;your-cluster-name&amp;gt;/logout" class="bare"&gt;https://oauth-openshift.apps.&amp;lt;your-cluster-name&amp;gt;/logout&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This is done in the client 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/authentication/images/keycloak/set-logout-url.png" alt="Set Logout URL"/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_openshift_configuration"&gt;OpenShift Configuration&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Now we need to configure OpenShift to use the logout URL. This is done by adding a new &lt;strong&gt;Post Logout Redirect URI&lt;/strong&gt; to the OpenShift Console configuration.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Modify the existing Console resource named cluster and add the logoutRedirect parameter to the authentication section.
The important pieces of the logout URL are the &lt;strong&gt;client_id&lt;/strong&gt; and the &lt;strong&gt;post_logout_redirect_uri&lt;/strong&gt;. The client ID must be the same as the Keycloak client ID, and the post logout redirect URI must be the OpenShift logout URL.
(Also change the &amp;lt;keycloakURL&amp;gt; and &amp;lt;realm&amp;gt; to your Keycloak URL and realm name.)&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock caution"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-caution" title="Caution"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Actually, instead of &lt;strong&gt;client_id&lt;/strong&gt;, the &lt;strong&gt;id_token_hint&lt;/strong&gt; should be used. But OpenShift does not store the token, so we are using the client ID instead.
&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: config.openshift.io/v1
kind: Console
metadata:
name: cluster
spec:
authentication:
logoutRedirect: &amp;#39;https://&amp;lt;keycloakURL&amp;gt;/realms/&amp;lt;realm&amp;gt;/protocol/openid-connect/logout?client_id=&amp;lt;realm&amp;gt;&amp;amp;post_logout_redirect_uri=https://console-openshift-console.apps.ocp.aws.ispworld.at&amp;#39; &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&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;The logout URL for OpenShift. This is the URL that will be called when you log out from Keycloak. The URL must contain the client ID and the post logout redirect URI (console of OpenShift).&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Wait a few moments until the OpenShift Console Operator applies the new configuration.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_testing_the_configuration"&gt;Testing the configuration&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Now that the configuration is done, we can test the Single Log Out functionality.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;We are logged into OpenShift already as user &lt;strong&gt;testuser&lt;/strong&gt;&lt;/p&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/openshift-platform/security/authentication/images/keycloak/logged-in.png" alt="Logged in to OpenShift"/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Now we log out from OpenShift using the &lt;strong&gt;Log out&lt;/strong&gt; button in the upper right corner.&lt;/p&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/openshift-platform/security/authentication/images/keycloak/logging-out.png" alt="Log out from OpenShift"/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;This will redirect us to the Keycloak logout page where we need to confirm the logout.&lt;/p&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/openshift-platform/security/authentication/images/keycloak/keycloak-logout.png?width=420" alt="Keycloak Logout"/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;This will log us out from Keycloak and redirect us back to the OpenShift logout page.&lt;/p&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/openshift-platform/security/authentication/images/keycloak/logged-out.png?width=420" alt="Logged out from OpenShift"/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This is it. You are now logged out from both Keycloak and OpenShift. You can also check the Sessions in Keycloak to see that the session for the user is terminated.&lt;/p&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;As promised, this was a short article about how to configure Keycloak and OpenShift for Single Log Out. This is a beneficial feature if you want to ensure that users are logged out from all applications when they log out from Keycloak. It is also a good security practice to ensure that users are logged out from all applications when they are done using them.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item><item><title>Step by Step - Using Keycloak Authentication in OpenShift</title><link>https://blog.stderr.at/openshift-platform/security/authentication/2025-05-17-step-by-step-keycloak-and-openshift/</link><pubDate>Sat, 17 May 2025 00:00:00 +0000</pubDate><guid>https://blog.stderr.at/openshift-platform/security/authentication/2025-05-17-step-by-step-keycloak-and-openshift/</guid><description>&lt;div class="paragraph"&gt;
&lt;p&gt;I was recently asked about how to use Keycloak as an authentication provider for OpenShift. How to install Keycloak using the Operator and how to configure Keycloak and OpenShift so that users can log in to OpenShift using OpenID.
I have to admit that the exact steps are not easy to find, so I decided to write a blog post about it, describing each step in detail.
This time I will not use GitOps, but the OpenShift and Keycloak Web Console to show the steps, because before we put it into GitOps, we need to understand what is actually happening.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This article tries to explain every step required so that a user can authenticate to OpenShift using Keycloak as an Identity Provider (IDP) and that Groups from Keycloak are imported into OpenShift. This article does not cover a production grade installation of Keycloak, but only a test installation, so you can see how it works. For production, you might want to consider a proper database (maybe external, but at least with a backup), high availability, etc.).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_prerequisites"&gt;Prerequisites&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The following prerequisites are required to follow this article:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;OpenShift 4.16 or higher with cluster-admin privileges&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_installing_keycloak_operator"&gt;Installing Keycloak Operator&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The very first step is to install the Keycloak Operator. This can be done using the OpenShift Web Console or the CLI or GitOps. I will show the steps using the OpenShift Web Console.&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;
Installing the Keycloak Operator via GitOps can be seen in this &lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/management-cluster/setup-rh-build-of-keycloak" target="_blank" rel="noopener"&gt;GitHub repository&lt;/a&gt;. By the time you read this article, this repository will install and configure a Keycloak instance automatically using a local &lt;strong&gt;EXAMPLE&lt;/strong&gt; Postgres database. It does not yet import any Realm or additional configuration, but I will try to add this in the future when time allows. Oh, and it is just a demo and not production ready, so please do not use it in production.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;Log in to the OpenShift Web Console as a user with cluster-admin privileges.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In the OpenShift Web Console, navigate to the Operators → OperatorHub.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In the OperatorHub, search for &lt;strong&gt;Red Hat build of Keycloak&lt;/strong&gt; and select the Operator from the list.&lt;/p&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/openshift-platform/security/authentication/images/keycloak/search-operator.png?width=320" alt="Search Keycloak Operator"/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install the latest version of the Keycloak Operator.&lt;/p&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;
You can keep the default settings for the installation, but I recommend using a specific namespace. In this example, I will use the namespace &lt;code&gt;keycloak&lt;/code&gt; for the installation.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/openshift-platform/security/authentication/images/keycloak/install-operator.png?width=840" alt="Install Keycloak Operator"/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Press the &lt;strong&gt;Install&lt;/strong&gt; button to install the Keycloak Operator. After a few minutes, the Keycloak Operator should be installed and running in the OpenShift cluster. You can check the status of the Operator in the &lt;strong&gt;Installed Operators&lt;/strong&gt; view.&lt;/p&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/openshift-platform/security/authentication/images/keycloak/installed-operator.png" alt="Installed Keycloak Operator"/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_create_a_local_example_postgres_database"&gt;Create a local example Postgres Database&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="admonitionblock caution"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-caution" title="Caution"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Demo only! This is not production ready and should not be used in production.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;As a next step, we need to create a local Postgres database for Keycloak. Secrets are used to store the database credentials, and a simple StatefulSet is used to create the database. The database will be created in the same namespace as the Keycloak Operator.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;First, let’s create a Secret for the database credentials. In OpenShift select the &lt;strong&gt;keycloak&lt;/strong&gt; namespace and navigate to the &lt;strong&gt;Secrets&lt;/strong&gt; view. Create a new Secret with the following test configuration:&lt;/p&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;kind: Secret
apiVersion: v1
metadata:
name: keycloak-db-secret
namespace: keycloak
stringData:
password: thisisonly4testingNOT4prod &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
username: testuser
type: Opaque&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;The password for the database. This is just a test password and should not be used in production.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Next, we need to create a StatefulSet for the Postgres database, again in the &lt;strong&gt;keycloak&lt;/strong&gt; namespace.
Again, as I cannot mention that enough, this is just a test configuration and should not be used in production.&lt;/p&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: StatefulSet
metadata:
name: postgresql-db
namespace: keycloak
spec:
serviceName: postgresql-db-service
selector:
matchLabels:
app: postgresql-db
replicas: 1 &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
template:
metadata:
labels:
app: postgresql-db
spec:
containers:
- name: postgresql-db
image: postgres:15 &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
volumeMounts:
- mountPath: /data
name: psql
env: &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
- name: POSTGRES_USER
value: testuser
- name: POSTGRES_PASSWORD
value: thisisonly4testingNOT4prod
- name: PGDATA
value: /data/pgdata
- name: POSTGRES_DB
value: keycloak
volumeClaimTemplates: &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
- metadata:
name: psql
spec:
accessModes: [ &amp;#34;ReadWriteOnce&amp;#34; ]
storageClassName: &amp;#34;gp3-csi&amp;#34;
resources:
requests:
storage: 10Gi&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;The number of replicas for the database. In this example, we are using a single replica.&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;The image for the database, postgres version 15 in this example.&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;The environment variables for the database. The username and password are the same as in the Secret we created before, and &lt;strong&gt;clear text&lt;/strong&gt;&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;The volume for the database. In this example, the StatefulSet uses a volume claim template to create a volume with the size of 10 GB for the database. The volume is created using the &lt;code&gt;gp3-csi&lt;/code&gt; storage class. You can use any other storage class that is available in your OpenShift cluster or even remove this line and use the default class instead.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Finally, we need to create a Service for the database so that the Keycloak Operator can access the database. Again, in the &lt;strong&gt;keycloak&lt;/strong&gt; namespace.&lt;/p&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: Service
metadata:
name: postgres-db
namespace: keycloak
spec:
selector:
app: postgresql-db &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
type: LoadBalancer
ports:
- port: 5432
targetPort: 5432&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;The selector for the Service. This must match the label of the StatefulSet we created before.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_creating_a_keycloak_instance"&gt;Creating a Keycloak Instance&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Now that the Keycloak Operator is installed and our example database is running, we can create a Keycloak instance.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;In the OpenShift Web Console, navigate to the &lt;strong&gt;Installed Operators&lt;/strong&gt; view and select the Keycloak Operator. (Maybe you need to select the keycloak namespace first.)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In the Keycloak Operator view, create a new instance of &lt;strong&gt;Keycloak&lt;/strong&gt; and switch to the &lt;strong&gt;YAML&lt;/strong&gt; view.&lt;/p&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/openshift-platform/security/authentication/images/keycloak/create-keycloak-instance.png?width=550" alt="Create Keycloak Instance"/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="admonitionblock caution"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-caution" title="Caution"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
The fun part here is that the YAML example the Operator provides is actually &lt;strong&gt;wrong and does not work&lt;/strong&gt;. Something that kept me busy for a while.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Replace the YAML with the following configuration:&lt;/p&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: k8s.keycloak.org/v2alpha1
kind: Keycloak
metadata:
name: keycloak &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
namespace: keycloak
labels:
app: sso
spec:
db: &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
host: postgres-db
passwordSecret:
key: password
name: keycloak-db-secret
usernameSecret:
key: username
name: keycloak-db-secret
vendor: postgres
hostname: &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
hostname: sso.apps.ocp.aws.ispworld.at
http: &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
tlsSecret: keycloak-certificate
instances: 1 &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;The name and the namespace of the Keycloak instance.&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;The database configuration. In this example, we are using a local Postgres database. You can also use an external database, but you need to configure the connection string accordingly.&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;Hostname of our Keycloak instance.&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;The TLS secret for the Keycloak instance. You need to create a TLS secret with the certificate and key for the hostname. This is where the example YAML is wrong. It tries to put &lt;em&gt;tlsSecret&lt;/em&gt; under &lt;em&gt;spec&lt;/em&gt;, but it should be under &lt;em&gt;http&lt;/em&gt;.&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;The number of instances of Keycloak. In this example, we are using a single instance.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_what_about_the_ssl_certificate"&gt;What about the SSL Certificate?&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The Keycloak Operator does not create a certificate for the Keycloak instance. You need to create a certificate manually and store it in a secret. The Operator will use this secret to create the TLS certificate for the Keycloak instance.
In the example above we are referencing a secret called &lt;code&gt;keycloak-certificate&lt;/code&gt; in the &lt;code&gt;keycloak&lt;/code&gt; namespace. This secret was created using the &lt;strong&gt;Cert Manager Operator&lt;/strong&gt;. For example, you can use the following configuration to create a certificate for the Keycloak instance.&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: cert-manager.io/v1
kind: Certificate
metadata:
name: keycloak-certificate
namespace: keycloak
spec:
dnsNames:
- sso.apps.ocp.aws.ispworld.at &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
duration: 2160h0m0s
issuerRef:
kind: ClusterIssuer
name: letsencrypt-prod &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
privateKey:
algorithm: RSA
encoding: PKCS1
rotationPolicy: Always
secretName: keycloak-certificate &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;The DNS name the Certificate is valid for. This should be the same as the hostname in the Keycloak instance.&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;The issuer for the certificate. In this example, we are using the &lt;strong&gt;LetsEncrypt&lt;/strong&gt; ClusterIssuer.&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;The name of the secret where the certificate is stored. This should be the same as the TLS secret in the Keycloak instance.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;I strongly recommend using the &lt;strong&gt;Cert Manager Operator&lt;/strong&gt; to automatically request and approve the certificate. However, if you do not have this automation in place, you can use a self-signed certificate. This certificate must be created manually and stored as a secret.
For example, you can use the following command to create a self-signed certificate and store it in a secret:&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;openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj &amp;#34;/CN=test.keycloak.org/O=Test Keycloak./C=US&amp;#34;
oc create secret -n keycloak tls keycloak-certificate2 --cert=tls.crt --key=tls.key&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="_login_in_to_keycloak"&gt;Login in to Keycloak&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Once the Keycloak instance is created and all Pods (1) are running, you can log in to the Keycloak Admin Console using the following URL: &lt;a href="https://sso.apps.ocp.aws.ispworld.at" class="bare"&gt;https://sso.apps.ocp.aws.ispworld.at&lt;/a&gt;
This is the hostname we configured in the keycloak instance.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To authenticate, you need to fetch the initial password for the admin user. This password is stored in a secret called &lt;strong&gt;keycloak-initial-admin&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;You can use the following command to fetch the password:&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 extract secret/keycloak-initial-admin -n keycloak --to=-&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;or you can use the OpenShift Web Console to view the secret.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Once authenticated, you should see the Keycloak Admin Console:&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/authentication/images/keycloak/keycloak-initial-login.png" alt="Keycloak Admin Console"/&gt;
&lt;/div&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;
The first thing you should do is to change the password for the admin user. I trust you know how to do this :)
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_configure_keycloak_to_be_used_by_openshift"&gt;Configure Keycloak to be used by OpenShift&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The next steps are to configure Keycloak to be used as an Identity Provider (IDP) for OpenShift. This is done by creating a new Realm and a new Client in Keycloak. The following steps will show you the minimum configuration required to use Keycloak as an IDP for OpenShift. It does not cover all the options and features of Keycloak (and there are a lot), but it should be enough to get you started.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The full documentation for Keycloak can be found at &lt;a href="https://docs.redhat.com/en/documentation/red_hat_build_of_keycloak/" target="_blank" rel="noopener"&gt;Keycloak Documentation&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_create_a_new_realm_and_client"&gt;Create a new Realm and Client&lt;/h3&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;In the Realm Dropdown (upper left corner) select &lt;strong&gt;Create new Realm&lt;/strong&gt;&lt;/p&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/openshift-platform/security/authentication/images/keycloak/create-new-realm.png?width=420" alt="Create new Realm"/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a new Realm called &lt;strong&gt;openshift&lt;/strong&gt; (Enabled, of course) and press &lt;strong&gt;Create&lt;/strong&gt;.&lt;/p&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/openshift-platform/security/authentication/images/keycloak/create-new-realm-openshift.png?width=1024" alt="Create new Realm OpenShift"/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Now, inside the Realm &lt;strong&gt;openshift&lt;/strong&gt;, select &lt;strong&gt;Clients&lt;/strong&gt; and press the &lt;strong&gt;Create client&lt;/strong&gt; button.&lt;/p&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/openshift-platform/security/authentication/images/keycloak/create-new-client.png?width=1024" alt="Create new Client"/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a new Client with the following configuration. Name it, for example, &lt;strong&gt;openshift&lt;/strong&gt;.&lt;/p&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Be sure the &lt;strong&gt;Client type&lt;/strong&gt; is set to &lt;strong&gt;OpenID Connect&lt;/strong&gt;&lt;/p&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/openshift-platform/security/authentication/images/keycloak/new-client-screen-1.png?width=1024" alt="Create new Client"/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Enable &lt;strong&gt;Client authentication&lt;/strong&gt;. The rest can be left as default.&lt;/p&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/openshift-platform/security/authentication/images/keycloak/new-client-screen-2.png?width=1024" alt="Create new Client"/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add the following redirect URL and Web origin and press &lt;strong&gt;Save&lt;/strong&gt;.:&lt;/p&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Redirect URL: &lt;a href="https://oauth-openshift.apps.&amp;lt;your-cluster-name&amp;gt;/oauth2callback/*" class="bare"&gt;https://oauth-openshift.apps.&amp;lt;your-cluster-name&amp;gt;/oauth2callback/*&lt;/a&gt; …​ redirecting everything under oauth2callback&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Web origin: &lt;a href="https://oauth-openshift.apps.&amp;lt;your-cluster-name&amp;gt;" class="bare"&gt;https://oauth-openshift.apps.&amp;lt;your-cluster-name&amp;gt;&lt;/a&gt;;&lt;/p&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/openshift-platform/security/authentication/images/keycloak/new-client-screen-3.png?width=1024" alt="Create new Client"/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_create_a_new_user_and_a_group"&gt;Create a new User and a Group&lt;/h3&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;In the &lt;strong&gt;openshift&lt;/strong&gt; Realm, select &lt;strong&gt;Groups&lt;/strong&gt;, press the &lt;strong&gt;Create group&lt;/strong&gt; button and create a group called, for example, &lt;strong&gt;openshift-users&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In the &lt;strong&gt;openshift&lt;/strong&gt; Realm, select &lt;strong&gt;Users&lt;/strong&gt; and press the &lt;strong&gt;Add user&lt;/strong&gt; button. Be sure to join the group &lt;strong&gt;openshift-users&lt;/strong&gt;.&lt;/p&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/openshift-platform/security/authentication/images/keycloak/new-user.png?width=1024" alt="Create new User"/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;No more configuration is needed for the (test) user at this point.&lt;/p&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set the password for the user. Select the user we have just created, select the &lt;strong&gt;Credentials&lt;/strong&gt; tab and press &lt;strong&gt;Set password&lt;/strong&gt;. Set the password to &lt;strong&gt;Temporary&lt;/strong&gt; to force the user to change the password on the first login.&lt;/p&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/openshift-platform/security/authentication/images/keycloak/new-user-password.png?width=1024" alt="Set password for new User"/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_configure_a_group_mapper"&gt;Configure a Group Mapper&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The above configuration is enough to log in to OpenShift using Keycloak as an IDP (except that we need to configure OpenShift itself). However, we also want to import the groups from Keycloak into OpenShift. This configuration was not easy to find, and is done by creating a Group Mapper in Keycloak.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;In the &lt;strong&gt;openshift&lt;/strong&gt; Realm, select &lt;strong&gt;Clients scopes&lt;/strong&gt; and select the &lt;strong&gt;profile&lt;/strong&gt; scope:&lt;/p&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/openshift-platform/security/authentication/images/keycloak/client-scopes.png?width=1024" alt="Client Scopes"/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Select the &lt;strong&gt;Mappers&lt;/strong&gt; tab and Add a mapper &lt;strong&gt;By configuration&lt;/strong&gt;:&lt;/p&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/openshift-platform/security/authentication/images/keycloak/client-scopes-mappers.png?width=1024" alt="Client Scopes Mappers"/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Select the &lt;strong&gt;Group Membership&lt;/strong&gt; mapper.&lt;/p&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/openshift-platform/security/authentication/images/keycloak/client-scopes-new-mapper.png?width=1024" alt="Client Scopes Create Group Membership Mapper"/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Configure the mapper with the following settings:&lt;/p&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Mapper Type: &lt;strong&gt;Group Membership&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Name: &lt;strong&gt;openshift-groups&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Token Claim Name: &lt;strong&gt;groups&lt;/strong&gt; → This is the name of the claim that will be used to map the groups from Keycloak to OpenShift.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Full group path: &lt;strong&gt;OFF&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add to ID token: &lt;strong&gt;ON&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add to access token: &lt;strong&gt;ON&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add to userinfo: &lt;strong&gt;ON&lt;/strong&gt;&lt;/p&gt;
&lt;div class="admonitionblock caution"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-caution" title="Caution"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Disable the &lt;strong&gt;Full groupo path&lt;/strong&gt; option, otherwise the group name will be prefixed with a &lt;strong&gt;/&lt;/strong&gt;. Moreover, be sure that you set the &lt;strong&gt;Token Claim Name&lt;/strong&gt; correctly to the claim we will configure in OpenShift (groups).
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/openshift-platform/security/authentication/images/keycloak/client-scopes-new-mapper-2.png?width=1024" alt="Client Scopes Create Group Membership Mapper"/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_configure_openshift_to_use_keycloak_as_an_idp"&gt;Configure OpenShift to use Keycloak as an IDP&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Now that Keycloak is configured, we need to configure OpenShift to use Keycloak as an IDP. This is done by creating a new Identity Provider in OpenShift.
Before we do this, we need to create a new OAuth client secret for OpenShift in the Namespace &lt;strong&gt;openshift-config&lt;/strong&gt; The secret will be used to authenticate OpenShift with Keycloak.
When we created the keycloak client, we enabled the &lt;strong&gt;Client authentication&lt;/strong&gt; option. This created a client secret we need to use in OpenShift.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;In Keycloak, select the &lt;strong&gt;openshift&lt;/strong&gt; client and select the &lt;strong&gt;Credentials&lt;/strong&gt; tab and copy the &lt;strong&gt;Client secret&lt;/strong&gt;.&lt;/p&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/openshift-platform/security/authentication/images/keycloak/keycloak-client-secret.png?width=1024" alt="Keycloak Client Secret"/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Back in OpenShift, navigate to the &lt;strong&gt;openshift-config&lt;/strong&gt; namespace and select the &lt;strong&gt;Secrets&lt;/strong&gt; view. Create a new secret with the following configuration:&lt;/p&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;kind: Secret
apiVersion: v1
metadata:
name: openid-client-secret
namespace: openshift-config
stringData:
clientSecret: &amp;lt;you client secret from Keycloak&amp;gt; &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
type: Opaque&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;The client secret we copied from Keycloak. This is the secret we will use to authenticate OpenShift with Keycloak.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Now we need to create a new Identity Provider in OpenShift. In the OpenShift Web Console, navigate to the &lt;strong&gt;Administration&lt;/strong&gt; → &lt;strong&gt;Cluster Settings&lt;/strong&gt; → &lt;strong&gt;Configuration&lt;/strong&gt; search for &lt;strong&gt;OAuth&lt;/strong&gt; and select the YAML view.
Here the following must be created or added to an existing OAuth configuration:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&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;[...]
spec:
identityProviders:
- mappingMethod: claim
name: rhsso &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
openID:
claims: &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
email:
- email
groups:
- groups
name:
- name
preferredUsername:
- preferred_username
clientID: openshift &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
clientSecret:
name: openid-client-secret &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
extraScopes: []
issuer: &amp;#39;https://sso.apps.ocp.aws.ispworld.at/realms/openshift&amp;#39; &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
type: OpenID &lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;(6)&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;The name of the Identity Provider. This is the name that will be displayed in the OpenShift login screen.&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;The claims that will be used to map the user to OpenShift. In this example, we are using the email, groups, name and preferred_username claims from Keycloak.&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;The client ID we created in Keycloak. This is the client ID that will be used to authenticate OpenShift with Keycloak.&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;The name of the secret we created in the &lt;strong&gt;openshift-config&lt;/strong&gt; namespace.&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;The issuer URL for Keycloak. It is &amp;lt;hostname of keycloak&amp;gt;/realms/&amp;lt;realm name&amp;gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;6&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The type of the Identity Provider. In this example, we are using OpenID Connect.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The above configuration will trigger a restart of the authentication Pods in OpenShift. Wait until all Pods have been restarted and the Operator is running again.&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/authentication/images/keycloak/restart-oauth.png" alt="OpenShift OAuth Restart"/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_test_the_configuration"&gt;Test the configuration&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Now it is time to test the configuration. Open a new browser window (or incognito window), navigate to the OpenShift login page and try to log in using Keycloak as an IDP &lt;strong&gt;rhsso&lt;/strong&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 you have multiple IDPs configured, it is important to select the correct IDP. &lt;strong&gt;rhsso&lt;/strong&gt; in this example.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;By selecting the &lt;strong&gt;rhsso&lt;/strong&gt; Identity Provider, you should be redirected to the Keycloak login page.&lt;/p&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/openshift-platform/security/authentication/images/keycloak/openshift-keycloak-login.png?width=1024" alt="OpenShift Login Page"/&gt;
&lt;/div&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 you selected &lt;strong&gt;Temporary&lt;/strong&gt; for the password, you will now be asked to change the password on the first login.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;After a successful login, you should see the OpenShift Web Console, and you should be logged in as the user you created in Keycloak.&lt;/p&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/openshift-platform/security/authentication/images/keycloak/openshift-web-console.png?width=1024" alt="OpenShift Web Console"/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In OpenShift you will see the user created. In the Identities column you will see that it starts with &lt;strong&gt;rhsso&lt;/strong&gt;, indicating that the user was authenticated using the &lt;strong&gt;rhsso&lt;/strong&gt; Identity Provider.&lt;/p&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/openshift-platform/security/authentication/images/keycloak/openshift-user.png" alt="OpenShift User"/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;And finally, if you navigate to &lt;strong&gt;User Management&lt;/strong&gt; → &lt;strong&gt;Groups&lt;/strong&gt;, you should see the group &lt;strong&gt;openshift-users&lt;/strong&gt; that was created in Keycloak.&lt;/p&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/openshift-platform/security/authentication/images/keycloak/openshift-groups.png" alt="OpenShift Group"/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&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;In this article, I have shown how to install and configure Keycloak in OpenShift for authentication. I have also shown how to configure Keycloak to be used as an Identity Provider for OpenShift and how to import groups from Keycloak into OpenShift.
The biggest two challenges were to find the correct callback URL and to configure the Group Mapper in Keycloak. The rest was pretty straightforward.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;What’s next? With the groups now mapped into OpenShift, you can now create RoleBindings and ClusterRoleBindings to assign the appropriate roles to the users in Keycloak. This is quite nice, as I do not need to create Users manually in OpenShift anymore (previously used HTPasswd) but instead use Keycloak as the single source of truth for users and groups. All I need to configure is the RoleBindings and ClusterRoleBindings in OpenShift.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;I hope this article was helpful and you learned something new. Remember, this is just a test configuration. In production you should use a proper database and keycloak setup (high Availability, backup, etc.).
If you have any questions or comments, please feel free to reach out to me on LinkedIn, Email or via GitHub issues.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_references"&gt;References&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://docs.redhat.com/en/documentation/red_hat_build_of_keycloak/" target="_blank" rel="noopener"&gt;Keycloak Documentation&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://blog.badgerops.net/keycloak-open-shift/" target="_blank" rel="noopener"&gt;Keycloak &amp;amp; Open Shift&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/management-cluster/setup-rh-build-of-keycloak" target="_blank" rel="noopener"&gt;Set up Keycloak using GitOps (No Realm/Client configuration yet)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item><item><title>Introducing AdminNetworkPolicies</title><link>https://blog.stderr.at/openshift-platform/security/network-policies/2024-11-06-using-adminnetworkpolicies-and-baselineadminnetworkpolicies/</link><pubDate>Wed, 06 Nov 2024 00:00:00 +0000</pubDate><guid>https://blog.stderr.at/openshift-platform/security/network-policies/2024-11-06-using-adminnetworkpolicies-and-baselineadminnetworkpolicies/</guid><description>&lt;div class="paragraph"&gt;
&lt;p&gt;Classic Kubernetes/OpenShift offer a feature called NetworkPolicy that allows users to control the traffic to and from their assigned Namespace.
NetworkPolicies are designed to give project owners or tenants the ability to protect their own namespace. Sometimes, however, I worked with customers where the
cluster administrators or a dedicated (network) team need to enforce these policies.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Since the NetworkPolicy API is namespace-scoped, it is not possible to enforce policies across namespaces. The only solution was to create custom (project) admin and edit
roles, and remove the ability of creating, modifying or deleting NetworkPolicy objects. Technically, this is possible and easily done. But shifts the whole network security to cluster administrators.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Luckily, this is where &lt;strong&gt;AdminNetworkPolicy&lt;/strong&gt; (ANP) and &lt;strong&gt;BaselineAdminNetworkPolicy&lt;/strong&gt; (BANP) comes into play.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_adminnetworkpolicy_anp_and_baselineadminnetworkpolicy_banp"&gt;AdminNetworkPolicy (ANP) and BaselineAdminNetworkPolicy (BANP)&lt;/h2&gt;
&lt;div class="sectionbody"&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;
This article demonstrates the configuration of the new AdminNetworkPolicy and BaselineAdminNetworkPolicy objects using the Helm Chart &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/admin-networkpolicies" target="_blank" rel="noopener"&gt;admin-networkpolicies&lt;/a&gt;. The NetworkPolicy object is not covered in this article.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;ANP and BANP are designed for cluster administrators to protect the entire cluster by creating &lt;strong&gt;cluster-scoped policies&lt;/strong&gt;. They are not replacing NetworkPolicies,
but instead create a tier model and can be used together. Administrators can use ANPs to enforce non-overridable policies that take precedence over NetworkPolicy objects.
Administrators can use BANP to set up and enforce optional cluster-scoped network policy rules that are overridable by users using NetworkPolicy objects when necessary.
When used together, ANP, BANP, and network policy can achieve full multi-tenant isolation that administrators can use to secure their cluster.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The three resources create a 3-Tier Access Control List (ACL) that is evaluated in descending order:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Tier 1 - AdminNetworkPolicy (ANP): If the traffic matches an &lt;strong&gt;allow&lt;/strong&gt; or &lt;strong&gt;deny&lt;/strong&gt; rule, then any existing
NetworkPolicy and BaselineAdminNetworkPolicy (BANP) objects in the cluster are skipped from evaluation. If a &lt;strong&gt;pass&lt;/strong&gt; rule is matched, then the evaluation is handed over to
the next tier (NetworkPolicy). This means, that Cluster Administrators can enforce policies that cannot be overwritten by users (allow/deny rules) or pass the evaluation to the Network Policy,
where the project owners can decide further.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Tier 2 - NetworkPolicy (NP): If the traffic passed the ANP then the NetworkPolicy is evaluating the traffic. The NetworkPolicy resources are controlled by the project owners by default.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Tier 3 - BaselineAdminNetworkPolicy (BANP): If the traffic passed the ANP and the NetworkPolicy, then the BANP is evaluating the traffic.
These objects are controlled by the cluster administrators again and are cluster scoped. There can only be one BANP (named &amp;#34;default&amp;#34;) configured on the cluster.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_adminnetworkpolicy"&gt;AdminNetworkPolicy&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;An AdminNetworkPolicy (ANP) is a cluster-scoped resource, that allow cluster administrators to secure the network traffic &lt;strong&gt;before&lt;/strong&gt; NetworkPolicies in the namespaces are evaluated.
These rules cannot be overwritten by project owners or developers and allow the administrators to enforce the security. Use cases could be, for example:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;You want to enforce only specific egress endpoints (e.g. only allow traffic to the specific database servers)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You want to be sure that traffic from OpenShift monitoring is always allowed&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You want to allow the management of NetworkPolicies to project owners and do not want to take care for them or during the onboarding.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The ANP allows cluster administrators to define:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;A priority value that determines the order of its evaluation. The lower the value the higher the precedence.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A set of pods that consists of a set of namespaces or namespace on which the policy is applied.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A list of ingress rules to be applied for all ingress traffic towards the subject.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A list of egress rules to be applied for all egress traffic from the subject.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_adminnetworkpolicy_actions_for_rules"&gt;AdminNetworkPolicy Actions for Rules&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The AdminNetworkPolicy allows three actions for the rules:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Allow&lt;/strong&gt;: The traffic is allowed, and no further rules are evaluated.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Deny&lt;/strong&gt;: The traffic is denied, and no further rules are evaluated.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Pass&lt;/strong&gt;: The traffic is passed to the next tier (NetworkPolicy).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_subject_of_a_policy"&gt;Subject of a Policy&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;In any ANP (or BANP) a &lt;strong&gt;subject&lt;/strong&gt; can be defined and they specify the pods to which this AdminNetworkPolicy applies. (Note that host-networked pods are not included in subject.selection.) There are two ways to define the subject:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;namespaces&lt;/strong&gt;: The namespaces block is used to select pods via namespace selectors. Here, &lt;strong&gt;matchLabels&lt;/strong&gt; or &lt;strong&gt;matchExpressions&lt;/strong&gt; can be used to limit the namespaces.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;pods&lt;/strong&gt;: The Pods-Array is used to select pods via namespace AND pod selectors. Here &lt;strong&gt;namespaceSelector&lt;/strong&gt; and &lt;strong&gt;podSelector&lt;/strong&gt; can be set to limit the Pods.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;If &lt;strong&gt;subject&lt;/strong&gt; is not defined, the policy applies to all pods and namespaces in the cluster.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;In my Helm chart the options are supported like the following snippets show:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_select_namespaces_with_matchexpressions"&gt;Select Namespaces with matchExpressions&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Values in the Helm Chart:&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;anp:
- name: sample-anp-rule-1
enabled: true
priority: 50
subject:
matchNamespaces: &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
matchExpressions: &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
- key: kubernetes.io/metadata.name
operator: NotIn
values:
- kube-system
- openshift*
- default
- kubde-info&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;matchNamespaces is used to select namespaces&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;matchExpressions is used to select namespaces with &lt;strong&gt;matchExpressions&lt;/strong&gt;. In this example all namespaces that do not match (operator == NotIn) the values, so all namespaces except &amp;#34;kube-system, kube-info, default and openshift*&amp;#34; are selected.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This will result in the following AdminNetworkPolicy snippet:&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; subject:
namespaces:
matchExpressions:
- key: kubernetes.io/metadata.name
operator: NotIn
values:
- &amp;#34;kube-system&amp;#34;
- &amp;#34;openshift*&amp;#34;
- &amp;#34;default&amp;#34;
- &amp;#34;kubde-info&amp;#34;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_select_namespaces_with_matchlabels"&gt;Select Namespaces with matchLabels&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Values in the Helm Chart:&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;anp:
- name: sample-anp-rule-1
enabled: true
priority: 5
subject:
matchNamespaces: &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
matchLabels: &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
apps: my-apps
tenant: my-tenant&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;matchNamespaces is used to select namespaces&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;&lt;strong&gt;matchLabels&lt;/strong&gt; is used to select namespaces based on labels. In this example, all namespaces that have the labels &amp;#34;apps: my-apps&amp;#34; and &amp;#34;tenant: my-tenant&amp;#34; are selected.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This will result in the following AdminNetworkPolicy snippet:&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;spec:
priority: 5
subject:
namespaces:
matchLabels:
apps: &amp;#34;my-apps&amp;#34;
tenant: &amp;#34;my-tenant&amp;#34;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_select_pods_with_podselectors_and_namespaceselectors"&gt;Select Pods with podSelectors and namespaceSelectors&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Values in the Helm Chart:&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;anp:
- name: sample-anp-rule-1
enabled: true
priority: 5
subject:
matchPods:
- pods: &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
namespaceSelector: &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
labels:
kubernetes.io/metadata.name: openshift-dns
podSelector: &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
labels:
app: dns&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;matchPods is used to select pods. Here a list of pods can be defined.&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;&lt;strong&gt;namespaceSelector&lt;/strong&gt; is used to select namespaces based on labels. In this example all namespaces that have the label &amp;#34;kubernetes.io/metadata.name: openshift-dns&amp;#34; are selected.&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;&lt;strong&gt;podSelector&lt;/strong&gt; is used to select pods based on labels. In this example all pods that have the label &amp;#34;app: dns&amp;#34; are selected.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This will result in the following AdminNetworkPolicy snippet:&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; subject:
- pods:
namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: openshift-dns
podSelector:
matchLabels:
app: dns&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="_baselineadminnetworkpolicy"&gt;BaselineAdminNetworkPolicy&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;BaselineAdminNetworkPolicy (BANP) is a cluster-scoped resource, that allow cluster administrators to secure the network traffic &lt;strong&gt;after&lt;/strong&gt; NetworkPolicies in the namespaces have been evaluated. These rules can be overwritten by project owners or developers using NetworkPolicies.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock caution"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-caution" title="Caution"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
BANP is a singleton resource, meaning it can be defined only one time. Therefore, its name must be &lt;strong&gt;default&lt;/strong&gt;. Moreover, the &lt;strong&gt;priority&lt;/strong&gt; field is not required here.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Use cases could be, for example:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Creating default rules, such as blocking any intra-cluster traffic by default. Users will need to explicitly use NetworkPolicy objects to allow known traffic.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;A BANP allows administrators to specify:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;A subject that consists of a set of namespaces or namespace.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A list of ingress rules to be applied for all ingress traffic towards the subject.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A list of egress rules to be applied for all egress traffic from the subject.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_baselineadminnetworkpolicy_actions_for_rules"&gt;BaselineAdminNetworkPolicy Actions for Rules&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The BaselineAdminNetworkPolicy allows two actions for the rules. They are like the AdminNetworkPolicy, except for the &lt;strong&gt;pass&lt;/strong&gt; action, which does not make sense here as BANP is the last tier (nowhere to pass).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Allow&lt;/strong&gt;: The traffic is allowed, and no further rules are evaluated.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Deny&lt;/strong&gt;: The traffic is denied, and no further rules are evaluated.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_examples_examples_examples"&gt;Examples Examples Examples&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The following examples are taken directly from &lt;a href="https://network-policy-api.sigs.k8s.io/blog/2024/01/30/getting-started-with-the-adminnetworkpolicy-api/" target="_blank" rel="noopener"&gt;Kubernetes Blog: Getting started with the AdminNetworkPolicy API&lt;/a&gt; and &lt;a href="https://docs.openshift.com/container-platform/4.16/networking/network_security/network-policy-apis.html" target="_blank" rel="noopener"&gt;Official OpenShift Documentation&lt;/a&gt;. Verify the values-file of the &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/admin-networkpolicies" target="_blank" rel="noopener"&gt;Helm Chart&lt;/a&gt; for the further examples.&lt;/p&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;
I will show, how to configure them using the Helm Chart &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/admin-networkpolicies" target="_blank" rel="noopener"&gt;admin-networkpolicies&lt;/a&gt; and the actual result. The chart is already configured with these examples and prepared to be used with GitOps/Argo CD.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_example_1_allow_all_traffic_from_the_openshift_monitoring_namespace"&gt;Example 1: Allow all traffic from the OpenShift monitoring namespace&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Typically, it makes sense to allow the traffic from OpenShift Monitoring to all namespaces. After all, monitoring is useful :)
The following example shows the possible configuration for the Helm Chart, which will render a valid ANP resource for us. It will allow ALL (including OpenShift internal Namespaces) traffic from the OpenShift monitoring namespace (labeled as &lt;code&gt;kubernetes.io/metadata.name: monitoring&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;---
anp:
- name: sample-anp-rule-1 &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
enabled: true &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
syncwave: 10
priority: 5 &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
subject: {} &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
ingress: &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
- name: allow-ingress-from-monitoring &lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;(6)&lt;/b&gt;
enabled: true &lt;i class="conum" data-value="7"&gt;&lt;/i&gt;&lt;b&gt;(7)&lt;/b&gt;
action: Allow &lt;i class="conum" data-value="8"&gt;&lt;/i&gt;&lt;b&gt;(8)&lt;/b&gt;
peers: &lt;i class="conum" data-value="9"&gt;&lt;/i&gt;&lt;b&gt;(9)&lt;/b&gt;
- type: namespaces
labels:
kubernetes.io/metadata.name: monitoring&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;Name of the ANP&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;Enable or disable the ANP. If disabled, the ANP will not be created. (Default is &lt;code&gt;false&lt;/code&gt;)&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;Priority of the ANP. The lower the value the higher the precedence. (Default is &lt;code&gt;50&lt;/code&gt;)&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;Subject of the ANP. In this case, it is empty, which means all namespaces including OpenShift internal namespaces.&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;Ingress rules of the ANP. Here a list of ingress rules for this ANP can be defined&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;6&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Name of the ingress rule&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="7"&gt;&lt;/i&gt;&lt;b&gt;7&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Enable or disable the ingress rule. If disabled, the particular ingress rule will not be created. (Default is &lt;code&gt;false&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="8"&gt;&lt;/i&gt;&lt;b&gt;8&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Action of the ingress rule. In this case, it is &lt;code&gt;Allow&lt;/code&gt;, which means all traffic from the OpenShift monitoring namespace will be allowed. Other options are described at &lt;a href="#_adminnetworkpolicy_actions_for_rules"&gt;AdminNetworkPolicy Actions for Rules&lt;/a&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="9"&gt;&lt;/i&gt;&lt;b&gt;9&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Peers of the ingress rule. In this case, all namespaces labeled as &lt;code&gt;kubernetes.io/metadata.name: monitoring&lt;/code&gt; are allowed to access all namespaces.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The ANP that will be created is the following. It is a valid ANP resource and can be applied to the cluster. (Typically applied by Argo CD)
As described above it will allow incoming access from the OpenShift monitoring namespace to all namespaces.&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: policy.networking.k8s.io/v1alpha1
kind: AdminNetworkPolicy
metadata:
name: &amp;#34;sample-anp-rule-1&amp;#34;
labels:
helm.sh/chart: admin-networkpolicies-1.0.2
app.kubernetes.io/name: admin-networkpolicies
app.kubernetes.io/instance: release-name
app.kubernetes.io/managed-by: Helm
annotations:
argocd.argoproj.io/sync-wave: &amp;#34;10&amp;#34;
argocd.argoproj.io/sync-options: SkipDryRunOnMissingResource=true
spec:
priority: 5
subject:
namespaces: {}
ingress:
- name: &amp;#34;allow-ingress-from-monitoring&amp;#34;
action: &amp;#34;Allow&amp;#34;
from:
- namespaces:
matchLabels:
kubernetes.io/metadata.name: &amp;#34;monitoring&amp;#34;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_example_2_allow_all_traffic_from_labeled_namespaces"&gt;Example 2: Allow all traffic from labeled namespaces&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;As a second example, we want to allow all traffic from namespaces that are labeled with &lt;code&gt;tenant: restricted&lt;/code&gt; to all namespaces that are labeled with &lt;code&gt;anp: cluster-control-anp&lt;/code&gt;.
This is useful, if you want to restrict access to certain namespaces. However, the rule action is configured as &lt;strong&gt;Pass&lt;/strong&gt; which means that the traffic will be allowed but might be further restricted by a NetworkPolicy in the tenant namespace.&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;---
anp:
- name: sample-anp-rule-2
enabled: true
priority: 5
subject:
matchNamespaces: &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
matchLabels:
anp: cluster-control-anp &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
ingress:
- name: pass-from-restricted-tenants
enabled: true
action: Pass &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
peers:
- type: namespaces &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
labels:
tenant: restricted&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;Subject of the ANP. In this case, we select based on labels.&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;Label selector for the namespaces. In this case, all namespaces that are labeled with &lt;code&gt;anp: cluster-control-anp&lt;/code&gt; are subject of this ANP.&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;Action of the ingress rule. In this case, it is &lt;code&gt;Pass&lt;/code&gt;, which means the traffic is allowed, but might be restricted by NetworkPolicies in the tenant namespace. Other options are described at &lt;a href="#_adminnetworkpolicy_actions_for_rules"&gt;AdminNetworkPolicy Actions for Rules&lt;/a&gt;.&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;Peers of the ingress rule. In this case, all namespaces labeled as &lt;code&gt;tenant: restricted&lt;/code&gt; are allowed to access all namespaces.&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: policy.networking.k8s.io/v1alpha1
kind: AdminNetworkPolicy
metadata:
name: &amp;#34;sample-anp-rule-2&amp;#34;
labels:
helm.sh/chart: admin-networkpolicies-1.0.2
app.kubernetes.io/name: admin-networkpolicies
app.kubernetes.io/instance: release-name
app.kubernetes.io/managed-by: Helm
annotations:
argocd.argoproj.io/sync-wave: &amp;#34;10&amp;#34;
argocd.argoproj.io/sync-options: SkipDryRunOnMissingResource=true
spec:
priority: 5
subject:
namespaces:
matchLabels:
anp: &amp;#34;cluster-control-anp&amp;#34;
ingress:
- name: &amp;#34;pass-from-restricted-tenants&amp;#34;
action: &amp;#34;Pass&amp;#34;
from:
- namespaces:
matchLabels:
tenant: &amp;#34;restricted&amp;#34;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_example_3_show_possible_peers_settings"&gt;Example 3: Show possible peers settings&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The most important settings for the rules are the &lt;code&gt;peers&lt;/code&gt; settings. The following examples show the snippets of possible peers.
For further information, please refer to the example in the values file: &lt;a href="https://github.com/tjungbauer/helm-charts/blob/main/charts/admin-networkpolicies/values.yaml" target="_blank" rel="noopener"&gt;Helm Chart Values with further examples&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;
The following rules are examples of &lt;strong&gt;EGRESS&lt;/strong&gt; rules.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;Allow egress traffic &lt;strong&gt;to namespaces labeled&lt;/strong&gt; splunk on ports 80 and 443:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&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; peers:
- type: namespaces
labels:
tenant: splunk
ports:
- protocol: TCP
portNumber: 80
- portName: https&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic" start="2"&gt;
&lt;li&gt;
&lt;p&gt;Allow egress traffic &lt;strong&gt;to nodes&lt;/strong&gt; where the key &amp;#34;node-role.kubernetes.io/control-plane&amp;#34; exists &lt;strong&gt;on the port 6443&lt;/strong&gt;:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&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; peers:
- type: nodes
expr:
- key: node-role.kubernetes.io/control-plane
operator: Exists
ports:
- protocol: TCP
portNumber: 6443&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic" start="3"&gt;
&lt;li&gt;
&lt;p&gt;Allow egress traffic &lt;strong&gt;to pods&lt;/strong&gt; labeled &amp;#34;app: dns&amp;#34; &lt;strong&gt;in the namespace&lt;/strong&gt; openshift-dns &lt;strong&gt;on the port 53 and 5353&lt;/strong&gt;:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&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; peers:
- type: pods
namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: openshift-dns
podSelector:
matchLabels:
app: dns
ports:
- protocol: TCP
port: 5353
- protocol: TCP
port: 53
- protocol: UDP
port: 53
- protocol: UDP
port: 5353&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic" start="4"&gt;
&lt;li&gt;
&lt;p&gt;Allow egress traffic &lt;strong&gt;to a list of IPs&lt;/strong&gt;:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&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; peers:
- type: networks
ips:
- 172.29.0.0/30
- 10.0.54.0/19
- 10.0.56.38/32
- 10.0.69.0/24&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic" start="5"&gt;
&lt;li&gt;
&lt;p&gt;Allows egress traffic &lt;strong&gt;to a list of domain names&lt;/strong&gt; (*.kubernetes.io and kubernetes.io)&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&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; peers:
- type: domainNames
domains:
- &amp;#39;*.kubernetes.io&amp;#39;
- kubernetes.io&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic" start="6"&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Deny all egress traffic&lt;/strong&gt;. This should be the last rule when full egress traffic shall be disabled. This might also be put into the BANP.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&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; - name: default-deny
enabled: true
action: Deny
peers:
- type: networks
ips:
- 0.0.0.0/0&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_example_4_baselineadminnetworkpolicy"&gt;Example 4: BaselineAdminNetworkPolicy&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The BANP is more or less identical to ANP, except that you cannot define a &amp;#34;name&amp;#34; and a &amp;#34;priority&amp;#34;. The following example creates a BANP that allows incoming and outgoing traffic to namespaces labeled &amp;#34;tenant-1&amp;#34;.&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;banp: &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
- enabled: true &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
syncwave: 10
subject:
matchNamespaces:
matchLabels:
kubernetes.io/metadata.name: example.name
ingress:
- name: &amp;#34;deny-all-ingress-from-tenant-1&amp;#34;
enabled: true
action: Deny
peers:
- type: pods
namespaceSelector:
matchLabels:
custom-banp: tenant-1
podSelector:
matchLabels:
custom-banp: tenant-1
egress:
- name: allow-all-egress-to-tenant-1
enabled: true
action: Allow
peers:
- type: pods
namespaceSelector:
matchLabels:
custom-banp: tenant-1
podSelector:
matchLabels:
custom-banp: tenant-1&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;Using the key &lt;strong&gt;banp&lt;/strong&gt; (instead of &lt;strong&gt;anp&lt;/strong&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;No &lt;strong&gt;name&lt;/strong&gt; or &lt;strong&gt;priority&lt;/strong&gt; are defined here.&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="_further_information"&gt;Further Information&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://github.com/tjungbauer/helm-charts/blob/main/charts/admin-networkpolicies/values.yaml" target="_blank" rel="noopener"&gt;Helm Chart Values with further examples&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://docs.openshift.com/container-platform/4.16/networking/network_security/network-policy-apis.html" target="_blank" rel="noopener"&gt;Official OpenShift Documentation&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://network-policy-api.sigs.k8s.io/blog/2024/01/30/getting-started-with-the-adminnetworkpolicy-api/" target="_blank" rel="noopener"&gt;Kubernetes Blog: Getting started with the AdminNetworkPolicy API&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://network-policy-api.sigs.k8s.io/reference/spec/#policy.networking.k8s.io/v1alpha1.AdminNetworkPolicyEgressPeer" target="_blank" rel="noopener"&gt;Kubernetes API Documentation&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item><item><title>[Ep.6] Setup &amp; Configure Advanced Cluster Security</title><link>https://blog.stderr.at/gitopscollection/2024-04-28-installing-advanced-cluster-security/</link><pubDate>Sun, 28 Apr 2024 00:00:00 +0000</pubDate><guid>https://blog.stderr.at/gitopscollection/2024-04-28-installing-advanced-cluster-security/</guid><description>&lt;div class="paragraph"&gt;
&lt;p&gt;Today I want to demonstrate the deployment and configuration of &lt;strong&gt;Advanced Cluster Security&lt;/strong&gt; (ACS) using a GitOps approach. The required operator shall be installed, verified if it is running and then ACS shall be initialized. This initialization contains the deployment of several components:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;Central - as UI and as a main component of ACS&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;SecuredClusters - installs a Scanner, Controller pods etc.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Console link into OpenShift UI - to directly access the ACS Central UI&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Job to create an initialization bundle to install the Secured Cluster&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Job to configure authentication using OpenShift&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Let’s start …​&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_prerequisites"&gt;Prerequisites&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;Argo CD (OpenShift GitOps) deployed&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;App-Of-Apps deployed&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&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;The main components of ACS are the two custom resources: &lt;strong&gt;Central&lt;/strong&gt; and &lt;strong&gt;SecuredCluster&lt;/strong&gt;. The recommended way to deploy ACS is to use the Operator (alternatives would be the command line or Helm Chart). When I first came across ACS I thought about how to automate the full deployment. I did not want to install the Operator, then the Central, then manually create a so-called init-bundle, then deploy the Secured Cluster, then find the route that has been used, then find the secret that stores the initial administrator credentials and then, finally, log into ACS and activate OpenShift authentication.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;As you can see there are a lot of tasks to do before I can start a customer demo.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;At the same time, I started to dig into GitOps and I thought this would be a good option to create my very first Helm Chart.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Long story short, I have now a Helm Chart (actually three, because I outsourced some of the features into sub-charts) that automatically does all these things above. Once I am synchronizing my Argo CD Application everything will happen automatically.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The &lt;a href="https://blog.stderr.at/gitopscollection/2024-04-02-configure_app_of_apps/"&gt;Configure App-of-Apps&lt;/a&gt; installed an Argo CD Application called &lt;strong&gt;in-cluster-setup-acs&lt;/strong&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/gitopscollection/images/setup-acs.png?width=720px" alt="Argo CD Application: setup-acs"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 1. Argo CD Application: setup-acs&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This Argo CD Application used the following path to find the Helm Chart: &lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/blob/main/clusters/management-cluster/setup-acs"&gt;setup-acs&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This Helm chart is a wrapper chart that uses sub-charts as dependencies to install and configure the operator as well as to do some OpenShift Jobs on top, for example, creating a ConsoleLink or creating an init-bundle.&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;
Please check out the article &lt;a href="https://blog.stderr.at/gitopscollection/2024-04-25-installing-compliance-operator/#_why_empty_helm_charts"&gt;Setup Compliance Operator&lt;/a&gt; on why I am using a wrapper chart.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_installing_advanced_cluster_security"&gt;Installing Advanced Cluster Security&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_analysing_chart_yaml"&gt;Analysing Chart.yaml&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Let’s examine the Chart.yaml file to see which dependencies are used:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The file looks like the following. Three sub-charts are defined as required to deploy and configure the ACS. This is pretty much the same as it was for the &lt;a href="https://blog.stderr.at/gitopscollection/2024-04-25-installing-compliance-operator/#_analysing_chart_yaml"&gt;Setup Compliance Operator&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;apiVersion: v2
name: setup-acs
description: Deploys Advanced Cluster Security (ACS) on target cluster. If enabled Central will be deployed too.
version: 1.0.0
dependencies:
- name: rhacs-setup &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
version: ~1.0.0 &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
repository: https://charts.stderr.at/
- name: helper-operator &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
version: ~1.0.23
repository: https://charts.stderr.at/
- name: helper-status-checker &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
version: ~4.0.0
repository: https://charts.stderr.at/
condition: helper-status-checker.enabled &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;Dependency: &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/rhacs-setup" target="_blank" rel="noopener"&gt;RHACS Setup&lt;/a&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;Version that will be used. The &amp;#34;~&amp;#34; means that the latest version of 1.0.X will be used.&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;Dependency: &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/helper-operator" target="_blank" rel="noopener"&gt;Helper Operator&lt;/a&gt;&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;Dependency: &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/helper-status-checker" target="_blank" rel="noopener"&gt;Helper Status Checker&lt;/a&gt;&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;Only use this dependency when &amp;#34;enabled&amp;#34; is set&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;
Verify the READMEs of the different Charts for detailed information on how to configure them.
&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="_configuration_of_the_chart"&gt;Configuration of the Chart&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To configure Advanced Cluster Security the &lt;strong&gt;values file&lt;/strong&gt; of the wrapper Chart must be prepared accordingly.&lt;/p&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;
The important thing here is, that any value that should be bypassed to a sub-chart is defined under the name of the sub-chart. For example, everything under &lt;strong&gt;helper-operator:&lt;/strong&gt; will be sent to the helper-operator Chart and is used there for its configuration.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Check out the example &lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/blob/main/clusters/management-cluster/setup-acs/values.yaml" target="_blank" rel="noopener"&gt;values file&lt;/a&gt; I use to configure ACS and the
&lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/rhacs-setup" target="_blank" rel="noopener"&gt;README&lt;/a&gt; to find further information about the possible settings that can be done.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Let’s check the main example, to quickly start:&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_installing_and_verifying_the_operator"&gt;Installing and verifying the Operator&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The first thing to do is to deploy the Operator and to verify if the Operator installation finished successfully.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The two Helm Charts &lt;strong&gt;helper-operator&lt;/strong&gt; and &lt;strong&gt;helper-status-checker&lt;/strong&gt; are responsible to do so.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;They are configured as follows:&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;helper-operator:
operators:
rhacs-operator: &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
enabled: true &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
syncwave: &amp;#39;0&amp;#39; &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
namespace:
name: rhacs-operator &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
create: true
subscription:
channel: stable &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
approval: Automatic
operatorName: rhacs-operator
source: redhat-operators
sourceNamespace: openshift-marketplace
operatorgroup: &lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;(6)&lt;/b&gt;
create: true
# rhacs does not support to monitor own namespace,
# therefore the spec in the OperatorGroup must be empty
notownnamespace: true
# Subchart helper-status-checker
# checks if ACS operator is ready
helper-status-checker:
enabled: true &lt;i class="conum" data-value="7"&gt;&lt;/i&gt;&lt;b&gt;(7)&lt;/b&gt;
checks: &lt;i class="conum" data-value="8"&gt;&lt;/i&gt;&lt;b&gt;(8)&lt;/b&gt;
- operatorName: rhacs-operator &lt;i class="conum" data-value="9"&gt;&lt;/i&gt;&lt;b&gt;(9)&lt;/b&gt;
namespace:
name: rhacs-operator &lt;i class="conum" data-value="10"&gt;&lt;/i&gt;&lt;b&gt;(10)&lt;/b&gt;
syncwave: 3
serviceAccount:
name: &amp;#34;status-checker-acs&amp;#34; &lt;i class="conum" data-value="11"&gt;&lt;/i&gt;&lt;b&gt;(11)&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;Key that can be freely defined. Theoretically, you can deploy multiple operators at once.&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;Is this Operator enabled yes/no.&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;Syncwave for the Operator deployment. (Subscription and OperatorGroup etc.)&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;The Namespace where the Operator shall be deployed and if this namespace shall be created.&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;Configuration of the Subscription resource.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;6&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Configuration of the OperatorGroup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="7"&gt;&lt;/i&gt;&lt;b&gt;7&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Enable status checker or not. Default: false&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="8"&gt;&lt;/i&gt;&lt;b&gt;8&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;List of operators to check. Typically, only one is checked, but there could be more.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="9"&gt;&lt;/i&gt;&lt;b&gt;9&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Name of the Operator to check (same as for helper-operator)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="10"&gt;&lt;/i&gt;&lt;b&gt;10&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Namespace where the Operator has been installed (same as for helper-operator)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="11"&gt;&lt;/i&gt;&lt;b&gt;11&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Name of the ServiceAccount that will be created to check the status.&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;
Verify the READMEs at &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/helper-operator" target="_blank" rel="noopener"&gt;Helper Operator&lt;/a&gt; and &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/helper-status-checker" target="_blank" rel="noopener"&gt;Helper Operator Status Checker&lt;/a&gt; to find additional possible configurations.
&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;
Also verify the separate article &lt;a href="https://blog.stderr.at/openshift/2023-03-20-operator-installation-with-argocd/"&gt;Operator Installation with Argo CD&lt;/a&gt; to understand why I am verifying the status of the Operator installation.
&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="_configuring_advanced_cluster_security"&gt;Configuring Advanced Cluster Security&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Besides the deployment of the Operator, the configuration of ACS is the most important part here. The ACS Operator provides two custom resources: Central and SecuredCluster. On the Central cluster both CRDs are required. On any other (spoke) cluster, the SecuredCluster resource is enough.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;In the following example, I am going to configure both Central and SecuredCluster. Since the values file is quite huge I removed most of the additional comments, to keep this article short and readable.
You can read the example values file or the README at &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/rhacs-setup" target="_blank" rel="noopener"&gt;Advanced Cluster Security Chart&lt;/a&gt; to find additional possible configurations. Especially, if you like to add tolerations or set resource limits.&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;#########################################
# Settings for Advanced Cluster Security
#########################################
rhacs-setup:
rhacs:
namespace: &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
name: stackrox
syncwave: &amp;#39;0&amp;#39;
descr: &amp;#39;Red Hat Advanced Cluster Security&amp;#39;
################
# CENTRAL of ACS
################
# Settings for the Central of ACS
central: &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
enabled: true
syncwave: &amp;#39;3&amp;#39;
egress:
connectivityPolicy: Online
###############
# CENTRAL DB
###############
# Settings for Central DB, which is responsible for data persistence.
db: &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
# -- Set Central DB resources.requests for a DEMO environment to save resources.
resources:
requests:
cpu: &amp;#39;1&amp;#39;
memory: &amp;#39;1Gi&amp;#39;
# -- If you want this component to only run on specific nodes, you can
# configure tolerations of tainted nodes.
tolerations: {}
# - effect: NoSchedule
# key: infra
# operator: Equal
# value: reserved
# - effect: NoSchedule
# key: infra
# operator: Equal
# value: reserved
###############
# SCANNER
###############
scanner: &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
enabled: true
analyzer:
# The following settings are NOT suitable for a production environment
autoscaling:
status: &amp;#34;Disabled&amp;#34;
max: 1
min: 1
# When autoscaling is disabled, the number of replicas will always be
# configured to match this value.
replicas: 1
tolerations: {}
###############
# SCANNER DB
###############
db:
tolerations: {}
#################
# SECURED CLUSTER
#################
secured_cluster: &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
enabled: true
syncwave: &amp;#39;4&amp;#39;
clustername: local-cluster
sensor:
tolerations: {}
admissioncontrol:
listenOn:
creates: true
events: true
updates: true
tolerations: {}
# -- Basic settings for ACS authentication
# This configuration is done by a Job, that will configure the OpenShift oauth for ACS.
basic_acs_settings: &lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;(6)&lt;/b&gt;
auth_provider: &amp;#39;OpenShift&amp;#39;
auth_provider_type: &amp;#39;openshift&amp;#39;
min_access_role: &amp;#39;None&amp;#39;
syncwave: 5
####################################################
# Additional settings for Central and possible Jobs
####################################################
job_vars: &lt;i class="conum" data-value="7"&gt;&lt;/i&gt;&lt;b&gt;(7)&lt;/b&gt;
max_attempts: 20
job_init_bundle: &lt;i class="conum" data-value="8"&gt;&lt;/i&gt;&lt;b&gt;(8)&lt;/b&gt;
enabled: true
syncwave: &amp;#39;3&amp;#39;
consolelink: &lt;i class="conum" data-value="9"&gt;&lt;/i&gt;&lt;b&gt;(9)&lt;/b&gt;
enabled: true
syncwave: &amp;#39;3&amp;#39;
location: ApplicationMenu
text: Advanced Cluster Security
section: Observability&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;Create the Namespace &lt;strong&gt;stackrox&lt;/strong&gt; and install the ACS resources there.&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;Enable the Central during Syncwave 3 and set the connectivityPolicy to Online&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;The Central DB and its configuration. Here the resource requests are modified to allow a small installation on the DEMO environment. Also, tolerations might be set here, as well a PVCs etc.&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;Settings for the Scanner and its databases. Again, tolerations might be configurated here, but also, not shown in this example, resource limits and requests and other settings. Since I am configuring for a DEMO environment, I disabled the autoscaler and set the replica to 1.&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;The SecuredCluster is the 2nd CRD that is provided by ACS Operator. It is installed after the Central (thus a higher Syncwave). The most important setting here is the clustername. In our &amp;#34;local&amp;#34; example, the name is set to &lt;strong&gt;local-cluster&lt;/strong&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;6&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Some basic settings, that will configure the OpenShift authentication and the minimum role for authenticated users (None)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="7"&gt;&lt;/i&gt;&lt;b&gt;7&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Some default settings for Jobs that are started by this Helm chart.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="8"&gt;&lt;/i&gt;&lt;b&gt;8&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The Job that initializes the creation of the init-bundle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="9"&gt;&lt;/i&gt;&lt;b&gt;9&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The Job and its configuration to generate a direct link to ACS in the OpenShift UI.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;With this ACS is about to be installed on the cluster. Let’s see what will happen during the synchronization.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_deploying_advanced_cluster_security_acs"&gt;Deploying Advanced Cluster Security (ACS)&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Let’s hit the sync button inside OpenShift GitOps. This will start the whole process, walking through the syncwaves and the hooks that have been defined.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/gitopscollection/images/setup_acs/syncing-argocd.png?width=720px" alt="Syncing Argo CD"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 2. Syncing Argo CD&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;
Since hooks are used, you must sync the whole Argo CD Application. As you can see inside Argo CD, the hooks are not shown, because they will only appear when their time has come (and will disappear afterward again). This means, that if you perform a selective sync, Argo CD does not know when it should start such a hook and they are never triggered.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The Operator installation is now started. At the moment the Operator has the status &lt;strong&gt;Installing&lt;/strong&gt;. Currently, no CRDs (Central or SecuredCluster) are available yet. If we would just let Argo CD continue, it would try to create the Central configuration, based on a CRD which does not yet exist. Thus, the syncing process will fail and therefore the &lt;strong&gt;status-checker&lt;/strong&gt; is going to verify if the installation was truly successful.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/gitopscollection/images/setup_acs/installing-operator.png?width=720px" alt="Operator is installed"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 3. Operator is installed&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The Status Checker is a simple Pod that is triggered by a Kubernetes Job. If waits until the status of the Operator is &lt;strong&gt;Succeeded&lt;/strong&gt;. Until this is the case, Argo CD waits before it continues with the synchronization. (It waits until the hook ends the Job)&lt;/p&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/gitopscollection/images/setup_acs/status-checker.png?width=720px" alt="Status Checker"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 4. Status Checker&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;In the logs file of the Pod, we can see that the Operator is ready.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/gitopscollection/images/setup_acs/status-checker-logs.png?width=720px" alt="Status Checker Logs"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 5. Status Checker Logs&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;And indeed, the status of the Operator is now &lt;strong&gt;Succeeded&lt;/strong&gt;. Now it is time for Argo CD to continue the synchronization.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/gitopscollection/images/setup_acs/operator-ready.png?width=720px" alt="Operator Ready"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 6. Operator Ready&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The next step is to create the Central CRD. This will deploy the UI of ACS and the local Scanner. You can also see two other Jobs that have been created by the Helm Charts &lt;strong&gt;create_cluster_link&lt;/strong&gt; and &lt;strong&gt;create_cluster_init_bundle&lt;/strong&gt;. They will finish when the Central becomes ready.&lt;/p&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;
Until the Central becomes ready, these two additional Jobs may show errors. Do not worry, OpenShift will reschedule them.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/gitopscollection/images/setup_acs/installing-central.png?width=720px" alt="Installing Central"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 7. Installing Central&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;You can also see two other Jobs that have been created by the Helm Charts &lt;strong&gt;create_cluster_link&lt;/strong&gt; and &lt;strong&gt;create_cluster_init_bundle&lt;/strong&gt;. They will finish when the Central becomes ready.&lt;/p&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;
Until the Central becomes ready, these two additional Jobs may show errors. Do not worry, OpenShift will reschedule them.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/gitopscollection/images/setup_acs/init-job-waits.png?width=720px" alt="Init Job is waiting for Central"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 8. Init Job is waiting for Central&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Once the Central has been deployed, the second CRD &lt;strong&gt;SecuredCluster&lt;/strong&gt; will be added. This will trigger the installation of the Collectors and other components.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/gitopscollection/images/setup_acs/installing-securedcluster.png?width=720px" alt="Installing Secured Cluster"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 9. Installing SecuredCluster&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Eventually, all Pods are running at the end. The additional Jobs are completed and ACS is ready to take care of the cluster security.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/gitopscollection/images/setup_acs/acs-all-pods-running.png?width=720px" alt="All Pods running"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 10. All Pods running&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;We can now use the console link that was created.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/gitopscollection/images/setup_acs/acs-consolelink.png?width=720px" alt="ACS ConsoleLink"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 11. ACS ConsoleLink&lt;/div&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 you disabled the creation of the ConsoleLink you would need to find the Route that ACS Operator created. Honestly, I do not know why the Operator does not create such ConsoleLink out of the box.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Since I am lazy I created another Job that automatically configures authentication via OpenShift for ACS. This way, we can simply use our OpenShift credentials to login.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/gitopscollection/images/setup_acs/acs-login.png?width=720px" alt="ACS Login"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 12. ACS Login&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;And that’s it, we can now use ACS, which was deployed fully automatically.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/gitopscollection/images/setup_acs/acs.png?width=720px" alt="Advanced Cluster Security"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 13. Advanced Cluster Security&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;As you can see Advanced Cluster Security was completely installed. This included the Operator, the Central, the creation and installation of an init-bundle, the creation of a ConsoleLink, the configuration of the SecuredCluster CRD and the initial configuration of the auth provider inside ACS. You can now start using ACS or add additional clusters.&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;
Speaking of additional clusters: The create-cluster-init-bundle created three certificates: collector-tls, sensor-tls and admission-control-tls. They are required so that the SecuredClusters can communicate with the Central. You could now create a separate init-bundle for each SecuredCluster, which is not really easy to automate, or you simply take these created secrets and put them into your GitOps and re-use them for any other SecuredCluster.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;All these steps and configurations seem quite complicated but honestly, it is straightforward. I install any Operator using &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/helper-operator" target="_blank" rel="noopener"&gt;Helper Operator&lt;/a&gt; and in most cases. I also use &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/helper-status-checker" target="_blank" rel="noopener"&gt;Helper Operator Status Checker&lt;/a&gt; to find additional possible configurations. Both require simple configuration only, which you would need to know anyway when you create the Subscription object manually. Once done, you can repeat this for any other Operator.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The real magic happens when the Operator is configured at the same time because this is very individual to the Operator.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item><item><title>[Ep.5] Setup &amp; Configure Compliance Operator</title><link>https://blog.stderr.at/gitopscollection/2024-04-25-installing-compliance-operator/</link><pubDate>Thu, 25 Apr 2024 00:00:00 +0000</pubDate><guid>https://blog.stderr.at/gitopscollection/2024-04-25-installing-compliance-operator/</guid><description>&lt;div class="paragraph"&gt;
&lt;p&gt;In the previous articles, we have discussed the &lt;a href="https://blog.stderr.at/gitopscollection/2023-12-28-gitops-repostructure/"&gt;Git repository folder structure&lt;/a&gt; and the configuration of the &lt;a href="gitopscollection/2024-04-02-configure_app_of_apps/"&gt;App-Of-Apps&lt;/a&gt;. Now it is time to deploy our first configuration. One of the first things I usually deploy is the &lt;a href="https://docs.openshift.com/container-platform/4.15/security/compliance_operator/co-overview.html" target="_blank" rel="noopener"&gt;Compliance Operator&lt;/a&gt;. This Operator is recommended for any cluster and can be deployed without any addition to the Subscription.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;In this article, I will describe how it is installed and how the Helm Chart is configured.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_prerequisites"&gt;Prerequisites&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;Argo CD (OpenShift GitOps) deployed&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;App-Of-Apps deployed&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&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;As a reminder, at &lt;a href="https://blog.stderr.at/gitopscollection/2023-12-28-gitops-repostructure/"&gt;Git repository folder structure&lt;/a&gt; I described my preferred folder structure. I would like to deploy the Compliance Operator in the Management Cluster now. All my examples can be found at GitHub repository &lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops" target="_blank" rel="noopener"&gt;OpenShift Clusterconfig GitOps&lt;/a&gt;. The folder &lt;strong&gt;clusters/management-cluster/setup-compliance-operator&lt;/strong&gt; is the one I am interested in.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Inside this folder, you will find another Helm Chart. The Helm Chart has no local templates, instead, it uses dependencies to call other (sub-) charts. However, the &lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/blob/main/clusters/management-cluster/setup-compliance-operator/values.yaml" target="_blank" rel="noopener"&gt;values.yaml&lt;/a&gt; is the main part to configure everything.&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;
In case you want to have any local template, that you do NOT want to integrate into one of the sub-charts, you can easily do so, by storing them in the templates folder.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_why_empty_helm_charts"&gt;Why &amp;#34;empty&amp;#34; Helm Charts?&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Actually, it would be possible to use the Helm Chart of the Chart repository directly, without creating a separate chart, that does nothing else than using dependency charts.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The reasons why I am using such an &amp;#34;empty&amp;#34; Chart are the following (in no particular order):&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;With that way it is possible to add templates (i.e. SealedSecrets) and modify the values-file without packaging and releasing a new Chart version every time you change a small thing.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The Multi-Source Option, which allows you to use a Helm Chart from repository A and a values file from repository B is still a TechPreview feature (Argo CD 2.10). I am using this for the App-of-Apps already, but I did not do this for all charts. This feature is on the list for Argo CD version 2.11 to become globally available.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;As an alternative, it is also possible to mix Kustomize and Helm. That way you only need a kustomization.yaml file and reference to a Helm Chart. In the folder &lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/management-cluster/ingresscontroller" target="_blank" rel="noopener"&gt;clusters/management-cluster/ingresscontroller&lt;/a&gt; I have such an example.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_installing_compliance_operator"&gt;Installing Compliance Operator&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_analysing_chart_yaml"&gt;Analysing Chart.yaml&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;As any Helm Chart a Chart.yaml file exists, that stores the basic information. The most important ones for now are the dependencies.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The file looks like the following. Three sub-charts are defined as required to deploy and configure the Compliance Operator.&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: v2
name: setup-compliance-operator
description: Deploy and configure the Compliance Operator
version: 1.0.1
dependencies:
- name: compliance-operator-full-stack &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
version: ~1.0.0 &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
repository: https://charts.stderr.at/
- name: helper-operator &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
version: ~1.0.21
repository: https://charts.stderr.at/
- name: helper-status-checker &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
version: ~4.0.0
repository: https://charts.stderr.at/
condition: helper-status-checker.enabled &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;Dependency: &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/compliance-operator-full-stack" target="_blank" rel="noopener"&gt;Compliance Operator Full Stack&lt;/a&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;Version that will be used. The &amp;#34;~&amp;#34; means that the latest version of 1.0.X will be used.&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;Dependency: &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/helper-operator" target="_blank" rel="noopener"&gt;Helper Operator&lt;/a&gt;&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;Dependency: &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/helper-status-checker" target="_blank" rel="noopener"&gt;Helper Status Checker&lt;/a&gt;&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;Only use this dependency when &amp;#34;enabled&amp;#34; is set&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;
Verify the READMEs of the different Charts for detailed information on how to configure them.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;As you can see three other Helm Charts are used to actually deploy and configure the Operator.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_configuration_of_the_chart"&gt;Configuration of the Chart&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To configure the Compliance Operator, the values files must be prepared accordingly.&lt;/p&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;
The important thing here is, that any value that should be bypassed to a sub-chart is defined under the name of the sub-chart. For example, everything under &lt;strong&gt;helper-operator:&lt;/strong&gt; will be sent to the helper-operator Chart and is used there for its configuration.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The following is a full example of the values I typically use.&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;# Install Operator Compliance Operator
# Deploys Operator --&amp;gt; Subscription and Operatorgroup
helper-operator:
operators:
compliance-operator:
enabled: true
syncwave: &amp;#39;0&amp;#39;
namespace:
name: openshift-compliance
create: true
subscription:
channel: stable
approval: Automatic
operatorName: compliance-operator
source: redhat-operators
sourceNamespace: openshift-marketplace
operatorgroup:
create: true
notownnamespace: true
# Verify if the Operator has been successfully deployed
helper-status-checker:
enabled: true
checks:
- operatorName: compliance-operator
namespace:
name: openshift-compliance
serviceAccount:
name: &amp;#34;status-checker-compliance&amp;#34;
# Setting for the Compliance Operator
compliance-operator-full-stack:
compliance:
namespace:
name: openshift-compliance
syncwave: &amp;#39;0&amp;#39;
descr: &amp;#39;Red Hat Compliance&amp;#39;
scansettingbinding:
enabled: true
syncwave: &amp;#39;3&amp;#39;
profiles:
- name: ocp4-cis-node
kind: Profile # Could be Profile or TailedProfile
- name: ocp4-cis
kind: Profile
scansetting: default&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Let us walk through the settings in more detail.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_installing_the_operator"&gt;Installing the Operator&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The first thing to do is to deploy the Operator. Two resources are relevant to install an Operator:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;Subscription&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;OperatorGroup&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Both objects should be deployed at the very beginning of Argo CD synchronisation. This is done by setting the Syncwave to 0.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The main settings are the operatorName, the channel (which is the version of the operator) and the approval (which defines if the Operator is updated automatically or manually).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;In addition, a Namespace object is deployed, because this Operator should run in its very own namespace.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This will start the Operator installation process.&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;helper-operator:
operators:
compliance-operator: &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
enabled: true &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
syncwave: &amp;#39;0&amp;#39; &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
namespace:
name: openshift-compliance &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
create: true
subscription: &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
channel: stable # Version of the Operator
approval: Automatic # Automatic or Manual
operatorName: compliance-operator # Name of the Operator
source: redhat-operators
sourceNamespace: openshift-marketplace
operatorgroup: &lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;(6)&lt;/b&gt;
create: true
notownnamespace: true&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;Key that can be freely defined. Theoretically, you can deploy multiple operators at once.&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;Is this Operator enabled yes/no.&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;Syncwave for the Operator deployment. (Subscription and OperatorGroup etc.)&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;The Namespace where the Operator shall be deployed and if this namespace shall be created.&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;Configuration of the Subscription resource.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;6&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Configuration of the OperatorGroup&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;
Verify the README at &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/helper-operator" target="_blank" rel="noopener"&gt;Helper Operator&lt;/a&gt; to find additional possible configurations.
&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_the_status_of_the_operator"&gt;Verify the Status of the Operator&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;After Argo CD creates the subscription and operatorgroup resources (and namespace), OpenShift will start the installation of the Operator. This installation will take a while but Argo CD does not see this. All it sees is that the Subscription resource is available and it tries to continue with the configuration of the Operator. Here it will fail because the CRDs are not available yet.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Therefore, I created a mechanism to verify if an Operator is ready or not.&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;
Also verify the separate article &lt;a href="https://blog.stderr.at/openshift/2023-03-20-operator-installation-with-argocd/"&gt;Operator Installation with Argo CD&lt;/a&gt; that addresses the problem in more detail.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;All it does is to start a small Job inside OpenShift and to verify the status of the Operator installation. If everything is fine, the Job will end successfully and Argo CD will continue with the next syncwave. Argo CD Hook and syncwaves are required here. The Job should be started &lt;em&gt;after&lt;/em&gt; the Subscription/OperatorGroup resources have been created, which means any syncwave after &amp;#34;0&amp;#34;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The following annotations will be used by the Job:&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; argocd.argoproj.io/hook: Sync &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
argocd.argoproj.io/hook-delete-policy: HookSucceeded &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
argocd.argoproj.io/sync-wave: {{ .syncwave | default 1 | quote }} &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;Hooks are ways to run scripts before, during, and after a Sync operation.&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;Deletes the OpenShift Job again. The hook resource is deleted after the hook succeeded (e.g. Job/Workflow completed successfully).&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;Syncwave: can be configured. Must be after helper-operator (default 0) and before the Operator is configured further. Default value is 1.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The configuration for &lt;strong&gt;hepler_status_checker&lt;/strong&gt; will look like the following:&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;# Verify if the Operator has been successfully deployed
helper-status-checker:
enabled: true &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
checks: &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
- operatorName: compliance-operator &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
namespace:
name: openshift-compliance &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
serviceAccount:
name: &amp;#34;status-checker-compliance&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 status checker or not. Default: false&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;List of operators to check. Typically, only one is checked, but there could be more.&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;Name of the Operator to check (same as for helper-operator)&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;Namespace where the Operator has been installed (same as for helper-operator)&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;Name of the ServiceAccount that will be created to check the status.&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;
Verify the README at &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/helper-status-checker" target="_blank" rel="noopener"&gt;Helper Operator Status Checker&lt;/a&gt; to find additional possible configurations.
&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="_configuring_compliance_operator"&gt;Configuring Compliance Operator&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Finally, the Operator has been deployed and has been verified. Now the time is right to configure the Operator with any configuration we would like. This means, using CRDs to do whatever the Operator offers.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This is reflected in the following part of the values file. All these settings are handed over to the sub-chart &lt;strong&gt;compliance-operator-full-stack&lt;/strong&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;
Verify the README at &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/compliance-operator-full-stack" target="_blank" rel="noopener"&gt;Compliance Operator Chart&lt;/a&gt; to find additional possible configurations. Especially, if you like to do Tailored Profiles.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The compliance operator requires a so-called ScanSettingBinding that uses Profiles which are used to check the cluster compliance once a day. In this case, I am using CIS Benchmarks. There are two profiles:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;ocp4-cis-node: will check the node operating system for missing but suggested configuration.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ocp4-cis: will check the OpenShift cluster for missing but suggested configuration.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&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;# Setting for the Compliance Operator
compliance-operator-full-stack: &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
compliance:
namespace:
name: openshift-compliance &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
syncwave: &amp;#39;0&amp;#39;
descr: &amp;#39;Red Hat Compliance&amp;#39;
scansettingbinding: &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
enabled: true
syncwave: &amp;#39;3&amp;#39;
profiles: &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
- name: ocp4-cis-node
kind: Profile # Could be Profile or TailedProfile
- name: ocp4-cis
kind: Profile
scansetting: default&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;Handing everything that comes below to the sub-chart &lt;strong&gt;compliance-operator-full-stack&lt;/strong&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;Namespace where the configuration should be deployed. The Syncwave at this point could be omitted.&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;The configuration for the ScanSettingBinding. It is enabled (default = false) and has a Syncwave AFTER the helper-status-checker.&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;The list of profiles that shall be used. These must exist. The Compliance Operator offers several profiles. I usually use these two for full CIS compliance check.&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="_conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;With this configuration, the Compliance Operator will not only be installed but also configured with the same Argo CD Application. All you need to do is to synchronize Argo CD and let the magic happen. After a few minutes, everything should be in sync.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/gitopscollection/images/setup-compliance-operator.png?width=720px" alt="Sync Compliance Operator"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 1. Sync Compliance Operator&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Inside OpenShift the Operator is configured and starts doing its job:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/gitopscollection/images/configured-compliance-operator.png?width=720px" alt="Configured Compliance Operator"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 2. Configured Compliance Operator&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This concludes the deployment of the Compliance Operator. For further information about the Operator itself, please read the documentation or articles:&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://docs.openshift.com/container-platform/4.15/security/compliance_operator/co-overview.html" target="_blank" rel="noopener"&gt;Official Documentation: Compliance Operator&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://blog.stderr.at/compliance/2021/07/compliance-operator/"&gt;Blog: Compliance Operator&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Also, be sure to check out the READMEs of the different Charts:&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://github.com/tjungbauer/helm-charts/tree/main/charts/helper-operator" target="_blank" rel="noopener"&gt;Helper Operator&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/helper-status-checker" target="_blank" rel="noopener"&gt;Helper Operator Status Checker&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/compliance-operator-full-stack" target="_blank" rel="noopener"&gt;Compliance Operator Chart&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/blob/main/clusters/management-cluster/setup-compliance-operator/" target="_blank" rel="noopener"&gt;Compliance Operator Setup&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;If you have any questions or problems, feel free to create a GitHub issue at any time.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item><item><title>Secrets Management - Vault on OpenShift</title><link>https://blog.stderr.at/openshift-platform/security/secrets-management/2022-08-16-hashicorp-vault/</link><pubDate>Tue, 16 Aug 2022 00:00:00 +0000</pubDate><guid>https://blog.stderr.at/openshift-platform/security/secrets-management/2022-08-16-hashicorp-vault/</guid><description>&lt;div class="paragraph"&gt;
&lt;p&gt;Sensitive information in OpenShift or Kubernetes is stored as a so-called Secret. The management of these Secrets is one of the most important questions,
when it comes to OpenShift. Secrets in Kubernetes are encoded in base64. This is &lt;strong&gt;not&lt;/strong&gt; an encryption format.
Even if etcd is encrypted at rest, everybody can decode a given base64 string which is stored in the Secret.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;For example: The string &lt;code&gt;Thomas&lt;/code&gt; encoded as base64 is &lt;code&gt;VGhvbWFzCg==&lt;/code&gt;. This is simply a masked plain text and it is not secure to share these values, especially not on Git.
To make your CI/CD pipelines or Gitops process secure, you need to think of a secure way to manage your Secrets. Thus, your Secret objects must be encrypted somehow. HashiCorp Vault is one option to achieve this requirement.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_hashicorp_vault"&gt;HashiCorp Vault&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;HashiCorp Vault is a solution to solve our Secret management problem. It stores and encrypts our sensitive information at rest and in transit and enables you to create fine grained access
controls (ACL). This defines who has access to a specific secret. Application A should only get access to secret A and so on. However, that is just the tip of the iceberg. Vault can do much more.
If you are interested in further details, check out the introduction video by Armon, the Co-Founder of Hashicorp Vault at: &lt;a href="https://learn.hashicorp.com/tutorials/vault/getting-started-intro?in=vault/getting-started" class="bare"&gt;https://learn.hashicorp.com/tutorials/vault/getting-started-intro?in=vault/getting-started&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;For this article we will keep it simple and cover:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Installing HashiCorp Vault to OpenShift&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Integrating the plugin &amp;#34;Kubernetes Authentication&amp;#34; to access the Secrets&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Accessing a static secret stored in HashiCorp Vault by an example application&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_installing_vault"&gt;Installing Vault&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The easiest way to install Vault is by using the supported Helm chart.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Add the Helm repository to your repo:&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;helm repo add hashicorp https://helm.releases.hashicorp.com&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Update the repository to get the latest updates.&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;helm repo update&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Before we install Vault, we need to create a values file to set certain variables. Let’s create a file called &amp;#34;overwrite-values.yaml&amp;#34; with the following content&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;global:
openshift: true
server:
ha:
enabled: true
replicas: 3
raft:
enabled: true&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This will tell HashiCorp Vault the environment we are going to install is (OpenShift), enables high availability with 3 replicas and enables RAFT (see below for some introduction).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Finally, let us deploy HashiCorp Vault into the namespace &lt;strong&gt;vault&lt;/strong&gt; using the values file we have created in the previous step:&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;helm upgrade --install vault hashicorp/vault --values bootstrap/vault/overwrite-values.yaml --namespace=vault --create-namespace&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This will start the agent-injector and 3 vault-pods:&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 vault
NAME READY STATUS RESTARTS AGE
vault-0 0/1 Running 0 36s
vault-1 0/1 Running 0 36s
vault-2 0/1 Running 0 36s
vault-agent-injector-74c848f67b-sq4dq 1/1 Running 0 37s&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;vault agent injector: detecting applications with annotations that require vault agent which will get injected&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;vault-0: vault server&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The vault servers remain in &lt;strong&gt;not-ready&lt;/strong&gt; state until Vault has been unsealed by you.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;When you check the logs of one of the pods you will see:&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 logs vault-0 -n vault
...
2022-08-11T12:31:22.497Z [INFO] core: security barrier not initialized
2022-08-11T12:31:22.497Z [INFO] core: seal configuration missing, not initialized&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Vault always starts uninitialized and sealed, to protect the secrets. You need to give at least three different unseal-keys in order to be able to use Vault.&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;
Vault’s seal mechanism uses &lt;a href="https://en.wikipedia.org/wiki/Shamir%27s_Secret_Sharing" target="_blank" rel="noopener"&gt;Shamir’s secret sharing&lt;/a&gt;. This is a manual process to secure the cluster if it restarts. You can use auto-unseal for specific cloud providers to bypass the manual requirement.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_raft_storage"&gt;Raft Storage&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;During the deployment we have enabled Raft which is the integrated HA storage for Vault. Vault can use several different styles of storage backends, but when it comes to HA and Kubernetes, Raft is
easiest one since it has no other dependencies, and it is well known inside OpenShift. Etcd is using Raft as well. It is a distributed Consensus Algorithm where multiple members form a cluster and elect one leader. The leader has the responsibility to replicate everything to the followers. Since this leader election requires a majority an odd number of cluster members must be available to ensure there is a minimum number left if a member is failing.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_initialize_and_unseal_vault"&gt;Initialize and Unseal Vault&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;As mentioned, the Vault Pods are not fully available yet, since Vault it currently sealed and cannot be used. The first thing to do is to initialize and unseal it.&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;
The following commands will login into one Pod and execute commands from there.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Let’s get the status of our Vault fist:&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 -n vault exec -it vault-0 -- vault operator init -status&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This will return the message:
&lt;code&gt;Vault is not initialized&lt;/code&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The following command will initialize Vault for further usage:&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 -ti -n vault vault-0 -- vault operator init -format=json &amp;gt; unseal.json&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This will create the file &lt;strong&gt;unseal.json&lt;/strong&gt; locally on your machine. &lt;strong&gt;Keep this file secure&lt;/strong&gt; It contains by default 5 key shards and the root token you will need to unseal Vault and authenticate as root.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock caution"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-caution" title="Caution"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Keep this file secure, it contains keys to unseal Vault and the root_token to authenticate against.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Never share this file. It will look like this:&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-json hljs" data-lang="json"&gt;{
&amp;#34;unseal_keys_b64&amp;#34;: [
&amp;#34;key_1&amp;#34;,
&amp;#34;key_2&amp;#34;,
&amp;#34;key_3&amp;#34;,
&amp;#34;key_4&amp;#34;,
&amp;#34;key_5&amp;#34;
],
&amp;#34;unseal_keys_hex&amp;#34;: [
&amp;#34;key_hex_1&amp;#34;,
&amp;#34;key_hex_2&amp;#34;,
&amp;#34;key_hex_3&amp;#34;,
&amp;#34;key_hex_4&amp;#34;,
&amp;#34;key_hex_5&amp;#34;
],
&amp;#34;unseal_shares&amp;#34;: 5,
&amp;#34;unseal_threshold&amp;#34;: 3,
&amp;#34;recovery_keys_b64&amp;#34;: [],
&amp;#34;recovery_keys_hex&amp;#34;: [],
&amp;#34;recovery_keys_shares&amp;#34;: 5,
&amp;#34;recovery_keys_threshold&amp;#34;: 3,
&amp;#34;root_token&amp;#34;: &amp;#34;root.token&amp;#34;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;With the initialization in place Vault is put into a sealed mode. This means Vault cannot decrypt secrets at this moment.
To unseal Vault you need the unseal key, which is split into multiple shards using &lt;a href="https://en.wikipedia.org/wiki/Shamir%27s_Secret_Sharing" target="_blank" rel="noopener"&gt;Shamir’s secret sharing&lt;/a&gt;. A certain number of individual shards (default 3) must be provided to reconstruct the unseal key.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To unseal Vault lets login to our Pod &amp;#34;vault-0&amp;#34; and unseal it. Use the following command and provide one of the keys:&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 -ti -n vault vault-0 -- vault operator unseal
Unseal Key (will be hidden):
Key Value
--- -----
Seal Type shamir
Initialized true &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
Sealed true &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
Total Shares 5
Threshold 3
Unseal Progress 1/3 &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
Unseal Nonce 08b01535-be15-e865-251c-f948ed0661c9
Version 1.11.2
Build Date 2022-07-29T09:48:47Z
Storage Type raft
HA Enabled true&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 is initialized&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;Vault is still sealed&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;The unseal progress: Currently 1 out of 3 keys have been provided&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Use the same command another 2 times using &lt;strong&gt;different&lt;/strong&gt; keys to complete the unseal process:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;At the end the following output should be shown:&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;Unseal Key (will be hidden):
Key Value
--- -----
Seal Type shamir
Initialized true
Sealed false &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
Total Shares 5
Threshold 3
Version 1.11.2
Build Date 2022-07-29T09:48:47Z
Storage Type raft
Cluster Name vault-cluster-f7402e5b &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
Cluster ID aff648f0-b3a2-1fdd-12f6-492842b08b2b
HA Enabled true
HA Cluster https://vault-0.vault-internal:8201
HA Mode active &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
Active Since 2022-08-16T07:20:45.828215961Z
Raft Committed Index 36
Raft Applied Index 36&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 is now unsealed&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 our cluster&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;High availability is enabled&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong&gt;Vault-0&lt;/strong&gt; is now initialized, but there are 2 other members in our HA cluster which must be added.
Let &lt;strong&gt;vault-1&lt;/strong&gt; and &lt;strong&gt;vault-2&lt;/strong&gt; join the cluster and perform the same unseal process as previously: use 3 different keys:&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 -ti vault-1 -n vault -- vault operator raft join http://vault-0.vault-internal:8200
# 3 times....
oc exec -ti vault-1 -n vault -- vault operator unseal
oc exec -ti vault-2 -n vault -- vault operator raft join http://vault-0.vault-internal:8200
# 3 times...
oc exec -ti vault-2 -n vault -- vault operator unseal&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_verify_vault_cluster"&gt;Verify Vault Cluster&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To verify if the Raft cluster has successfully been initialized, run the following.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;First, login using the &lt;strong&gt;root_token&lt;/strong&gt;, that was created above, on the vault-0 pod.&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 -ti vault-0 -n vault -- vault login
Token (will be hidden): &amp;lt;root_token&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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 -ti vault-0 -n vault -- vault operator raft list-peers&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This should return:&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;Node Address State Voter
---- ------- ----- -----
16ec7490-f621-42ea-976d-5f054cfaeecc vault-0.vault-internal:8201 leader true
60ba2885-432a-c7d3-d280-a824f0acce42 vault-1.vault-internal:8201 follower true
bcc4f551-79bc-47e5-d01b-97fc12d1afa5 vault-2.vault-internal:8201 follower true&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;As you can see Vault-0 is the leader while the other two members are followers.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_configure_kubernetes_authentication"&gt;Configure Kubernetes Authentication&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;There are multiple ways how an application can interact with Vault. One example is to use Tokens. This is quite easy but has the disadvantage that it does require additional steps of managing the life cycle of such token, moreover they might be shared, which is not what we want.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;HashiCorp Vault supports different authentication methods. One of which is the &lt;strong&gt;Kubernetes Auth Method&lt;/strong&gt; that must be enabled before we can use.
The Kubernetes Auth Method makes use of Jason Web Tokens (JWT)s that are bound to a Service Account. When we tell Vault that a Service Account is fine to authenticate, then a Deployment using this account is able to authenticate and
request Secrets.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Vault has a plugin ecosystem, which allows to enable certain plugins. To enable &lt;strong&gt;Kubernetes Auth Method&lt;/strong&gt; use the following process:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;Login vault-0 pods
&lt;code&gt;oc exec -it vault-0 -n vault — /bin/sh&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;execute the command:
&lt;code&gt;vault auth enable kubernetes&lt;/code&gt; which returns:
&lt;em&gt;Success! Enabled kubernetes auth method at: kubernetes/&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set up the Kubernetes configuration to use Vault’s service account JWT.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&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;
the address to the OpenShift API (KUBERNETES_PORT_443_TCP_ADDR) is automatically available via an environment variable.
&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;vault write auth/kubernetes/config issuer=&amp;#34;&amp;#34; \
token_reviewer_jwt=&amp;#34;$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)&amp;#34; \
kubernetes_host=&amp;#34;https://$KUBERNETES_PORT_443_TCP_ADDR:443&amp;#34; \
kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
Success! Data written to: auth/kubernetes/config&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;With this step authentication against OpenShift is enabled.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_configure_a_secret"&gt;Configure a Secret&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;With the Kubernetes Auth Method in place we can configure a secret to test our setup. We will use an example application called &lt;strong&gt;expenses&lt;/strong&gt; that has a MySQL database.
The static password to bootstrap this database shall be stored in Vault. A plugin called &lt;strong&gt;key-value secrets&lt;/strong&gt; engine will be used to achieve this.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;There are other plugins that are specifically designed to automatically rotate secrets. For example, it is possible to dynamically create user credentials für MySQL.&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;
You can list available engines by using the command: &lt;code&gt;oc -n vault exec -it vault-0 — vault secrets list&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;There are currently two versions of this key/value engine:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;KV Version 1: does not versionize the key/values, thus updates will overwrite the old values.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;KV Version 2: does versionize the key/value pairs&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;In our example we will use version 2.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Like the authentication method, we need to enable the secrets engine:&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 -n vault exec -it vault-0 -- vault secrets enable \
-path=expense/static \ &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
-version=2 \ &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
kv &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;API path where our secrets are stored&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;Version 2&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;name of our engine&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;You can list the enabled engines with the following command:&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 -n vault exec -it vault-0 -- vault secrets list
Path Type Accessor Description
---- ---- -------- -----------
cubbyhole/ cubbyhole cubbyhole_f1e955f9 per-token private secret storage
expense/static/ kv kv_6db09e5d n/a
identity/ identity identity_ca05e6ab identity store &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
sys/ system system_e34a76c3 system endpoints used for control, policy and debugging&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;Enabled KV secrets engine using the path &lt;strong&gt;expense/static/&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Now lets put a secret into our store. We will store our super-secure MySQL password into &lt;strong&gt;expense/static/mysql&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;MYSQL_DB_PASSWORD=mysuperpassword$
oc -n vault exec -it vault-0 -- vault kv put expense/static/mysql db_login_password=${MYSQL_DB_PASSWORD}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This command will store the key &lt;strong&gt;db_login_password&lt;/strong&gt; with the database as value. We can get the secret by calling:&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 -n vault exec -it vault-0 -- vault kv get expense/static/mysql
====== Secret Path ======
expense/static/data/mysql &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
======= Metadata =======
Key Value
--- -----
created_time 2022-08-17T06:08:05.839663508Z
custom_metadata &amp;lt;nil&amp;gt;
deletion_time n/a
destroyed false
version 1
========== Data ==========
Key Value
--- -----
db_login_password mysuperpassword$ &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&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;The data path of our secret&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;our password&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_configuring_policies"&gt;Configuring policies&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The Secret is now stored at &lt;strong&gt;expense/static/mysql&lt;/strong&gt; but there is no policy in place. Everybody who is authenticated and is calling this path will get to see the secrets.
Luckily, one or more policies can be assigned to the authentication method. A policy defines capabilities that allow you to perform certain actions.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The following capabilities are known:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;create&lt;/strong&gt; - to create new data&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;read&lt;/strong&gt; - to read data&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;delete&lt;/strong&gt; - to delete data&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;list&lt;/strong&gt; - to list data&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Policies can be written either in JSON or HCL (HashiCorp Configuration Language). Let’s create a file with the following content:&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-json hljs" data-lang="json"&gt;path &amp;#34;expense/static/data/mysql&amp;#34; {
capabilities = [&amp;#34;read&amp;#34;, &amp;#34;list&amp;#34;]
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="admonitionblock caution"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-caution" title="Caution"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
KV Version2 stores the secrets in a path with the prefix &lt;code&gt;data/&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This will limit my access to &lt;strong&gt;read&lt;/strong&gt; and &lt;strong&gt;list&lt;/strong&gt; only.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Write the policy:&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;cat my-policy.hcl | oc -n vault exec -it vault-0 -- vault policy write expense-db-mysql -&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Next, we are going to bind the Vault secret to a service account and a namespace. Both objects will be created later, when we deploy the application.&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 -n vault exec -it vault-0 -- vault write auth/kubernetes/role/expense-db-mysql \ &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
bound_service_account_names=expense-db-mysql \ &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
bound_service_account_namespaces=expenses \ &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
policies=expense-db-mysql \ &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
ttl=1h &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;Path or our new role&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 service account we will create and that will be used by the application&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;Name of the namespace we will create&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;Name of the policy we created earlier&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;The token is valid for 1 hour, after this period the service account must re-authenticate&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_lets_start_an_application"&gt;Let’s start an Application&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Now we will create our MySQL application into the namespace &lt;strong&gt;expenses&lt;/strong&gt;. Use the following command to create the namespace and the application containing the objects Deployment, ServiceAccount (expense-db-mysql) and Service.&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;
See at the &lt;a href="https://raw.githubusercontent.com/joatmon08/vault-argocd/part-1/database/deployment.yaml" target="_blank" rel="noopener"&gt;Github Page&lt;/a&gt; for a full yaml specification of the three objects.
&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 new-project expenses
oc apply -f https://raw.githubusercontent.com/joatmon08/vault-argocd/part-1/database/deployment.yaml&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The deployment will start a Pod with a sidecar container &lt;strong&gt;vault-agent&lt;/strong&gt;. This sidecar is automatically created and must not be defined inside the Deployment specification.
Instead, some annotations in the Deployment define what the container should be automatically injected and also where to find our secret:&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/role: &amp;#34;expense-db-mysql&amp;#34; &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
vault.hashicorp.com/agent-inject-secret-db: &amp;#34;expense/static/data/mysql&amp;#34; &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
vault.hashicorp.com/agent-inject-template-db: | &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
{{ with secret &amp;#34;expense/static/data/mysql&amp;#34; -}}
export MYSQL_ROOT_PASSWORD=&amp;#34;{{ .Data.data.db_login_password }}&amp;#34;
{{- end }}
...
spec:
serviceAccountName: expense-db-mysql &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;Defines that the &lt;strong&gt;vault-agent&lt;/strong&gt; side car container shall be automatically injected. This is the most important annotation.&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 role that was created created previously&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;The agent will inject the data from &lt;strong&gt;expense/static/data/mysql&lt;/strong&gt; and stores it in a file &lt;strong&gt;db&lt;/strong&gt; The file name is everything that comes after &lt;strong&gt;vault.hashicorp.com/agent-inject-secret-&lt;/strong&gt;&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;Configuration…​ the template that defines how the secret will be rendered&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;The service account name we bound our secret to, using the Consul language. In this case the MySQL password is simply exported&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The vault-agent is requesting the database password from Vault and provides it to the application where it is stored at &lt;code&gt;/vault/secrets/db&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 -n expenses exec -it $(oc get pods -l=app=expense-db-mysql -o jsonpath=&amp;#39;{.items[0].metadata.name}&amp;#39;) -c expense-db-mysql -- cat /vault/secrets/db
# output
export MYSQL_ROOT_PASSWORD=&amp;#34;mysuperpassword$&amp;#34;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The Deployment sources this file when it starts and MySQL will take this information to configure itself.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_tip_using_vault_cli_on_your_local_environment"&gt;TIP: Using vault CLI on your local environment&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;All above commands that are dealing with Vault commands, first login to a pod and then execure the commands from there.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;If you have the &lt;a href="https://www.vaultproject.io/docs/install" target="_blank" rel="noopener"&gt;Vault CLI&lt;/a&gt; installed on your local machine, you can open a port forwarding to your Vault cluster at OpenShift and execute the commands locally:&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 port-forward -n vault svc/vault 8200
export VAULT_ADDR=http://localhost:8200
vault login
...&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_thanks"&gt;Thanks&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Thanks to the wonderful Rosemary Wang and her Github repository: &lt;a href="https://github.com/joatmon08/vault-argocd/tree/part-1" class="bare"&gt;https://github.com/joatmon08/vault-argocd/tree/part-1&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Also check out the Youtube Video: &lt;a href="https://www.youtube.com/watch?v=Bce_0qa6ias" target="_blank" rel="noopener"&gt;GitOps Guide to the Galaxy (Ep 31) | GitOps With Vault Part 1&lt;/a&gt; in which Rosemary and Christian discuss this setup&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item><item><title>Automated ETCD Backup</title><link>https://blog.stderr.at/openshift-platform/day-2/etcd/2022-01-29-automatedetcdbackup/</link><pubDate>Sat, 29 Jan 2022 00:00:00 +0000</pubDate><guid>https://blog.stderr.at/openshift-platform/day-2/etcd/2022-01-29-automatedetcdbackup/</guid><description>&lt;div class="paragraph"&gt;
&lt;p&gt;Securing ETCD is one of the major Day-2 tasks for a Kubernetes cluster. This article will explain how to create a backup using OpenShift Cronjob.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock caution"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-caution" title="Caution"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
There is absolutely no warranty. Verify your backups regularly and perform restore tests.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_prerequisites"&gt;Prerequisites&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The following is required:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;OpenShift Cluster 4.x&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Integrated Storage, might be NFS or anything. Best practice would be a RWX enabled storage.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_configure_project_cronjob"&gt;Configure Project &amp;amp; Cronjob&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Create the following objects in OpenShift. This fill create:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;A Project called &lt;code&gt;ocp-etcd-backup&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A PersistentVolumeClaim to store the backups. &lt;strong&gt;Change to your appropriate StorageClass and accessMode&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A ServiceAccount called &lt;code&gt;openshift-backup&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A dedicated ClusterRole which is able to start (debug pods)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A ClusterRoleBinding between the created ServiceAccount and the customer ClusterRole&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A 2nd ClusterRoleBinding, which gives our ServiceAccount the permission to start privileged containers. This is required to start a debug pod on a control plane node.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A CronJob which performs the backup …​ see Callouts for inline explanations.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&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;
A helm chart, which would create these objects below, can be found at: &lt;a href="https://github.com/tjungbauer/ocp-auto-backup" class="bare"&gt;https://github.com/tjungbauer/ocp-auto-backup&lt;/a&gt;. This is probably a better way to manage the variables via the values.yaml file.
&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;kind: Namespace
apiVersion: v1
metadata:
name: ocp-etcd-backup
annotations:
openshift.io/description: Openshift Backup Automation Tool
openshift.io/display-name: Backup ETCD Automation
openshift.io/node-selector: &amp;#39;&amp;#39;
spec: {}
---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: etcd-backup-pvc
namespace: ocp-etcd-backup
spec:
accessModes:
- ReadWriteOnce &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
resources:
requests:
storage: 100Gi
storageClassName: gp2
volumeMode: Filesystem
---
kind: ServiceAccount
apiVersion: v1
metadata:
name: openshift-backup
namespace: ocp-etcd-backup
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: cluster-etcd-backup
rules:
- apiGroups: [&amp;#34;&amp;#34;]
resources:
- &amp;#34;nodes&amp;#34;
verbs: [&amp;#34;get&amp;#34;, &amp;#34;list&amp;#34;]
- apiGroups: [&amp;#34;&amp;#34;]
resources:
- &amp;#34;pods&amp;#34;
- &amp;#34;pods/log&amp;#34;
verbs: [&amp;#34;get&amp;#34;, &amp;#34;list&amp;#34;, &amp;#34;create&amp;#34;, &amp;#34;delete&amp;#34;, &amp;#34;watch&amp;#34;]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: openshift-backup
subjects:
- kind: ServiceAccount
name: openshift-backup
namespace: ocp-etcd-backup
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-etcd-backup
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: etcd-backup-scc-privileged
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:openshift:scc:privileged
subjects:
- kind: ServiceAccount
name: openshift-backup
namespace: ocp-etcd-backup
---
kind: CronJob
apiVersion: batch/v1
metadata:
name: cronjob-etcd-backup
namespace: ocp-etcd-backup
labels:
purpose: etcd-backup
spec:
schedule: &amp;#39;*/5 * * * *&amp;#39; &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
startingDeadlineSeconds: 200
concurrencyPolicy: Forbid
suspend: false
jobTemplate:
metadata:
creationTimestamp: null
spec:
backoffLimit: 0
template:
metadata:
creationTimestamp: null
spec:
nodeSelector:
node-role.kubernetes.io/master: &amp;#39;&amp;#39; &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
restartPolicy: Never
activeDeadlineSeconds: 200
serviceAccountName: openshift-backup
schedulerName: default-scheduler
hostNetwork: true
terminationGracePeriodSeconds: 30
securityContext: {}
containers:
- resources:
requests:
cpu: 300m
memory: 250Mi
terminationMessagePath: /dev/termination-log
name: etcd-backup
command: &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
- /bin/bash
- &amp;#39;-c&amp;#39;
- &amp;gt;-
oc get no -l node-role.kubernetes.io/master --no-headers -o
name | grep `hostname` | head -n 1 | xargs -I {} -- oc debug
{} -- bash -c &amp;#39;chroot /host sudo -E
/usr/local/bin/cluster-backup.sh /home/core/backup&amp;#39; ; echo
&amp;#39;Moving Local Master Backups to target directory (from
/home/core/backup to mounted PVC)&amp;#39;; mv /home/core/backup/*
/etcd-backup/; echo &amp;#39;Deleting files older than 30 days&amp;#39; ; find
/etcd-backup/ -type f -mtime +30 -exec rm {} \;
securityContext:
privileged: true
runAsUser: 0
imagePullPolicy: IfNotPresent
volumeMounts:
- name: temp-backup
mountPath: /home/core/backup &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
- name: etcd-backup
mountPath: /etcd-backup &lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;(6)&lt;/b&gt;
terminationMessagePolicy: FallbackToLogsOnError
image: registry.redhat.io/openshift4/ose-cli
serviceAccount: openshift-backup
volumes:
- name: temp-backup
hostPath:
path: /home/core/backup
type: &amp;#39;&amp;#39;
- name: etcd-backup
persistentVolumeClaim:
claimName: etcd-backup-pvc
dnsPolicy: ClusterFirst
tolerations:
- operator: Exists
effect: NoSchedule
- operator: Exists
effect: NoExecute
successfulJobsHistoryLimit: 5
failedJobsHistoryLimit: 5&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;RWO is used here, since I have no other available storage on my test cluster.&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;How often shall the job be executed. Here, every 5 minutes.&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;Bind the job to &amp;#34;Master&amp;#34; nodes.&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;Command to be executed…​ It fetches the actual local master nodename and starts a debugging Pod there. The backup script is called and moves the backup to /home/core/backup which is a folder on the control plane itself. The move command will move the backups from the local folder to the actual backup target volume. Finally, it will remove backups older than 30 days.&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;Mounted /home/core/backup on the master nodes, here the command will store the backups before they are moved&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;6&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Target destination for the etcd backup on the mounted PVC&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_start_a_job"&gt;Start a Job&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;If you do not want to wait until the CronJob is triggered, you can manually start the Job using the following commands:&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 job backup --from=cronjob/cronjob-etcd-backup -n ocp-etcd-backup&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This will start a Pod which will do the backup:&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-none hljs"&gt;Starting pod/ip-10-0-196-187us-east-2computeinternal-debug ...
To use host binaries, run `chroot /host`
found latest kube-apiserver: /etc/kubernetes/static-pod-resources/kube-apiserver-pod-15
found latest kube-controller-manager: /etc/kubernetes/static-pod-resources/kube-controller-manager-pod-10
found latest kube-scheduler: /etc/kubernetes/static-pod-resources/kube-scheduler-pod-9
found latest etcd: /etc/kubernetes/static-pod-resources/etcd-pod-3
etcdctl is already installed
{&amp;#34;level&amp;#34;:&amp;#34;info&amp;#34;,&amp;#34;ts&amp;#34;:1638199790.980932,&amp;#34;caller&amp;#34;:&amp;#34;snapshot/v3_snapshot.go:119&amp;#34;,&amp;#34;msg&amp;#34;:&amp;#34;created temporary db file&amp;#34;,&amp;#34;path&amp;#34;:&amp;#34;/home/core/backup/snapshot_2021-11-29_152949.db.part&amp;#34;}
{&amp;#34;level&amp;#34;:&amp;#34;info&amp;#34;,&amp;#34;ts&amp;#34;:&amp;#34;2021-11-29T15:29:50.991Z&amp;#34;,&amp;#34;caller&amp;#34;:&amp;#34;clientv3/maintenance.go:200&amp;#34;,&amp;#34;msg&amp;#34;:&amp;#34;opened snapshot stream; downloading&amp;#34;}
{&amp;#34;level&amp;#34;:&amp;#34;info&amp;#34;,&amp;#34;ts&amp;#34;:1638199790.9912837,&amp;#34;caller&amp;#34;:&amp;#34;snapshot/v3_snapshot.go:127&amp;#34;,&amp;#34;msg&amp;#34;:&amp;#34;fetching snapshot&amp;#34;,&amp;#34;endpoint&amp;#34;:&amp;#34;https://10.0.196.187:2379&amp;#34;}
{&amp;#34;level&amp;#34;:&amp;#34;info&amp;#34;,&amp;#34;ts&amp;#34;:&amp;#34;2021-11-29T15:29:53.306Z&amp;#34;,&amp;#34;caller&amp;#34;:&amp;#34;clientv3/maintenance.go:208&amp;#34;,&amp;#34;msg&amp;#34;:&amp;#34;completed snapshot read; closing&amp;#34;}
Snapshot saved at /home/core/backup/snapshot_2021-11-29_152949.db
{&amp;#34;level&amp;#34;:&amp;#34;info&amp;#34;,&amp;#34;ts&amp;#34;:1638199793.3482974,&amp;#34;caller&amp;#34;:&amp;#34;snapshot/v3_snapshot.go:142&amp;#34;,&amp;#34;msg&amp;#34;:&amp;#34;fetched snapshot&amp;#34;,&amp;#34;endpoint&amp;#34;:&amp;#34;https://10.0.196.187:2379&amp;#34;,&amp;#34;size&amp;#34;:&amp;#34;180 MB&amp;#34;,&amp;#34;took&amp;#34;:2.367303503}
{&amp;#34;level&amp;#34;:&amp;#34;info&amp;#34;,&amp;#34;ts&amp;#34;:1638199793.348459,&amp;#34;caller&amp;#34;:&amp;#34;snapshot/v3_snapshot.go:152&amp;#34;,&amp;#34;msg&amp;#34;:&amp;#34;saved&amp;#34;,&amp;#34;path&amp;#34;:&amp;#34;/home/core/backup/snapshot_2021-11-29_152949.db&amp;#34;}
{&amp;#34;hash&amp;#34;:1180914745,&amp;#34;revision&amp;#34;:10182252,&amp;#34;totalKey&amp;#34;:19360,&amp;#34;totalSize&amp;#34;:179896320}
snapshot db and kube resources are successfully saved to /home/core/backup
Removing debug pod ...
Moving Local Master Backups to target directory (from /home/core/backup to mounted PVC)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_verifying_the_backup"&gt;Verifying the Backup&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Let’s start a dummy Pod which can access the PVC to verify if the backup is really there.&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: verify-etcd-backup
spec:
containers:
- name: verify-etcd-backup
image: registry.access.redhat.com/ubi8/ubi
command: [&amp;#34;sleep&amp;#34;, &amp;#34;3000&amp;#34;]
volumeMounts:
- name: etcd-backup
mountPath: /etcd-backup
volumes:
- name: etcd-backup
persistentVolumeClaim:
claimName: etcd-backup-pvc&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Logging into that Pod will show the available backups stored at /etcd-backup which is the mounted PVC.&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 rsh -n ocp-etcd-backup verify-etcd-backup ls -la etcd-backup
total 1406196
drwxr-xr-x. 3 root root 4096 Nov 29 17:00 .
dr-xr-xr-x. 1 root root 25 Nov 29 17:06 ..
drwx------. 2 root root 16384 Nov 29 15:21 lost+found
-rw-------. 1 root root 179896352 Nov 29 15:21 snapshot_2021-11-29_152150.db
-rw-------. 1 root root 179896352 Nov 29 15:29 snapshot_2021-11-29_152949.db
-rw-------. 1 root root 179896352 Nov 29 15:32 snapshot_2021-11-29_153159.db
-rw-------. 1 root root 179896352 Nov 29 15:36 snapshot_2021-11-29_153618.db
-rw-------. 1 root root 179896352 Nov 29 15:55 snapshot_2021-11-29_155513.db
-rw-------. 1 root root 179896352 Nov 29 16:00 snapshot_2021-11-29_160020.db
-rw-------. 1 root root 179896352 Nov 29 16:55 snapshot_2021-11-29_165521.db
-rw-------. 1 root root 179896352 Nov 29 17:00 snapshot_2021-11-29_170020.db
-rw-------. 1 root root 89875 Nov 29 15:21 static_kuberesources_2021-11-29_152150.tar.gz
-rw-------. 1 root root 89875 Nov 29 15:29 static_kuberesources_2021-11-29_152949.tar.gz
-rw-------. 1 root root 89875 Nov 29 15:32 static_kuberesources_2021-11-29_153159.tar.gz
-rw-------. 1 root root 89875 Nov 29 15:36 static_kuberesources_2021-11-29_153618.tar.gz
-rw-------. 1 root root 89875 Nov 29 15:55 static_kuberesources_2021-11-29_155513.tar.gz
-rw-------. 1 root root 89875 Nov 29 16:00 static_kuberesources_2021-11-29_160020.tar.gz
-rw-------. 1 root root 89875 Nov 29 16:55 static_kuberesources_2021-11-29_165521.tar.gz
-rw-------. 1 root root 89875 Nov 29 17:00 static_kuberesources_2021-11-29_170020.tar.gz&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item></channel></rss>