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:
parent
9a241a1df1
commit
52c931c3a7
5 changed files with 193 additions and 248 deletions
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Reference in a new issue