swarm UI: an alerts page, read by the controller with a Viewer Grafana token from bao
swarm-bao-grafana-controller-token, on the host that runs Grafana, logs in to Grafana's API over its socket as the built-in admin (the password swarm-bao-grafana-admin minted, #5138) and keeps a `swarm-controller` service account that is an enabled Viewer. It mints a token for that account and writes it to bao at swarm/controller/swarm-controller/grafana/token under Grafana's leaf. A ten-minute timer runs it again. The unit keeps a stored token Grafana accepts. If Grafana refuses the stored token with 401, or bao holds none, the unit deletes every token of the account and mints a new one. Grafana's grant gains create/read/update on that leaf, and the controller's gains read. swarm-controller serves GET /api/alerts from Grafana's /api/prometheus/grafana/api/v1/alerts. It reads the token from bao, keeps it in memory, and re-reads bao once on a 401. The HTTP client has 5s connect and 15s request timeouts. Any failure is a 503 that names its cause. swarm-ui gets an /alerts page listing them, built the same way as the hives page. This is option (c) on #5129, the operator's choice.
This commit is contained in:
parent
8cb6273782
commit
7d09776742
23 changed files with 888 additions and 37 deletions
54
swarm-secret-client/src/grafana.rs
Normal file
54
swarm-secret-client/src/grafana.rs
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
//! The Grafana agreement: where `swarm-controller`'s token for Grafana's API
|
||||
//! lives, and what the object at that path holds.
|
||||
//!
|
||||
//! `swarm-bao-grafana-controller-token`, on the host that runs Grafana, mints
|
||||
//! the token for a Viewer service account and writes it here under Grafana's
|
||||
//! own certificate; `swarm-controller` reads it under its own. Those two
|
||||
//! principals' stanzas are the only ones in the store that cover this path.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
Error,
|
||||
path::{Kind, principal_prefix},
|
||||
};
|
||||
|
||||
/// The path holding `swarm-controller`'s Grafana token.
|
||||
///
|
||||
/// Under [`Kind::Controller`], which no hive's policy reads. The nix half is
|
||||
/// `swarm-bao.nix`'s `controllerGrafanaTokenLeaf`.
|
||||
///
|
||||
/// # Errors
|
||||
/// Never in practice: the name segment is a constant. The `Result` is
|
||||
/// [`principal_prefix`]'s.
|
||||
pub fn controller_token_path() -> Result<String, Error> {
|
||||
let prefix = principal_prefix(Kind::Controller, crate::matrix::CONTROLLER)?;
|
||||
Ok(format!("{prefix}/grafana/token"))
|
||||
}
|
||||
|
||||
/// What [`controller_token_path`] holds.
|
||||
///
|
||||
/// No `Debug` derive: `value` reads every alert and dashboard in the swarm's
|
||||
/// Grafana, and a derived `Debug` is one `{:?}` away from a log line.
|
||||
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ControllerToken {
|
||||
/// The token itself. The minting unit writes it with
|
||||
/// `bao kv put … value=-`, so this name is load-bearing for a writer this
|
||||
/// crate does not control.
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn the_controllers_grafana_token_is_under_the_controllers_prefix() {
|
||||
// The literal is what `swarm-bao.nix` grants Grafana's role and the
|
||||
// controller's, and what the minting unit writes.
|
||||
assert_eq!(
|
||||
controller_token_path().expect("a constant segment is legal"),
|
||||
"swarm/controller/swarm-controller/grafana/token"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -5,10 +5,10 @@
|
|||
//! the rules every path obeys ([`path`]), the translation from this
|
||||
//! deployment's environment into a logged-in client ([`client`]), and, per kind
|
||||
//! of secret, the path it lives at together with the fields it holds
|
||||
//! ([`matrix`], [`queue`], [`mtls`], [`forge`], [`github`], [`acp`], [`ci`],
|
||||
//! [`review_classifier`], [`builder`], [`claude_login`]). Each of those is a
|
||||
//! thing the controller and a hive must say identically, so it is said once
|
||||
//! here.
|
||||
//! ([`matrix`], [`queue`], [`mtls`], [`forge`], [`github`], [`grafana`],
|
||||
//! [`acp`], [`ci`], [`review_classifier`], [`builder`], [`claude_login`]).
|
||||
//! Each of those is a thing the controller and a hive must say identically, so
|
||||
//! it is said once here.
|
||||
//!
|
||||
//! [`policy`] is the same kind of agreement seen from the other side: which of
|
||||
//! those paths a given principal's own token may read — a hive's, and an
|
||||
|
|
@ -31,6 +31,7 @@ pub mod claude_login;
|
|||
pub mod client;
|
||||
pub mod forge;
|
||||
pub mod github;
|
||||
pub mod grafana;
|
||||
pub mod matrix;
|
||||
pub mod mtls;
|
||||
pub mod path;
|
||||
|
|
|
|||
Loading…
Reference in a new issue