Watch
0
0
Fork
You've already forked hyperhive
0

move agent_token from swarm-queue-client to swarm-agent-verify

per #5471, verifiers stop depending on the queue client. the module is
store-free and needs only thiserror, so swarm-agent-verify gains one
dependency and loses its swarm-queue-client edge. consumers import it
from swarm_agent_verify; swarm-logs and swarm-metrics, whose only use of
swarm-queue-client was this module, drop that dependency, as does
swarm-logs-auth.
This commit is contained in:
atlas 2026-10-18 12:18:35 +02:00 • committed by mara
commit 87b1f94ac6
18 changed files with 34 additions and 33 deletions

9
Cargo.lock generated
View file

@ -1689,6 +1689,7 @@ dependencies = [
"serde",
"serde_json",
"sha1",
"swarm-agent-verify",
"swarm-queue-client",
"swarm-secret-client",
"tempfile",
@ -2056,6 +2057,7 @@ dependencies = [
"schemars",
"serde",
"serde_json",
"swarm-agent-verify",
"swarm-queue-client",
"swarm-secret-client",
"tokio",
@ -4769,8 +4771,8 @@ version = "0.1.0"
dependencies = [
"anyhow",
"subtle",
"swarm-queue-client",
"swarm-secret-client",
"thiserror 2.0.18",
"tokio",
"tracing",
]
@ -4923,7 +4925,7 @@ dependencies = [
"clap-markdown",
"reqwest",
"serde_json",
"swarm-queue-client",
"swarm-agent-verify",
"swarm-secret-client",
"tokio",
]
@ -4937,7 +4939,6 @@ dependencies = [
"clap",
"sha2 0.11.0",
"swarm-agent-verify",
"swarm-queue-client",
"swarm-secret-client",
"tokio",
"tracing",
@ -4976,7 +4977,7 @@ dependencies = [
"clap",
"clap-markdown",
"reqwest",
"swarm-queue-client",
"swarm-agent-verify",
"swarm-secret-client",
"tokio",
]

View file

@ -52,6 +52,8 @@ swarm-queue-client = { workspace = true, features = [
"agents-md-initial",
"operator-profiles",
] }
# The spelling of the token the queue connection presents.
swarm-agent-verify.workspace = true
# This agent's own queue secret, read from the store under its own certificate.
swarm-secret-client.workspace = true
# The directory an AGENTS.md edit is applied in, and the file it is written

View file

@ -386,7 +386,7 @@ async fn connect_as_agent<S: SecretSource>(
// `Sync` as async-nats's auth callback requires; the store read is not.
let attempt = tokio::spawn(async move {
let value = secret.for_attempt().await.map_err(|e| format!("{e:#}"))?;
swarm_queue_client::agent_token::format_agent_token(&name, &value)
swarm_agent_verify::agent_token::format_agent_token(&name, &value)
.map_err(|e| e.to_string())
});
async move { attempt.await.map_err(|e| e.to_string())? }

View file

@ -34,6 +34,8 @@ swarm-queue-client = { workspace = true, features = [
"agent-inference",
"agent-permissions",
] }
# The spelling of the token the queue connection presents.
swarm-agent-verify.workspace = true
# The agent's own queue credential, read under its store identity.
swarm-secret-client.workspace = true
tokio.workspace = true

View file

@ -237,7 +237,7 @@ pub(crate) async fn connect(target: &Arc<Target>) -> anyhow::Result<async_nats::
// `Sync` as async-nats's auth callback requires; the store read is not.
let attempt = tokio::spawn(async move {
let value = target.store.read().await.map_err(|e| format!("{e:#}"))?;
swarm_queue_client::agent_token::format_agent_token(&target.agent, &value)
swarm_agent_verify::agent_token::format_agent_token(&target.agent, &value)
.map_err(|e| e.to_string())
});
async move { attempt.await.map_err(|e| e.to_string())? }

View file

@ -7,10 +7,9 @@ edition.workspace = true
anyhow.workspace = true
# Comparing an agent's presented secret with the stored one.
subtle.workspace = true
# The agent-token spelling the agent also uses.
swarm-queue-client.workspace = true
# The read of the stored credential the token is checked against.
swarm-secret-client.workspace = true
thiserror.workspace = true
tokio.workspace = true
tracing.workspace = true

View file

@ -1,12 +1,10 @@
//! The one spelling of the token an agent presents its own queue secret in,
//! shared by the agent that formats it and the auth-callout responder that
//! parses it back:
//! [`AGENT_TOKEN_PREFIX`](crate::agent_token::AGENT_TOKEN_PREFIX), the agent's
//! name, `.`, the secret.
//! shared by the agent that formats it and the verifiers that parse it back:
//! [`AGENT_TOKEN_PREFIX`], the agent's name, `.`, the secret.
//!
//! The secret itself lives at `swarm/agents/<agent>/queue` in the swarm's
//! secret store (`swarm_secret_client::queue`). This module knows nothing of
//! the store, so an agent formats its token without linking a store client.
//! the store.
/// Marks an `auth_token` as an agent's own credential.
///

View file

@ -1,22 +1,27 @@
//! Checking an agent's own credential against the swarm secret store.
//!
//! The token is `swarm_queue_client::agent_token`'s spelling: a prefix, the
//! The token is [`agent_token`]'s spelling: a prefix, the
//! agent's name, and its secret. The name is only a claim. It becomes an
//! identity when the secret equals the one stored at
//! `swarm/agents/<agent>/queue`, and [`verify`] grants nothing on the name
//! alone: every path that does not reach that comparison, and every lookup
//! that fails, is a denial.
//!
//! [`agent_token`] also holds the spelling the agents, `swarm-logs` and
//! `swarm-metrics` format their token in.
//!
//! Two verifiers use this: the queue's auth callout (`swarm-nats-auth`) and
//! the log store's `auth_request` responder (`swarm-logs-auth`). Each reads
//! the store under its own certificate.
pub mod agent_token;
use std::future::Future;
use std::time::Duration;
use crate::agent_token::AgentToken;
use anyhow::Context;
use subtle::ConstantTimeEq;
use swarm_queue_client::agent_token::AgentToken;
use swarm_secret_client::client::{DEFAULT_CERT_MOUNT, Settings};
use swarm_secret_client::queue::{self, AgentCredential};
@ -118,7 +123,7 @@ pub async fn verify(
#[cfg(test)]
mod tests {
use super::*;
use swarm_queue_client::agent_token::parse_agent_token;
use crate::agent_token::parse_agent_token;
/// A store holding at most one credential, or failing outright.
enum Fake {

View file

@ -14,8 +14,6 @@ clap.workspace = true
# The verdict cache's key: a digest of the bearer, never the bearer.
sha2.workspace = true
swarm-agent-verify.workspace = true
# The agent-token spelling.
swarm-queue-client.workspace = true
swarm-secret-client.workspace = true
tokio.workspace = true
tracing.workspace = true

View file

@ -28,7 +28,7 @@ use axum::extract::State;
use axum::http::{HeaderMap, StatusCode, header};
use clap::Parser;
use swarm_agent_verify::CredentialSource;
use swarm_queue_client::agent_token::parse_agent_token;
use swarm_agent_verify::agent_token::parse_agent_token;
use verdicts::Verdicts;
/// How long one store lookup may take before the request is denied. Well

View file

@ -13,7 +13,7 @@ anyhow.workspace = true
clap.workspace = true
clap-markdown = "0.1"
# The agent-token spelling the gateway's responder parses.
swarm-queue-client.workspace = true
swarm-agent-verify.workspace = true
# The read of this agent's own queue secret.
swarm-secret-client.workspace = true
# Only to drive that one async read; the query itself is blocking.

View file

@ -25,7 +25,7 @@ As the agent itself. On each query it reads the agent's own queue secret,
store certificate (`nix/agent-modules/bao.nix`), the same read `hive-agent`
makes for its queue connection. It sends the secret as
`Authorization: Bearer swarm-agent.<agent>.<secret>`, spelled by
`swarm_queue_client::agent_token`.
`swarm_agent_verify::agent_token`.
At the gateway, nginx hands a bearer of that shape to `swarm-logs-auth`, which
compares it with the stored secret (`swarm-agent-verify`, the check the queue's

View file

@ -5,7 +5,7 @@
//! The read is `hive-agent`'s `swarm_queue` one: the same variables, the same
//! path (`swarm/agents/<agent>/queue`) and the same cert-auth role. The secret
//! goes out as `Bearer swarm-agent.<agent>.<secret>`, spelled by
//! `swarm_queue_client::agent_token`; the gateway's `swarm-logs-auth`
//! `swarm_agent_verify::agent_token`; the gateway's `swarm-logs-auth`
//! responder checks it against the store.
//!
//! 🩸 The secret is never logged and never rendered into an error.
@ -87,7 +87,7 @@ impl Config {
.with_context(|| format!("reading {}", self.path))
})?;
let stored = stored.with_context(|| format!("nothing is stored at {}", self.path))?;
swarm_queue_client::agent_token::format_agent_token(&self.agent, &stored.value)
swarm_agent_verify::agent_token::format_agent_token(&self.agent, &stored.value)
.with_context(|| format!("spelling the credential stored at {}", self.path))
}
}

View file

@ -13,7 +13,7 @@ anyhow.workspace = true
clap.workspace = true
clap-markdown = "0.1"
# The agent-token spelling the gateway's responder parses.
swarm-queue-client.workspace = true
swarm-agent-verify.workspace = true
# The read of this agent's own queue secret.
swarm-secret-client.workspace = true
# Only to drive that one async read; the query itself is blocking.

View file

@ -5,7 +5,7 @@
//! The same read and the same bearer as `swarm-logs`' `auth` module: path
//! `swarm/agents/<agent>/queue`, the agent's cert-auth role, and
//! `Bearer swarm-agent.<agent>.<secret>` spelled by
//! `swarm_queue_client::agent_token`. On the metrics store's host a
//! `swarm_agent_verify::agent_token`. On the metrics store's host a
//! `swarm-logs-auth` instance checks it against the store.
//!
//! 🩸 The secret is never logged and never rendered into an error.
@ -86,7 +86,7 @@ impl Config {
.with_context(|| format!("reading {}", self.path))
})?;
let stored = stored.with_context(|| format!("nothing is stored at {}", self.path))?;
swarm_queue_client::agent_token::format_agent_token(&self.agent, &stored.value)
swarm_agent_verify::agent_token::format_agent_token(&self.agent, &stored.value)
.with_context(|| format!("spelling the credential stored at {}", self.path))
}
}

View file

@ -1,6 +1,6 @@
//! Classifying a presented `auth_token`, and the grant for an agent's own.
//!
//! The spelling is `swarm_queue_client::agent_token`'s, shared with the agent
//! The spelling is `swarm_agent_verify::agent_token`'s, shared with the agent
//! that presents it. Whether its secret is the stored one is
//! [`swarm_agent_verify::verify`]'s answer; nothing here grants on the name
//! alone.
@ -9,7 +9,7 @@
//! unchanged.
use swarm_agent_verify::CredentialSource;
use swarm_queue_client::agent_token::{AgentToken, Malformed, parse_agent_token};
use swarm_agent_verify::agent_token::{AgentToken, Malformed, parse_agent_token};
use crate::policy::{Permissions, Policy};

View file

@ -200,10 +200,6 @@ pub mod wanted;
/// inside it. See the module doc for why the two must not merge.
pub mod agent_status;
/// The token an agent presents its own queue secret in. Store-free, so an agent
/// formats it without linking the secret-store client.
pub mod agent_token;
/// The bucket agent icons are published into — one key per agent, with no
/// hive in it, unlike [`agent_status`]. See the module doc for why the
/// placement stays out of the key, and for who may write it.

View file

@ -24,7 +24,7 @@
//! renewing the other.
//!
//! The token an agent presents that secret in at the queue is spelled by
//! `swarm_queue_client::agent_token`, which needs no store client.
//! `swarm_agent_verify::agent_token`.
use serde::{Deserialize, Serialize};