Skip to content

This is the multi-page printable view of this section. .

Return to the regular view of this page.

Module: VAULT

Deploy an OpenBao secrets management cluster with Pigsty for centralized KMS and secrets storage for transparent encryption, object encryption, and applications.

OpenBao is an open-source key and secrets management system (MPL-2.0), forked by the community after HashiCorp Vault adopted the BSL license and hosted by the Linux Foundation. Its API is largely compatible with Vault. Pigsty’s VAULT module deploys an OpenBao cluster in the fixed vault group as the deployment’s KMS (key management service) and secrets store. Pigsty manages TLS, auto-unseal, the HA endpoint, backups, monitoring, and lifecycle.

Current status: Beta

VAULT is in Beta. Deployment, repeated execution, rolling restarts, standby read forwarding, VIP access, and snapshot backup/recovery have been validated on a three-member Ubuntu 24.04 cluster. Grafana display, alert behavior, and other operating systems still need validation. Automatic integration is not yet implemented for PGSQL pg_tde or MINIO KES; manual configuration is required. Test thoroughly in your own environment before production use, especially snapshot recovery.

VAULT follows the naming convention of the MINIO module and Silo engine: Pigsty uses the module name (vault group, vault_* parameters, vault.yml playbook, and files/pki/vault/ directory), while the OS uses the engine name (openbao.service, /etc/openbao/, and the bao CLI).


Capabilities

The VAULT module currently provides:

  • Topology determines mode: one member for labs, three for production, with no separate mode parameter. OpenBao’s built-in Raft storage requires neither ETCD nor an external database.
  • Auto-unseal: a 32-byte static seal key is generated on the admin node and distributed to members. Nodes unseal automatically after a restart, without manual key-share entry.
  • Safe initialization: only the member with the lowest vault_seq runs bao operator init, once, after probing every member. Scaling or rebuilding a member cannot accidentally bootstrap a second empty cluster.
  • TLS throughout: Pigsty CA signs server certificates, with TLS 1.3 as the minimum. Clients use https://vault.pigsty:8200.
  • HA endpoint: NODE’s L2 VIP and HAProxy provide TLS passthrough to the active member, switching automatically on failure.
  • Ready to use: secret/ (KV v2), transit/ (encryption as a service), and approle authentication are enabled by default.
  • Rolling updates: changes to configuration, certificates, the seal key, or the systemd unit restart members one at a time, waiting for each to rejoin before proceeding.
  • Daily snapshots: each member retrieves a Raft snapshot from the active member daily and retains seven locally. An initial snapshot is taken immediately after initialization and retrieved to the admin node.
  • Observability: Prometheus metrics over HTTPS, 14 recording rules, six alerts, and a Vault Overview dashboard. Service and backup logs go to VictoriaLogs.
  • Removal safeguards: vault_safeguard is enabled by default, like MYSQL’s mysql_safeguard: true. Removal never touches key material on the admin node.

Architecture

VAULT depends on NODE for node management, repositories, the shared CA, and L2 VIP, and on INFRA for VictoriaMetrics, VictoriaLogs, Grafana, and Alertmanager. It does not depend on ETCD or PGSQL. PGSQL (pg_tde transparent encryption) and MINIO (KES server-side encryption) are planned consumers.

flowchart LR
    subgraph CONSUMER [Consumers]
        app[Applications]
        tde["PGSQL pg_tde<br>(planned)"]
        kes["MINIO KES<br>(planned)"]
    end

    subgraph VAULT [VAULT]
        entry["vault.pigsty:8200<br>VIP + HAProxy"]
        bao["OpenBao × 3<br>Raft consensus"]
    end

    subgraph NODE [NODE]
        node["Repositories / CA / VIP"]
    end

    CONSUMER -->|HTTPS| VAULT -->|depends on| NODE
    entry --> bao

    style CONSUMER fill:#3E668F,stroke:#2d4a66,color:#fff
    style VAULT fill:#5B9CD5,stroke:#4178a8,color:#fff
    style NODE fill:#FCDB72,stroke:#d4b85e,color:#333

    style app fill:#2d4a66,stroke:#1e3347,color:#fff
    style tde fill:#2d4a66,stroke:#1e3347,color:#fff,stroke-dasharray: 5 5
    style kes fill:#2d4a66,stroke:#1e3347,color:#fff,stroke-dasharray: 5 5
    style entry fill:#4178a8,stroke:#2d5a7a,color:#fff
    style bao fill:#4178a8,stroke:#2d5a7a,color:#fff
    style node fill:#d4b85e,stroke:#b89a4a,color:#333

A Pigsty deployment needs only one VAULT cluster. Consumers access it through the same domain, with separate mounts, policies, and credentials for isolation.

The trust roots reside on the admin node: the seal key, root token, and recovery keys are stored in files/pki/vault/, alongside the Pigsty CA private key at files/pki/ca/ca.key. This follows Pigsty’s existing security model: backing up files/pki preserves all trust roots.


Components and Ports

ComponentPurposeEndpoint
OpenBao APIClient access, inter-member forwarding, Prometheus metrics<node-IP>:8200, 127.0.0.1:8200
OpenBao RaftRaft replication and elections<node-IP>:8201
HAProxy endpointHA access on the VIP, with TLS passthrough to the active member (VIP enabled only)<VIP>:8200
vault-backupDaily Raft snapshot script scheduled by cron/usr/local/bin/vault-backup
baoOpenBao CLI; environment set by /etc/profile.d/bao.shLocal node

OpenBao listens only on the node IP and loopback, not 0.0.0.0, leaving <VIP>:8200 for HAProxy. The client port is therefore always 8200 in both lab and production deployments.


Limitations

VAULT deliberately provides a small, fixed platform. Review these current limitations before use:

  • No automatic consumer integration yet: mounts, policies, and credentials for PGSQL pg_tde and MINIO KES require manual configuration. The prefixes tde/<pg_cluster> and kes/<minio_cluster> are reserved for them.
  • Static seal security boundary: the auto-unseal key resides on member nodes and cannot protect against a compromised VAULT or admin node. It protects leaked data disks, backups, and snapshots; see the FAQ.
  • Long-lived credentials: the root token remains on the admin node. Backup tokens last ten years, comparable to Pigsty certificates; no agent or renewal component is introduced.
  • No declarative application objects: there is no vault_apps parameter. Create application policies and AppRoles manually with bao.
  • Routine snapshots remain local: each member retains seven. The initial snapshot and the final snapshot before whole-cluster removal are retrieved automatically to the admin node. Other off-site replication is an operator responsibility, and recovery is manual.
  • No external Vault management: existing HashiCorp Vault deployments and managed KMS services are not adopted.

Platform Support

The Pigsty Infra repository provides openbao (currently 2.7.2, official native builds). It is included in the default infra-package alias and covers all supported platforms:

ArchitectureSupported Systems
x86_64EL 8/9/10、Debian 12/13、Ubuntu 22/24/26
aarch64EL 8/9/10、Debian 12/13、Ubuntu 22/24/26

Documentation

DocumentDescription
ConfigurationSingle-node and three-node topologies, VIP, DNS, key material, and filesystem layout
ParametersEight public parameters and fixed conventions
PlaybooksTasks, tags, and commands for vault.yml and vault-rm.yml
AdministrationCreate, scale, rebuild members, renew certificates, back up, restore, and remove
UsageKV, Transit, and AppRole through bao and the HTTP API
MonitoringDashboards, scraping, logs, and alerts
MetricsRecording rules and common raw metrics
FAQCluster sizing, auto-unseal, initialization guards, and troubleshooting

Quick Start

Define the vault group in the inventory, and resolve vault.pigsty to its member:

all:
  children:
    vault: { hosts: { 10.10.10.10: { vault_seq: 1 } }, vars: { vault_cluster: vault } }
  vars:
    node_etc_hosts: [ '10.10.10.10 vault.pigsty' ]   # All nodes access vault through this domain

After provisioning NODE, run vault.yml:

./node.yml  -t node_hosts          # Write vault.pigsty into /etc/hosts on all nodes
./vault.yml -l vault               # Deploy the vault cluster (or use bin/vault-add)

deploy.yml automatically deploys a predefined vault group, so a separate vault.yml run is unnecessary.

After deployment, check cluster status as root on a member:

sudo -i bao status                 # Healthy: Initialized=true, Sealed=false

The root token is stored on the admin node in files/pki/vault/init.json. See Usage to get started; open Vault Overview in Grafana to inspect the cluster.

1 - Configuration

Choose a single-node or three-node VAULT topology and configure the HA VIP endpoint and service domain.

Before deploying VAULT, define a group named vault in the inventory. There is no profile or mode parameter: the mode is derived entirely from the number of members in vault.

  • Single node: no HA; suitable for development, testing, demos, and sandboxes.
  • Three nodes: tolerates one member failure and provides HA access with an L2 VIP; suitable for production.
  • Two nodes: functional but has no fault tolerance; treat it as a single-node setup.
MembersQuorumTolerated FailuresModeService Endpoint
110Labvault.pigsty points to the member
220WarningAny member failure loses quorum
321Productionvault.pigsty points to the VIP; HAProxy forwards to the active member

VAULT uses OpenBao’s built-in Raft storage. Only one active member handles writes at a time; the remaining standby members forward requests to it.


Single Node

A single-node VAULT instance needs only one line of configuration:

vault: { hosts: { 10.10.10.10: { vault_seq: 1 } }, vars: { vault_cluster: vault } }

Besides the IP address, the only required parameter is vault_seq. It determines the instance name vault-<seq> and which member performs initial cluster initialization: the one with the lowest vault_seq.

Single-node VAULT usually shares a machine with INFRA, ETCD, and PGSQL, so keys and data are not separated. Obtaining its disk exposes both encrypted data and the key. This is suitable for development and demos, but should not supply transparent-encryption keys in production.


Three Nodes

Three members tolerate one failure. Enable NODE’s L2 VIP on the vault group in production so the service endpoint is also highly available:

vault:
  hosts:
    10.10.10.11: { vault_seq: 1 }   # Required identity parameter; instance name vault-1
    10.10.10.12: { vault_seq: 2 }   # Nonnegative integer, unique within the group
    10.10.10.13: { vault_seq: 3 }   # Keep immutable; the lowest seq performs initial initialization
  vars:
    vault_cluster: vault            # Cluster name for monitoring labels and instance prefixes
    node_cluster: vault             # Same node cluster name ensures consistent VRRP authentication
    vip_enabled: true               # Enable an L2 VIP for the vault node cluster
    vip_vrid: 129                   # VRRP router ID, 1-254; unique on the L2 network
    vip_address: 10.10.10.8         # VIP address; vault.pigsty should resolve here

all:
  vars:
    node_etc_hosts: [ '10.10.10.8 vault.pigsty' ]   # All nodes access vault through the VIP

The resulting topology is:

                  vault.pigsty -> 10.10.10.8 (VIP, node_etc_hosts)
                                   |
        +--------------------------+--------------------------+
        |    keepalived VIP + haproxy :8200 (check /v1/sys/health == 200)
   vault-1 10.10.10.11      vault-2 10.10.10.12      vault-3 10.10.10.13
   openbao :8200/:8201      openbao :8200/:8201      openbao :8200/:8201
        +------------- Raft (quorum 2, tolerates 1 failure) -----------+

When vip_enabled is true and there is more than one member, vault.yml generates /etc/haproxy/conf.d/vault.cfg on every member:

  • Binds <VIP>:8200 in TCP mode with TLS passthrough. Clients and OpenBao still perform certificate validation.
  • Checks GET /v1/sys/health: active returns 200, standby 429, sealed 503, and uninitialized 501. Only a member returning 200 receives traffic.
  • Health checks also use HTTPS and validate member certificates with /etc/pki/ca.crt.
  • Members without the VIP must also bind <VIP>:8200, requiring net.ipv4.ip_nonlocal_bind=1. NODE’s tuned profiles set this. With node_tune: none, set it yourself or the playbook stops here.

With the VIP enabled, server certificate SANs include its address. Members must share the same node_cluster; otherwise NODE derives different VRRP authentication values and VIP failover does not work.

Three nodes without a VIP

A three-member cluster can run without a VIP. Resolve vault.pigsty to one member; a standby forwards requests to the active member. The cluster still tolerates one member failure, but the member named in DNS becomes a single point of failure for access.

HAProxy configuration cleared

Rerunning node.yml with haproxy_clean=true on vault nodes clears /etc/haproxy/conf.d/. Run ./vault.yml -l vault -t vault_haproxy to regenerate the VAULT endpoint.


Two Nodes

A two-member Raft cluster requires a quorum of two and tolerates no failures. Either member failing makes the whole cluster unavailable, potentially reducing availability below that of a single node.

VAULT permits two-member deployments but prints a warning. Treat this as a temporary state during scaling, not a permanent topology.

Raft supports larger clusters, but VAULT is designed and validated for one or three members.


Service Domain

All clients should use vault_domain, defaulting to vault.pigsty. The full address is:

https://vault.pigsty:8200

Consumers such as pg_tde persist the KMS endpoint in their metadata. Using a domain allows migration from one to three members, or a change of VIP, by updating /etc/hosts without changing every consumer’s configuration.

NODE’s node_etc_hosts writes the mapping to each node’s /etc/hosts, without depending on INFRA DNS. Databases can therefore locate KMS at startup even if INFRA is unavailable.

./node.yml -t node_hosts           # Refresh /etc/hosts on all nodes after changing node_etc_hosts

Pigsty CA signs the server certificates. Every managed node has /etc/pki/ca.crt, which clients can use to verify VAULT’s identity.


Placement

In production, deploy VAULT on dedicated nodes or alongside control components such as ETCD and INFRA. Do not co-locate it with PostgreSQL or Silo data nodes that use its keys. Transparent encryption depends on separating keys from data: a leaked data disk, database backup, or object store should not expose its key in the same place.

VAULT needs few resources. Low disk latency matters most because each Raft log commit must be persisted.


Key Material

All VAULT trust roots are stored on the admin node in files/pki/vault/, alongside Pigsty CA. files/pki/ is ignored by Git by default:

FileContentsNotes
unseal.key32-byte static seal keyGenerated on first deployment and never overwritten; used by every member for auto-unseal
init.jsonRoot token and five recovery shares, threshold threeOutput of bao operator init; the root of administrative access
backup.tokenToken with read-only snapshot accessUsed by vault-backup; valid for ten years
backup/*.snapInitial snapshot after initialization and final snapshot before whole-cluster removalMode 0600
vault-<seq>.crt / .keyMember server certificates and private keysSigned by Pigsty CA; validity set by cert_validity
Back up files/pki

A Raft snapshot and its matching unseal.key can recover the encrypted store. The root token and recovery keys in init.json are needed to regain administrative access afterward. Losing unseal.key makes all snapshots unreadable. Keep files/pki/ and snapshots safely off-site.

Each member holds a copy of unseal.key. Before distributing it, Pigsty compares checksums. If an existing member key differs from the admin node’s key, the playbook stops immediately to avoid wrapping data with the wrong key.


Filesystem Layout

VAULT creates the following files and directories on each member:

PathPurposePermissions
/etc/openbao/Configuration directory0750, root:openbao
/etc/openbao/openbao.hclMain OpenBao configuration0640, root:openbao
/etc/openbao/tls/CA certificate, server certificate, and private keyDirectory 0750, files 0640, root:openbao
/etc/openbao/unseal.keyLocal copy of the static seal key0640, root:openbao
/etc/openbao/backup.tokenLocal copy of the backup token0600, root:root
/data/vault/raft/Raft data directory (configurable)0700, openbao:openbao
/data/vault/backup/Seven local snapshots and the .backup.lock concurrency lock0700, root:root
/var/log/openbao/audit.log, rotated daily with seven retained files0750, openbao:openbao
/etc/systemd/system/openbao.servicesystemd service definition0644, root:root
/etc/profile.d/bao.shbao client environment variables0644, root:root
/etc/logrotate.d/openbaoAudit log rotation0644, root:root
/usr/local/bin/vault-backupSnapshot script0755, root:root
/etc/cron.d/vault-backupDaily snapshot schedule0644, root:root
/var/lib/node_exporter/textfile/vault.promLatest successful snapshot timestamp and sizeCollected by node_exporter
/etc/haproxy/conf.d/vault.cfgHAProxy VIP endpoint (VIP enabled only)0644, root:root

On INFRA nodes, /infra/targets/vault/<vault_cluster>-<vault_seq>.yml defines the member’s monitoring scrape target.

2 - Parameters

VAULT exposes eight parameters; other conventions are fixed or derived from the topology.

VAULT provides eight parameters in two groups:

  • VAULT: five parameters for deployment and configuration.
  • VAULT_REMOVE: three parameters controlling cluster removal.

VAULT deliberately exposes few settings. Member count determines cluster mode, HA access reuses NODE’s VIP parameters, and other ports, directories, engines, and backup policies follow fixed conventions.


Parameter Overview

The VAULT group configures instance identity, cluster name, data directory, port, and service domain.

ParameterTypeLevelDescription
vault_seqintIRequired VAULT instance identifier
vault_clusterstringCCluster name, default vault
vault_datapathCData directory, default /data/vault
vault_portportGAPI port, default 8200
vault_domainstringGService domain, default vault.pigsty

The VAULT_REMOVE group controls removal safeguards, data cleanup, and package removal.

ParameterTypeLevelDescription
vault_safeguardboolG/C/ARemoval safeguard, default true, blocks removal
vault_rm_databoolG/C/ADelete VAULT data during removal? Default true
vault_rm_pkgboolG/C/AUninstall openbao during removal? Default false

VAULT

This section lists parameters for the vault role, used by the vault.yml playbook.

They are defined in roles/vault/defaults/main.yml.

#vault_seq: 1                     # Required vault instance identifier
vault_cluster: vault              # Cluster name, default vault
vault_data: /data/vault           # Data directory for Raft storage and local snapshots
vault_port: 8200                  # API port; Raft cluster port is this value +1
vault_domain: vault.pigsty        # Service domain for certificate SANs and all clients

vault_seq

Name: vault_seq, type: int, level: I

Required VAULT instance number. Assign every member a nonnegative integer unique within the group.

The instance name is <vault_cluster>-<vault_seq>, for example vault-1. It is also the Raft node ID, server certificate CN, and monitoring label ins.

The member with the lowest vault_seq performs initial cluster initialization. Keep instance numbers immutable once assigned; do not reuse them for other nodes.

vault:
  hosts:
    10.10.10.11: { vault_seq: 1 }  # vault-1 performs initial initialization
    10.10.10.12: { vault_seq: 2 }  # vault-2
    10.10.10.13: { vault_seq: 3 }  # vault-3

The playbook verifies that every vault_seq is a nonnegative integer unique in the vault group, stopping immediately on failure.

vault_cluster

Name: vault_cluster, type: string, level: C

VAULT cluster name, default vault.

Used as the instance name prefix, OpenBao cluster_name, and monitoring label cls, and in generating the seal key ID.

The Ansible group name is hardcoded as vault, independently of vault_cluster. Each Pigsty deployment has one VAULT cluster; this parameter usually needs no change.

vault_data

Name: vault_data, type: path, level: C

VAULT data directory, default /data/vault.

Contains raft/ for Raft storage and backup/ for local snapshots.

Use a dedicated absolute path at least two levels deep, such as /data/vault, with no .. components. Removal deletes the entire directory; never point it at a shared parent such as /var/lib.

vault_port

Name: vault_port, type: port, level: G

VAULT API port, default 8200.

The Raft cluster port is fixed at vault_port + 1, default 8201. This follows OpenBao’s convention and has no separate parameter.

Clients, HAProxy, and monitoring all use this port. Do not change it after creating the cluster.

vault_domain

Name: vault_domain, type: string, level: G

VAULT service domain, default vault.pigsty.

Included in every member’s server certificate SAN. All clients should use https://<vault_domain>:<vault_port>.

Pigsty does not resolve this name automatically. Use node_etc_hosts to point it to the single member or the three-member cluster’s VIP; see Service Domain.


VAULT_REMOVE

This section lists parameters for the vault_remove role, used by the vault-rm.yml playbook.

They are defined in roles/vault_remove/defaults/main.yml.

vault_safeguard: true             # Removal safeguard, default true
vault_rm_data: true               # Delete vault data and configuration? Default true
vault_rm_pkg: false               # Uninstall openbao? Default false

vault_safeguard

Name: vault_safeguard, type: bool, level: G/C/A

VAULT removal safeguard, default true. When enabled, vault-rm.yml aborts before taking any action.

Like MYSQL’s mysql_safeguard, this safeguard is enabled by default. Deleting VAULT destroys all keys it stores: databases, objects, and all their backups encrypted with those keys become permanently unreadable.

To remove it, explicitly override the parameter on the command line:

./vault-rm.yml -l <exact-target> -e vault_safeguard=false   # Override only after verifying the target and snapshot

vault_rm_data

Name: vault_rm_data, type: bool, level: G/C/A

Delete VAULT data and configuration during removal? Default true.

Deletes /etc/openbao on members, including local copies of TLS certificates, the seal key, and backup token; the vault_data directory, including local snapshots; the systemd unit, client environment, log rotation configuration, and vault-backup script.

When removing the whole cluster with this enabled, the playbook first takes a final snapshot and retrieves it to files/pki/vault/backup/ on the admin node. Failure aborts removal.

Regardless of this setting, files/pki/vault/ on the admin node and /var/log/openbao/ audit logs on members are never deleted.

./vault-rm.yml -l 10.10.10.13 -e vault_safeguard=false -e vault_rm_data=false  # Stop service while retaining data

vault_rm_pkg

Name: vault_rm_pkg, type: bool, level: G/C/A

Uninstall openbao during removal? Default false.

./vault-rm.yml -l vault -e vault_safeguard=false -e vault_rm_pkg=true  # Also uninstall the package

VAULT reuses existing parameters for HA access and domain resolution:

ParameterModuleRole in VAULT
vip_enabledNODEEnable an L2 VIP, HAProxy endpoint, and VIP certificate SAN for the vault cluster
vip_addressNODEVIP address to which vault.pigsty should resolve
vip_vridNODEVRRP router ID, unique on the L2 network
node_clusterNODEUse the same node cluster name for consistent VRRP authentication
node_etc_hostsNODEWrite vault_domain into /etc/hosts on all nodes
haproxy_enabledNODEWhen false, no VAULT HAProxy endpoint is generated
cert_validityINFRAServer certificate validity, default 20 years

Fixed Conventions

The following conventions have no configurable parameters:

ItemConvention
Group / instance nameFixed group vault; instance <vault_cluster>-<vault_seq>
Cluster modeDerived from member count: one for labs, three for production
PortsAPI vault_port (8200), Raft vault_port + 1 (8201)
Service / user / CLIopenbao.service / openbao / bao, all from the upstream package
VersionDetermined by openbao in the Pigsty Infra repository
Seal methodStatic seal; key at files/pki/vault/unseal.key on the admin node
InitializationFive recovery shares, threshold three
Default enginessecret/ (KV v2), transit/, auth/approle
TokensMaximum lease for token/ auth: ten years (87600h); backup token validity: ten years
AuditFile device at /var/log/openbao/audit.log; daily rotation, seven retained files
BackupDaily at 03:00 with minute (vault_seq × 10) % 60 to stagger jobs; seven local snapshots
TLSPigsty CA certificates, TLS 1.3 minimum
TelemetryUnauthenticated /v1/sys/metrics on the node IP listener; disabled on loopback

3 - Playbooks

Manage VAULT with predefined Ansible playbooks and common administration commands.

VAULT provides two playbooks: vault.yml creates, updates, and scales clusters, while vault-rm.yml removes members or the whole cluster. The helper scripts bin/vault-add and bin/vault-rm wrap these playbooks.


vault.yml

Source: vault.yml

Installs, configures, and starts OpenBao in the hardcoded vault group. It is idempotent: repeated runs reconcile configuration changes without reinitializing the cluster.

vault.yml provides these task subsets:

  • id: derive node identity and variables through node_id.
  • vault_assert: validate member IDs, data paths, ports, and VIP configuration; derive instance names and members (always runs).
  • vault_install: install openbao and ensure the openbao system user exists.
  • vault_dir: create configuration, data, snapshot, and audit log directories.
  • vault_config: generate VAULT configuration.
    • vault_cert: issue and distribute TLS certificates.
      • vault_cert_issue: issue server certificates with Pigsty CA on the admin node.
      • vault_cert_copy: copy the CA certificate, server certificate, and private key to members.
    • vault_seal: generate and distribute the static seal key; abort if an existing member key differs from the admin node’s key.
    • vault_conf: render openbao.hcl, the systemd unit, bao client environment, and log rotation configuration.
  • vault_launch: start stopped members. Members already running before execution restart one at a time only if their certificates, seal key, configuration, or unit changed, waiting for each to rejoin. Newly started members skip a second restart to avoid startup races.
  • vault_init: initialize once on the member with the lowest vault_seq, then wait for all members to unseal and complete Raft election (leader_address nonempty).
  • vault_provision: enable secret/, transit/, and approle; issue the backup token, reissuing it automatically if an existing token is invalid, for example from an old cluster.
  • vault_haproxy: generate the <VIP>:8200 HAProxy endpoint on members when VIP is enabled; otherwise remove it.
  • vault_backup: install vault-backup, its daily schedule, and take the first snapshot.
  • vault_register (aliases register, add_metrics): register members with VictoriaMetrics.

vault_init runs bao operator init only when all these conditions hold:

  1. The current member has the lowest vault_seq in vault.
  2. Every member is reachable and reports that it is uninitialized.
  3. files/pki/vault/init.json does not exist on the admin node.

Using -l <ip> to add or rebuild a member therefore cannot bootstrap a second empty cluster. The new member joins through Raft retry_join and unseals with the same key. The playbook aborts if an unreachable member prevents determining initialization state. It also aborts if all members are uninitialized but init.json exists, indicating a previously initialized cluster whose data was lost. See Restore a Snapshot.

The generated root token and recovery keys are saved to files/pki/vault/init.json on the admin node. If saving fails, they remain temporarily in /root/vault-init.json on the member and the playbook stops; move them to the admin node manually.


vault-rm.yml

Source: vault-rm.yml

Removes members or the whole cluster. vault-rm.yml provides these task subsets:

  • vault_safeguard: check the removal safeguard, enabled by default; abort if enabled.
  • vault_consumer: before whole-cluster removal, list tde/* and kes/* consumers whose data will become unreadable.
  • vault_pause: pause five seconds to allow cancellation with Ctrl-C.
  • vault_snapshot: before whole-cluster removal, take a final snapshot and retrieve it to files/pki/vault/backup/ on the admin node; abort on failure without deleting data.
  • vault_leave: for partial removal, run bao operator raft remove-peer on a surviving member; abort on failure without deleting data.
  • vault_deregister (aliases deregister, rm_metrics): remove members from VictoriaMetrics targets.
  • vault_svc: remove the backup cron job and metrics, stop and disable openbao, and remove the HAProxy endpoint.
  • vault_data: delete data, configuration, local TLS certificates, seal key and backup token copies, and local snapshots; disable with vault_rm_data=false.
  • vault_pkg: uninstall openbao; explicitly enable with vault_rm_pkg=true.

The playbook uses vault_remove and supports:

If the run targets every member of vault, it is treated as whole-cluster removal. Otherwise, it is partial removal and the selected members must first leave Raft. Every selected member must be reachable before removal begins; an unreachable member aborts the playbook. Peer removal uses the root token in the admin node’s init.json, so members being removed must remain in the inventory’s vault group during execution.

Destructive operation

Removing the whole VAULT cluster destroys all its keys. Databases, objects, and all their backups encrypted with them become permanently unreadable unless VAULT can be restored from a snapshot and files/pki/vault/unseal.key. vault_rm_data defaults to true, so local snapshots are deleted with member data. The playbook first retrieves a final snapshot to the admin node. Verify that files/pki/vault/ is intact before proceeding. A broken or never-initialized cluster cannot produce a snapshot; removing it requires --skip-tags vault_snapshot.

The admin node’s files/pki/vault/ (seal key, initialization material, initial and final snapshots) and members’ /var/log/openbao/ audit logs are never deleted.


Command Reference

Installation and configuration:

./vault.yml -l vault                              # Create or update the vault cluster
./vault.yml -l vault --check                      # Preview proposed changes
./vault.yml -l vault -t vault_conf,vault_launch   # Refresh configuration; restart members sequentially as needed
./vault.yml -l vault -t vault_cert,vault_launch   # Reissue/distribute certificates; restart sequentially as needed
./vault.yml -l vault -t vault_haproxy             # Refresh the HAProxy VIP endpoint
./vault.yml -l vault -t vault_backup              # Refresh snapshot script, schedule, and backup token
./vault.yml -l vault -t vault_register            # Register monitoring targets again
./vault.yml -l 10.10.10.14                        # Add a new member to the existing cluster

Removal and cleanup:

./vault-rm.yml -l 10.10.10.13 -e vault_safeguard=false                       # Remove peer, stop service, delete local data
./vault-rm.yml -l 10.10.10.13 -e vault_safeguard=false -e vault_rm_data=false # Remove peer and stop service, retain local data
./vault-rm.yml -l vault -e vault_safeguard=false                             # Destroy the whole cluster
./vault-rm.yml -l vault -e vault_safeguard=false -e vault_rm_pkg=true        # Destroy cluster and uninstall package

Helper Scripts

bin/vault-add                  # Create/update the whole cluster; equivalent to ./vault.yml -l vault
bin/vault-add 10.10.10.14      # Add members (multiple IPs accepted)
bin/vault-rm                   # Destroy the whole cluster; type vault to confirm
bin/vault-rm 10.10.10.13       # Remove specified members (multiple IPs accepted)

Both scripts accept only full IPv4 addresses, provide a countdown, and suggest follow-up actions. For example, after adding or removing members, they prompt you to refresh the remaining members’ configuration and HAProxy endpoints:

./vault.yml -l 'vault,!10.10.10.14' -t vault_config,vault_launch,vault_haproxy,vault_backup

Safeguards

VAULT includes safeguards for deployment and removal:

SafeguardPurpose
Initialization guardInitialize only if all members are reachable and uninitialized and init.json is absent; prevent a second empty cluster
Seal key validationAbort if a member’s existing key differs from the admin node’s key
Sequential restartWith a leader present, wait for each member to rejoin before the next; without a leader, start the other members first
Removal safeguardvault_safeguard defaults to true; requires an explicit command-line override
Consumer warningList tde/* and kes/* consumers before whole-cluster removal
Abort on peer removal failureStop if raft remove-peer fails, preventing stale members from silently reducing fault tolerance
Preserve trust rootsNever delete files/pki/vault/ on the admin node; retrieve the final snapshot there

Regardless of safeguard settings, recheck bao status, Raft membership, and fault tolerance after a real run. Playbook success does not replace runtime validation.

4 - Administration

VAULT administration procedures for creation, status checks, scaling, rebuilding, configuration, certificates, backup, recovery, upgrades, and removal.

Common VAULT administration procedures:

See the VAULT FAQ for other questions.


Create a Cluster

Define the vault group in the inventory, and resolve its service domain with node_etc_hosts. See Configuration for details:

vault:
  hosts:
    10.10.10.11: { vault_seq: 1 }
    10.10.10.12: { vault_seq: 2 }
    10.10.10.13: { vault_seq: 3 }
  vars: { vault_cluster: vault, node_cluster: vault, vip_enabled: true, vip_vrid: 129, vip_address: 10.10.10.8 }

Provision the nodes through NODE, including the VIP, then run vault.yml:

./node.yml  -l vault               # Provision nodes and configure the L2 VIP
./node.yml  -t node_hosts          # Write vault.pigsty into /etc/hosts on all nodes
./vault.yml -l vault               # Create the cluster; bin/vault-add is also available

The playbook creates the seal key, initialization material, backup token, and initial snapshot in files/pki/vault/ on the admin node. Immediately back up the entire files/pki/ directory to a secure location.


Check Status

/etc/profile.d/bao.sh sets the bao environment: BAO_ADDR points to local https://127.0.0.1:8200, and BAO_CACERT to /etc/openbao/tls/ca.crt. Only root and the openbao group can read the TLS directory, so run bao as root on a member:

sudo -i                            # Switch to root and load /etc/profile.d/bao.sh
bao status                         # No token needed: initialization, seal, HA role, and Raft indexes

A healthy standby member produces output similar to:

Key                      Value
---                      -----
Seal Type                static
Recovery Seal Type       shamir
Initialized              true
Sealed                   false
Total Recovery Shares    5
Threshold                3
Version                  2.7.2
Storage Type             raft
Cluster Name             vault
HA Enabled               true
HA Mode                  standby
Active Node Address      https://10.10.10.13:8200
Raft Committed Index     1150
Raft Applied Index       1150

To run as a regular user, use the globally readable Pigsty CA: export BAO_CACERT=/etc/pki/ca.crt.

Raft membership and Autopilot health checks require an administrator token; see Administrator Credentials:

bao operator raft list-peers                 # node_id, address, role, and voter status
bao operator raft autopilot state            # Cluster health, fault tolerance, and member health

Health endpoints require no token and can be accessed by service domain from any managed node:

curl -s --cacert /etc/pki/ca.crt https://vault.pigsty:8200/v1/sys/health   # Active returns 200
curl -s --cacert /etc/pki/ca.crt https://vault.pigsty:8200/v1/sys/leader   # Current active member address

Grafana’s Vault Overview shows members, roles, seal status, Raft replication lag, and snapshot times. See Monitoring.


Administrator Credentials

The root token and recovery keys generated during initialization are stored in files/pki/vault/init.json on the admin node:

jq -r .root_token ~/pigsty/files/pki/vault/init.json   # Read the root token on the admin node

On a member, enter the root token interactively into an environment variable to keep it out of command arguments and shell history:

sudo -i
read -rs BAO_TOKEN && export BAO_TOKEN      # Paste the root token and press Enter; input is hidden
bao operator raft list-peers

The root token has full privileges; use it briefly for administrative tasks only. Give applications policies and AppRoles with the minimum required privileges; see Usage.

In auto-unseal mode, the five recovery shares (threshold three) cannot unseal the service. They authorize quorum operations such as bao operator generate-root, for example to replace a lost root token.


Add Members

# First add the new member to the inventory's vault group with a unique vault_seq, then run:
bin/vault-add 10.10.10.14

The script runs ./vault.yml -l 10.10.10.14: it issues certificates, distributes the seal key, joins the existing cluster through Raft retry_join, unseals automatically, and registers monitoring. The new member does not have the lowest vault_seq, so it never initializes the cluster.

Then refresh existing members’ configuration, certificates, HAProxy endpoints, and backup scripts:

./vault.yml -l 'vault,!10.10.10.14' -t vault_config,vault_launch,vault_haproxy,vault_backup

If configuration or certificates change, vault_launch restarts existing members one at a time to apply the updates.

Scale from One to Three Nodes

When scaling to three production members, the service domain changes from pointing to one member to pointing to a VIP. Existing certificates must include the VIP:

  1. Add two members and configure node_cluster, vip_enabled, vip_vrid, and vip_address on the vault group.

  2. Provision nodes and the VIP with ./node.yml -l vault. Already managed nodes need only ./node.yml -l vault -t node_vip.

  3. Add members: bin/vault-add 10.10.10.12 10.10.10.13.

  4. Refresh the original member’s certificate, configuration, and endpoint. Certificate changes trigger its restart:

    ./vault.yml -l 10.10.10.11 -t vault_config,vault_launch,vault_haproxy,vault_backup
  5. Point vault.pigsty at the VIP in node_etc_hosts, then run ./node.yml -t node_hosts to update all nodes.

  6. Verify that bao operator raft list-peers shows three voters and /v1/sys/health returns 200 through the VIP.

Check whether a certificate includes the VIP with openssl x509 -in /etc/openbao/tls/server.crt -noout -ext subjectAltName as root on the member. If it does not, delete the corresponding files/pki/vault/vault-<seq>.crt on the admin node and repeat step 4.


Remove Members

bin/vault-rm 10.10.10.13          # Remove 10.10.10.13 from the cluster

The script runs ./vault-rm.yml -l 10.10.10.13 -e vault_safeguard=false: it executes bao operator raft remove-peer vault-3 on a survivor, deregisters monitoring, stops services, and deletes local data and configuration.

Keep the member in the inventory’s vault group during execution. The playbook needs the inventory to locate a surviving member for peer removal. If removal fails, for example due to an invalid administrator token, it aborts without deleting data.

After success, remove the member from the inventory and refresh the survivors’ configuration and HAProxy endpoints:

vi pigsty.yml                     # Remove 10.10.10.13 from vault
./vault.yml -l vault -t vault_config,vault_launch,vault_haproxy,vault_backup
Check fault tolerance

Removing a member from a three-node cluster leaves two members with no fault tolerance. Add a replacement promptly. In production, it is safer to add the replacement first, temporarily forming four members, then retire the old member.

Manual Steps

To preserve local data and snapshots on the removed member:

./vault-rm.yml -l 10.10.10.13 -e vault_safeguard=false -e vault_rm_data=false   # Remove peer and stop service; retain data

Rebuild a Member

For damaged data or hardware replacement at the same address, remove the member, then add it again. Keep it in the inventory throughout:

bin/vault-rm  10.10.10.13          # Remove from Raft, stop service, clean local data
bin/vault-add 10.10.10.13          # Rejoin with the same vault_seq

This applies only to multi-member clusters. Running bin/vault-rm against the sole member destroys the entire cluster; use Restore a Snapshot instead.

The rebuilt member synchronizes all data from the active member. Even if it has the lowest vault_seq, the playbook does not initialize again because the other members are already initialized.

If the host is completely unavailable and cannot run vault-rm.yml, remove it manually from a survivor using an administrator token, then add its replacement:

bao operator raft remove-peer vault-3      # Run as root on a survivor with BAO_TOKEN set

Update Configuration

The playbook generates VAULT configuration. Edit the inventory and rerun it instead of manually changing /etc/openbao/openbao.hcl.

./vault.yml -l vault -t vault_conf,vault_launch     # Refresh config; restart changed members sequentially

vault_launch first starts selected stopped services, then restarts previously running members whose certificates, seal key, configuration, or systemd unit changed. Newly started members already loaded the latest configuration and skip a second restart to avoid cold-start and initialization races. With a leader present, it waits for each member to rejoin before proceeding; without one, startup continues to allow quorum recovery. Check cluster health before updating. If an earlier run stopped after writing configuration, manually restart members that have not loaded it, one at a time.

Renew certificates: server certificates default to 20 years. To reissue them, for example after changing the VIP or domain, delete the corresponding certificates on the admin node and run:

rm -f files/pki/vault/vault-*.crt                   # Remove old certificates; retain private keys
./vault.yml -l vault -t vault_cert,vault_launch     # Reissue, distribute, and restart sequentially

Refresh HAProxy after enabling/disabling the VIP, changing membership, or running node.yml with haproxy_clean=true:

./vault.yml -l vault -t vault_haproxy

Backups

Daily snapshots: each member runs vault-backup at 03:00, staggered by vault_seq, retrieving a Raft snapshot from the active member into /data/vault/backup/ and retaining the latest seven. Thus every member of a three-node cluster holds complete snapshot copies.

The backup token can only read sys/storage/raft/snapshot. The script uses flock on ${BACKUP_DIR}/.backup.lock to serialize concurrent local jobs, such as scheduled and manual backups. Successful jobs write completion time and size into node_exporter’s textfile directory. No successful snapshot for 26 hours triggers VaultBackupStale.

vault-backup                       # Run as root; the last output line is the snapshot path
vault-backup 14                    # Take a snapshot and retain the latest 14
journalctl -t vault-backup         # Inspect scheduled backup logs

Initial and final snapshots: immediately after initialization, the playbook takes a snapshot and retrieves it to files/pki/vault/backup/ on the admin node. It also retrieves a final snapshot before destroying the cluster. Operators may copy other snapshots there; these files are not cleaned automatically.

If initial deployment stops before retrieval, a rerun detects the missing admin-node snapshot, takes one, and retrieves it.

Off-site copies: scheduled snapshots stay on members; operators are responsible for off-site replication. Snapshots are encrypted by OpenBao’s barrier key and cannot be opened without unseal.key, so they may be copied to other storage. Store files/pki/vault/ separately from snapshots. For example, retrieve a member’s latest snapshot from the admin node:

(umask 077; ssh 10.10.10.11 "sudo bash -c 'cat \$(ls -t /data/vault/backup/*.snap | head -1)'" \
  > files/pki/vault/backup/vault-$(date +%Y%m%d).snap)
Avoid circular dependencies

If object storage uses KES encryption and its keys reside in VAULT, do not keep VAULT’s only snapshots there: recovery would depend on the service being recovered.


Restore a Snapshot

Recovery requires a snapshot, its matching files/pki/vault/unseal.key, and the original files/pki/vault/init.json. The original root token and recovery keys become valid again after restoration.

Manual procedure

Snapshot recovery is destructive: keys, tokens, and leases created after the snapshot are lost. Rehearse in an isolated environment and take a fresh snapshot of the current state before proceeding.

Roll Back a Working Cluster

To roll a healthy cluster back to a snapshot, for example after accidental key deletion, run as root on the active member:

read -rs BAO_TOKEN && export BAO_TOKEN                          # Enter the root token
vault-backup                                                    # Snapshot the current state first
bao operator raft snapshot restore /data/vault/backup/vault-<timestamp>.snap

Raft replicates the restored state to the other members. Check bao status and application keys afterward.

Rebuild After Total Data Loss

If all members lose their data, vault.yml refuses to initialize: all members report uninitialized, but init.json on the admin node identifies a previously initialized cluster. Bootstrap a temporary empty cluster, then overwrite it with the snapshot:

  1. Prepare the snapshot and verify that files/pki/vault/unseal.key on the admin node is the key used when it was created.

  2. Archive the original initialization material; otherwise the playbook refuses initialization:

    mkdir -p files/pki/vault/orig
    mv files/pki/vault/init.json files/pki/vault/orig/
  3. Bootstrap the temporary cluster, generating a new init.json. Its old backup.token is invalid, so the playbook reissues it and backs up the old file as backup.token.*~:

    ./vault.yml -l vault
  4. Copy the snapshot to the temporary cluster’s active member (HA Mode: active in bao status), for example /tmp/restore.snap. Use the temporary cluster’s root token to force restoration; -force permits a snapshot from another cluster:

    sudo -i
    read -rs BAO_TOKEN && export BAO_TOKEN    # Enter the root token from the new init.json
    bao operator raft snapshot restore -force /tmp/restore.snap
  5. Wait for members to become healthy. In OpenBao 2.7.2 testing, a standby sometimes continued reporting decryption failed: cipher: message authentication failed after a forced cross-cluster restore. Verify its unseal.key matches the admin node, then restart only that member and wait for normal bao status before continuing. Do not restart all members together:

    ansible 10.10.10.13 -b -m systemd -a 'name=openbao state=restarted'   # Substitute the affected member's address
  6. After restoration, the original root token works again and temporary cluster credentials are invalid. Restore the original init.json, then run token provisioning and backup tasks. The playbook detects the invalid backup token and reissues/distributes it:

    mv -f files/pki/vault/orig/init.json files/pki/vault/
    ./vault.yml -l vault -t vault_provision,vault_backup
  7. Use the original root token to check bao operator raft list-peers and application keys. Run vault-backup manually on every member to verify backups work again.

Snapshots also preserve audit device configuration. After unsealing, OpenBao writes to /var/log/openbao/audit.log. An unwritable path causes post-unseal initialization to fail; check directory permissions. Do not disable auditing to bypass this problem.

If VAULT holds other components’ keys, restore in dependency order: VAULT → KES → Silo → pgBackRest → PostgreSQL.


Upgrade OpenBao

The Pigsty Infra repository provides openbao. Upgrades are manual; refresh the local repository and take a snapshot first:

  1. Run vault-backup on a member and copy the snapshot to a secure location.
  2. Upgrade standby members one at a time: update the package, run systemctl restart openbao, wait for bao status to show unsealed and Autopilot to report the member healthy, then proceed.
  3. Upgrade the active member last. Its restart triggers a new active election and the VIP endpoint switches accordingly.
ansible 10.10.10.12 -b -m package -a 'name=openbao state=latest'   # Upgrade one member's package
ansible 10.10.10.12 -b -m systemd -a 'name=openbao state=restarted'

Before a major upgrade, read the OpenBao release notes and upgrade guide.


Destroy a Cluster

Use vault-rm.yml or bin/vault-rm to destroy the whole cluster.

Destructive operation

Destroying VAULT destroys all its keys. Databases, objects, and all their backups encrypted with those keys become permanently unreadable. Confirm that no consumers still use VAULT and that the admin node’s files/pki/vault/ is intact. Local snapshots are deleted with member data; the playbook retrieves a final snapshot first.

bin/vault-rm                                        # Type vault to confirm
./vault-rm.yml -l vault -e vault_safeguard=false    # Equivalent command

Whole-cluster removal lists tde/* and kes/* consumers, pauses five seconds, takes a final snapshot on a member, and retrieves it to the admin node’s files/pki/vault/backup/ before deregistering monitoring, stopping services, and deleting data. It does not run raft remove-peer or delete the admin node’s files/pki/vault/ or members’ audit logs.

If the final snapshot fails, removal aborts before deleting anything. A broken or never-initialized cluster cannot produce a snapshot, so explicitly skip this step if necessary:

./vault-rm.yml -l vault -e vault_safeguard=false --skip-tags vault_snapshot   # Remove without a final snapshot

Create a new empty cluster: after removal, archive the old initialization material before creating a fresh empty cluster instead of restoring a snapshot; otherwise vault.yml refuses initialization:

mkdir -p files/pki/vault/archive-$(date +%Y%m%d)
mv files/pki/vault/init.json files/pki/vault/archive-$(date +%Y%m%d)/
./vault.yml -l vault

The old backup.token is invalid in the new cluster. The playbook reissues it and backs up the old file as backup.token.*~.

You may retain and reuse unseal.key. Old snapshots still require that key, so do not delete it.

5 - Usage

Use VAULT through bao and HTTP APIs for KV secrets, Transit encryption, and AppRole credentials.

After configuring and deploying VAULT, use this page to get started. The following engines and authentication methods are enabled by default:

PathTypePurpose
secret/KV v2 secrets engineVersioned static secrets such as passwords, API keys, and certificates
transit/Transit secrets engineEncryption as a service; applications send data for encryption/decryption while keys remain in VAULT
auth/approle/AppRole authenticationMachine credentials for applications and automation
auth/token/Token authenticationBuilt-in; maximum lease adjusted to ten years

OpenBao’s API is largely compatible with HashiCorp Vault, so common Vault client libraries and tools generally work directly. See the OpenBao documentation for full usage.


Endpoint

All clients should use the service domain over HTTPS and validate the server certificate with Pigsty CA:

ItemValue
Addresshttps://vault.pigsty:8200
CA certificate/etc/pki/ca.crt on managed nodes; files/pki/ca/ca.crt on the admin node
TLS versionTLS 1.3 minimum

In a three-member cluster, vault.pigsty points to the VIP and HAProxy forwards to the active member. Direct standby access also works; the standby forwards requests to the active member.

export BAO_ADDR=https://vault.pigsty:8200
export BAO_CACERT=/etc/pki/ca.crt

bao is installed with openbao on VAULT members. Other nodes can use curl with the HTTP API, or install openbao to obtain bao.


Administrator Login

Run these examples as root on a VAULT member with the root token. See Administrator Credentials:

sudo -i                                     # BAO_ADDR and BAO_CACERT are set by /etc/profile.d/bao.sh
read -rs BAO_TOKEN && export BAO_TOKEN      # Enter the root token; input is hidden
bao secrets list                            # List enabled secrets engines
bao auth list                               # List enabled authentication methods

Use the root token only for administration. Issue credentials with minimal privileges to applications.


KV Secrets

secret/ uses KV v2, which saves a new version on every write:

bao kv put secret/app/db username=dbuser_app password='S3cret!'   # Write a secret
bao kv get secret/app/db                                          # Read the latest version
bao kv get -field=password secret/app/db                          # Read one field
bao kv get -version=1 secret/app/db                               # Read a historical version
bao kv list secret/app                                            # List secrets under a path
bao kv metadata get secret/app/db                                 # Inspect version metadata
bao kv delete secret/app/db                                       # Soft-delete the latest version; recover with undelete

KV v2 policies and HTTP APIs use secret/data/<path> for secret reads/writes and secret/metadata/<path> for metadata.


Transit Encryption

transit/ provides encryption as a service. Applications send data for encryption or decryption without handling keys, suitable for encrypting sensitive database fields in the application layer.

bao write -f transit/keys/app                                    # Create the app encryption key
bao write transit/encrypt/app plaintext=$(echo -n 'hello' | base64)
# ciphertext    vault:v1:8SDd3WHDOjf7mq69CyCqYjBXAiQQAVZRkFM13ok481zoCmHnSeDX9vyf7w==
bao write -field=plaintext transit/decrypt/app \
  ciphertext='vault:v1:8SDd3WHDOjf7mq69CyCqYjBXAiQQAVZRkFM13ok481zoCmHnSeDX9vyf7w==' | base64 -d
bao write -f transit/keys/app/rotate                             # Rotate the key; old ciphertext remains readable

Base64-encode plaintext first. After rotation, new data uses the new key version, while older ciphertext remains decryptable. Use transit/rewrap/app to rewrap old ciphertext with the new version.


Issue Application Credentials

Create a policy with only the permissions each application needs, then issue machine credentials through AppRole:

# 1. Create a policy: read/write secret/app/ and use the app Transit key only
bao policy write app - <<'EOF'
path "secret/data/app/*"     { capabilities = ["create", "read", "update", "delete", "list"] }
path "secret/metadata/app/*" { capabilities = ["read", "list"] }
path "transit/encrypt/app"   { capabilities = ["update"] }
path "transit/decrypt/app"   { capabilities = ["update"] }
EOF

# 2. Create an AppRole whose login tokens use this policy
bao write auth/approle/role/app token_policies=app token_ttl=1h token_max_ttl=24h

# 3. Read role_id and secret_id and distribute them to the application
bao read -field=role_id auth/approle/role/app/role-id
bao write -f -field=secret_id auth/approle/role/app/secret-id

role_id acts as a username and secret_id as a password. Applications exchange them for a short-lived token, then use that token to access secrets:

bao write auth/approle/login role_id=<role_id> secret_id=<secret_id>

Deliver secret_id through a secure channel. Restrict credentials further with secret_id_ttl, secret_id_num_uses, and token_bound_cidrs as needed.

Through the VIP, HAProxy forwards in TCP mode, so OpenBao sees the VAULT member’s address rather than the original client. Audit logs record that member address, and token_bound_cidrs set to client networks will reject these requests.


HTTP API

Clients without bao can call the HTTP API directly. For example, log in with AppRole and read a secret:

CA=/etc/pki/ca.crt
URL=https://vault.pigsty:8200

# Log in with AppRole to obtain a client token
TOKEN=$(curl -s --cacert $CA -X POST $URL/v1/auth/approle/login \
  -d '{"role_id":"<role_id>","secret_id":"<secret_id>"}' | jq -r .auth.client_token)

# Read a KV v2 secret; note data/ in the path
curl -s --cacert $CA -H "X-Vault-Token: $TOKEN" $URL/v1/secret/data/app/db | jq .data.data

Unauthenticated endpoints support health checks and monitoring:

curl -s --cacert /etc/pki/ca.crt https://vault.pigsty:8200/v1/sys/health     # Health status
curl -s --cacert /etc/pki/ca.crt https://vault.pigsty:8200/v1/sys/leader     # Active member

Reserved Paths

These paths and names are used by Pigsty or reserved for future features; do not use them for applications:

Path / NamePurpose
tde/<pg_cluster>Reserved for PGSQL pg_tde keys, KV v2, one per PG cluster
kes/<minio_cluster>Reserved for MINIO KES keys, KV v2, one per object-storage cluster
tde-*, kes-* policiesReserved for those consumers
vault-backup policyBackup token; read-only access to sys/storage/raft/snapshot

Before whole-cluster removal, vault-rm.yml lists existing tde/* and kes/* mounts to identify consumers that will permanently lose keys.

6 - Monitoring

VAULT dashboards, metric collection, log queries, and alert rules.

VAULT reuses Pigsty’s observability stack. OpenBao metrics flow over HTTPS to VictoriaMetrics; service and backup logs flow through Journald / Vector to VictoriaLogs. vmalert loads 14 recording rules and six alerts, and Grafana provides the Vault Overview dashboard.


Dashboard

VAULT provides one dashboard: Vault Overview.

Vault Overview Dashboard

Vault Overview is available at /ui/d/vault-overview. Use the Cluster variable at the top to select a cluster.

vault-overview.jpg

The initial Overview section shows:

  • Vault Spec: members, unsealed members, quorum, and fault tolerance. Vault Members: each member’s current role (Active / Standby / Sealed / Down).
  • Vault Member List: availability, seal state, role, Autopilot health, Raft applied index, replication lag, lease/token counts, and time since the last snapshot.
  • Vault Health: Autopilot cluster health. Has Leader: whether exactly one member is active. Backup Age: age of the oldest of the members’ latest successful snapshots.
  • Vault Aliveness: the main timeline. Its top band shows whether exactly one member is active; member bands use Active / Standby / Sealed colors, with red for unavailable members.
  • Leader Elections, Raft statistics, request rate, average latency, audit failures, lease/secret counts, and active alerts.

Expand other groups for details:

GroupContents
RequestsRequest/login rates, latency quantiles, and request-weighted mean latency
Raft StorageCommit/applied indexes, FSM backlog, follower lag and heartbeats, commit latency, BoltDB size, fault tolerance, and peers
Secrets & LeasesLeases, tokens, token/lease operations, KV secrets, and identity entities
AuditAudit write rates and failures
RuntimeGoroutines, memory, GC pauses, and heap objects
HA ForwardingStandby-to-active forwarding rates and errors
BackupMember snapshot times, sizes, and backup logs
LogsOpenBao service log rates by level and log details

Metric Collection

Monitoring registration creates a file-discovery target on INFRA nodes for each member. VictoriaMetrics’ vault scrape job consumes this directory:

# /infra/targets/vault/vault-1.yml
- labels: { ip: 10.10.10.11 , ins: vault-1 , cls: vault }
  targets: [ 10.10.10.11:8200 ]

The job accesses https://<ip>:8200/v1/sys/metrics?format=prometheus directly and validates member certificates with Pigsty CA. Metrics share the API port and require no separate exporter:

  • unauthenticated_metrics_access is enabled only on the node IP listener, so scraping requires no token. The loopback listener does not allow unauthenticated metrics.
  • Scraping uses HTTPS. OpenBao’s listeners provide TLS only; a separate plaintext metrics port is unnecessary.
  • Sealed members remain scrapeable. vault_up indicates process/network reachability, while vault_core_unsealed indicates seal state.

After each successful snapshot, vault-backup writes two metrics to /var/lib/node_exporter/textfile/vault.prom. node_exporter collects them, and rules associate them with VAULT instances by ip.

VAULT metrics carry job="vault", cls, ins, ip, and instance. See Metrics for details.


Logs

OpenBao runs under systemd and logs to Journald. Vector collects those logs into VictoriaLogs. Scheduled vault-backup output uses logger -t vault-backup and follows the same path. Use these LogsQL queries in Grafana:

job:syslog unit:openbao                  # OpenBao service logs
job:syslog app:="vault-backup"           # Snapshot backup logs

Audit logs remain in /var/log/openbao/audit.log on members, rotated daily with seven files retained. They are not collected into VictoriaLogs. They include request paths and client information; configure centralized collection to meet your requirements.

If an audit device cannot write, for example because the disk is full, OpenBao rejects all requests. VaultAuditFailure covers this condition.


Alert Rules

VAULT provides six alerts in files/victoria/rules/vault.yml:

AlertSeverityConditionDescription
VaultDownCRITvault_up < 1 for 1 minuteMember metrics cannot be scraped: process stopped or network unreachable
VaultSealedWARNvault_core_unsealed < 1 for 1 minuteSealed member cannot serve keys; a sealed single-member cluster also triggers CRIT VaultNoLeader
VaultNoLeaderCRITvault:cls:active != 1 for 30 secondsNo active member, or more than one
VaultAuditFailureCRITAudit write failures over 5 minutes exceed zero, for 1 minuteOpenBao rejects requests when audit writes fail; check disk
VaultNoFailureToleranceWARNFault tolerance below one in a cluster of at least three members, for 2 minutesOne more member failure would lose quorum
VaultBackupStaleWARNNo successful member snapshot in 26 hours, or ever, for 5 minutesCheck journalctl -t vault-backup and node_exporter

INFRA’s generic AgentDown alert excludes the vault job to avoid duplicating VaultDown.

The following excerpt reproduces the current rule source:


# metrics scrape failure: process down or unreachable (sealed members are still scraped)
- alert: VaultDown
  expr: vault_up < 1
  for: 1m
  labels: { level: 0, severity: CRIT, category: vault }
  annotations:
    summary: "CRIT VaultDown {{ $labels.ins }}@{{ $labels.instance }}"
    description: |
      vault_up[ins={{ $labels.ins }}, instance={{ $labels.instance }}] = {{ $value }} < 1
      metrics of the member can not be scraped: check openbao service on {{ $labels.ip }}
      /ui/d/vault-overview?var-cls={{ $labels.cls }}

# member reports itself sealed: a sealed single node also triggers VaultNoLeader
- alert: VaultSealed
  expr: vault_core_unsealed{job="vault"} < 1
  for: 1m
  labels: { level: 1, severity: WARN, category: vault }
  annotations:
    summary: "WARN VaultSealed {{ $labels.ins }}@{{ $labels.instance }}"
    description: |
      vault_core_unsealed[ins={{ $labels.ins }}] = {{ $value }} < 1
      the member is sealed and can not serve keys: check `bao status` on {{ $labels.ip }}
      /ui/d/vault-overview?var-cls={{ $labels.cls }}

# not exactly one active member observed: kms outage for cache misses & restarts
- alert: VaultNoLeader
  expr: vault:cls:active != 1
  for: 30s
  labels: { level: 0, severity: CRIT, category: vault }
  annotations:
    summary: "CRIT VaultNoLeader: {{ $labels.cls }} {{ $value }}"
    description: |
      vault:cls:active[cls={{ $labels.cls }}] = {{ $value }} != 1
      /ui/d/vault-overview?from=now-5m&to=now&var-cls={{ $labels.cls }}

# audit device rejects writes: openbao rejects requests, check disk
- alert: VaultAuditFailure
  expr: vault:ins:audit_failure_5m > 0
  for: 1m
  labels: { level: 0, severity: CRIT, category: vault }
  annotations:
    summary: "CRIT VaultAuditFailure: {{ $labels.cls }} {{ $labels.ins }}"
    description: |
      vault:ins:audit_failure_5m[ins={{ $labels.ins }}] = {{ $value | printf "%.0f" }} > 0
      requests are rejected while the audit log can not be written, check disk on {{ $labels.ip }}
      /ui/d/vault-overview?var-cls={{ $labels.cls }}

# production cluster (3+ members) can not survive another member failure
- alert: VaultNoFailureTolerance
  expr: >-
    (vault:cls:failure_tolerance < 1)
    and on (job,cls) (count by (job,cls) (vault_up) >= 3)
  for: 2m
  labels: { level: 1, severity: WARN, category: vault }
  annotations:
    summary: "WARN VaultNoFailureTolerance: {{ $labels.cls }} {{ $value }}"
    description: |
      vault:cls:failure_tolerance[cls={{ $labels.cls }}] = {{ $value }} < 1
      losing one more member loses quorum
      /ui/d/vault-overview?var-cls={{ $labels.cls }}

# no successful local snapshot on the member for 26h (or never)
- alert: VaultBackupStale
  expr: time() - (vault:ins:backup_time or vault_up * 0) > 26 * 3600
  for: 5m
  labels: { level: 1, severity: WARN, category: vault }
  annotations:
    summary: "WARN VaultBackupStale {{ $labels.ins }}@{{ $labels.instance }}"
    description: |
      last successful snapshot of {{ $labels.ins }} is {{ $value | humanizeDuration }} ago (or never)
      check `journalctl -t vault-backup` and node_exporter on {{ $labels.ip }}
      /ui/d/vault-overview?var-cls={{ $labels.cls }}

7 - Metrics

VAULT labels, 14 recording rules, and raw OpenBao metrics used by the monitoring dashboard.

This page distinguishes raw OpenBao telemetry from recording metrics evaluated by vmalert. files/victoria/rules/vault.yml defines 14 recording rules. Their results are not available from /v1/sys/metrics.

Raw metrics vary with OpenBao version, member role, and runtime state. The metrics below are those used by Vault Overview and its rules, not the full telemetry output. See OpenBao telemetry metrics for the complete list.


Labels

VAULT metrics use these labels:

LabelMeaningExample
jobScrape jobvault
clsCluster name, vault_clustervault
insInstance name, <vault_cluster>-<vault_seq>vault-1
ipMember address10.10.10.11
instanceScrape address10.10.10.11:8200

Recording rules use vault:ins:* for instances and vault:cls:* for clusters.


Raw Metric Behavior

OpenBao retains Vault’s vault_ prefix and exports Prometheus metrics through go-metrics. Note:

  • Counters lack a _total suffix, for example vault_raft_apply and vault_token_creation.
  • Summaries use milliseconds: quantiles and _sum for vault_core_handle_request are in milliseconds. Recording rules divide average latency by 1,000 to convert it to seconds.
  • Some counters appear only after their first event, such as vault_audit_log_request_failure. Rules use increase_pure to count increases from zero.
  • Only the leader reports Autopilot and follower metrics. They persist for 24 hours after it steps down (prometheus_retention_time). Rules use only the current active member’s values and map node_id / peer_id back to ins.
  • Usage gauges (tokens, KV secrets, identity entities) are computed every ten minutes (usage_gauge_period).

Recording Rules

Expressions preserve source semantics. ins and cls indicate instance and cluster aggregation; actual retained labels follow the by and vector-matching clauses.

Recording MetricDescriptionExpression
vault:ins:req_rpsNon-login requests per second per instancerate(vault_core_handle_request_count{job="vault"}[1m])
vault:ins:login_rpsLogin requests per second per instancerate(vault_core_handle_login_request_count{job="vault"}[1m])
vault:ins:req_rt_avg_5mFive-minute mean instance request latency, in secondsrate(vault_core_handle_request_sum{job="vault"}[5m]) / rate(vault_core_handle_request_count{job="vault"}[5m]) / 1000
vault:cls:req_rt_avg_5mFive-minute mean cluster request latency, weighted by request volume, in secondssum by (job,cls) (rate(vault_core_handle_request_sum{job="vault"}[5m])) / sum by (job,cls) (rate(vault_core_handle_request_count{job="vault"}[5m])) / 1000
vault:cls:unsealedNumber of unsealed cluster memberssum by (job,cls) (vault_core_unsealed{job="vault"}) or (0 * count by (job,cls) (up{job="vault"}))
vault:cls:activeNumber of active cluster members; normally exactly onesum by (job,cls) (vault_core_active{job="vault"}) or (0 * count by (job,cls) (up{job="vault"}))
vault:cls:autopilot_healthyCluster health as reported by the active membermax by (job,cls) (vault_autopilot_healthy{job="vault"} and on(ins) (vault_core_active{job="vault"} == 1))
vault:cls:failure_toleranceCluster fault tolerance as reported by the active membermax by (job,cls) (vault_autopilot_failure_tolerance{job="vault"} and on(ins) (vault_core_active{job="vault"} == 1))
vault:ins:autopilot_healthyMember health as reported by the active membermax by (job,cls,ins) (label_replace(vault_autopilot_node_healthy{job="vault"} and on(ins) (vault_core_active{job="vault"} == 1), "ins", "$1", "node_id", "(.+)"))
vault:ins:raft_lagNumber of log entries by which a follower’s applied index trails the leadermax by (job,cls,ins) (label_replace(vault_raft_storage_follower_applied_index_delta{job="vault"} and on(ins) (vault_core_active{job="vault"} == 1), "ins", "$1", "peer_id", "(.+)"))
vault:ins:raft_heartbeat_msMilliseconds since the follower’s last heartbeatmax by (job,cls,ins) (label_replace(vault_raft_storage_follower_last_heartbeat_ms{job="vault"} and on(ins) (vault_core_active{job="vault"} == 1), "ins", "$1", "peer_id", "(.+)"))
vault:ins:audit_failure_5mAudit write failures per instance over five minutessum by (job,cls,ins,ip,instance) (increase_pure({__name__=~"vault_audit_log_(request|response)_failure",job="vault"}[5m]))
vault:ins:backup_timeUnix timestamp of the instance’s latest successful snapshotmax by (ip) (vault_backup_last_success_timestamp_seconds) + on(ip) group_right() vault_up * 0
vault:ins:backup_sizeSize of the instance’s latest successful snapshot, in bytesmax by (ip) (vault_backup_size_bytes) + on(ip) group_right() vault_up * 0

With no requests in a time window, average latency is 0/0 = NaN, which is discarded and appears as no data.

vault_up comes from INFRA’s shared aliveness rules, based on up{job="vault"}. It is not counted among the module’s rules. It indicates scrape success, not whether a member is unsealed or serving requests.


Common Raw Metrics

State and requests

MetricTypeDescription
vault_core_unsealedGaugeWhether the member is unsealed; 1 means unsealed
vault_core_activeGaugeWhether the member is active; 1 means active
vault_core_handle_requestSummary (ms)Non-login request duration, including quantiles, _sum, and _count
vault_core_handle_login_requestSummary (ms)Login request duration
vault_ha_rpc_client_forward_count / _errorsSummary / CounterStandby-to-active request forwarding count and errors
vault_ha_rpc_client_echo_count / _errorsSummary / CounterStandby-to-active heartbeat count and errors

Raft and Autopilot

MetricTypeDescription
vault_raft_storage_stats_commit_indexGaugeMember Raft commit index
vault_raft_storage_stats_applied_indexGaugeMember Raft applied index
vault_raft_storage_stats_fsm_pendingGaugeLog entries pending application to the state machine
vault_raft_storage_follower_applied_index_deltaGaugeFollower lag in log entries; reported only by the leader, with peer_id
vault_raft_storage_follower_last_heartbeat_msGaugeMilliseconds since follower heartbeat; leader only, with peer_id
vault_raft_state_leaderCounterTimes this member became leader; used to observe elections
vault_raft_peersGaugeNumber of Raft peers
vault_raft_applyCounterRaft log writes
vault_raft_commitTimeSummary (ms)Raft log commit duration
vault_raft_leader_lastContactSummary (ms)Time since the leader last contacted a follower
vault_raft_storage_bolt_page_bytes_allocatedGaugeAllocated BoltDB page bytes
vault_raft_storage_bolt_freelist_used_bytesGaugeBoltDB freelist bytes
vault_autopilot_healthyGaugeCluster health; leader only
vault_autopilot_failure_toleranceGaugeCluster fault tolerance; leader only
vault_autopilot_node_healthyGaugeMember health; leader only, with node_id

Secrets, tokens, and audit

MetricTypeDescription
vault_expire_num_leasesGaugeCurrent lease count
vault_expire_renew_count / vault_expire_revoke_countSummaryLease renewals and revocations
vault_token_countGaugeToken count (usage gauge)
vault_token_creationCounterToken creations
vault_secret_kv_countGaugeKV secret count (usage gauge)
vault_identity_num_entitiesGaugeIdentity entity count
vault_audit_log_request_count / vault_audit_log_response_countSummaryRequests and responses recorded by audit devices
vault_audit_log_request_failure / vault_audit_log_response_failureCounterAudit write failures; appears only after the first failure

Runtime

MetricTypeDescription
vault_runtime_num_goroutinesGaugeGoroutine count
vault_runtime_alloc_bytes / vault_runtime_sys_bytesGaugeAllocated heap memory and memory requested from the OS
vault_runtime_heap_objectsGaugeHeap object count
vault_runtime_total_gc_pause_ns / vault_runtime_total_gc_runsGaugeCumulative GC pause time and number of GC runs

Snapshot backups (from node_exporter’s textfile collector, under the node scrape job)

MetricTypeDescription
vault_backup_last_success_timestamp_secondsGaugeUnix timestamp of the latest successful local snapshot
vault_backup_size_bytesGaugeSize of the latest successful local snapshot, in bytes

vault-backup writes these two metrics to /var/lib/node_exporter/textfile/vault.prom only after success. The file is deleted when a member is removed.

8 - FAQ

Frequently asked questions about Pigsty’s VAULT module.

What Does VAULT Do?

VAULT supplies KMS (key management) and secrets storage for a Pigsty deployment, covering three main uses:

  • Transparent data encryption: store PGSQL pg_tde master keys so leaked database disks and backups cannot be decrypted without VAULT keys.
  • Object storage encryption: store keys for MINIO (Silo) KES server-side encryption.
  • Application secrets: use KV, Transit, and AppRole to store/access passwords and API keys or call encryption services.

The first two integrations currently require manual configuration; automatic integration is still being developed. Applications can use the third immediately after deployment; see Usage.


Why OpenBao Instead of HashiCorp Vault?

HashiCorp changed Vault’s license to BSL in 2023. Later versions are no longer open-source software and are not packaged for distribution by Pigsty. OpenBao is a community fork from before that license change, hosted by the Linux Foundation under MPL-2.0. Its API is largely compatible; Vault libraries, tools, and most documentation remain useful references.

VAULT names the secrets-vault capability rather than a specific engine, following the same convention as the MINIO module using Silo.


How Many Members Should a Cluster Have?

Use one member for development, testing, and demos. Use three with an L2 VIP for production HA; see Configuration.

Raft requires a majority. If half or more of the members are unavailable, the cluster cannot serve requests. Three members tolerate one failure. Two members tolerate no failures and offer lower availability than one. The playbook warns about this topology; use it only temporarily during scaling.


What Happens When VAULT Is Unavailable?

The impact depends on how consumers use keys:

  • Running consumers that have cached keys in memory can usually continue.
  • Operations that must retrieve keys fail, such as restarting PostgreSQL encrypted with pg_tde or KES fetching a new data key.
  • Application secret reads and encryption/decryption requests fail.

In production, VAULT should be at least as available as the databases that depend on it. VaultDown and VaultNoLeader are CRIT alerts.


Is Auto-Unseal Secure?

VAULT uses OpenBao static seal: a 32-byte key in files/pki/vault/unseal.key on the admin node is distributed to /etc/openbao/unseal.key on each member. Members unseal automatically after restart without manual key-share entry.

ScenarioProtection
Leaked database disks, database backups, or object-storage disksYes: decryption requires VAULT keys; this is the purpose of separating keys from data
Leaked VAULT snapshotYes: it cannot be opened without unseal.key
Compromised VAULT memberNo: the auto-unseal key resides there; without an HSM this applies to any automatic unseal scheme
Compromised admin nodeNo: Pigsty already keeps the CA private key and passwords on the admin node
Root or superuser access to a running databaseNo: transparent encryption protects data at rest

This balances operations and security: database restarts do not wait for manual unsealing. For stricter requirements, switch to manual unsealing or an external KMS seal after deployment; that is outside the current module’s automation.


Which Files Must Be Backed Up?

Back up the admin node’s entire files/pki/ directory and copy VAULT snapshots off-site:

  • files/pki/vault/unseal.key: without it, all snapshots are unreadable.
  • files/pki/vault/init.json: root token and recovery keys for administrative access after restoration.
  • files/pki/ca/: Pigsty CA, which signs VAULT server certificates.
  • A recent Raft snapshot from members’ /data/vault/backup/ or the admin node’s files/pki/vault/backup/.

Snapshots are encrypted and can be copied to other storage, but keep them separate from unseal.key. See Backups and Restore a Snapshot.


Why Does bao status Return Permission Denied?

BAO_CACERT points to /etc/openbao/tls/ca.crt, readable only by root and the openbao group. Regular users see:

failed to read environment: Error loading CA File: open /etc/openbao/tls/ca.crt: permission denied

Run as root (sudo -i), or use the globally readable CA: export BAO_CACERT=/etc/pki/ca.crt.


Why Does vault.yml Refuse to Initialize?

vault.yml initializes only when all required conditions hold. Common reasons for refusal:

  • An unreachable member: the playbook cannot determine whether the cluster is initialized and aborts to prevent creating a second cluster. Restore network/service access to every member and rerun.
  • All members are uninitialized, but files/pki/vault/init.json exists: the cluster was initialized before and has lost its data. To recover, see Rebuild After Total Data Loss. To create a new empty cluster, archive init.json first; see Destroy a Cluster.

Why Does vault-rm.yml Abort by Default?

vault_safeguard defaults to true, like MYSQL’s mysql_safeguard. Deleting VAULT deletes all its keys, making encrypted data and all backups permanently unreadable. The impact extends beyond any one database cluster.

After verifying the target and snapshot, explicitly override it with ./vault-rm.yml -l <exact-target> -e vault_safeguard=false, or use bin/vault-rm.


Why Are Metrics Token-Free and Served over HTTPS?

OpenBao normally requires a token for /v1/sys/metrics. VAULT enables unauthenticated_metrics_access on the node IP listener so monitoring needs no VAULT credentials. The loopback listener does not allow unauthenticated access. Metrics contain counts and state, not secrets.

OpenBao provides TLS only, with metrics and business APIs sharing port 8200. A separate plaintext port would increase exposure without benefit, so VictoriaMetrics scrapes over HTTPS using Pigsty CA.


Why Does VaultBackupStale Fire?

Each member runs vault-backup daily. The alert fires if a member has no successful snapshot for 26 hours, or has never succeeded. Common causes:

  • Scheduled job failure: inspect journalctl -t vault-backup for an invalid token, missing active member, or full disk.
  • node_exporter’s textfile collector is disabled: newer NODE versions configure its directory. Refresh older nodes with ./node.yml -l vault -t node_exporter.
  • An invalid backup token, for example after snapshot restoration: run ./vault.yml -l vault -t vault_provision,vault_backup. The playbook checks the token and reissues/distributes it if necessary.

After fixing the issue, run vault-backup manually. The alert clears at the next evaluation.


What Causes Frequent Leader Changes?

Raft leaders maintain their term through heartbeats. Timeouts cause elections, commonly due to network jitter, high disk latency, or host load that prevents timely scheduling, especially on oversubscribed VMs.

Inspect Leader Elections, Follower Last Heartbeat, and Raft Commit Time in Vault Overview, then correlate with node CPU, disk, and network metrics. Occasional leader changes do not affect data consistency. HAProxy switches to the new active member automatically.


Where Are Audit Logs?

Each member enables a file audit device at /var/log/openbao/audit.log, rotated daily with seven files retained. OpenBao applies HMAC to sensitive fields.

Standbys forward requests to the active member, which records them. A member that has never been active may have no audit.log; this does not indicate broken audit configuration.

Audit logs are not collected into VictoriaLogs or deleted on VAULT removal. If audit writes fail, OpenBao rejects requests and VaultAuditFailure fires; check disk space first.


Can I Use an Existing External Vault?

Currently no. The module manages only OpenBao clusters deployed by Pigsty, not external or managed HashiCorp Vault, OpenBao, or cloud KMS services.

Applications may still access your existing Vault directly, but Pigsty deployment, monitoring, and future automatic consumer integration do not manage it.