npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@rimiti/vault-to-openbao

v1.5.0

Published

Migrate secrets from HashiCorp Vault to OpenBao

Downloads

427

Readme

@rimiti/vault-to-openbao

Migrate secrets, policies and auth methods from HashiCorp Vault to OpenBao, designed to run against a Kubernetes cluster.

Prerequisites

  • Node.js >= 18
  • Network access to both instances (Vault and OpenBao)
  • Root token, or a token with read/list rights on Vault and write rights on OpenBao

Installation

Global (CLI usage)

npm install -g @rimiti/vault-to-openbao

Local (programmatic usage)

yarn add @rimiti/vault-to-openbao

CLI

Configuration

All options can be passed as CLI flags or environment variables. CLI flags take precedence.

| Flag | Env var | Description | Default | | --------------------- | -------------------- | -------------------------------------------------- | ------------------------ | | --vault-addr | VAULT_ADDR | URL of the source Vault instance | — | | --vault-token | VAULT_TOKEN | Vault authentication token | — | | --openbao-addr | OPENBAO_ADDR | URL of the destination OpenBao instance | — | | --openbao-token | OPENBAO_TOKEN | OpenBao authentication token | — | | --dry-run | DRY_RUN=true | Simulate migration without writing | false | | --skip-tls-verify | SKIP_TLS_VERIFY=true | Disable TLS certificate verification | false | | --skip-mounts | SKIP_MOUNTS | Comma-separated KV mounts to skip | sys,identity,cubbyhole | | --skip-policies | SKIP_POLICIES | Comma-separated policy names to skip | — | | --skip-auth-methods | SKIP_AUTH_METHODS | Comma-separated auth method paths to skip | — | | --concurrency | CONCURRENCY | Number of secrets migrated in parallel | 5 | | -V, --version | | Display version | | | -h, --help | | Display help | |

Usage

Dry-run first (recommended):

vault-to-openbao \
  --vault-addr https://vault.example.com \
  --vault-token hvs.xxxx \
  --openbao-addr https://openbao.example.com \
  --openbao-token s.xxxx \
  --dry-run

Real migration:

vault-to-openbao \
  --vault-addr https://vault.example.com \
  --vault-token hvs.xxxx \
  --openbao-addr https://openbao.example.com \
  --openbao-token s.xxxx

Using a .env file instead of flags:

cp .env.example .env
# edit .env, then:
vault-to-openbao

Programmatic API

import { migrate } from "@rimiti/vault-to-openbao";

const stats = await migrate({
  vault: {
    addr: "https://vault.example.com",
    token: "hvs.xxxx",
  },
  openbao: {
    addr: "https://openbao.example.com",
    token: "s.xxxx",
  },
  dryRun: false,
  skipTlsVerify: false,
  skipMounts: ["sys", "identity", "cubbyhole"],
  skipPolicies: [],
  skipAuthMethods: [],
  concurrency: 5,
});

console.log(`Policies  : ${stats.migratedPolicies} / ${stats.totalPolicies}`);
console.log(`Auth      : ${stats.migratedAuthMethods} / ${stats.totalAuthMethods}`);
console.log(`Entities  : ${stats.migratedEntities} / ${stats.totalEntities}`);
console.log(`Groups    : ${stats.migratedGroups} / ${stats.totalGroups}`);
console.log(`Leases    : ${stats.totalLeases} active (inventory only)`);
console.log(`Secrets   : ${stats.migratedSecrets} / ${stats.totalSecrets}`);

migrate(config) return value

interface MigrationStats {
  // Policies
  totalPolicies: number;
  migratedPolicies: number;
  failedPolicies: number;
  // Auth methods
  totalAuthMethods: number;
  migratedAuthMethods: number;
  failedAuthMethods: number;
  // Identity — entities
  totalEntities: number;
  migratedEntities: number;
  failedEntities: number;
  // Identity — groups
  totalGroups: number;
  migratedGroups: number;
  failedGroups: number;
  // Leases (inventory only — not migrated)
  totalLeases: number;
  // KV secrets
  totalMounts: number;
  skippedMounts: number;
  totalSecrets: number;
  migratedSecrets: number;
  failedSecrets: number;
  // All errors
  errors: Array<{ path: string; error: string }>;
}

How it works

The migration runs in seven steps:

Step 1/7 — Migrating policies
  → Lists all ACL policies via LIST /v1/sys/policies/acl
  → Skips built-in policies (root, default)
  → Writes each policy to OpenBao

Step 2/7 — Migrating auth methods
  → Lists all auth mounts via /v1/sys/auth
  → Skips built-in auth (token)
  → Enables each method in OpenBao, then migrates its config and roles/users

Step 3/7 — Migrating identity (entities & groups)
  → Builds an accessor mapping (Vault auth accessor → OpenBao auth accessor)
  → Creates all entities (upsert by name), resolves old→new entity ID mapping
  → Creates entity aliases with remapped accessors and entity IDs
  → Creates internal groups (two-pass: create without members, then update members)
  → Creates group aliases for external groups with remapped accessors

Step 4/7 — Lease inventory
  → Lists all active leases recursively from /v1/sys/leases/lookup/
  → Logs a summary grouped by prefix
  → Does NOT migrate leases (they are runtime credentials tied to Vault)

Step 5/7 — Discovering KV mounts
  → Queries /v1/sys/mounts on Vault
  → Filters mounts of type KV (v1 and v2)
  → Excludes system mounts (sys, identity, cubbyhole)

Step 6/7 — Enumerating secrets
  → Recursively walks each mount using the LIST operation
  → Creates any missing mounts in OpenBao

Step 7/7 — Migrating secrets
  → Reads each secret from Vault
  → Writes it to OpenBao (in parallel batches)

Supported auth method types

| Type | Config | Roles | Users / Groups | Notes | | ------------ | :----: | :---: | :------------: | ------------------------------------------- | | approle | — | ✓ | — | Secret IDs cannot be extracted | | kubernetes | ✓ | ✓ | — | | | userpass | — | — | ✓ | Passwords are hashed — users must reset | | jwt | ✓ | ✓ | — | | | oidc | ✓ | ✓ | — | | | ldap | ✓ | — | ✓ + groups | | | github | ✓ | — | ✓ + teams | | | others | — | — | — | Mount is enabled, content is skipped (warn) |

KV version handling

| Version | Vault read | OpenBao write | | ------- | ----------------------------- | ------------------------------ | | KV v1 | GET /v1/<mount>/<path> | POST /v1/<mount>/<path> | | KV v2 | GET /v1/<mount>/data/<path> | POST /v1/<mount>/data/<path> |

The version is detected automatically from the mount options (options.version).

Output

============================================================
Vault → OpenBao Migration
============================================================
2026-03-27T10:00:00.000Z INFO  Source:            https://vault.example.com
2026-03-27T10:00:00.000Z INFO  Destination:       https://openbao.example.com
...
2026-03-27T10:00:00.000Z OK    Migrated policy: app-readonly
2026-03-27T10:00:00.000Z OK    Migrated policy: app-readwrite
2026-03-27T10:00:01.000Z OK    Migrated auth [kubernetes] kubernetes/
2026-03-27T10:00:01.000Z OK    Migrated: secret/app/database
2026-03-27T10:00:01.000Z OK    Migrated: secret/app/api-keys
...
============================================================
Migration Summary
============================================================
  Policies            : 2 / 2 (0 failed)
  Auth methods        : 1 / 1 (0 failed)
  Entities            : 5 / 5 (0 failed)
  Groups              : 3 / 3 (0 failed)
  Active leases       : 12 (inventory only — not migrated)
  KV mounts           : 3 discovered, 0 skipped
  Secrets             : 42 / 42 (0 failed)

The CLI exits with code 1 if at least one item failed to migrate.

Required permissions

Vault (source) — minimal policy

# KV secrets
path "sys/mounts" {
  capabilities = ["read"]
}
path "<mount>/metadata/*" {
  capabilities = ["list"]
}
path "<mount>/data/*" {
  capabilities = ["read"]
}
# KV v1
path "<mount>/*" {
  capabilities = ["read", "list"]
}

# Policies
path "sys/policies/acl" {
  capabilities = ["list"]
}
path "sys/policies/acl/*" {
  capabilities = ["read"]
}

# Auth methods
path "sys/auth" {
  capabilities = ["read"]
}
path "auth/*" {
  capabilities = ["read", "list"]
}

# Identity
path "identity/*" {
  capabilities = ["read", "list"]
}

# Leases inventory
path "sys/leases/lookup/*" {
  capabilities = ["list", "sudo"]
}

OpenBao (destination) — minimal policy

# KV secrets
path "sys/mounts" {
  capabilities = ["read"]
}
path "sys/mounts/<mount>" {
  capabilities = ["create", "update"]
}
path "<mount>/data/*" {
  capabilities = ["create", "update"]
}
# KV v1
path "<mount>/*" {
  capabilities = ["create", "update"]
}

# Policies
path "sys/policies/acl/*" {
  capabilities = ["create", "update"]
}

# Auth methods
path "sys/auth" {
  capabilities = ["read"]
}
path "sys/auth/*" {
  capabilities = ["create", "update"]
}
path "auth/*" {
  capabilities = ["create", "update"]
}

# Identity
path "identity/*" {
  capabilities = ["create", "update", "read", "list"]
}