Watch
0
0
Fork
You've already forked hyperhive
0

swarm-nats: require the responder's store identity, and drop its no-store mode

The responder reads the callout xkey before it connects, so a responder
with no secret store no longer starts, and its "deny every agent token,
serve OIDC" mode could not run. That mode is gone: `agent_token::Store`
is built from the settings the xkey load already read, and `authorize`
takes a source rather than an `Option` of one.

On the nix side, `deploy.nats.authBaoClientCertFile` and
`authBaoClientKeyFile` are now asserted at eval when the queue is
enabled. The units' no-identity branches, the copy unit's empty-file
fallback and its "denies agent tokens" message are removed; an absent
leaf file fails `swarm-nats-auth-secrets` like an absent seed does.
This commit is contained in:
atlas 2026-10-05 22:13:58 +02:00 • committed by mara
commit 52c931c3a7
5 changed files with 193 additions and 248 deletions

View file

@ -58,19 +58,11 @@ pub struct Store {
}
impl Store {
/// The store named by the `BAO_*` environment, or `None` when that is
/// unset: a responder with no store identity denies every agent token and
/// serves the OIDC path unchanged.
pub fn from_env(cert_role: String) -> Option<Self> {
match Settings::from_env() {
Ok(settings) => Some(Self {
settings,
cert_role,
}),
Err(e) => {
tracing::info!(reason = %e, "no secret store configured; agent tokens are denied");
None
}
/// The store at `settings`, logged in to as `cert_role`.
pub fn new(settings: Settings, cert_role: String) -> Self {
Self {
settings,
cert_role,
}
}
}
@ -149,20 +141,11 @@ async fn verify(source: &impl CredentialSource, token: &AgentToken<'_>) -> bool
}
/// The grant for an agent token, or `None` for a denial.
///
/// `source` is `None` when this responder has no store identity, which denies.
pub async fn authorize<S: CredentialSource>(
pub async fn authorize(
policy: &Policy,
source: Option<&S>,
source: &impl CredentialSource,
token: &AgentToken<'_>,
) -> Option<Permissions> {
let Some(source) = source else {
tracing::warn!(
agent = token.agent,
"agent token presented, but this responder has no secret store; denying"
);
return None;
};
if !verify(source, token).await {
return None;
}
@ -237,7 +220,7 @@ mod tests {
.expect("valid")
}
async fn grant(source: Option<&Fake>, token: &str) -> Option<Permissions> {
async fn grant(source: &Fake, token: &str) -> Option<Permissions> {
let Presented::Agent(token) = classify(token) else {
panic!("{token:?} must classify as an agent token");
};
@ -246,7 +229,7 @@ mod tests {
#[tokio::test]
async fn a_valid_agent_token_is_granted_exactly_its_own_subjects() {
let g = grant(Some(&atlas()), "swarm-agent.atlas.Ab9_-zSECRET")
let g = grant(&atlas(), "swarm-agent.atlas.Ab9_-zSECRET")
.await
.expect("granted");
assert_eq!(
@ -262,12 +245,12 @@ mod tests {
#[tokio::test]
async fn a_wrong_secret_is_denied() {
assert!(
grant(Some(&atlas()), "swarm-agent.atlas.Ab9_-zSECREX")
grant(&atlas(), "swarm-agent.atlas.Ab9_-zSECREX")
.await
.is_none()
);
assert!(
grant(Some(&atlas()), "swarm-agent.atlas.Ab9_-zSECRE")
grant(&atlas(), "swarm-agent.atlas.Ab9_-zSECRE")
.await
.is_none()
);
@ -279,7 +262,7 @@ mod tests {
#[tokio::test]
async fn another_agents_name_with_this_agents_secret_is_denied() {
assert!(
grant(Some(&atlas()), "swarm-agent.argus.Ab9_-zSECRET")
grant(&atlas(), "swarm-agent.argus.Ab9_-zSECRET")
.await
.is_none()
);
@ -288,19 +271,16 @@ mod tests {
#[tokio::test]
async fn an_agent_with_nothing_stored_is_denied() {
assert!(
grant(
Some(&stored("argus", "argus")),
"swarm-agent.atlas.Ab9_-zSECRET"
)
.await
.is_none()
grant(&stored("argus", "argus"), "swarm-agent.atlas.Ab9_-zSECRET")
.await
.is_none()
);
}
#[tokio::test]
async fn a_failed_lookup_is_denied() {
assert!(
grant(Some(&Fake::Fails), "swarm-agent.atlas.Ab9_-zSECRET")
grant(&Fake::Fails, "swarm-agent.atlas.Ab9_-zSECRET")
.await
.is_none()
);
@ -313,7 +293,7 @@ mod tests {
let bound = crate::introspect::INTROSPECTION_TIMEOUT;
let started = std::time::Instant::now();
assert!(
grant(Some(&Fake::Hangs), "swarm-agent.atlas.Ab9_-zSECRET")
grant(&Fake::Hangs, "swarm-agent.atlas.Ab9_-zSECRET")
.await
.is_none()
);
@ -325,24 +305,12 @@ mod tests {
);
}
#[tokio::test]
async fn no_store_is_denied() {
assert!(
grant(None, "swarm-agent.atlas.Ab9_-zSECRET")
.await
.is_none()
);
}
#[tokio::test]
async fn a_stored_object_naming_another_agent_is_denied() {
assert!(
grant(
Some(&stored("argus", "atlas")),
"swarm-agent.argus.Ab9_-zSECRET"
)
.await
.is_none()
grant(&stored("argus", "atlas"), "swarm-agent.argus.Ab9_-zSECRET")
.await
.is_none()
);
}
@ -359,7 +327,7 @@ mod tests {
let Presented::Agent(token) = classify("swarm-agent.atlas.Ab9_-zSECRET") else {
panic!("an agent token");
};
assert!(authorize(&bare, Some(&atlas()), &token).await.is_none());
assert!(authorize(&bare, &atlas(), &token).await.is_none());
}
/// Everything without the prefix reaches introspection as it arrived.