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:
parent
741fe388f9
commit
87b1f94ac6
18 changed files with 34 additions and 33 deletions
9
Cargo.lock
generated
9
Cargo.lock
generated
|
|
@ -1689,6 +1689,7 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sha1",
|
"sha1",
|
||||||
|
"swarm-agent-verify",
|
||||||
"swarm-queue-client",
|
"swarm-queue-client",
|
||||||
"swarm-secret-client",
|
"swarm-secret-client",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
|
|
@ -2056,6 +2057,7 @@ dependencies = [
|
||||||
"schemars",
|
"schemars",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"swarm-agent-verify",
|
||||||
"swarm-queue-client",
|
"swarm-queue-client",
|
||||||
"swarm-secret-client",
|
"swarm-secret-client",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
|
@ -4769,8 +4771,8 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"subtle",
|
"subtle",
|
||||||
"swarm-queue-client",
|
|
||||||
"swarm-secret-client",
|
"swarm-secret-client",
|
||||||
|
"thiserror 2.0.18",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
@ -4923,7 +4925,7 @@ dependencies = [
|
||||||
"clap-markdown",
|
"clap-markdown",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"swarm-queue-client",
|
"swarm-agent-verify",
|
||||||
"swarm-secret-client",
|
"swarm-secret-client",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
@ -4937,7 +4939,6 @@ dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"sha2 0.11.0",
|
"sha2 0.11.0",
|
||||||
"swarm-agent-verify",
|
"swarm-agent-verify",
|
||||||
"swarm-queue-client",
|
|
||||||
"swarm-secret-client",
|
"swarm-secret-client",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
|
@ -4976,7 +4977,7 @@ dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"clap-markdown",
|
"clap-markdown",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"swarm-queue-client",
|
"swarm-agent-verify",
|
||||||
"swarm-secret-client",
|
"swarm-secret-client",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,8 @@ swarm-queue-client = { workspace = true, features = [
|
||||||
"agents-md-initial",
|
"agents-md-initial",
|
||||||
"operator-profiles",
|
"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.
|
# This agent's own queue secret, read from the store under its own certificate.
|
||||||
swarm-secret-client.workspace = true
|
swarm-secret-client.workspace = true
|
||||||
# The directory an AGENTS.md edit is applied in, and the file it is written
|
# The directory an AGENTS.md edit is applied in, and the file it is written
|
||||||
|
|
|
||||||
|
|
@ -386,7 +386,7 @@ async fn connect_as_agent<S: SecretSource>(
|
||||||
// `Sync` as async-nats's auth callback requires; the store read is not.
|
// `Sync` as async-nats's auth callback requires; the store read is not.
|
||||||
let attempt = tokio::spawn(async move {
|
let attempt = tokio::spawn(async move {
|
||||||
let value = secret.for_attempt().await.map_err(|e| format!("{e:#}"))?;
|
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())
|
.map_err(|e| e.to_string())
|
||||||
});
|
});
|
||||||
async move { attempt.await.map_err(|e| e.to_string())? }
|
async move { attempt.await.map_err(|e| e.to_string())? }
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,8 @@ swarm-queue-client = { workspace = true, features = [
|
||||||
"agent-inference",
|
"agent-inference",
|
||||||
"agent-permissions",
|
"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.
|
# The agent's own queue credential, read under its store identity.
|
||||||
swarm-secret-client.workspace = true
|
swarm-secret-client.workspace = true
|
||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
|
|
|
||||||
|
|
@ -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.
|
// `Sync` as async-nats's auth callback requires; the store read is not.
|
||||||
let attempt = tokio::spawn(async move {
|
let attempt = tokio::spawn(async move {
|
||||||
let value = target.store.read().await.map_err(|e| format!("{e:#}"))?;
|
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())
|
.map_err(|e| e.to_string())
|
||||||
});
|
});
|
||||||
async move { attempt.await.map_err(|e| e.to_string())? }
|
async move { attempt.await.map_err(|e| e.to_string())? }
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,9 @@ edition.workspace = true
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
# Comparing an agent's presented secret with the stored one.
|
# Comparing an agent's presented secret with the stored one.
|
||||||
subtle.workspace = true
|
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.
|
# The read of the stored credential the token is checked against.
|
||||||
swarm-secret-client.workspace = true
|
swarm-secret-client.workspace = true
|
||||||
|
thiserror.workspace = true
|
||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,10 @@
|
||||||
//! The one spelling of the token an agent presents its own queue secret in,
|
//! 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
|
//! shared by the agent that formats it and the verifiers that parse it back:
|
||||||
//! parses it back:
|
//! [`AGENT_TOKEN_PREFIX`], the agent's name, `.`, the secret.
|
||||||
//! [`AGENT_TOKEN_PREFIX`](crate::agent_token::AGENT_TOKEN_PREFIX), the agent's
|
|
||||||
//! name, `.`, the secret.
|
|
||||||
//!
|
//!
|
||||||
//! The secret itself lives at `swarm/agents/<agent>/queue` in the swarm's
|
//! The secret itself lives at `swarm/agents/<agent>/queue` in the swarm's
|
||||||
//! secret store (`swarm_secret_client::queue`). This module knows nothing of
|
//! 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.
|
/// Marks an `auth_token` as an agent's own credential.
|
||||||
///
|
///
|
||||||
|
|
@ -1,22 +1,27 @@
|
||||||
//! Checking an agent's own credential against the swarm secret store.
|
//! 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
|
//! agent's name, and its secret. The name is only a claim. It becomes an
|
||||||
//! identity when the secret equals the one stored at
|
//! identity when the secret equals the one stored at
|
||||||
//! `swarm/agents/<agent>/queue`, and [`verify`] grants nothing on the name
|
//! `swarm/agents/<agent>/queue`, and [`verify`] grants nothing on the name
|
||||||
//! alone: every path that does not reach that comparison, and every lookup
|
//! alone: every path that does not reach that comparison, and every lookup
|
||||||
//! that fails, is a denial.
|
//! 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
|
//! 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 log store's `auth_request` responder (`swarm-logs-auth`). Each reads
|
||||||
//! the store under its own certificate.
|
//! the store under its own certificate.
|
||||||
|
|
||||||
|
pub mod agent_token;
|
||||||
|
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use crate::agent_token::AgentToken;
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use subtle::ConstantTimeEq;
|
use subtle::ConstantTimeEq;
|
||||||
use swarm_queue_client::agent_token::AgentToken;
|
|
||||||
use swarm_secret_client::client::{DEFAULT_CERT_MOUNT, Settings};
|
use swarm_secret_client::client::{DEFAULT_CERT_MOUNT, Settings};
|
||||||
use swarm_secret_client::queue::{self, AgentCredential};
|
use swarm_secret_client::queue::{self, AgentCredential};
|
||||||
|
|
||||||
|
|
@ -118,7 +123,7 @@ pub async fn verify(
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
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.
|
/// A store holding at most one credential, or failing outright.
|
||||||
enum Fake {
|
enum Fake {
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,6 @@ 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.
|
|
||||||
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
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ use axum::extract::State;
|
||||||
use axum::http::{HeaderMap, StatusCode, header};
|
use axum::http::{HeaderMap, StatusCode, header};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use swarm_agent_verify::CredentialSource;
|
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;
|
use verdicts::Verdicts;
|
||||||
|
|
||||||
/// How long one store lookup may take before the request is denied. Well
|
/// How long one store lookup may take before the request is denied. Well
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ 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.
|
# 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.
|
# The read of this agent's own queue secret.
|
||||||
swarm-secret-client.workspace = true
|
swarm-secret-client.workspace = true
|
||||||
# Only to drive that one async read; the query itself is blocking.
|
# Only to drive that one async read; the query itself is blocking.
|
||||||
|
|
|
||||||
|
|
@ -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`
|
store certificate (`nix/agent-modules/bao.nix`), the same read `hive-agent`
|
||||||
makes for its queue connection. It sends the secret as
|
makes for its queue connection. It sends the secret as
|
||||||
`Authorization: Bearer swarm-agent.<agent>.<secret>`, spelled by
|
`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
|
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
|
compares it with the stored secret (`swarm-agent-verify`, the check the queue's
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
//! The read is `hive-agent`'s `swarm_queue` one: the same variables, the same
|
//! 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
|
//! path (`swarm/agents/<agent>/queue`) and the same cert-auth role. The secret
|
||||||
//! goes out as `Bearer swarm-agent.<agent>.<secret>`, spelled by
|
//! 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.
|
//! responder checks it against the store.
|
||||||
//!
|
//!
|
||||||
//! 🩸 The secret is never logged and never rendered into an error.
|
//! 🩸 The secret is never logged and never rendered into an error.
|
||||||
|
|
@ -87,7 +87,7 @@ impl Config {
|
||||||
.with_context(|| format!("reading {}", self.path))
|
.with_context(|| format!("reading {}", self.path))
|
||||||
})?;
|
})?;
|
||||||
let stored = stored.with_context(|| format!("nothing is stored at {}", 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))
|
.with_context(|| format!("spelling the credential stored at {}", self.path))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ 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.
|
# 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.
|
# The read of this agent's own queue secret.
|
||||||
swarm-secret-client.workspace = true
|
swarm-secret-client.workspace = true
|
||||||
# Only to drive that one async read; the query itself is blocking.
|
# Only to drive that one async read; the query itself is blocking.
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
//! The same read and the same bearer as `swarm-logs`' `auth` module: path
|
//! The same read and the same bearer as `swarm-logs`' `auth` module: path
|
||||||
//! `swarm/agents/<agent>/queue`, the agent's cert-auth role, and
|
//! `swarm/agents/<agent>/queue`, the agent's cert-auth role, and
|
||||||
//! `Bearer swarm-agent.<agent>.<secret>` spelled by
|
//! `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.
|
//! `swarm-logs-auth` instance checks it against the store.
|
||||||
//!
|
//!
|
||||||
//! 🩸 The secret is never logged and never rendered into an error.
|
//! 🩸 The secret is never logged and never rendered into an error.
|
||||||
|
|
@ -86,7 +86,7 @@ impl Config {
|
||||||
.with_context(|| format!("reading {}", self.path))
|
.with_context(|| format!("reading {}", self.path))
|
||||||
})?;
|
})?;
|
||||||
let stored = stored.with_context(|| format!("nothing is stored at {}", 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))
|
.with_context(|| format!("spelling the credential stored at {}", self.path))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
//! Classifying a presented `auth_token`, and the grant for an agent's own.
|
//! 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
|
//! that presents it. Whether its secret is the stored one is
|
||||||
//! [`swarm_agent_verify::verify`]'s answer; nothing here grants on the name
|
//! [`swarm_agent_verify::verify`]'s answer; nothing here grants on the name
|
||||||
//! alone.
|
//! alone.
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
//! unchanged.
|
//! unchanged.
|
||||||
|
|
||||||
use swarm_agent_verify::CredentialSource;
|
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};
|
use crate::policy::{Permissions, Policy};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -200,10 +200,6 @@ pub mod wanted;
|
||||||
/// inside it. See the module doc for why the two must not merge.
|
/// inside it. See the module doc for why the two must not merge.
|
||||||
pub mod agent_status;
|
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
|
/// 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
|
/// 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.
|
/// placement stays out of the key, and for who may write it.
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@
|
||||||
//! renewing the other.
|
//! renewing the other.
|
||||||
//!
|
//!
|
||||||
//! The token an agent presents that secret in at the queue is spelled by
|
//! 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};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue