SecretMana (secret_mana v0.1.1)

View Source

SecretMana is a module for managing encrypted secrets build to support various backends. Currently only age (https://github.com/FiloSottile/age) is supported.

This module is a wrapper for the SecretMana.Backend:

  • Read encrypted secrets with support for nested key access
  • Edit secrets using your preferred editor
  • Encrypt/decrypt files in supported formats
  • Generate keys for storing secrets
  • Install backend

Examples

import SecretMana

# Read all secrets
secrets = read!()

# Read a specific nested key
password = read!(["database", "password"])

# Edit secrets in your preferred editor
:ok = SecretMana.edit(config)

# Encrypt a new secrets file
:ok = SecretMana.encrypt(config, "new_secrets.json")

# Generate a new key pair
:ok = SecretMana.gen_key(config)

# Install age binary
:ok = SecretMana.install(config)

Summary

Functions

Release step function that copies secrets from development directories into the release.

Opens the decrypted secrets in your editor for modification, then re-encrypts them when done.

Encrypts a file using the age public key.

Generates a new age key pair in the configured directory.

Sets the private key for age encryption at runtime.

Downloads and installs the age binary for the current platform.

Reads and decrypts secrets from the configured secret file.

Callback implementation for Application.start/2.

Functions

copy_secrets_for_release(release, embed_private_key? \\ false)

Release step function that copies secrets from development directories into the release.

This function can be used as a release step in mix.exs to automatically copy encrypted secrets and keys from secrets/<env>/ into the release's config/secrets/ directory during the build process. Only the target environment's secrets are copied to avoid including secrets from other environments in the release.

Security Note:

For enhanced security, set embed_private_key? to false and use the runtime configuration approach with SecretMana.generate_private_key_file/1 in runtime.exs instead of embedding the private key in the release artifact.

Usage in mix.exs:

# Option 1: Include private key in release (less secure)
def project do
  [
    # ... other config
    releases: [
      my_app: [
        steps: [:assemble, &SecretMana.copy_secrets_for_release/1]
      ]
    ]
  ]
end

# Option 2: Exclude private key from release (more secure)
def project do
  [
    # ... other config
    releases: [
      my_app: [
        steps: [:assemble, fn release ->
          SecretMana.copy_secrets_for_release(release, false)
        end]
      ]
    ]
  ]
end

Runtime Configuration (when embed_private_key? is false):

# runtime.exs - Option 1: Using SecretMana module
SecretMana.generate_private_key_file(System.get_env("SECRET_MANA_PRIVATE_KEY"))

# runtime.exs - Option 2: Using AgeBackend directly
SecretMana.generate_private_key_file(System.get_env("SECRET_MANA_PRIVATE_KEY"))

Directory Structure:

# Development:
config/secrets/dev/age.key
config/secrets/dev/age.pub
config/secrets/dev/age.enc

# Release (when embed_private_key? is true):
lib/my_app-x.x.x/config/secrets/age.key
lib/my_app-x.x.x/config/secrets/age.pub
lib/my_app-x.x.x/config/secrets/age.enc

# Release (when embed_private_key? is false):
lib/my_app-x.x.x/config/secrets/age.pub
lib/my_app-x.x.x/config/secrets/age.enc

Parameters

  • release - The Mix.Release struct
  • embed_private_key? - Whether to include the private key in the release (defaults to false for security)

Returns

  • release - The unmodified release struct (following release step convention)

edit(config)

Opens the decrypted secrets in your editor for modification, then re-encrypts them when done.

Uses the EDITOR environment variable to determine which editor to use, falls back to vim if not set.

Parameters

  • config - The SecretMana configuration struct

Returns

  • :ok - Successfully edited and re-encrypted secrets

Examples

:ok = SecretMana.edit(config)

encrypt(config, file, check_file_type \\ true)

Encrypts a file using the age public key.

The file must be in the format specified by the configuration (JSON or YAML).

Parameters

  • config - The SecretMana configuration struct
  • file - Path to the file to encrypt
  • check_file_type - Whether to validate the file format matches the configured format, defaults to true

Returns

  • :ok - Successfully encrypted the file

Examples

:ok = SecretMana.encrypt(config, "secrets.json")
:ok = SecretMana.encrypt(config, "secrets.json", false)

gen_key(config)

Generates a new age key pair in the configured directory.

Creates both a private key file and a public key file.

Parameters

  • config - The SecretMana configuration struct

Returns

  • :ok - Successfully generated key pair

Examples

:ok = SecretMana.gen_key(config)

generate_private_key_file(private_key)

Sets the private key for age encryption at runtime.

This function allows you to configure the private key dynamically at runtime, typically called from runtime.exs. This is more secure than embedding the private key in the release artifact.

Parameters

  • private_key - The private key content as a string

Returns

  • :ok - Successfully configured the private key

Examples

# In runtime.exs
SecretMana.generate_private_key_file(System.get_env("SECRET_MANA_PRIVATE_KEY"))

install(config)

Downloads and installs the age binary for the current platform.

Automatically detects the correct version based on the current system architecture.

Parameters

  • config - The SecretMana configuration struct

Returns

  • :ok - Successfully installed age binary

Examples

:ok = SecretMana.install(config)

read!(access_path \\ nil)

(macro)

Reads and decrypts secrets from the configured secret file.

Parameters

  • access_path - Optional list of keys to traverse the secret structure, defaults to nil which returns the entire secret

Returns

  • term() - The decrypted secrets

Examples

import SecretMana

# Read all secrets
secrets = read!()

# Read a specific nested key
password = read!(["database", "password"])

start(_, _)

Callback implementation for Application.start/2.