swarm-queue-client: one agent-token spelling, and no hive in AgentCredential
`swarm_queue_client::agent_token::format_agent_token` / `parse_agent_token` are the spelling an agent presents its own queue secret in, `swarm-agent.<agent>.<secret>`, and the one the auth-callout responder reads back. The prefix is what separates it from an OIDC access token, which may itself contain `.`. Parsing distinguishes "not an agent token" (no prefix) from "a malformed one"; the error names the problem and never the value. The module is store-free, so the agent formats its token without linking the secret-store client. `swarm_secret_client::queue::AgentCredential` loses `hive`: an agent's identity is not tied to a hive, and nothing reads the field. Objects already in the store carry it and still decode, since unknown fields are ignored; a test parses one. The controller stops writing it. With the credential no longer naming a hive, and the agent's policy naming none since #4762, nothing in the mint consumes one. `hive` goes from `mint_and_verify`, from the `MintAgentIdentity` node, and from `POST /api/agents/{name}/identity`, which now takes no body and no longer checks a hive against the roster; a caller that still sends one is not refused, the body is ignored. `swarmctl agent mint-identity` loses `--hive`, so passing it is now a usage error.
This commit is contained in:
parent
6170e74a31
commit
fc97c237dc
10 changed files with 234 additions and 201 deletions
|
|
@ -22,6 +22,9 @@
|
|||
//! reaching the store and nothing else; deriving a queue identity from it would
|
||||
//! couple the two credentials' lifetimes, so that renewing one would mean
|
||||
//! 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.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
|
@ -57,14 +60,11 @@ pub fn agent_queue_path(agent: &str) -> Result<String, Error> {
|
|||
Ok(format!("{prefix}/queue"))
|
||||
}
|
||||
|
||||
/// What [`agent_queue_path`] holds: the secret, and the principal it proves.
|
||||
/// What [`agent_queue_path`] holds: the secret, and the agent it proves.
|
||||
///
|
||||
/// Both names ride **in the object** rather than being parsed back out of a
|
||||
/// composite principal string. Hive and agent names draw from the same
|
||||
/// alphabet (`hive_types::Ident`, `[a-z0-9-]`), so a principal spelled
|
||||
/// `hive-<hive>-agent-<agent>` parses two ways for a name containing `-agent-`
|
||||
/// — and an ambiguous principal parse in an authorisation path is a caller that
|
||||
/// authenticates fine and is handed somebody else's grant.
|
||||
/// No hive: an agent's identity is not tied to one, and the subjects the
|
||||
/// verifier grants are keyed on the agent alone. Objects written with a `hive`
|
||||
/// field still decode, because unknown fields are ignored.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct AgentCredential {
|
||||
/// The secret itself. Named to match [`Credential::value`] and
|
||||
|
|
@ -74,13 +74,6 @@ pub struct AgentCredential {
|
|||
|
||||
/// The agent this secret authenticates.
|
||||
pub agent: String,
|
||||
|
||||
/// The hive that agent belongs to.
|
||||
///
|
||||
/// Here because the verifying end has no roster to look it up in, and
|
||||
/// because the subjects an agent is granted are hive-templated — without
|
||||
/// this field the verifier would know *who* is connecting and not *where*.
|
||||
pub hive: String,
|
||||
}
|
||||
|
||||
/// What the path holds: the client secret, plus the client id it belongs to.
|
||||
|
|
@ -198,7 +191,6 @@ mod tests {
|
|||
let c = AgentCredential {
|
||||
value: "s3cr3t".to_owned(),
|
||||
agent: "atlas".to_owned(),
|
||||
hive: "alpha".to_owned(),
|
||||
};
|
||||
let json = serde_json::to_string(&c).expect("serialises");
|
||||
assert_eq!(
|
||||
|
|
@ -212,29 +204,39 @@ mod tests {
|
|||
let json = serde_json::to_value(AgentCredential {
|
||||
value: "s3cr3t".to_owned(),
|
||||
agent: "atlas".to_owned(),
|
||||
hive: "alpha".to_owned(),
|
||||
})
|
||||
.expect("serialises");
|
||||
assert_eq!(json["value"], "s3cr3t");
|
||||
assert_eq!(json["agent"], "atlas");
|
||||
assert_eq!(json["hive"], "alpha");
|
||||
assert!(json.get("hive").is_none(), "{json}");
|
||||
}
|
||||
|
||||
/// Neither name is optional. An object missing one is not a usable
|
||||
/// credential — a verifier holding `None` for the hive can only guess at
|
||||
/// the subjects to grant, and guessing is the failure this shape exists to
|
||||
/// prevent.
|
||||
/// A stored object may carry a `hive` field. It is ignored, and the object
|
||||
/// decodes.
|
||||
#[test]
|
||||
fn an_agent_object_missing_a_principal_does_not_decode() {
|
||||
assert!(
|
||||
serde_json::from_str::<AgentCredential>(r#"{"value":"s","agent":"atlas"}"#).is_err()
|
||||
fn a_stored_agent_object_that_still_names_a_hive_decodes() {
|
||||
let c: AgentCredential =
|
||||
serde_json::from_str(r#"{"value":"s3cr3t","agent":"atlas","hive":"alpha"}"#)
|
||||
.expect("a stored object carrying `hive` decodes");
|
||||
assert_eq!(
|
||||
c,
|
||||
AgentCredential {
|
||||
value: "s3cr3t".to_owned(),
|
||||
agent: "atlas".to_owned(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/// The agent is required: it is what the verifier checks the presented
|
||||
/// name against.
|
||||
#[test]
|
||||
fn an_agent_object_missing_its_agent_does_not_decode() {
|
||||
assert!(serde_json::from_str::<AgentCredential>(r#"{"value":"s"}"#).is_err());
|
||||
assert!(
|
||||
serde_json::from_str::<AgentCredential>(r#"{"value":"s","hive":"alpha"}"#).is_err()
|
||||
);
|
||||
}
|
||||
|
||||
/// The two kinds are different objects at different paths, and neither
|
||||
/// decodes as the other — the property that keeps a reader from picking up
|
||||
/// a hive-shared credential where a per-agent one was meant.
|
||||
#[test]
|
||||
|
|
@ -249,7 +251,6 @@ mod tests {
|
|||
let agent_json = serde_json::to_string(&AgentCredential {
|
||||
value: "s".to_owned(),
|
||||
agent: "atlas".to_owned(),
|
||||
hive: "alpha".to_owned(),
|
||||
})
|
||||
.expect("serialises");
|
||||
assert!(serde_json::from_str::<Credential>(&agent_json).is_err());
|
||||
|
|
|
|||
Loading…
Reference in a new issue