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#

  • OpenBao Helm chart https://github.com/openbao/openbao-helm

  • I’ll use cert-manager for creating the necessary certificates and ingress-nginx for exposing the UI

  • I’ll 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.

  2. 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
    
  3. Store the unseal key(s) and the root token somewhere safe

  4. 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).