Skip to content

Secrets Management

Overview

The Secrets Management module provides a secure and centralized system for handling sensitive data like API keys, database passwords, and authentication tokens. Instead of hardcoding secrets in configuration files or environment variables, Athomic treats them as references to be resolved at runtime from a secure, external backend.

This approach significantly improves the security posture of the application and is a best practice for modern cloud-native development.

Key Features

  • Multiple Backends: Out-of-the-box support for fetching secrets from HashiCorp Vault, environment variables, or local files.
  • Runtime Resolution: Secrets are fetched "just-in-time" only when they are first needed, not at application startup. This reduces the time they are held in memory and supports dynamic secret rotation.
  • Extensible Provider Model: The system is built on a protocol, making it easy to add new secret providers in the future.
  • Resilient and Observable: All interactions with the secrets backend are fully instrumented with tracing and metrics.

The Just-in-Time Resolution Flow

The core of the secrets module is its just-in-time (JIT) resolution mechanism. Here is how it works:

  1. Configure a Reference: In your settings.toml, you define a reference to a secret instead of the secret itself. This is done using a path and key structure.

    ```toml

    Instead of this:

    password = "my-secret-password" # pragma: allowlist secret

    You do this:

    password = { path = "database/mongo/prod", key = "password" } ```

  2. Startup & Proxy Replacement: When the application starts, the SecretsManager service traverses the entire configuration object. When it finds a secret reference, it replaces it in-memory with a lightweight CredentialProxy object. This proxy knows how to fetch the secret but doesn't do it yet.

  3. First Access & Resolution: Later, when a component (like a database connector) needs the password, it tries to access the value. This triggers the CredentialProxy, which then makes a live, asynchronous call to the configured secrets provider (e.g., Vault) to fetch the actual secret value.

This lazy-loading approach is highly secure and efficient.


Available Providers

VaultSecretsProvider

The recommended provider for production environments. It connects to a HashiCorp Vault server and supports both Token and AppRole authentication methods. It also includes a background task for automatic, continuous renewal of the Vault token, ensuring long-running services don't lose access.

EnvSecretsProvider

This provider reads secrets from environment variables. It constructs the variable name to look for based on the reference's path and key. For example, a reference with path="database/mongo" and key="password" would resolve to the environment variable NALA_SECRET_DATABASE_MONGO_PASSWORD.

FileSecretsProvider

This provider reads secrets from files on the local filesystem. This is particularly useful for containerized environments like Docker and Kubernetes, where secrets are often mounted as files into the container (e.g., /var/run/secrets/db-password).


Configuration

The secrets provider is configured under the [security.secrets] section in your settings.toml.

Vault Example

[default.security.secrets]
backend = "vault"

  [default.security.secrets.provider]
  addr = "http://localhost:8200"
  auth_method = "token"
  token = "my-root-token" # Use a secret reference in production!

Environment Variable Example

[default.security.secrets]
backend = "env"

File Example

[default.security.secrets]
backend = "file"
  [default.security.secrets.provider]
  base_path = "/var/run/secrets" # Base directory for secret files

API Reference

nala.athomic.security.secrets.manager.SecretsManager

Orchestrates the resolution of external secret references within application settings.

This manager is responsible for traversing the entire AppSettings structure and replacing static SecretValue objects (references to external backends like Vault) with lazy, asynchronous CredentialProxy objects. This ensures that actual secret values are only fetched just-in-time when first accessed by a consuming component, supporting secure, runtime secret rotation.

Attributes:

Name Type Description
app_settings AppSettings

The validated application configuration object.

secrets_provider SecretsProtocol

The concrete provider (e.g., Vault, Env) used to fetch the raw secret data.

__init__(settings, secrets_provider=None)

Initializes the SecretsManager.

Parameters:

Name Type Description Default
settings AppSettings

The application's root configuration object.

required
secrets_provider SecretsProtocol

The concrete provider (e.g., Vault, Env) used to fetch the raw secret data.

None

resolve_settings() async

Traverses the entire AppSettings object and replaces all SecretValue instances with CredentialProxy instances that fetch the secret just-in-time.

This method is typically called early in the application's startup sequence after configuration is loaded but before services connect.

nala.athomic.security.secrets.protocol.SecretsProtocol

Bases: BaseServiceProtocol, Protocol

Defines the core contract for a secrets provider implementation.

This protocol adheres to the Dependency Inversion Principle (DIP), decoupling the SecretsManager and consumers of credentials from the concrete source (e.g., HashiCorp Vault, environment variables). By inheriting from BaseServiceProtocol, all providers gain automatic lifecycle management (connect/close) by the framework.

get_secret(path, key) abstractmethod async

Retrieves a specific secret value as raw bytes from the configured backend.

Parameters:

Name Type Description Default
path str

The logical path or mount point where the secret resides (e.g., 'database/production').

required
key str

The specific key within the path that holds the secret value (e.g., 'password').

required

Returns:

Type Description
Optional[bytes]

Optional[bytes]: The resolved secret value as raw bytes, or None if the secret is not found.

Raises:

Type Description
SecretNotFoundError

If the path or key does not exist.

SecretAccessError

For permission or other access-related failures.

nala.athomic.credentials.proxy.CredentialProxy

A lazy-loading proxy for a secret value.

It holds a reference to the secrets provider and the secret's location (path/key). It fetches the secret's actual value "just-in-time" only when its get() method is explicitly awaited. This ensures that the application always uses the most up-to-date credential, supporting secret rotation.

get() async

Fetches the secret's value just-in-time from the secrets provider.

Returns:

Type Description
Optional[Any]

A Resolved secret value, or None if not found.

nala.athomic.security.secrets.providers.vault_secrets_provider.VaultSecretsProvider

Bases: SecretsBase

A secrets provider that fetches secrets from HashiCorp Vault.

This provider implements the SecretsProtocol using the synchronous hvac client, wrapping all blocking operations in asyncio.to_thread. It handles: 1. Authentication: Supports AppRole and Token methods. 2. Token Renewal: Runs a background loop (_run_loop) to automatically renew the token before expiration. 3. Lifecycle: Manages authentication (_connect) and token revocation (_close).

Attributes:

Name Type Description
settings

The Vault-specific configuration settings.

client

The underlying synchronous hvac.Client instance.

__init__(settings)

Initializes the VaultSecretsProvider.

Parameters:

Name Type Description Default
settings SecretsSettings

The configuration for the secrets module.

required