Vaultx.Secrets.KV (Vaultx v0.7.0)

View Source

Unified Key-Value secrets engine interface for HashiCorp Vault.

This module provides a unified interface for both KV v1 and KV v2 engines, automatically detecting the engine version and delegating operations to the appropriate implementation. It offers a seamless experience for working with KV secrets regardless of the underlying engine version.

Features

  • Automatic Version Detection: Detects KV v1 vs v2 automatically
  • Unified API: Same interface works with both versions
  • Version-Specific Features: Advanced features available when supported
  • Graceful Degradation: Unsupported operations return clear errors
  • Performance Optimized: Caches version detection results
  • Convenience Functions: Additional utility functions for common operations

Supported Operations

Core Operations (Both v1 and v2)

  • read/2 - Read secrets from any path
  • write/3 - Write secrets to any path
  • delete/2 - Delete secrets (soft delete in v2)
  • list/2 - List secret keys at a path

KV v2 Specific Operations

  • read_metadata/2 - Read secret metadata and version history
  • write_metadata/3 - Update secret metadata without creating new version
  • delete_metadata/2 - Permanently delete all versions and metadata
  • undelete/2 - Restore soft-deleted versions
  • destroy/2 - Permanently destroy specific versions
  • list_versions/2 - List all versions of a secret

Convenience Functions

Usage Examples

# Basic operations (work with both v1 and v2)
{:ok, secret} = Vaultx.Secrets.KV.read("myapp/config", mount_path: "secret")
{:ok, result} = Vaultx.Secrets.KV.write("myapp/config", %{"key" => "value"}, mount_path: "secret")
:ok = Vaultx.Secrets.KV.delete("myapp/config", mount_path: "secret")
{:ok, keys} = Vaultx.Secrets.KV.list("myapp/", mount_path: "secret")

# KV v2 specific operations (gracefully fail on v1)
{:ok, secret} = Vaultx.Secrets.KV.read_version("myapp/config", 2, mount_path: "secret")
{:ok, result} = Vaultx.Secrets.KV.write_cas("myapp/config", %{"key" => "value"}, 1, mount_path: "secret")
:ok = Vaultx.Secrets.KV.undelete("myapp/config", versions: [1, 2], mount_path: "secret")

# Metadata operations (KV v2 only)
{:ok, metadata} = Vaultx.Secrets.KV.read_metadata("myapp/config", mount_path: "secret")
:ok = Vaultx.Secrets.KV.write_metadata("myapp/config", %{"max_versions" => 5}, mount_path: "secret")

# Convenience functions
true = Vaultx.Secrets.KV.exists?("myapp/config", mount_path: "secret")
{:ok, ["username", "password"]} = Vaultx.Secrets.KV.keys("myapp/config", mount_path: "secret")
{:ok, "admin"} = Vaultx.Secrets.KV.get_field("myapp/config", "username", mount_path: "secret")

Version Detection

The module automatically detects the KV engine version by:

  1. Checking engine mount information via /sys/mounts
  2. Caching the result for subsequent operations
  3. Falling back to API behavior analysis if needed

API Compliance

This implementation fully complies with HashiCorp Vault's official KV API:

Configuration

# KV v1 engine
vault secrets enable -version=1 -path=kv-v1 kv

# KV v2 engine (default for new installations)
vault secrets enable -version=2 -path=secret kv

Error Handling

Operations return standardized errors with clear messages:

{:error, %Vaultx.Base.Error{
  type: :unsupported_operation,
  message: "KV v1 does not support versioning",
  details: %{operation: :read, version: 2}
}}

Performance Considerations

  • Version detection results are cached per mount path
  • Cache can be cleared with clear_version_cache/1
  • First operation per mount may be slightly slower due to detection

Summary

Functions

Initializes the KV module and sets up version detection cache. This is called automatically when the module is loaded.

Clears the version detection cache for a specific mount path or all mount paths.

Delete specific versions of a secret (KV v2 only).

Detects the KV engine version for a given mount path.

Check if a secret exists at the given path.

Get a specific field from a secret.

Get secret keys (field names) without values.

Get the latest version of a secret.

Read a secret from KV store with version support.

Update a single field in a secret (preserves other fields).

Write a secret to KV store with CAS (Check-And-Set) support.

Functions

__init__()

Initializes the KV module and sets up version detection cache. This is called automatically when the module is loaded.

clear_version_cache(mount_path)

@spec clear_version_cache(String.t() | :all) :: :ok

Clears the version detection cache for a specific mount path or all mount paths.

Parameters

  • mount_path - Mount path to clear cache for, or :all to clear all

Examples

:ok = Vaultx.Secrets.KV.clear_version_cache("secret")
:ok = Vaultx.Secrets.KV.clear_version_cache(:all)

configure(config, opts \\ [])

delete(path, opts \\ [])

delete_versions(path, versions, opts \\ [])

@spec delete_versions(String.t(), [pos_integer()], keyword()) ::
  :ok | {:error, Vaultx.Base.Error.t()}

Delete specific versions of a secret (KV v2 only).

This is an alias for delete/2 with explicit versions parameter.

detect_kv_version(mount_path, opts \\ [])

@spec detect_kv_version(
  String.t(),
  keyword()
) :: {:ok, 1 | 2} | {:error, Vaultx.Base.Error.t()}

Detects the KV engine version for a given mount path.

Parameters

  • mount_path - The mount path to detect version for
  • opts - Operation options

Returns

  • {:ok, 1} - KV v1 engine detected
  • {:ok, 2} - KV v2 engine detected
  • {:error, error} - Detection failed

Examples

{:ok, 1} = Vaultx.Secrets.KV.detect_kv_version("kv-v1")
{:ok, 2} = Vaultx.Secrets.KV.detect_kv_version("secret")

exists?(path, opts \\ [])

@spec exists?(
  String.t(),
  keyword()
) :: boolean()

Check if a secret exists at the given path.

get_field(path, field, opts \\ [])

@spec get_field(String.t(), String.t(), keyword()) ::
  {:ok, any()} | {:error, Vaultx.Base.Error.t()}

Get a specific field from a secret.

keys(path, opts \\ [])

@spec keys(
  String.t(),
  keyword()
) :: {:ok, [String.t()]} | {:error, Vaultx.Base.Error.t()}

Get secret keys (field names) without values.

list(path, opts \\ [])

read(path, opts \\ [])

read_latest(path, opts \\ [])

@spec read_latest(
  String.t(),
  keyword()
) :: {:ok, map()} | {:error, Vaultx.Base.Error.t()}

Get the latest version of a secret.

This is a convenience function that reads the latest version.

read_version(path, version, opts \\ [])

@spec read_version(String.t(), pos_integer(), keyword()) ::
  {:ok, map()} | {:error, Vaultx.Base.Error.t()}

Read a secret from KV store with version support.

This is an alias for read/2 with explicit version parameter.

update_field(path, field, value, opts \\ [])

@spec update_field(String.t(), String.t(), any(), keyword()) ::
  {:ok, map()} | {:error, Vaultx.Base.Error.t()}

Update a single field in a secret (preserves other fields).

write(path, data, opts \\ [])

write_cas(path, data, cas, opts \\ [])

@spec write_cas(String.t(), map(), pos_integer(), keyword()) ::
  {:ok, map()} | {:error, Vaultx.Base.Error.t()}

Write a secret to KV store with CAS (Check-And-Set) support.

This is an alias for write/3 with explicit cas parameter.