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:
-
Configure a Reference: In your
settings.toml, you define a reference to a secret instead of the secret itself. This is done using apathandkeystructure.```toml
Instead of this:
password = "my-secret-password" # pragma: allowlist secret
You do this:
password = { path = "database/mongo/prod", key = "password" } ```
-
Startup & Proxy Replacement: When the application starts, the
SecretsManagerservice traverses the entire configuration object. When it finds a secret reference, it replaces it in-memory with a lightweightCredentialProxyobject. This proxy knows how to fetch the secret but doesn't do it yet. -
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 |
__init__(settings)
Initializes the VaultSecretsProvider.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
settings
|
SecretsSettings
|
The configuration for the secrets module. |
required |