When nats-server has an auth_callout xkey it seals each request to that key and names its own public xkey in the Nats-Server-Xkey header. The responder now loads the callout xkey seed from the secret store before it connects, opens a sealed request with it, and seals its reply (grant or deny) back to the header's key. A request with no header is answered in plaintext, as before. A new `ensure-xkey` verb creates the pair when the store holds no seed: the seed at swarm/services/swarm-nats/callout/xkey-seed, the public half at swarm/public/swarm-nats/callout-xkey. Each write is a create-only (cas=0) write, and a run whose create fails reads the stored value back, so two concurrent runs end up with one key. The responder itself only reads the seed and refuses to start without it. swarm-secret-client gains SecretStore::create, the cas=0 write.
53 lines
2.1 KiB
TOML
53 lines
2.1 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
|
|
|
|
[lints]
|
|
workspace = true
|