swarmctl: log in with the operator's own SSO account and reach the controller through the swarm domain
`swarmctl --swarm https://<swarm> login` runs the OAuth 2.0 authorization-code flow with PKCE against the swarm's authelia as a new public `swarmctl` client: a pushed authorization request, the browser login and explicit consent, and the code delivered by form_post to a loopback port. The access and refresh tokens are cached at mode 0600 under $XDG_STATE_HOME/swarmctl/ and refreshed under a file lock, since authelia rotates the refresh token and revokes the login when a used one is presented again. With --swarm (or SWARMCTL_SWARM), the agent, schedule and forge verbs send their requests to https://<swarm>/api/ with the bearer token instead of over the controller's unix socket, which stays the default. authelia registers the client on every authelia host with a swarm domain: `kind = "native"`, public, no minted secret, scopes offline_access and authelia.bearer.authz, audience https://<swarm>/api/ (matched as a URL prefix), redirect http://127.0.0.1/callback (any loopback port). Every field is one authelia 4.39.20's validator requires for a public client holding the bearer scope. The swarm apex's /api/ answers a refused caller that sent an Authorization header with the 401 itself instead of the login redirect, so the CLI can tell "log in again" apart from an answer; a browser still gets the redirect. The bearer token is not forwarded to the controller. Refs #4409 (mara: c94764, c94782).
This commit is contained in:
parent
5c15580060
commit
2df19e4899
17 changed files with 1118 additions and 122 deletions
|
|
@ -9,12 +9,11 @@
|
|||
//! The shapes below mirror the controller's `InferenceSettings` and
|
||||
//! `AgentInferenceView`, for the reason [`crate::agent`]'s module doc gives.
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::{Context as _, Result, bail};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::agent::{parse_ident, send};
|
||||
use crate::remote::Target;
|
||||
|
||||
/// One agent's settings; an absent field is not set at swarm level.
|
||||
#[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
|
|
@ -66,7 +65,7 @@ impl SetFlags {
|
|||
|
||||
fn runtime() -> Result<tokio::runtime::Runtime> {
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
.enable_io()
|
||||
.enable_all()
|
||||
.build()
|
||||
.context("starting a tokio runtime for the controller request")
|
||||
}
|
||||
|
|
@ -79,10 +78,10 @@ fn member(agent: &str) -> Result<String> {
|
|||
}
|
||||
|
||||
/// Run `swarmctl agent inference get`.
|
||||
pub(crate) fn get(socket: &Path, agent: &str) -> Result<()> {
|
||||
pub(crate) fn get(target: &Target, agent: &str) -> Result<()> {
|
||||
let uri = member(agent)?;
|
||||
let view: View = runtime()?.block_on(send(
|
||||
socket,
|
||||
target,
|
||||
hyper::Method::GET,
|
||||
&uri,
|
||||
None::<&()>,
|
||||
|
|
@ -98,11 +97,11 @@ pub(crate) fn get(socket: &Path, agent: &str) -> Result<()> {
|
|||
|
||||
/// Run `swarmctl agent inference set`: read the agent's settings, write the
|
||||
/// flags over them, and store the result.
|
||||
pub(crate) fn set(socket: &Path, agent: &str, flags: SetFlags) -> Result<()> {
|
||||
pub(crate) fn set(target: &Target, agent: &str, flags: SetFlags) -> Result<()> {
|
||||
let uri = member(agent)?;
|
||||
let rt = runtime()?;
|
||||
let current: View = rt.block_on(send(
|
||||
socket,
|
||||
target,
|
||||
hyper::Method::GET,
|
||||
&uri,
|
||||
None::<&()>,
|
||||
|
|
@ -110,7 +109,7 @@ pub(crate) fn set(socket: &Path, agent: &str, flags: SetFlags) -> Result<()> {
|
|||
))?;
|
||||
let wanted = flags.merge(current.configured.unwrap_or_default())?;
|
||||
let stored: Settings = rt.block_on(send(
|
||||
socket,
|
||||
target,
|
||||
hyper::Method::PUT,
|
||||
&uri,
|
||||
Some(&wanted),
|
||||
|
|
@ -121,10 +120,10 @@ pub(crate) fn set(socket: &Path, agent: &str, flags: SetFlags) -> Result<()> {
|
|||
}
|
||||
|
||||
/// Run `swarmctl agent inference clear`.
|
||||
pub(crate) fn clear(socket: &Path, agent: &str) -> Result<()> {
|
||||
pub(crate) fn clear(target: &Target, agent: &str) -> Result<()> {
|
||||
let uri = member(agent)?;
|
||||
runtime()?.block_on(send::<(), ()>(
|
||||
socket,
|
||||
target,
|
||||
hyper::Method::DELETE,
|
||||
&uri,
|
||||
None,
|
||||
|
|
|
|||
Loading…
Reference in a new issue