Multi-Vault

Registering a single machine with multiple SikkerKey vaults.

A machine can be registered with multiple SikkerKey vaults. Each vault gets its own directory, keypair, and identity file under ~/.sikkerkey/vaults/. The SDK resolves which identity to use based on the vault ID you provide.

Directory Structure

After registering with two vaults, the filesystem looks like:

~/.sikkerkey/
  vaults/
    vault_a1b2c3d4e5f6g7h8/
      identity.json
      private.pem
    vault_x9y8z7w6v5u4t3s2/
      identity.json
      private.pem

Each vault has its own Ed25519 keypair. The machine authenticates as a different machine identity to each vault. There is no shared key material between vaults.

Registering with Multiple Vaults

Run the bootstrap command once per vault. Each vault owner generates their own token from their dashboard:

# Register with first vault
curl -sSL https://api.sikkerkey.com/v1/bootstrap/TOKEN_A | sh

# Register with second vault
curl -sSL https://api.sikkerkey.com/v1/bootstrap/TOKEN_B | sh

Each registration creates a separate directory. They do not interfere with each other.

SDK Usage

Specifying the Vault

When a machine is registered with multiple vaults, you must specify which vault to use:

val production = SikkerKey("vault_a1b2c3d4e5f6g7h8")
val staging = SikkerKey("vault_x9y8z7w6v5u4t3s2")

val prodDb = production.getSecret("sk_db_prod")
val stagingDb = staging.getSecret("sk_db_staging")
production = SikkerKey("vault_a1b2c3d4e5f6g7h8")
staging = SikkerKey("vault_x9y8z7w6v5u4t3s2")

prod_db = production.get_secret("sk_db_prod")
staging_db = staging.get_secret("sk_db_staging")

The SDK looks up ~/.sikkerkey/vaults/{vaultId}/identity.json for the specified vault ID. If the vault ID does not start with vault_, the SDK prepends it automatically.

Single Vault (Auto-detect)

If only one vault is registered, the SDK auto-detects it. You don't need to specify the vault ID:

val sk = SikkerKey()
val secret = sk.getSecret("sk_a1b2c3d4e5")

If multiple vaults are registered and you do not specify which one, the SDK throws an error listing the available vaults.

Explicit Path

You can point the SDK directly at an identity file:

val sk = SikkerKey("/home/deploy/.sikkerkey/vaults/vault_a1b2c3d4e5f6g7h8/identity.json")

This bypasses all automatic resolution.

Environment Variable

Set SIKKERKEY_IDENTITY to the path of an identity file:

export SIKKERKEY_IDENTITY=$HOME/.sikkerkey/vaults/vault_a1b2c3d4e5f6g7h8/identity.json

The SDK checks this environment variable if no vault ID or path is provided.

You can also set SIKKERKEY_HOME to change the base directory (defaults to ~/.sikkerkey).

Identity Resolution Order

The SDK resolves the identity file in this order:

  1. Explicit path: if the argument starts with / or contains identity.json, it is treated as a file path
  2. Vault ID: looks up ~/.sikkerkey/vaults/{vaultId}/identity.json
  3. Environment variable: reads SIKKERKEY_IDENTITY
  4. Auto-detect: if exactly one vault directory exists under ~/.sikkerkey/vaults/, uses it

If none of these resolve to a valid identity file, the SDK throws an error with the paths it checked.

Listing Registered Vaults

val vaults = SikkerKey.listVaults()
// ["vault_a1b2c3d4e5f6g7h8", "vault_x9y8z7w6v5u4t3s2"]
vaults = SikkerKey.list_vaults()

This scans ~/.sikkerkey/vaults/ for directories containing an identity.json file and returns their names.

Isolation Between Vaults

Each vault registration is fully independent:

  • Separate Ed25519 keypair (different private key, different public key)
  • Separate machine ID
  • Separate approval status (must be approved in each vault)
  • Separate project memberships and secret grants
  • Separate encryption keys (each vault's projects have their own)

Compromising one vault's machine identity has zero effect on other vaults. The private keys are different and the machine IDs are different.