swarm-nats-auth: give issued user JWTs an exp
The user JWT the callout issues carried no exp, so nats-server armed no expiry timer and a connection kept its grant until the client dropped it: a deleted or re-minted agent queue secret, a removed OIDC client or an expired access token never reached a live connection. Every issued user JWT now expires at the presented OIDC token's exp (read from the introspection answer) or an hour after issue, whichever is sooner; the agent queue-secret path has no exp of its own and gets the hour. Every queue client reconnects through an auth callback that re-reads or re-mints its credential, so the forced reconnect presents a current one. Fixes #5432
This commit is contained in:
parent
de4871cab1
commit
e405c2b098
9 changed files with 245 additions and 69 deletions
|
|
@ -278,7 +278,9 @@ async fn serve(args: &Args) -> anyhow::Result<()> {
|
|||
// normal thing for a client to attempt and an abnormal thing to
|
||||
// grant. Neither the store nor introspection is reached until
|
||||
// something was presented.
|
||||
let (caller, permissions) = match req
|
||||
// `expires` is the presented credential's own expiry, when it has one:
|
||||
// an agent's queue secret has none.
|
||||
let (caller, permissions, expires) = match req
|
||||
.connect_opts
|
||||
.auth_token
|
||||
.as_deref()
|
||||
|
|
@ -289,15 +291,16 @@ async fn serve(args: &Args) -> anyhow::Result<()> {
|
|||
Some(agent_token::Presented::Agent(token)) => (
|
||||
Some(format!("agent:{}", token.agent)),
|
||||
agent_token::authorize(&policy, &store, &token).await,
|
||||
None,
|
||||
),
|
||||
Some(agent_token::Presented::Malformed(e)) => {
|
||||
tracing::warn!(error = %e, "malformed agent token; denying");
|
||||
(None, None)
|
||||
(None, None, None)
|
||||
}
|
||||
Some(agent_token::Presented::Bearer(token)) => {
|
||||
introspected(&policy, &http, args, &client_secret, token).await
|
||||
}
|
||||
None => (None, None),
|
||||
None => (None, None, None),
|
||||
};
|
||||
// The caller is an identifier, not a credential: a client id, or
|
||||
// `agent:<name>` for an agent token. One line per auth request, a connect
|
||||
|
|
@ -326,6 +329,7 @@ async fn serve(args: &Args) -> anyhow::Result<()> {
|
|||
&req.server_id.id,
|
||||
&req.user_nkey,
|
||||
permissions,
|
||||
expires,
|
||||
),
|
||||
None => respond::deny(&issuer, &req.server_id.id, &req.user_nkey),
|
||||
};
|
||||
|
|
@ -439,8 +443,9 @@ async fn ensure_callout_keys(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// The OIDC path: who the `IdP` says presented `token`, and what the policy
|
||||
/// grants that client. `None` permissions is a denial.
|
||||
/// The OIDC path: who the `IdP` says presented `token`, what the policy
|
||||
/// grants that client, and when the token expires. `None` permissions is a
|
||||
/// denial.
|
||||
///
|
||||
/// The caller is an identity or nothing — see `introspect`'s module docs.
|
||||
/// There is no "admitted, identity unknown" branch to write here because
|
||||
|
|
@ -451,7 +456,7 @@ async fn introspected(
|
|||
args: &Args,
|
||||
client_secret: &oidc_secret::OidcSecret,
|
||||
token: &str,
|
||||
) -> (Option<String>, Option<policy::Permissions>) {
|
||||
) -> (Option<String>, Option<policy::Permissions>, Option<i64>) {
|
||||
let deadline = tokio::time::Instant::now() + introspect::INTROSPECTION_TIMEOUT;
|
||||
let caller = client_secret
|
||||
.introspect(deadline, |secret| async move {
|
||||
|
|
@ -473,6 +478,7 @@ async fn introspected(
|
|||
tracing::warn!(error = ?e, "introspection failed; denying");
|
||||
None
|
||||
});
|
||||
let (caller, expires) = caller.map_or((None, None), |c| (Some(c.client_id), c.expires));
|
||||
// Admission said who; the policy says what. A caller the `IdP`
|
||||
// vouches for but no rule matches is denied — see `policy`'s module
|
||||
// docs for why that is deny and not "connect with nothing".
|
||||
|
|
@ -486,7 +492,7 @@ async fn introspected(
|
|||
"authenticated client matches no policy rule; denying"
|
||||
);
|
||||
}
|
||||
(caller, permissions)
|
||||
(caller, permissions, expires)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
|||
Loading…
Reference in a new issue