Use OpenBao rather than HashiCorp Vault
Status: Accepted Date: 2026-08-21 Deciders: Smana (Platform Owner) Related Design: N/A — records a choice predating the design workflow Related: ADR-0014 — the same licence trigger, applied to the toolchain
Context
This platform needs a self-hosted secrets engine and a private PKI: the
three-tier certificate chain that terminates TLS on every internal Gateway
API listener, and the AppRole machine credentials that cert-manager and
the Raft snapshot job authenticate with. That role is filled by OpenBao,
not HashiCorp Vault, the project OpenBao forked from.
HashiCorp originally published Vault under the Mozilla Public License 2.0. In 2023 it relicensed Vault, and its other tools, under the Business Source License (BUSL) — source-available, not open source: running a BUSL-licensed component as a competing hosted offering needs a commercial agreement with HashiCorp, and each release only becomes fully open again after a fixed embargo period. OpenBao is the response: a fork of the last MPL-2.0 Vault codebase, contributed to and now governed by the Linux Foundation, continuing under the original permissive licence.
This is not the platform’s only secrets store. External Secrets
Operator’s ClusterSecretStore
(security/base/external-secrets/clustersecretstore.yaml) already reads
from AWS Secrets Manager, and even OpenBao’s own bootstrap material — the
root token, the recovery keys, the operator password, every minted
AppRole credential — is published there rather than kept inside OpenBao
itself, since a still-sealed or freshly-initialised cluster cannot be the
thing that stores the keys to unseal it. What OpenBao specifically owns is
the private PKI and the namespace-scoped AppRole model: the root namespace
hosts the PKI mount and every shared platform-service AppRole, while
app is a worked example of a tenant namespace holding its own secret/
mount. This decision is about which engine backs that role, not about the
platform’s secrets architecture as a whole.
Decision Drivers
- Licence continuity. A reference platform meant to be cloned and run by anyone should not depend on a component whose commercial terms one vendor can change under it — avoiding that kind of licence cliff is a standing principle behind several of this platform’s other choices.
- Migration cost. Whatever replaces Vault has to work with the
Terraform/OpenTofu code already written against it —
vault_*resources, thehashicorp/vaultprovider, thebao/vaultCLI surface. - Self-hosted PKI and namespaced AppRole model. The chosen engine has to support namespaces as tenancy boundaries and AppRole-scoped policies for machine authentication, not just a flat key-value store.
- Fit with what AWS Secrets Manager already covers, so the decision stays scoped to what OpenBao actually needs to do, not a rewrite of the platform’s whole secrets architecture.
Considered Options
Option 1: OpenBao
The Linux Foundation fork of the last MPL-2.0 Vault codebase. Provisioned
as two stacks: opentofu/openbao/cluster/ stands up the Raft-backed EC2
fleet, and opentofu/openbao/management/ layers namespaces, the PKI and
AppRole auth on top through the hashicorp/vault OpenTofu provider.
Pros:
- Stays under a permissive open-source licence with neutral governance, instead of a vendor-controlled source-available one.
- API-compatible enough that the existing
hashicorp/vaultOpenTofu provider configures it unmodified (opentofu/openbao/management/versions.tf) — the Terraform/OpenTofu code written against Vault’s API needed no rewrite to target OpenBao. - Same
bao/vaultCLI surface and Raft storage backend, so the namespace, AppRole and PKI model this platform depends on ported directly.
Cons:
- Smaller ecosystem than Vault: fewer third-party integrations, less community documentation, fewer people to ask when something breaks.
- Younger project still finding its own operational rough edges — see Consequences.
Option 2: Stay on Vault under the BUSL
Keep HashiCorp Vault, accepting the relicensing rather than migrating.
Pros:
- Larger ecosystem, more mature tooling, and official commercial support available if the platform ever needed it.
- No migration effort at all — this is the status quo, and the existing Terraform code already targets Vault’s real API.
Cons:
- Runs the exact licence risk this decision exists to avoid: a source-available dependency whose commercial terms HashiCorp controls unilaterally, on a component this platform would depend on indefinitely.
- Undercuts the point of a reference platform — someone cloning this repo to learn from or build on shouldn’t have to first evaluate whether running Vault this way trips the BUSL’s competing-offering clause.
- The “no migration” advantage is smaller than it looks: OpenBao’s API compatibility with Vault is precisely why Option 1 is not a rewrite either, so staying on Vault buys almost nothing beyond avoiding the switch itself.
Option 3: AWS Secrets Manager + AWS Private CA only
Drop the self-hosted secrets engine entirely: keep using AWS Secrets
Manager for what External Secrets Operator already pulls from it, and
move the private PKI to AWS Private CA (ACM PCA) instead of OpenBao’s
pki_private_issuer mount. Not a strawman — the platform already runs
half of this: ClusterSecretStore reads from AWS Secrets Manager today,
and OpenBao’s own bootstrap secrets live there too.
Pros:
- Nothing to operate: no Raft cluster, no
bao operator init/unseal choreography, no EC2 fleet to patch. - Fully IAM-native — AWS Private CA issuance and Secrets Manager reads would both be authorised the same way every other AWS-managed dependency on this platform is, via EKS Pod Identity.
Cons:
- Recurring per-CA, per-certificate cost for AWS Private CA, in place of a cost this platform currently pays once, as EC2 capacity, regardless of how many certificates it issues.
- Loses the namespace-scoped, AppRole-authenticated tenancy model —
appas a worked example of a self-service tenant secrets mount has no equivalent shape in Secrets Manager, which is a flat, IAM-policy-scoped store, not a namespaced one. - Removes the one self-hosted PKI/secrets-engine reference implementation this platform demonstrates, in favour of a fully managed AWS dependency — a legitimate choice for a production platform, but a narrower lesson than a reference repo aims to teach.
Decision Outcome
Chosen option: “Option 1 — OpenBao”
Rationale: The trigger is licence, not technical superiority. Option 1 wins over Option 2 because a reference platform anyone can clone and run should not carry a source-available dependency whose terms one vendor can change, and OpenBao removes that risk for essentially no migration cost — the same Terraform code, the same provider, the same CLI. Option 3 is a real, partially-adopted alternative: this platform already uses AWS Secrets Manager for everything External Secrets delivers. But going further and dropping the self-hosted PKI and namespace model would trade a one-time infrastructure cost for a recurring per-certificate one, and would remove the self-hosted-secrets pattern this repository exists to demonstrate.
Consequences
Positive
- Secrets management and the private PKI stay on a permissively-licensed, neutrally-governed codebase, with no single vendor able to change the commercial terms this platform depends on.
- The
hashicorp/vaultOpenTofu provider configures OpenBao unmodified, so everyvault_*resource inopentofu/openbao/management/needed no rewrite to target it. - OpenBao’s role stays narrow and complementary rather than duplicating
AWS Secrets Manager: the private PKI and namespace-scoped AppRole
machine auth live in OpenBao, while External Secrets Operator’s
ClusterSecretStorekeeps delivering bulk application secrets from AWS Secrets Manager.
Negative
- Smaller ecosystem than Vault. Upstream integrations, Helm charts
and documentation across the wider ecosystem still assume Vault by
default; OpenBao-specific gaps get filled by this platform’s own docs
rather than found ready-made upstream.
- Mitigation: none automated; OpenBao’s API compatibility with Vault means most Vault-oriented troubleshooting knowledge still transfers even where the tooling itself doesn’t reference OpenBao by name.
- The 2.6 line carries a write-concurrency deadlock this platform hits
on every
tofu apply. openbao/openbao#3411 (inconsistent lock ordering between the core mounts lock and the namespace lock, still open upstream) wedges the core when the management stack writes namespaces, auth backends and mounts concurrently on a small node —bao statusthen hangs even against127.0.0.1, which is a core deadlock, never a VPN problem.- Mitigation:
-parallelism=1on the management stack’sapplyanddestroyinopentofu/openbao/management/workflows.tm.hcl, marked in-code as load-bearing rather than caution. This serialises the stack’s own writes; it is not a version pin, and it does not require staying off the 2.6 line.
- Mitigation:
- The root CA private key lives in the live
pki_private_issuermount, not offline.vault_pki_secret_backend_root_sign_intermediatesigns the intermediate’s CSR inside OpenBao so the deploy stays unattended — keeping the root offline would mean the CSR leaving OpenBao, getting signed elsewhere, and coming back, a manual stepterramate script run deploycannot perform. Accepted here because this is a reference platform; explicitly not acceptable for a deployment where the root CA matters. See PKI & Secrets.- Mitigation: none beyond documenting the trade-off; a deployment where the root CA’s confidentiality matters needs an offline root, which means giving up the unattended-deploy property this platform trades for it.
Neutral
- AWS Secrets Manager still holds OpenBao’s own bootstrap material — the root token, the recovery keys, the operator password, and every minted AppRole credential. This is not an incomplete migration to OpenBao; a still-sealed cluster cannot be where its own unseal material lives, so the two stores were always going to coexist for that specific material.
- The root namespace hosts every shared platform service — the PKI mount
and every platform-level AppRole — while
appis the only tenant namespace defined today, holding an otherwise-unconsumedsecret/mount. Namespaces are a tenancy boundary this platform has built but not yet exercised beyond the one worked example.
Implementation Notes
opentofu/openbao/cluster/ provisions the Raft-backed EC2 fleet and pins
the OpenBao release (openbao_version in variables.tf, currently on
the 2.6 line); opentofu/openbao/management/ layers namespaces, the PKI,
AppRole auth and policies on top through the hashicorp/vault provider,
and is the stack the -parallelism=1 mitigation applies to. The provider
name is unchanged from the Vault-era code —
required_providers { vault = { source = "hashicorp/vault" } } — because
OpenBao’s HTTP API, not a HashiCorp-specific product name, is what the
provider actually targets.
Operator access, namespace layout and the AppRole machine-auth pattern are documented in full on OpenBao.
References
- OpenBao — namespace layout, operator login, AppRole machine auth, backup/restore, and the 2.6.x concurrency constraint this record’s Negative section draws from
- PKI & Secrets — the three-tier certificate chain and the root-CA-in-live-mount trade-off
- CLAUDE.md — the OpenBao command reference and namespace-layout summary under “OpenBao”
opentofu/openbao/management/versions.tf— thehashicorp/vaultprovider pin that configures OpenBaoopentofu/openbao/cluster/variables.tf— theopenbao_versionpin and the in-code explanation of the 2.6.x deadlockopentofu/openbao/management/workflows.tm.hcl— the-parallelism=1mitigation and its reproduction notessecurity/base/external-secrets/clustersecretstore.yaml— the AWS Secrets ManagerClusterSecretStorethat External Secrets Operator reads from, alongside OpenBao- openbao/openbao#3411
— the open upstream lock-ordering bug behind the
-parallelism=1workaround