Files
harmony/fleet/harmony-fleet-auth/src/lib.rs
2026-05-20 13:41:40 -04:00

66 lines
2.4 KiB
Rust

//! Shared NATS auth plumbing for fleet processes.
//!
//! Two consumers today:
//!
//! - **`harmony-fleet-agent`** — reads `[credentials]` from
//! `/etc/fleet-agent/config.toml`. Per-device Zitadel machine user
//! with the `device` role.
//! - **`harmony-fleet-operator`** — reads the same TOML shape from a
//! single env var (the env var's value is the TOML snippet for the
//! `[credentials]` table). Singleton machine user with the
//! `fleet-admin` role.
//!
//! Both deserialize into the **same** [`CredentialsSection`], factory
//! into the **same** [`CredentialSource`], and use the **same**
//! [`connect_options_with_credentials`] helper to build a NATS client.
//! The only thing that differs between processes is where the bytes of
//! the TOML config come from and which Zitadel user signs the
//! JWT-bearer assertion.
//!
//! Adding a new mode (e.g. user JWT from a CLI session) is one new
//! variant on `CredentialsSection` + `CredentialSource`; everything
//! else flows through unchanged.
mod agent_config;
mod config;
mod credentials;
pub use agent_config::{AgentConfig, AgentSection, NatsSection, load_config};
pub use config::CredentialsSection;
pub use credentials::{
ASSERTION_LIFETIME_SECS, CachedToken, CredentialSource, MachineKeyFile, NatsCredential,
TOKEN_REFRESH_LEEWAY_SECS, credential_source_from_config,
};
use std::sync::Arc;
/// Build `async_nats::ConnectOptions` wired with the auth callback
/// that pulls fresh credentials from `creds` on every (re)connect.
///
/// Caller chains additional options (`ping_interval`, `event_callback`,
/// …) before invoking `.connect(urls)`.
pub fn connect_options_with_credentials(
creds: Arc<CredentialSource>,
) -> async_nats::ConnectOptions {
async_nats::ConnectOptions::with_auth_callback(move |_nonce| {
let cs = creds.clone();
async move {
let cred = cs
.next_credential()
.await
.map_err(|e| async_nats::AuthError::new(format!("credential source: {e}")))?;
let mut auth = async_nats::Auth::new();
match cred {
NatsCredential::UserPass { user, pass } => {
auth.username = Some(user);
auth.password = Some(pass);
}
NatsCredential::BearerToken(token) => {
auth.token = Some(token);
}
}
Ok(auth)
}
})
}