Vault on Kubernetes using OpenBao
Introduction#
OpenBao is an open-source fork of HashiCorp’s Vault, created to ensure the project remains community-driven and permissively licensed. It provides a robust, transparent, and accessible solution for secrets management and data protection, offering a viable alternative for users who relied on Vault’s original open-source model.
The default Helm installation of OpenBao is enough for a dev environment but it needs some modifications for a full-fledged production deployment. In this blog post we’ll learn about how a typical production deployment for OpenBao would look like.
Note: I’m very new to OpenBao myself. Apologies for any mistakes/inaccuracies in my blog post. Feel free to e-mail me if you find something wrong.
mail: nanibot@nanibot.net
Overview#
Here’s all the things that we’re going to configure for our OpenBao cluster:
- End-to-end TLS encryption for network traffic. Includes the OpenBao UI (with proxy SSL support!)
- High availability via OpenBao’s internal Raft implementation.
- Auto-unseal without relying on a cloud KMS solution (Note: This might not be secure - depending on whether you feel comfortable storing the unseal key as a kubernetes secret or not)
Note: Currently, static unseal is only available in a nightly build (openbao/openbao-nightly:2.4.0-nightly1752150785
) but is planned to be released as part of the 2.4.0 release
To understand why this might not be always secure please refer to the link https://openbao.org/docs/rfcs/static-auto-unseal/
Pre-requisites#
We assume the chart is going to be installed in the vault-system
namespace and the release is called vault-production
- Certificate to be used for TLS. In this example, I’m using a wildcard certificate issued by my own CA. The certificate is stored in a kubernetes secret named
internal-wildcard-cert-secret
in thevault-system
namespace
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: internal-wildcard-cert
namespace: vault-system
spec:
secretName: internal-wildcard-cert-secret
duration: 2160h
renewBefore: 720h
privateKey:
algorithm: RSA
encoding: PKCS1
size: 2048
rotationPolicy: Always
subject:
organizations:
- Umbrella
organizationalUnits:
- nanibot.net
dnsNames:
- "vault-production-openbao-active"
- "*.vault-production-openbao-internal"
- "*.vault-production-openbao-internal.vault-system"
- "*.vault-production-openbao-internal.vault-system.svc"
- "*.vault-production-openbao-internal.vault-system.svc.cluster.local"
ipAddresses:
- "127.0.0.1"
issuerRef:
name: pki-production-selfsigned-issuer
kind: ClusterIssuer
Note: The dnsName entry vault-production-openbao-active
refers to the Kubernetes service that’s created by the Helm chart. This will also be our API Address - the hostname that the Vault API will be exposed at.
- Unseal key for static auto-unseal
We need to create a kubernetes secret containing the unseal key for static auto-unseal to work. We can do this by running the following commands:
openssl rand -out unseal-umbrella-1.key 32
kubectl create secret generic unseal-key --from-file=unseal-umbrella-1.key=./unseal-umbrella-1.key
Configuration#
File: values.yaml
global:
tlsDisable: false
server:
image:
repository: "openbao/openbao-nightly"
tag: "2.4.0-nightly1752150785"
extraEnvironmentVars:
BAO_CACERT: "/certs/ca.crt"
ha:
enabled: true
apiAddr: "https://vault-production-openbao-active:8200"
raft:
enabled: true
config: |
ui = true
listener "tcp" {
address = "[::]:8200"
cluster_address = "[::]:8201"
tls_cert_file = "/certs/tls.crt"
tls_key_file = "/certs/tls.key"
}
storage "raft" {
path = "/openbao/data"
}
seal "static" {
current_key_id = "umbrella-1"
current_key = "file:///keys/unseal-umbrella-1.key"
}
service_registration "kubernetes" {}
auditStorage:
enabled: true
ingress:
enabled: true
annotations:
nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
nginx.ingress.kubernetes.io/proxy-ssl-verify: "on"
nginx.ingress.kubernetes.io/proxy-ssl-name: "vault-production-openbao-active"
nginx.ingress.kubernetes.io/proxy-ssl-secret: "vault-system/internal-wildcard-cert-secret"
ingressClassName: "nginx"
hosts:
- host: vault.nanibot.net
tls:
- secretName: public-wildcard-cert-secret
hosts:
- vault.nanibot.net
volumes:
- name: unseal-key
secret:
secretName: unseal-key
- name: certs
secret:
secretName: internal-wildcard-cert-secret
volumeMounts:
- mountPath: /keys
name: unseal-key
readOnly: true
- mountPath: /certs
name: certs
readOnly: true
ui:
enabled: true
- We enable TLS by setting
global.tlsDisable
tofalse
. This enables https endpoints for the relevant services. - We use the nightly build of OpenBao which has support for static auto-unseal (
openbao/openbao-nightly:2.4.0-nightly1752150785
). BAO_CACERT
is set to the path of our CA certificate so that OpenBao can verify the TLS certificate of other nodes in the cluster.- We enable HA and Raft storage.
- We configure the Raft listener to use TLS and bind to all interfaces. We also provide the paths to our TLS certificate and key.
- We configure static auto-unseal using a file-based unseal key.
- apiAddr is set to the DNS name of the active OpenBao node (Kubernetes service created by the Helm chart). This is required for the UI to work properly with proxy SSL.
- proxy-ssl-name is set to the DNS name of the active OpenBao node. This is required for the UI to work properly with proxy SSL.
- Other
proxy-*
parameters are set to ensure that the ingress controller can verify the TLS certificate of the OpenBao server. - We enable the UI by setting
ui.enabled
totrue
. - Volumes and volume mounts are added for the unseal key and TLS certificates.
First time setup#
- Install the helm chart using the above values.yaml file
- Initialize the OpenBao cluster by running the following command (Assuming the pod name is
vault-production-openbao-0
):
kubectl exec -it vault-production-openbao-0 -- bao operator init
- Store the unseal key(s) and the root token in a safe place
- Join the other nodes to the cluster by running the following command on each of them:
kubectl exec -it vault-production-openbao-1 -- bao operator raft join -leader-ca-cert=@/certs/ca.crt https://vault-production-openbao-0.vault-production-openbao-internal:8200
kubectl exec -it vault-production-openbao-2 -- bao operator raft join -leader-ca-cert=@/certs/ca.crt https://vault-production-openbao-0.vault-production-openbao-internal:8200
That’s it! You should now have a fully functional OpenBao cluster running on Kubernetes with TLS, HA and auto-unseal support.
The Web UI should be accessible at https://vault.nanibot.net
(or whatever host you configured in the ingress).