C5Store for Rust
C5Store is a Rust library providing a unified store for configuration and secrets. It aims to be a single point of access for your application's configuration needs, consolidating values from various sources (like YAML and TOML files or directories), handling environment variable overrides, managing secrets securely via built-in decryption, and allowing dynamic loading through providers.
The core idea is to simplify configuration management in complex applications by offering a hierarchical, type-aware, extensible, and environment-aware configuration layer.
Key Features
- Unified Access: Retrieve configuration values using simple dot-notation key paths (e.g.,
database.connection.pool_size). - Multiple Sources & Merging: Load configuration from YAML and TOML files, or entire directories containing such files. Configuration is intelligently merged based on load order.
- Environment Variable Overrides: Seamlessly override any configuration value using environment variables (e.g.,
C5_DATABASE__HOST=...). Values are intelligently parsed (bool, int, float, string). - Type-Safe Retrieval: Get values converted directly into expected Rust types using
get_into::<T>(), returning aResultfor robust error handling. - Flexible Struct Deserialization: Deserialize configuration sections directly into your custom Rust structs using
get_into_struct::<T>(). Supports both nested maps (from files) and flattened key structures (e.g., from environment variables). - Integrated Secrets Management (Optional Feature):
- Transparently decrypt secrets defined within configuration files using the
.c5encvalkey. - Supports pluggable decryption algorithms (includes
base64andecies_x25519). - Securely load decryption keys from files (including
.pem), environment variables, orsystemdcredentials (secrets_systemdfeature on Linux).
- Transparently decrypt secrets defined within configuration files using the
- Value Providers: Defer loading of specific configuration sections to external sources (e.g., files) using a provider system. Includes a built-in
C5FileValueProvider. - Periodic Refresh: Value providers can be configured to automatically refresh their data at specified intervals.
- Change Notifications: Subscribe to changes in configuration values at specific key paths or their ancestors using
subscribe(basic) orsubscribe_detailed(includes old value). Notifications are debounced. - Hierarchical Structure: Access nested configuration values easily and create "branches" for context-specific views of the configuration using
branch(). - Source Tracking: Identify the origin of any configuration value (File, Env Var, Provider) using
get_source(). .envFile Support (Optional Feature): Load environment variables from.envfiles at startup.- Extensible: Designed with traits for custom value providers and secret decryptors.
- Telemetry Hooks: Basic interfaces for integrating custom logging and statistics recording.
- Optional Feature Flags: Fine-tune dependencies (
dotenv,toml,secrets,secrets_systemd).
Getting Started
-
Add Dependency: Add
c5storeto yourCargo.toml. Enable optional features as needed:[] # Use the latest version # On Linux, "secrets_systemd" is enabled by default. = "0.5.0" # Example enabling .env file support (optional) # c5store = { version = "0.5.0", features = ["dotenv"] } # Example disabling default secrets features (optional, smaller binary) # c5store = { version = "0.5.0", default-features = false } # On non-Linux, to use the systemd types for cross-compilation, enable it explicitly: # c5store = { version = "0.5.0", features = ["secrets_systemd"] } # Other necessary dependencies = { = "1", = ["derive"] } -
Basic Usage:
use ; // Import types use PathBuf; use Deserialize; // Needed for get_into_struct // Example struct for deserialization
Configuration Files & Directories
C5Store loads configuration from specified paths in the create_c5store call. These paths can be:
- YAML files (
.yaml,.yml) - TOML files (
.toml) - Requirestomlfeature. - Directories: All files within the directory with supported extensions (
.yaml,.yml,.toml) will be loaded and merged alphabetically.
Configuration sources are merged in the order they are processed (files listed explicitly first, then files within directories alphabetically). Values from later sources override values from earlier sources for the same key path. Maps (objects/tables) are merged recursively; other types are replaced entirely.
Example (config/common.yaml):
service:
name: MyAwesomeApp
port: 8080
database:
host: prod-db.example.com
pool_size: 50
Example (config/local.toml):
# Overrides common.yaml values
# Assumes local.toml is processed after common.yaml
= 9090 # Overrides port 8080
[]
= "localhost" # Overrides prod host
= "dev_user" # Adds a new key
# service.name and database.pool_size are inherited from common.yaml
Environment Variables & Loading Priority
C5Store supports overriding configuration values using environment variables after all files have been loaded and merged.
- Prefix: Variables starting with
C5_(by default) are processed. - Separator: Double underscore (
__) is used to denote nesting levels (e.g.,C5_DATABASE__HOSTmaps todatabase.host). - Case: The key derived from the environment variable is converted to lowercase (e.g.,
C5_SERVICE__NAMEbecomesservice.name). - Value Parsing: Environment variable values are parsed into the most appropriate
C5DataValuetype (Boolean, Integer, Float, String).
Loading Priority (Highest to Lowest):
- Environment Variables (e.g.,
C5_...) - Configuration Files/Directories (processed in the order specified/discovered, with later files/directories overriding earlier ones).
Optional Features (dotenv, toml, secrets, secrets_systemd)
C5Store uses Cargo features to enable optional functionality:
dotenv:- Enables loading environment variables from a
.envfile at startup usingC5StoreOptions::dotenv_path. - Requires the
dotenvycrate. .envfiles are loaded before process environment variables are read, allowing process variables to override.envvariables.
- Enables loading environment variables from a
toml:- Enables parsing of
.tomlconfiguration files. - Requires the
tomlcrate.
- Enables parsing of
secrets:- Enables all secrets management functionality (loading
.c5encval,SecretOptions,SecretKeyStore, decryptors). - Requires crypto dependencies (
ecies_25519,curve25519-parser,sha2). - Enabled by default.
- Enables all secrets management functionality (loading
secrets_systemd:- Enables loading of decryption keys from
systemd's secure credential store. - Depends on the
secretsfeature. - Enabled by default on Linux targets.
- Enables loading of decryption keys from
full:- Convenience feature to enable
dotenv,toml,secrets, andsecrets_systemd.
- Convenience feature to enable
[]
# Minimal - no .env, no secrets, no toml
# c5store = { version = "0.5.0", default-features = false }
# Default - secrets and yaml enabled. On Linux, also enables secrets_systemd.
# c5store = "0.5.0"
# Enable all common features
= { = "0.5.0", = ["full"] }
# Just enable .env support
# c5store = { version = "0.5.0", default-features = false, features = ["dotenv"] }
Secrets Management (secrets feature)
(Requires the secrets feature, enabled by default).
Secrets are defined using a special .c5encval key (configurable via SecretOptions::secret_key_path_segment) within your configuration.
Structure:
# YAML Example
some_secret_key:
.c5encval:
# TOML Example
# [some_secret_key]
# ".c5encval" = ["<algorithm>", "<ref_key_name>", "<base64_encrypted_data>"]
<algorithm>: Name of registeredSecretDecryptor(e.g.,"base64","ecies_x25519").<ref_key_name>: Name used to look up the decryption key in theSecretKeyStore.<base64_encrypted_data>: The secret value, encrypted and then Base64 encoded.
Key Loading Methods:
You can load decryption keys into c5store from three sources, configured via SecretOptions.
- From a Directory (
secret_keys_path):- Loads key files from a specified directory. The filename (without extension) becomes the
ref_key_name.
- Loads key files from a specified directory. The filename (without extension) becomes the
- From Environment Variables (
load_secret_keys_from_env):- Loads keys from environment variables. The variable name (minus a prefix) becomes the
ref_key_name.
- Loads keys from environment variables. The variable name (minus a prefix) becomes the
- From
systemdCredentials (load_credentials_from_systemd):- (Linux-Only,
secrets_systemdfeature) Securely loads keys that have been provisioned bysystemd. This is the recommended method for production. See the dedicated section below.
- (Linux-Only,
Example Configuration (SecretOptions):
use ;
use ;
use EciesX25519;
use PathBuf;
// ... inside setup code ...
let mut options = default;
// Gate configuration if secrets might be disabled
let config_paths = vec!;
let = create_c5store?;
// Retrieving the secret automatically attempts decryption
match store.
Secure Key Loading with systemd (secrets_systemd feature)
(Requires the secrets_systemd feature, enabled by default on Linux).
For production deployments on Linux, c5store can securely load its decryption key directly from the systemd credential store. This avoids having plaintext private keys on the filesystem.
Administrator Workflow:
- Encrypt the Private Key for
systemd: On the target server, usesystemd-credsto encrypt your private key file (e.g.,my_app.c5.key.pem). The namemyapp.private.keyis the credential name.| - Configure the
systemdService: Edit your application's service file to use theLoadCredential=directive. The name must match the one used above.
Run# /etc/systemd/system/myapp.service [Service] DynamicUser=yes LoadCredential=myapp.private.key ExecStart=/usr/bin/myapp-serversystemctl daemon-reloadafter saving.
Application Configuration:
Enable the feature in your application's C5StoreOptions.
use SystemdCredential;
// ... inside setup code ...
let mut options = default;
options.secret_opts.load_credentials_from_systemd = vec!;
let = create_c5store?;
At runtime, systemd will securely provide the decrypted key to c5store, which will then use it to decrypt any secrets in your configuration that reference the "my_app" key name.
Value Providers
Value providers allow parts of your configuration to be loaded dynamically from external sources (like files, databases, or remote services). Mark a section in YAML/TOML with a .provider key specifying the provider's name. Register providers using C5StoreMgr::set_value_provider. C5Store includes a C5FileValueProvider for loading content from files specified in the configuration.
Example (config/providers.yaml):
files:
large_config:
.provider: resource # Name matches registered provider
path: large_data.json # Path relative to provider base or absolute
format: json # Instruct provider to parse as JSON
raw_template:
.provider: resource
path: template.txt
# format: raw (default)
# encoding: utf8 (default)
Registration:
use C5FileValueProvider;
// ... inside main after create_c5store ...
// Create provider, setting its base path for relative 'path' values
let file_provider = default; // Use built-in JSON/YAML deserializers
// Register with the store manager, optionally enable refresh
store_mgr.set_value_provider;
// Now access values loaded by the provider
match store.
Change Notifications
Subscribe to configuration changes using subscribe (new value only) or subscribe_detailed (new and old value). Listeners are called after a configurable debounce period (C5StoreOptions::change_delay_period).
// Subscribe to changes under the 'database' prefix
store.subscribe_detailed;
// Programmatic changes (or provider refreshes) will trigger notifications later
// store.set("database.pool_size", 100.into()); // Example change
License
This project is licensed under the Mozilla Public License Version 2.0 (MPL-2.0). See LICENSE file for details.
Contributing
Contributions welcome! Please open issues or PRs on the project repository.
Changelog
See CHANGELOG.md for a history of notable changes.