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:

  1. End-to-end TLS encryption for network traffic. Includes the OpenBao UI (with proxy SSL support!)
  2. High availability via OpenBao’s internal Raft implementation.
  3. 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

  1. 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 the vault-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.

  1. 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
  1. We enable TLS by setting global.tlsDisable to false. This enables https endpoints for the relevant services.
  2. We use the nightly build of OpenBao which has support for static auto-unseal (openbao/openbao-nightly:2.4.0-nightly1752150785).
  3. 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.
  4. We enable HA and Raft storage.
  5. We configure the Raft listener to use TLS and bind to all interfaces. We also provide the paths to our TLS certificate and key.
  6. We configure static auto-unseal using a file-based unseal key.
  7. 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.
  8. 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.
  9. Other proxy-* parameters are set to ensure that the ingress controller can verify the TLS certificate of the OpenBao server.
  10. We enable the UI by setting ui.enabled to true.
  11. Volumes and volume mounts are added for the unseal key and TLS certificates.

First time setup#

  1. Install the helm chart using the above values.yaml file
  2. 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
  1. Store the unseal key(s) and the root token in a safe place
  2. 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).