The responder reads its OIDC client secret at `swarm/services/<client-id>/oidc/client` with the store login it already opens for the callout keys, and holds it in memory. `--client-secret-file` is removed. When introspection answers non-2xx, the responder re-reads the secret from the store once and, if it changed, retries the introspection once. Both fit inside the request's 1.5s introspection budget, under the server's 2s callout timeout: the budget is now one deadline for the whole request. A re-read that does not finish by then keeps running in the background, the request is denied, and the next request introspects with what it read. One re-read runs at a time; a refusal while one runs waits on it. In swarm-nats.nix the `swarm-bao-nats-auth-oidc` fetch unit, the flag and the `oidc-client.secret` LoadCredential entry are gone, along with the unit's policy ordering in glue-bao-readers-policy-order.nix. A tmpfiles `r` rule removes the copy a container still holds at /var/lib/nixos-containers/swarm-nats/var/lib/swarm-nats-auth/oidc-client.secret; nothing reads it. No policy change: the `swarm-nats-auth` role already reads this path (`natsAuthReaders` in swarm-bao.nix), which is the role the removed fetch unit logged in as. Deploy order: none, and no operator step. The store's grant already exists, so the queue host can be deployed before, after or without the store's host and authelia's. Rolling back re-creates the fetch unit and the file; the store leaf is untouched.
56 lines
2.3 KiB
TOML
56 lines
2.3 KiB
TOML
[package]
|
|
name = "swarm-nats-auth"
|
|
version.workspace = true
|
|
readme = "README.md"
|
|
edition.workspace = true
|
|
|
|
[[bin]]
|
|
name = "swarm-nats-auth"
|
|
path = "src/main.rs"
|
|
|
|
[dependencies]
|
|
anyhow.workspace = true
|
|
async-nats.workspace = true
|
|
clap.workspace = true
|
|
# base64url for decoding the inbound request JWT.
|
|
data-encoding.workspace = true
|
|
# StreamExt::next on the subscription: async-nats returns a Stream.
|
|
futures-util.workspace = true
|
|
# `xkeys` for the callout xkey: opening sealed requests and sealing replies.
|
|
nkeys = { workspace = true, features = ["xkeys"] }
|
|
reqwest.workspace = true
|
|
serde.workspace = true
|
|
serde_json.workspace = true
|
|
# The jti digest: base32hex(sha256(claims)) over every JWT this crate signs.
|
|
sha2.workspace = true
|
|
# Comparing an agent's presented secret with the stored one.
|
|
subtle.workspace = true
|
|
# For `status::BUCKET` and `notices::STREAM` - the subjects a hive may
|
|
# publish to are derived from these names, and every end that touches them
|
|
# must agree on the same one. Deliberately WITHOUT the `kv` feature: this
|
|
# crate derives subject strings, it never opens the bucket. `notices`
|
|
# is name-only too (no `jetstream`/`kv` surface), same reason.
|
|
swarm-queue-client = { workspace = true, features = ["notices"] }
|
|
# The agent-token spelling the agent also uses, and the read of the stored
|
|
# credential it is checked against.
|
|
swarm-secret-client.workspace = true
|
|
tokio.workspace = true
|
|
tracing.workspace = true
|
|
tracing-subscriber.workspace = true
|
|
|
|
[dev-dependencies]
|
|
# A TEST ORACLE, not part of the production path. Neither JWT this crate emits
|
|
# is expressible through it - `Claims` has no `aud`, which the response wrapper
|
|
# needs (the server id) and the user token needs (the account name), and
|
|
# `Token::new_user` always sets `issuer_account`, which a non-operator server
|
|
# rejects outright. So both are hand-built, and this crate is what the encoder
|
|
# is checked *against*: `respond::tests::hand_built_matches_the_reference`
|
|
# builds a user token both ways and requires byte equality, on the one shape
|
|
# nats-jwt does model.
|
|
nats-jwt.workspace = true
|
|
# `test-util` for `#[tokio::test(start_paused = true)]`: `oidc_secret`'s tests
|
|
# run a re-read slower than the introspection budget on paused time.
|
|
tokio = { workspace = true, features = ["test-util"] }
|
|
|
|
[lints]
|
|
workspace = true
|