- Go 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .idea | ||
| .gitignore | ||
| .gitlab-ci.yml | ||
| go.mod | ||
| go.sum | ||
| main.go | ||
| README.md | ||
secrets‑migrator
A CLI tool for migrating secrets, policies and KV engines between HashiCorp Vault (or OpenBao) instances.
It can copy KV version 1 or version 2 data, as well as Vault policies, from a source server to a destination server while preserving namespaces and authentication tokens.
Table of Contents
| Section | Description |
|---|---|
| Features | What the tool can do |
| Prerequisites | Software you need before installing |
| Installation | Build from source or get a binary |
| Configuration | config.yaml format and interactive prompts |
| Usage | CLI reference and examples |
| How it works | Brief overview of the internals |
| Limitations & Known Issues | Things the current version does not support |
| Contributing | How to help improve the project |
| License | SPDX identifier |
Features
| Feature | Description |
|---|---|
| KV v2 migration | Recursively copies all secrets from a KV‑v2 mount on the source to a KV‑v2 mount on the destination (creates the mount if missing). |
| KV v1 migration | Same as above for the legacy KV‑v1 engine. |
| Policy migration | Copies every non‑system policy (admin, root, default, namespace-admin) from source to destination. |
| Namespace aware | Supports Vault/OpenBao namespaces – you can specify a namespace for each side. |
| Interactive configuration | If any required field is missing from config.yaml, the CLI will prompt you for it (including hidden password entry). |
| Idempotent | Running the same command twice does not duplicate data – mounts are created only once and existing secrets are overwritten with the source values. |
| Zero‑dependency runtime | The binary only depends on the official OpenBao client library and a few standard packages. |
Prerequisites
| Tool | Minimum version | Why |
|---|---|---|
| Go | 1.22 (or newer) | Required to compile the source (go build). |
| Vault / OpenBao | Any version that supports the API used by github.com/openbao/openbao/api/v2. |
The tool talks to the HTTP API, not the CLI. |
| git (optional) | — | To clone the repository. |
Installation
1. Install from source
# Clone the repo
git clone https://helsegitlab.nhn.no/kryptodrift/secret-migrator.git
cd secrets-migrator
# Build the binary (the resulting executable will be ./secrets-migrator)
go build -o secrets-migrator .
2. Use a pre‑built binary (Linux/macOS)
# Example – replace <VERSION> with the latest tag
curl -L -o secrets-migrator "TODO: Link to release page"
chmod +x secrets-migrator
Tip: Put the binary in a directory that’s on your
$PATH(e.g./usr/local/bin).
Configuration
The tool looks for a file called config.yaml in the working directory (the directory where you run the command).
If the file does not exist or some fields are empty, the program will ask you for the missing values interactively and then write them back to the file (permissions set to 0600).
Sample config.yaml
source_url: "https://vault-source.example.com"
source_namespace: "team-a" # optional – omit or set to "root"
source_token: "s.XXXXXXXXXXXXXXXXX"
dest_url: "https://vault-dest.example.com"
dest_namespace: "root" # root means root namespace or no namespace.
dest_token: "s.YYYYYYYYYYYYYYYYYYY"
Fields
| Field | Required? | Description |
|---|---|---|
source_url |
✅ | Base URL of the source Vault/OpenBao (e.g. https://vault.example.com). |
source_namespace |
❌ | Namespace to use for the source request. Leave blank or set to "root" for the root namespace. |
source_token |
✅ | Token that has read access to the source data. Input is hidden when prompted. |
dest_url |
✅ | Base URL of the destination server. |
dest_namespace |
❌ | Destination namespace (same rules as source). |
dest_token |
✅ | Token that has write/create rights on the destination. Hidden input when prompted. |
Security note: The file is stored with mode
0600(owner‑read/write only). Keep it out of version control.
Usage
Run the binary without arguments to see the top‑level help:
$ ./secrets-migrator --help
Used for migrating secrets from one vault to another
For migrating secrets, policies, and roles from one vault or openbao to another
Usage:
secrets-migrator [command]
Available Commands:
KVv1 Migrate KVv1 secrets in vault/OpenBao
KVv2 Migrate KVv2 secrets in vault/OpenBao
policies Migrate policies in vault/OpenBao
help Help about any command
All sub‑commands share the same configuration handling (see above).
1. Migrate KV‑v2
./secrets-migrator KVv2
The command will ask for the source mount and destination mount (e.g. secret/ and new-secret/).
It then:
- Recursively lists all secret paths under the source mount (
metadata/…endpoint). - Creates the destination mount if it does not exist (KV v2, version set automatically).
- Reads each secret (
data/…endpoint) and writes it to the destination mount.
Example interactive session
$ ./secrets-migrator KVv2
sourceMount: secret/
destMount: migrated/
Created mount migrated/
KV2 migration done!
2. Migrate KV‑v1
./secrets-migrator KVv1
Works the same way as KV‑v2 but uses the KV‑v1 API (/v1/<mount>/…).
3. Migrate Policies
./secrets-migrator policies
Copies every policy (except the built‑in ones listed in the source) from the source to the destination.
No extra user input is required.
4. Full command chain example
# 1. Create/complete config.yaml (run any sub‑command, it will prompt)
./secrets-migrator KVv2 # fill in source/dest URLs, tokens, etc.
# 2. Migrate KV‑v2 data
./secrets-migrator KVv2
# 3. Migrate KV‑v1 data (if you have legacy mounts)
./secrets-migrator KVv1
# 4. Migrate policies
./secrets-migrator policies
How it works (high‑level)
- Configuration loading –
loadConfigreadsconfig.yaml;missingConfigasks for anything still empty. - Client creation –
newClientbuilds an OpenBao/Vault client (bao.Client) with the supplied URL, token and namespace. - Recursive listing –
listRecursivelywalks the secret tree using theListAPI, distinguishing between KV‑v1 and KV‑v2 layout. - Secret transfer –
- For KV‑v2:
putSecretsFromListreads each secret viaKVv2().Getand writes it withKVv2().Put. - For KV‑v1:
putKV1SecretsFromListdoes the same with the KV‑v1 helpers.
- For KV‑v2:
- Policy transfer –
migratePoliciesenumerates policies viaSys().ListPolicies(), fetches each definition withSys().GetPolicy, and writes it to the destination withSys().PutPolicy.
All operations are performed with a background context.Context and errors are reported to the console.
Limitations & Known Issues
| Issue | Impact | Work‑around |
|---|---|---|
| Mount creation only for KV engines | Non‑KV mounts (e.g., transit, database) are ignored. |
Add a custom mount creation step manually before running the migration. |
| No role/auth method migration | The code contains a placeholder for role migration but it is not implemented. | Export roles manually or extend the tool (see Contributing). |
| No selective migration | All secrets under the specified mount are copied; you cannot filter by path prefix. | Run the tool against a more narrowly scoped mount or edit the source after the fact. |
Plain‑text token storage in config.yaml |
Tokens are persisted in clear text on disk. | Keep the file secured (chmod 600) or delete it after the migration. |
Namespace handling defaults to "root" |
If you leave the namespace blank, the code forces "root" which may not be appropriate for all setups. |
Explicitly set an empty string ("") in the yaml to use the true root namespace. |
| No TLS verification customization | The client always uses the default Go TLS settings. | Set SSL_SKIP_VERIFY environment variable before running the binary, or modify newClient to accept a custom transport. |
Contributing
Contributions are welcome! Follow these steps:
- Fork the repository.
- Create a feature branch:
git checkout -b feature/awesome‑thing. - Make your changes, ensuring they pass
go vetandgo test(if tests are added). - Run
go fmt ./...andgolint(optional) to keep code style consistent. - Open a Pull Request describing what you changed and why.
Development tips
| Tip | Command |
|---|---|
| Run the program locally with live config prompting | go run . KVv2 |
| Lint & format | go vet ./... && go fmt ./... |
| Add a new sub‑command | Create a new cobra.Command in main.go (or split into its own file) and register it in init(). |
| Unit testing (future)** | Add tests under *_test.go and run go test ./.... (Currently the repo has no tests.) |
License
secrets-migrator is released under the MIT License. See the LICENSE file for full text.
Acknowledgements
- OpenBao – The client library (
github.com/openbao/openbao/api/v2) provides a drop‑in replacement for HashiCorp Vault's Go SDK. - Cobra – CLI framework used for command parsing (
github.com/spf13/cobra). - Viper/YAML – Configuration handling (
go.yaml.in/yaml/v4).
Happy migrating! 🎉