swarm-controller: CreateIdentity node, auth-bridge client, POST /api/agents
This commit is contained in:
parent
c1eb6b9834
commit
d30f149338
5 changed files with 289 additions and 55 deletions
|
|
@ -321,6 +321,45 @@ async fn mint_token(http: &reqwest::Client, cfg: &QueueConfig) -> Result<CachedT
|
|||
})
|
||||
}
|
||||
|
||||
/// Build the HTTP client used to reach `cfg.token_endpoint`, trusting
|
||||
/// `cfg.ca_file` when set. Shared by [`connect`]'s auth callback and by
|
||||
/// [`mint_token_for`] — anything presenting this identity's credentials to
|
||||
/// the token endpoint needs the swarm's own CA trusted the same way, so the
|
||||
/// trust-anchor logic lives here once rather than once per caller.
|
||||
fn build_http_client(cfg: &QueueConfig) -> Result<reqwest::Client, Error> {
|
||||
let mut builder = reqwest::Client::builder().timeout(std::time::Duration::from_secs(10));
|
||||
if let Some(path) = &cfg.ca_file {
|
||||
let pem = std::fs::read(path).map_err(|source| Error::CaFile {
|
||||
path: path.display().to_string(),
|
||||
source,
|
||||
})?;
|
||||
let cert = reqwest::Certificate::from_pem(&pem).map_err(|source| Error::CaParse {
|
||||
path: path.display().to_string(),
|
||||
source,
|
||||
})?;
|
||||
builder = builder.add_root_certificate(cert);
|
||||
}
|
||||
builder.build().map_err(Error::HttpClient)
|
||||
}
|
||||
|
||||
/// Mint a fresh token for `cfg`'s identity and hand back just the string —
|
||||
/// no caching, a fresh HTTP client per call.
|
||||
///
|
||||
/// Public because the queue connection is not the only thing this identity
|
||||
/// authenticates: "one identity per principal" means a caller that already
|
||||
/// holds a [`QueueConfig`] for its queue connection authenticates anywhere
|
||||
/// else it needs to prove who it is from the exact same client, rather than
|
||||
/// a second identity being provisioned per destination. No caching here
|
||||
/// unlike [`connect`]'s callback: that one exists because `async-nats` reruns
|
||||
/// its callback per reconnect *attempt*, a hot path this isn't — a caller
|
||||
/// outside that loop (e.g. `swarm-controller::auth`'s bridge client) mints
|
||||
/// per call, same as this crate did before the reconnect-storm fix added the
|
||||
/// cache.
|
||||
pub async fn mint_token_for(cfg: &QueueConfig) -> Result<String, Error> {
|
||||
let http = build_http_client(cfg)?;
|
||||
Ok(mint_token(&http, cfg).await?.token)
|
||||
}
|
||||
|
||||
/// Fail fast unless the client is actually connected.
|
||||
///
|
||||
/// **Call this before every `JetStream` request.** `retry_on_initial_connect`
|
||||
|
|
@ -355,36 +394,14 @@ pub fn ensure_connected(client: &async_nats::Client) -> Result<(), Error> {
|
|||
/// token expires, the controller keeps serving, its status data quietly stops
|
||||
/// updating, and nothing says so until someone reads a dashboard.
|
||||
pub async fn connect(cfg: QueueConfig) -> Result<async_nats::Client, Error> {
|
||||
// A timeout, because this client runs INSIDE the auth callback: a token
|
||||
// endpoint that accepts the connection and then never answers would hang
|
||||
// the callback, and with it the connection attempt that invoked it, with
|
||||
// no retry and nothing in the log to say why. Failing fast lets
|
||||
// Trusts `cfg.ca_file` when set (the swarm's own CA, when the token
|
||||
// endpoint is signed by it) — see `build_http_client`. A timeout too,
|
||||
// because this client runs INSIDE the auth callback: a token endpoint
|
||||
// that accepts the connection and then never answers would hang the
|
||||
// callback, and with it the connection attempt that invoked it, with no
|
||||
// retry and nothing in the log to say why. Failing fast lets
|
||||
// `async-nats` do what it already does well — back off and try again.
|
||||
// 10s is generous for a form POST to a local IdP.
|
||||
let mut builder = reqwest::Client::builder().timeout(std::time::Duration::from_secs(10));
|
||||
|
||||
// The swarm's own CA, when the token endpoint is signed by it. ADDED, not
|
||||
// substituted: `add_root_certificate` extends the default set rather than
|
||||
// replacing it, so a swarm can front authelia publicly and still have
|
||||
// this work.
|
||||
//
|
||||
// Failing here rather than falling back to the platform roots is the
|
||||
// point — an operator who named a CA file wants that anchor, and a
|
||||
// silent fallback would turn their typo into `UnknownIssuer` five layers
|
||||
// away, inside an auth callback, on a retry loop.
|
||||
if let Some(path) = &cfg.ca_file {
|
||||
let pem = std::fs::read(path).map_err(|source| Error::CaFile {
|
||||
path: path.display().to_string(),
|
||||
source,
|
||||
})?;
|
||||
let cert = reqwest::Certificate::from_pem(&pem).map_err(|source| Error::CaParse {
|
||||
path: path.display().to_string(),
|
||||
source,
|
||||
})?;
|
||||
builder = builder.add_root_certificate(cert);
|
||||
}
|
||||
|
||||
let http = builder.build().map_err(Error::HttpClient)?;
|
||||
let http = build_http_client(&cfg)?;
|
||||
let url = cfg.url.clone();
|
||||
|
||||
// Shared across every invocation of the callback below, which is the
|
||||
|
|
|
|||
Loading…
Reference in a new issue