Watch
0
0
Fork
You've already forked hyperhive
0

move install_crypto_provider to swarm-secret-client

The rustls provider install is process setup every swarm binary runs, and
swarm-secret-client depends on no other swarm crate, so a binary that only
needs the provider and the store no longer links the queue client.

- swarm-secret-client/src/lib.rs, Cargo.toml: the function, its test, a
  `//!` line on why it lives there, and the rustls dependency; rustls
  `ring` as a dev-dependency feature, so the test builds both providers as
  the binaries do and fails if the install is dropped.
- swarm-queue-client/src/lib.rs, Cargo.toml: function and test removed;
  `connect()` calls the secret client's; rustls dropped (no other use),
  swarm-secret-client added.
- swarm-metrics-auth/Cargo.toml: drop swarm-queue-client, whose only use
  was the provider; the binary no longer links async-nats.
- swarm-logs-auth, swarm-logs, swarm-metrics, swarm-ci-metrics Cargo.toml:
  the dep comment no longer names the provider (they keep the dep for
  agent_token / the OIDC mint).
- Cargo.toml: the rustls comment names the provider's new home.
- 14 call sites (hive-agent, hive-c0re, hive-subagent-mcp x2,
  swarm-builder, swarm-ci-metrics, swarm-claude-login, swarm-controller,
  swarm-logs-auth, swarm-logs, swarm-metrics-auth, swarm-metrics,
  swarm-nats-auth, swarm-review-classifier): one-line path change.
- Cargo.lock: the dependency edges above.
This commit is contained in:
atlas 2026-10-18 02:46:00 +02:00
commit 44984e0d36
25 changed files with 64 additions and 47 deletions

4
Cargo.lock generated
View file

@ -4984,7 +4984,6 @@ dependencies = [
"clap", "clap",
"serde", "serde",
"subtle", "subtle",
"swarm-queue-client",
"swarm-secret-client", "swarm-secret-client",
"tokio", "tokio",
"tracing", "tracing",
@ -5026,10 +5025,10 @@ dependencies = [
"futures-util", "futures-util",
"hive-sh4re", "hive-sh4re",
"reqwest", "reqwest",
"rustls",
"serde", "serde",
"serde_json", "serde_json",
"strum", "strum",
"swarm-secret-client",
"thiserror 2.0.18", "thiserror 2.0.18",
"tokio", "tokio",
"tracing", "tracing",
@ -5066,6 +5065,7 @@ dependencies = [
"reqwest", "reqwest",
"rustify", "rustify",
"rustify_derive", "rustify_derive",
"rustls",
"serde", "serde",
"serde_json", "serde_json",
"strum", "strum",

View file

@ -256,7 +256,7 @@ async-nats = { version = "0.50", default-features = false, features = [
"nkeys", "nkeys",
"ring", "ring",
] } ] }
# Named only to install the process-wide provider (`swarm-queue-client`'s # Named only to install the process-wide provider (`swarm-secret-client`'s
# `install_crypto_provider`). `aws-lc-rs` is the provider reqwest already # `install_crypto_provider`). `aws-lc-rs` is the provider reqwest already
# falls back to, so installing it leaves every HTTPS client unchanged. # falls back to, so installing it leaves every HTTPS client unchanged.
rustls = { version = "0.23", default-features = false, features = [ rustls = { version = "0.23", default-features = false, features = [

View file

@ -121,7 +121,7 @@ struct Cli {
#[tokio::main] #[tokio::main]
async fn main() -> Result<()> { async fn main() -> Result<()> {
hive_log::init(); hive_log::init();
swarm_queue_client::install_crypto_provider(); swarm_secret_client::install_crypto_provider();
let cli = Cli::parse(); let cli = Cli::parse();
serve_main::<AgentSurface>(&cli.socket, cli.poll_ms).await serve_main::<AgentSurface>(&cli.socket, cli.poll_ms).await

View file

@ -129,7 +129,7 @@ enum Cmd {
#[tokio::main] #[tokio::main]
async fn main() -> Result<()> { async fn main() -> Result<()> {
hive_log::init(); hive_log::init();
swarm_queue_client::install_crypto_provider(); swarm_secret_client::install_crypto_provider();
let cli = Cli::parse(); let cli = Cli::parse();
match cli.cmd { match cli.cmd {

View file

@ -106,7 +106,7 @@ impl Reader {
return None; return None;
} }
}; };
swarm_queue_client::install_crypto_provider(); swarm_secret_client::install_crypto_provider();
let agent = target.agent.clone(); let agent = target.agent.clone();
let client = Arc::new(OnceLock::new()); let client = Arc::new(OnceLock::new());
tokio::spawn(connect_until_up(Arc::new(target), Arc::clone(&client))); tokio::spawn(connect_until_up(Arc::new(target), Arc::clone(&client)));

View file

@ -111,7 +111,7 @@ impl Publisher {
return None; return None;
} }
}; };
swarm_queue_client::install_crypto_provider(); swarm_secret_client::install_crypto_provider();
tracing::info!( tracing::info!(
url = %target.url, url = %target.url,
stream = %subagent_term::stream_name(&target.agent), stream = %subagent_term::stream_name(&target.agent),

View file

@ -57,7 +57,7 @@ const MAX_SLOT_WAIT: Duration = REPLY_DEADLINE
#[tokio::main] #[tokio::main]
async fn main() -> Result<()> { async fn main() -> Result<()> {
hive_log::init(); hive_log::init();
swarm_queue_client::install_crypto_provider(); swarm_secret_client::install_crypto_provider();
let var = |name: &str| { let var = |name: &str| {
let full = format!("{PREFIX}_{name}"); let full = format!("{PREFIX}_{name}");
std::env::var(&full).with_context(|| format!("{full} is unset")) std::env::var(&full).with_context(|| format!("{full} is unset"))

View file

@ -17,7 +17,7 @@ hive-log.workspace = true
reqwest.workspace = true reqwest.workspace = true
serde.workspace = true serde.workspace = true
serde_json.workspace = true serde_json.workspace = true
# The OIDC client-credentials mint, and the rustls provider. # The OIDC client-credentials mint.
swarm-queue-client.workspace = true swarm-queue-client.workspace = true
# The forge token and client secret paths, and the store login. # The forge token and client secret paths, and the store login.
swarm-secret-client.workspace = true swarm-secret-client.workspace = true

View file

@ -53,7 +53,7 @@ struct Config {
#[tokio::main] #[tokio::main]
async fn main() -> Result<()> { async fn main() -> Result<()> {
hive_log::init(); hive_log::init();
swarm_queue_client::install_crypto_provider(); swarm_secret_client::install_crypto_provider();
let var = |name: &str| { let var = |name: &str| {
let full = format!("{PREFIX}_{name}"); let full = format!("{PREFIX}_{name}");
std::env::var(&full).with_context(|| format!("{full} is unset")) std::env::var(&full).with_context(|| format!("{full} is unset"))

View file

@ -51,7 +51,7 @@ impl login::CredentialSink for AgentLeaf {
#[tokio::main] #[tokio::main]
async fn main() -> Result<()> { async fn main() -> Result<()> {
hive_log::init(); hive_log::init();
swarm_queue_client::install_crypto_provider(); swarm_secret_client::install_crypto_provider();
let var = |name: &str| { let var = |name: &str| {
let full = format!("{PREFIX}_{name}"); let full = format!("{PREFIX}_{name}");
std::env::var(&full).with_context(|| format!("{full} is unset")) std::env::var(&full).with_context(|| format!("{full} is unset"))

View file

@ -3475,7 +3475,7 @@ async fn start_forge(
#[tokio::main] #[tokio::main]
async fn main() -> Result<()> { async fn main() -> Result<()> {
hive_log::init(); hive_log::init();
swarm_queue_client::install_crypto_provider(); swarm_secret_client::install_crypto_provider();
let path = socket_path(); let path = socket_path();

View file

@ -14,7 +14,7 @@ clap.workspace = true
# The verdict cache's key: a digest of the bearer, never the bearer. # The verdict cache's key: a digest of the bearer, never the bearer.
sha2.workspace = true sha2.workspace = true
swarm-agent-verify.workspace = true swarm-agent-verify.workspace = true
# The agent-token spelling, and the rustls provider the store client needs. # The agent-token spelling.
swarm-queue-client.workspace = true swarm-queue-client.workspace = true
swarm-secret-client.workspace = true swarm-secret-client.workspace = true
tokio.workspace = true tokio.workspace = true

View file

@ -114,7 +114,7 @@ async fn handle(
#[tokio::main] #[tokio::main]
async fn main() -> anyhow::Result<()> { async fn main() -> anyhow::Result<()> {
swarm_queue_client::install_crypto_provider(); swarm_secret_client::install_crypto_provider();
tracing_subscriber::fmt() tracing_subscriber::fmt()
.with_env_filter( .with_env_filter(
tracing_subscriber::EnvFilter::try_from_default_env() tracing_subscriber::EnvFilter::try_from_default_env()

View file

@ -12,8 +12,7 @@ path = "src/main.rs"
anyhow.workspace = true anyhow.workspace = true
clap.workspace = true clap.workspace = true
clap-markdown = "0.1" clap-markdown = "0.1"
# The agent-token spelling the gateway's responder parses, and the rustls # The agent-token spelling the gateway's responder parses.
# provider the store client needs.
swarm-queue-client.workspace = true swarm-queue-client.workspace = true
# The read of this agent's own queue secret. # The read of this agent's own queue secret.
swarm-secret-client.workspace = true swarm-secret-client.workspace = true

View file

@ -85,7 +85,7 @@ fn main() -> Result<()> {
// keep working on a machine that has no log store wired up at // keep working on a machine that has no log store wired up at
// all — `markdown-docs` in particular runs in a nix check, where // all — `markdown-docs` in particular runs in a nix check, where
// no credential exists and none should. // no credential exists and none should.
swarm_queue_client::install_crypto_provider(); swarm_secret_client::install_crypto_provider();
let cfg = auth::Config::from_env()?; let cfg = auth::Config::from_env()?;
let stdout = std::io::stdout(); let stdout = std::io::stdout();
query::run(&cfg, &logsql, limit, format, &mut stdout.lock()) query::run(&cfg, &logsql, limit, format, &mut stdout.lock())

View file

@ -14,8 +14,6 @@ clap.workspace = true
serde.workspace = true serde.workspace = true
# Comparing the presented bearer with the stored one. # Comparing the presented bearer with the stored one.
subtle.workspace = true subtle.workspace = true
# Only for the rustls provider the store client needs.
swarm-queue-client.workspace = true
swarm-secret-client.workspace = true swarm-secret-client.workspace = true
tokio.workspace = true tokio.workspace = true
tracing.workspace = true tracing.workspace = true

View file

@ -190,7 +190,7 @@ async fn handle(State(responder): State<Arc<Responder<Store>>>, headers: HeaderM
#[tokio::main] #[tokio::main]
async fn main() -> anyhow::Result<()> { async fn main() -> anyhow::Result<()> {
swarm_queue_client::install_crypto_provider(); swarm_secret_client::install_crypto_provider();
tracing_subscriber::fmt() tracing_subscriber::fmt()
.with_env_filter( .with_env_filter(
tracing_subscriber::EnvFilter::try_from_default_env() tracing_subscriber::EnvFilter::try_from_default_env()

View file

@ -12,8 +12,7 @@ path = "src/main.rs"
anyhow.workspace = true anyhow.workspace = true
clap.workspace = true clap.workspace = true
clap-markdown = "0.1" clap-markdown = "0.1"
# The agent-token spelling the gateway's responder parses, and the rustls # The agent-token spelling the gateway's responder parses.
# provider the store client needs.
swarm-queue-client.workspace = true swarm-queue-client.workspace = true
# The read of this agent's own queue secret. # The read of this agent's own queue secret.
swarm-secret-client.workspace = true swarm-secret-client.workspace = true

View file

@ -169,7 +169,7 @@ fn main() -> Result<()> {
}; };
// Config read only for a store verb: `markdown-docs` runs in a nix check, // Config read only for a store verb: `markdown-docs` runs in a nix check,
// where no credential exists. // where no credential exists.
swarm_queue_client::install_crypto_provider(); swarm_secret_client::install_crypto_provider();
let cfg = auth::Config::from_env()?; let cfg = auth::Config::from_env()?;
let stdout = std::io::stdout(); let stdout = std::io::stdout();
query::run(&cfg, &request, &mut stdout.lock()) query::run(&cfg, &request, &mut stdout.lock())

View file

@ -192,7 +192,7 @@ struct Args {
#[tokio::main] #[tokio::main]
async fn main() -> anyhow::Result<()> { async fn main() -> anyhow::Result<()> {
swarm_queue_client::install_crypto_provider(); swarm_secret_client::install_crypto_provider();
tracing_subscriber::fmt() tracing_subscriber::fmt()
.with_env_filter( .with_env_filter(
tracing_subscriber::EnvFilter::try_from_default_env() tracing_subscriber::EnvFilter::try_from_default_env()

View file

@ -82,10 +82,11 @@ hive-sh4re = { workspace = true, optional = true }
# feature's comment above), and `cargo check -p swarm-queue-client` alone # feature's comment above), and `cargo check -p swarm-queue-client` alone
# must not depend on what else is in the build. # must not depend on what else is in the build.
reqwest = { workspace = true, features = ["blocking"] } reqwest = { workspace = true, features = ["blocking"] }
rustls.workspace = true
serde.workspace = true serde.workspace = true
serde_json.workspace = true serde_json.workspace = true
strum.workspace = true strum.workspace = true
# The rustls provider `connect` installs.
swarm-secret-client.workspace = true
# A library, so its errors are a matchable enum rather than an opaque # A library, so its errors are a matchable enum rather than an opaque
# `anyhow::Error`. The binaries that consume this keep anyhow; `?` converts. # `anyhow::Error`. The binaries that consume this keep anyhow; `?` converts.
thiserror.workspace = true thiserror.workspace = true

View file

@ -941,18 +941,6 @@ pub fn max_payload(client: &async_nats::Client) -> usize {
client.server_info().max_payload client.server_info().max_payload
} }
/// Install `aws-lc-rs` as the process-wide rustls crypto provider. Call it
/// first thing in `main` of every binary that dials the queue.
///
/// rustls is built with both `ring` and `aws-lc-rs`; without an installed
/// default, the first TLS handshake panics (async-nats builds its config with
/// `ClientConfig::builder()`) and the client never connects. `aws-lc-rs`
/// because it is what reqwest uses when nothing is installed.
pub fn install_crypto_provider() {
// `Err` only means a provider is already installed, which is all this needs.
let _already_installed = rustls::crypto::aws_lc_rs::default_provider().install_default();
}
/// Connect to the swarm queue, presenting a token on each connection attempt /// Connect to the swarm queue, presenting a token on each connection attempt
/// and minting a fresh one only when the cached one is near expiry. Reply /// and minting a fresh one only when the cached one is near expiry. Reply
/// inboxes are under [`client_inbox_prefix`] of `cfg.client_id`. /// inboxes are under [`client_inbox_prefix`] of `cfg.client_id`.
@ -962,7 +950,7 @@ pub fn install_crypto_provider() {
/// token expires, the controller keeps serving, its status data quietly stops /// token expires, the controller keeps serving, its status data quietly stops
/// updating, and nothing says so until someone reads a dashboard. /// updating, and nothing says so until someone reads a dashboard.
pub async fn connect(cfg: QueueConfig) -> Result<async_nats::Client, Error> { pub async fn connect(cfg: QueueConfig) -> Result<async_nats::Client, Error> {
install_crypto_provider(); swarm_secret_client::install_crypto_provider();
// Trusts `cfg.ca_file` when set (the swarm's own CA, when the token // Trusts `cfg.ca_file` when set (the swarm's own CA, when the token
// endpoint is signed by it) — see `build_http_client`. A timeout too, // endpoint is signed by it) — see `build_http_client`. A timeout too,
// because this client runs INSIDE the auth callback: a token endpoint // because this client runs INSIDE the auth callback: a token endpoint
@ -1134,14 +1122,6 @@ mod tests {
} }
} }
#[test]
fn a_tls_client_config_builds_once_the_provider_is_installed() {
install_crypto_provider();
let _config = rustls::ClientConfig::builder()
.with_root_certificates(rustls::RootCertStore::empty())
.with_no_client_auth();
}
#[tokio::test] #[tokio::test]
async fn a_debug_print_of_a_config_does_not_carry_a_held_secret() { async fn a_debug_print_of_a_config_does_not_carry_a_held_secret() {
let cfg = QueueConfig { let cfg = QueueConfig {

View file

@ -46,7 +46,7 @@ fn main() -> Result<()> {
async fn serve() -> Result<()> { async fn serve() -> Result<()> {
hive_log::init(); hive_log::init();
swarm_queue_client::install_crypto_provider(); swarm_secret_client::install_crypto_provider();
let var = |name: &str| { let var = |name: &str| {
let full = format!("{PREFIX}_{name}"); let full = format!("{PREFIX}_{name}");
std::env::var(&full).with_context(|| format!("{full} is unset")) std::env::var(&full).with_context(|| format!("{full} is unset"))

View file

@ -12,6 +12,9 @@ reqwest.workspace = true
# implement — see `client::WriteAclPolicy`. # implement — see `client::WriteAclPolicy`.
rustify.workspace = true rustify.workspace = true
rustify_derive.workspace = true rustify_derive.workspace = true
# `install_crypto_provider`, which the store client (reqwest, via vaultrs)
# needs before its first TLS handshake.
rustls.workspace = true
serde.workspace = true serde.workspace = true
# `read_versioned` decodes the data half of the response vaultrs hands back as # `read_versioned` decodes the data half of the response vaultrs hands back as
# a `serde_json::Value`. # a `serde_json::Value`.
@ -22,3 +25,9 @@ vaultrs.workspace = true
[lints] [lints]
workspace = true workspace = true
# `ring` as well, as every binary that links async-nats builds it: with one
# provider compiled in rustls picks it without an install, so the provider
# test would pass with `install_crypto_provider` doing nothing.
[dev-dependencies]
rustls = { workspace = true, features = ["ring"] }

View file

@ -24,6 +24,10 @@
//! //!
//! [`mtls`] is the one module about reaching the store rather than about a //! [`mtls`] is the one module about reaching the store rather than about a
//! value inside it, and its doc explains why that is not circular. //! value inside it, and its doc explains why that is not circular.
//!
//! [`install_crypto_provider`] is process setup, not an agreement. It lives
//! here because this crate depends on no other swarm crate, so a binary that
//! needs only the provider and the store links nothing else.
pub mod acp; pub mod acp;
pub mod builder; pub mod builder;
@ -105,3 +109,30 @@ impl From<vaultrs::error::ClientError> for Error {
Self::Vault(Box::new(e)) Self::Vault(Box::new(e))
} }
} }
/// Install `aws-lc-rs` as the process-wide rustls crypto provider. Call it
/// first thing in `main` of every binary that dials the queue or reaches the
/// secret store over TLS.
///
/// rustls is built with both `ring` and `aws-lc-rs`; without an installed
/// default, the first TLS handshake panics. On the queue side async-nats
/// builds its config with `ClientConfig::builder()`, so the client never
/// connects. On the store side the client is reqwest, via vaultrs.
/// `aws-lc-rs` because it is what reqwest uses when nothing is installed.
pub fn install_crypto_provider() {
// `Err` only means a provider is already installed, which is all this needs.
let _already_installed = rustls::crypto::aws_lc_rs::default_provider().install_default();
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_tls_client_config_builds_once_the_provider_is_installed() {
install_crypto_provider();
let _config = rustls::ClientConfig::builder()
.with_root_certificates(rustls::RootCertStore::empty())
.with_no_client_auth();
}
}