lilydocs
HTTP APIWebSocketConsumer
Fundamentals3 min read

Secrets

Resolve credentials at startup through SecretResolver or file references.

Use configuration references for deployment-specific values such as broker passwords. Lily resolves references at startup, validates typed configuration, and publishes an immutable snapshot. Your application chooses how its secret provider authenticates and which store it accesses.

Supported reference formats#

TOML
[rabbitmq.consumer]
password = "${secret:broker.password}"

This is a configuration fragment: replace only password in the first application's consumer section. ${secret:broker.password} calls the application's SecretResolver. ${file:/run/secrets/rabbitmq_password} reads a bounded UTF-8 value from an absolute file path; files are limited to 1–8192 bytes. The path must be canonical and absolute, and the target a regular file rather than a symlink. The file must contain exactly one nonempty line: one trailing LF or CRLF is removed, while embedded newlines, CR, or NUL are rejected.

The reference must occupy the entire field value. prefix-${secret:key} is not a template, and ${env:NAME} is not a secret reference scheme supported by this API. Use the LILY__... override path described in configuration. Keep topology identities such as queues, exchanges, and routing keys as explicit values without credentials.

Connect the application resolver#

TOML
lilyrs = { version = "0.1.0", features = ["consumer", "config"] }

The fragment below is a small adapter that reads one allowlisted secret from the environment. This application code performs the environment lookup, rather than Lily's reference parser. To use a secret store, call its client through the same trait.

Rust
use lilyrs::{
    config::{ConfigError, SecretResolver},
    consumer::{Consumer, ConsumerError},
};

struct ApplicationSecrets;

#[lilyrs::consumer::async_trait::async_trait]
impl SecretResolver for ApplicationSecrets {
    async fn resolve(&self, key: &str) -> Result<String, ConfigError> {
        if key != "broker.password" {
            return Err(ConfigError::KeyNotFound(key.to_owned()));
        }
        std::env::var("BROKER_PASSWORD").map_err(|_| ConfigError::SecretResolveError {
            key: key.to_owned(),
            error: "required secret is unavailable".to_owned(),
        })
    }
}

async fn run_with_secrets() -> Result<(), ConsumerError> {
    Consumer::builder().secret_resolver(ApplicationSecrets).run().await
}

Call this function inside a Tokio runtime in the binary containing your handlers. The resolver is connected before configuration loads. Error messages do not contain secret values. This trait does not automatically install a Vault, AWS, or other vendor adapter.

Use a caller-owned container#

Combining .container(...) with .secret_resolver(...) rejects the ownership conflict. For an external container, attach the resolver to a seeded ConfigService instead. This fragment uses the preceding ApplicationSecrets type:

Rust
use std::sync::Arc;
use lilyrs::{
    config::{ConfigOptions, ConfigService},
    consumer::{ApplicationContainer, ConsumerBuilder},
};

async fn caller_owned_consumer()
    -> Result<(ConsumerBuilder, Arc<ApplicationContainer>), Box<dyn std::error::Error>>
{
    let config = ConfigService::with_options_and_secret_resolver(
        ConfigOptions::production("./lily.toml"), ApplicationSecrets,
    );
    let container = Arc::new(ApplicationContainer::builder()
        .seed_singleton(config).build().await?);
    Ok((lilyrs::consumer::Consumer::builder().container(Arc::clone(&container)), container))
}

The function returns resources to the parent owner without closing them. After Consumer terminates, inspect queue-user completion and the shutdown report before closing the external container. Managed Consumer covers this ownership flow.

Rotation and safe diagnostics#

Once the snapshot is loaded, the resolver is not repeatedly called for hot rotation. A secret change requires a new application instance or startup. resolve_versioned can carry provider generation or lease metadata; this metadata does not create a background refresh mechanism.

ConfigSnapshot::redacted_values() provides the redacted diagnostic view. Avoid logging raw typed configuration, resolved strings, or secret-store responses. Secret resolution supplies broker connection credentials; authenticating the user behind a message is a separate guard or application policy.

Sources for this page v0.1.0

This guide is based on the reviewed framework commit.

QUICK START
Documentation
Source code