← All projects
Secrets management

uBixVault

v1.6.0 Go · BSD-3-Clause · Vault-compatible API

A from-scratch secrets manager in Go. It stores and generates secrets behind an encrypted, sealed barrier — and speaks a Vault-compatible HTTP API, so tools you already have — the vault CLI, External Secrets Operator, anything built on HashiCorp's Go client — can talk to it unchanged.

Language
Go
License
BSD-3-Clause
Interface
Vault-compatible HTTP
At rest
AES-256-GCM
New in 1.6.0 · Experimental

Your vault keeps its own data.

Integrated Raft storage: start with -storage raft and three replicas replicate every write to a majority before acknowledging it. No MySQL to provision, patch or lose. It ships as experimental — we run it on our own dev and then prod first, and the label comes off after that soak. MySQL and file storage are unchanged and remain the defaults.

What you get
Replicated, no external dependency
Each replica has its own volume and any one can be lost. Two of three down means no quorum, so writes fail with 503 rather than diverge.
Mutual TLS between nodes
Peer traffic is mutually authenticated TLS. A new replica joins explicitly, with a single-use, short-lived token against a pinned CA.
A way over from MySQL — and back
One maintenance window: export the ciphertext, restore it into the first replica, start with the same unseal key, join the others. Nothing above storage changes, so no token, lease or secret is invalidated; rollback is a snapshot back into the database.
Same operations
Auto-unseal, snapshots, the backup CronJob and rolling upgrades work as before. operator raft adds peers, join tokens, snapshots and recovery, and the endpoints use Vault's response envelope, so vault operator raft list-peers works.
What we measured
EventResult
Leader's node frozenNew leader in 6–7 s.
Leader partitioned from the majorityNew leader in 11–17 s; the isolated leader refused writes (503). Every acknowledged write read back in all three runs.
Whole cluster restarted (volumes kept, auto-unseal)Serving again in 18 s, nothing lost.
One replica's volume lostBack as a voter in 19 s (PVC loss on a replica).
MySQL → Raft migration48 of 48 entries intact; outage 33–35 s, from freezing the old release to the first read served by Raft.
Rollback to MySQLServing in 41–46 s, with a write made on Raft intact.
First node (-0) on a wiped volumeRefuses to bootstrap a split cluster; rejoins with a join token. No rejoin time measured.
Jepsen-style testingThree 2-minute runs, no violation found. Not a full proof: the checker could not decide all of the history.

Measured on kind, three replicas, a client writing every 100 ms with no retries, every acknowledged write checked afterwards; ranges span three chaos runs. These are CI-VM numbers — a regression baseline, not capacity planning.

Before you use it

Integrated storage is a new engine and experimental. Migrate when you have a window and a recent snapshot, and keep the database through the soak. uBixVault has had no independent security audit.

Run 3 or 5 replicas (the chart refuses an even number) on block storage with real fsync, not NFS/SMB. Auto-unseal is the practical mode: a sealed replica does not vote, so a full restart under Shamir shares needs a quorum unsealed first. It is not Vault's integrated storage byte for byte — autopilot covers membership and leader only.

Operate
raft
# new cluster from the chart (3 replicas, PVC each)
$ helm install vault ./deploy/charts/ubixvault \
    --set storage.type=raft --set replicaCount=3
$ ubixvault operator raft list-peers
$ ubixvault operator raft join-token         # single-use, 15 min
# migrate from MySQL (offline window), then verify
$ ubixvault operator raft export-mysql mysql.snap
$ ubixvault operator raft digest -against mysql.snap cluster.snap
# last resort, lost quorum: stage peers.json on a stopped node
$ ubixvault operator raft recover -data <dir> -peers peers.json
# rotate the cluster CA, then restart and unseal every node
$ ubixvault operator raft rotate-ca
Synopsis
shell
ubixvault server -data <dir> [-audit-log <file>]
          [-tls-cert <f> -tls-key <f>]
ubixvault operator init | unseal | seal-status
Description

All data at rest is AES-256-GCM encrypted — storage never sees plaintext, and the storage path is bound into the ciphertext so blobs can't be relocated.

The vault boots sealed. Its master key is reconstructed from Shamir k-of-n key shares before any secret is readable — or, given a key-encryption key, the vault can auto-unseal itself on restart, with recovery keys to regenerate a lost root token. Access is default-deny, path-based tokens with ACL policies, and audit logging is fail-closed — the client token is HMAC'd, never logged in the clear.

A built-in web console at /ui/ — initialize and unseal the vault, sign in with a username and password, LDAP, or a token — manages secrets (with version history), policies, tokens, and PKI certificates, and a Prometheus metrics endpoint exposes seal state and request counts for scraping.

Examples
build & run (dev)
# build and run in dev mode (plain HTTP, audit on)
$ go build -o bin/ubixvault ./cmd/ubixvault
$ ./bin/ubixvault server -data ./data -audit-log ./audit.log
initialize
# 3 unseal shares, threshold 2
$ curl -s -X POST $VAULT/v1/sys/init \
    -d '{"secret_shares":3,"secret_threshold":2}'
-> {"keys":["k0","k1","k2"], "root_token":"uv...."}
unseal & write a secret
$ curl -s -X POST $VAULT/v1/sys/unseal -d '{"key":"k0"}'
$ curl -s -X POST $VAULT/v1/sys/unseal -d '{"key":"k1"}'
$ curl -sH "$T" -X POST $VAULT/v1/secret/data/app \
    -d '{"data":{"api_key":"sk-123"}}'
deploy on Kubernetes (Helm)
# StatefulSet, auto-unseal, TLS
$ kubectl create secret generic uv-kek \
    --from-literal=auto-unseal-key=$(head -c32 /dev/urandom|xxd -p|tr -d '\n')
$ helm install vault ./deploy/charts/ubixvault \
    --set tls.existingSecret=uv-tls --set autoUnseal.existingSecret=uv-kek
# high availability: 3 replicas over MySQL, one active, standbys forward
$ helm upgrade vault ./deploy/charts/ubixvault --reuse-values \
    --set storage.type=mysql --set storage.mysql.dsnSecret=uv-dsn \
    --set ha.enabled=true --set replicaCount=3
operate
$ curl -s $VAULT/v1/sys/health    # readiness (200/429 standby/503/501)
$ curl -s $VAULT/v1/sys/leader    # which replica is active (HA)
$ curl -s $VAULT/v1/sys/metrics   # Prometheus text format
# manage it from the web console at $VAULT/ui/
Capabilities
Encryption barrier
AES-256-GCM at rest; the storage path is bound into the ciphertext so blobs can't be relocated.
Seal / unseal
In-house, constant-time Shamir over GF(2⁸). Nothing is readable until it's unsealed.
Tokens & ACL policies
Default-deny, path-based capabilities. Hand out scoped, non-root tokens: a child token only gets policies its creator holds (unless the creator has sudo) and cannot outlive it — revoking a token revokes the tokens it delegated — renewal is capped, and each auth role can set its own token_max_ttl.
KV v2 & Transit
Versioned secrets, plus crypto-as-a-service: encrypt/decrypt, rewrap, wrapped data keys, HMAC, sign/verify with ECDSA or Ed25519 keys, and per-context key derivation with optional convergent (deterministic) encryption — keys never leave the vault.
Dynamic database credentials
Short-lived MariaDB users generated on demand and auto-revoked on lease expiry.
Web admin console
A built-in console at /ui/ — initialize and unseal the vault; sign in with userpass, LDAP, or a token — to manage KV secrets (with version history), ACL policies, tokens, and PKI (issue certificates, manage roles and the CA) — served from the binary, no external assets.
Metrics & health
Prometheus metrics (seal state, uptime, request counts), a readiness endpoint, and a dedicated liveness endpoint (/v1/sys/livez) that stays green while sealed — so probes never crash-loop a vault waiting to be unsealed.
Kubernetes-native
Deploy with the bundled Helm chart — one replica, or several in HA; pods authenticate with their ServiceAccount token, and the vault auto-unseals on restart — with recovery keys to regenerate a lost root token.
Authentication methods
Tokens, AppRole and Kubernetes ServiceAccount auth for machines, userpass (username + PBKDF2-hashed password) for people, JWT/OIDC (signed tokens verified against static keys, a JWKS, or an OIDC issuer's discovery document), TLS client certificates (mutual-TLS, verified against a trusted CA or a pinned cert), and LDAP / Active Directory (bind + group search over verified TLS — including directories with their own CA, such as FreeIPA or an AD enterprise CA — groups mapped to policies or fed to identity external groups) — each mapped to policies.
Identity
One entity per subject, with aliases mapping each auth-method login to it and groups (nestable) that carry policies — so a person's several logins share one identity, and policy attached to an entity or group applies across every method they log in through, taking effect without re-issuing tokens.
Cubbyhole
Per-token private storage: a scratch space readable and writable only by the token that wrote it — no policy, not even root, reaches another token's cubbyhole — and destroyed when that token is revoked.
Resource quotas
API-managed, path-scoped rate-limit quotas (per-client token-bucket throttling, with a global default that also blunts brute-force against unseal shares) and lease-count quotas (cap the active dynamic-credential leases under a path) — Vault-compatible, enforced with a 429 at the limit; health, metrics, and the console are exempt.
Certificate authority
A built-in PKI engine: generate an internal root CA (its key never leaves the vault) and issue short-lived, role-constrained TLS certificates on demand.
Flexible auto-unseal
Shamir shares, a local key-encryption key, a transit seal via another vault, or a cloud KMS / HSM through an external command — the wrapping key stays off the host, and no provider SDK lives in the vault.
Response wrapping
Wrap a secret in a single-use, short-lived token and hand that to a consumer instead of the secret — unwrapped exactly once, then destroyed.
High availability
Run several replicas over MySQL/MariaDB: every replica unseals, one holds a fenced lock in the database and serves, and the rest are standbys that forward to it over their own mutual-TLS cluster port — so any replica answers. A node drain or rolling upgrade hands the active role over in about half a second (no failed requests in the kind failover test); a crashed active is replaced within the lock lease, and a replica that lost the lock without noticing can't write. sys/leader, sys/step-down and the health standby codes work as in Vault.
Durable database storage
Run against a MySQL/MariaDB database instead of a local disk, so the node is replaceable — it restarts against the same replicated store. The database sees only ciphertext; the vault creates its own tables.
Raft integrated storage experimental
Replicated storage with no database behind it, mutual TLS between nodes, and a rehearsed migration from MySQL with rollback. See Raft.
Rotation & backups
Rotate the unseal shares live with rekey (no downtime), and schedule off-node encrypted snapshots — plus snapshot/restore for migration between backends.
Configuration

Configuration reference.

The settings you'll actually reach for — the full reference lives in the deployment docs.

Server & CLI
SettingDefaultNotes
server -storage <file|mysql|raft>filemysql keeps the encrypted store in a MySQL/MariaDB database — durable, replaceable node, and the basis for HA. raft (experimental) replicates it across the replicas' own volumes; HA is implied.
server -raft-node-id / -raft-advertise-addr / -raft-bootstrap— · — · trueRaft node identity and peer address; set -raft-bootstrap=false on nodes that will join.
server -raft-retry-join / -raft-join-token-file / -raft-leader-ca-cert—Join an existing cluster declaratively with a single-use token; the leader's API certificate is verified against the given CA.
operator raft recover -data <dir> -peers <file>—Stage a peers.json on a stopped node to recover from lost quorum; applied once on next start. Writes acknowledged only by dropped members can be lost.
server -haoffActive/standby HA over -storage mysql; the replica holding the lock is active, the rest forward to it.
server -ha-cluster-listen / -ha-cluster-addr:8201 · https://$POD_IP:8201Replica-to-replica port, mutual TLS from a CA the vault keeps itself — nothing to configure.
server -ha-lock-ttl / -ha-retry-interval15s · 500msBound on an unplanned failover / how often standbys try for the lock.
server -data <dir>./dataDirectory for the encrypted store (-storage file).
server -storage-mysql-dsn$UBIXVAULT_STORAGE_DSNDSN for -storage mysql. Passed via env, never on the command line.
operator rekey init / update—Rotate the Shamir unseal shares live — no downtime, no data re-encryption.
server -tls-cert / -tls-key—Enable HTTPS. Without them the server logs a loud no-TLS warning (dev only).
server -max-token-ttl768hHow far renewal may extend a token whose own TTL is shorter. A token deliberately issued for longer keeps its full TTL; no token can renew itself past its ceiling.
server -log-tls-handshake-abortsfalseLog every TLS handshake a client abandons. By default these — TCP health checks from load balancers and ingress controllers, mostly — are counted and logged as one summary line every 5 minutes; other handshake failures are always logged.
server -audit-log <path>—Turns on fail-closed audit logging to that file.
server -auto-unseal-key <hex>$UBIXVAULT_AUTO_UNSEAL_KEYHex 32-byte key-encryption key; enables auto-unseal on restart.
operator init -shares / -threshold5 / 3Shamir k-of-n split created at initialization.
-address$UBIXVAULT_ADDRClient → server address for the CLI.
-token$UBIXVAULT_TOKENClient auth token for the CLI.
-ca-cert / -tls-skip-verify—Client TLS trust. Skipping verification is insecure — dev only.
Helm chart · values.yaml
ValueDefaultNotes
containerPort / service.port8200Vault-compatible API port.
storage.type / storage.mysql.dsnSecretfile · —mysql uses a database; the DSN comes from a Secret (never a chart value) and the data PVC is dropped. raft (experimental) runs integrated storage on a PVC per replica.
storage.raft.persistence.size10GiPer-replica volume for storage.type=raft; block storage with honest fsync only. replicaCount must be odd.
storage.raft.join.tokenSecret / leaderCASecret<fullname>-raft-join · —Secret with one join token per joining pod (<fullname>-1, -2), and the CA the joiners verify the leader against.
storage.networkPolicy.enabledfalseNetworkPolicy for Raft pods: API port from the cluster, cluster port from the pods themselves.
persistence.size / mountPath1Gi · /var/lib/ubixvaultEncrypted data volume (StatefulSet PVC), for storage.type=file.
backup.enabled / schedulefalse · 0 */6 * * *Scheduled off-node snapshot CronJob using a least-privilege token.
ha.enabled / replicaCountfalse · 1Active/standby HA (requires storage.type=mysql); 3 replicas recommended. Adds a PodDisruptionBudget and soft anti-affinity.
autoUnseal.existingSecret—Secret holding the KEK; auto-unseal is on by default.
tls.existingSecret / tls.certManager—Bring your own cert, or issue one via cert-manager.
audit.enabledfalseFail-closed audit log to its own PVC.
rateLimit.enabled / perSecondfalse · 20Per-client token-bucket limit (burst 40).
kubernetesAuth.enabledtruePods authenticate with their ServiceAccount token.
metrics.serviceMonitor.enabledfalseScrape via the Prometheus Operator.
ingress.enabledfalseExpose over an ingress with TLS.
Architecture

Layered, and sealed at the base.

Every request passes down through the same layers, and everything at rest sits below the barrier.

The stack
HTTP API · CLI
clients enter here — Vault-compatible /v1/*
Auth Methods → Token & Lease Manager
Policy (ACL) engine · Identity
Secrets Engines
KV v2 · Transit · Database-dynamic · PKI · Cubbyhole
Audit Devices
broker → file / syslog · fail-closed
Barrier — AES-256-GCM at rest
Seal / Unseal · Shamir k-of-n
Storage Backend
file · MySQL/MariaDB · Raft (experimental)
How it holds

A request enters through the Vault-compatible HTTP API (or the ubixvault CLI), is authenticated to a token, and checked against default-deny path ACLs before any secrets engine runs. Every request and response passes through the audit broker on the way through.

Beneath all of it sits the barrier: everything written to storage is AES-256-GCM encrypted, with the storage path and format version bound in as authenticated data — so a ciphertext only decrypts at the exact path it was written to, and can't be relocated.

The vault boots sealed, holding only ciphertext. Unsealing reconstructs the master key from Shamir k-of-n shares. The key hierarchy — unseal shares → master key → barrier key → data — lets the barrier key rotate without re-sharing.