The identity half of giving an agent container its own way into the store. The operator's ruling: minting an identity the agent itself can authenticate with takes the delivery from four pieces (mint -> store -> pass -> use) to three (mint -> store -> use), and makes the agent that much less dependent on the hive it runs on — but only if the grant behind that identity is scoped to that agent's secrets, read-only. Otherwise it is a second way to be everyone. So `render_agent` is a SIBLING of `render`, not a parameter added to it. A hive's document is wide in two of its kinds on purpose (the module header says why each of those is a decision), and none of that breadth transfers: an agent's path names the agent, so scoping to it costs nothing and drifts nowhere, and an agent has no business with a service's OIDC client secret, another agent's credentials, a hive's, or the controller's. The tests say that as an exhaustive check over `path::Kind` rather than as a list of paths, so a kind added later cannot be granted here by a renderer nobody re-read. `render`'s own text is untouched, and its byte-for-byte assertion is left exactly as it stands: a hive can still read every agent's secrets, and closing that is the separate decision its doc comment already prices. The object name is `hive-agent-<agent>`, one string for the policy, the cert-auth role and the certificate subject. Inside `hive-` rather than beside it because `sys/policies/acl/hive-*` and `auth/cert/certs/hive-*` are the whole of what the controller may create, and the controller is the only principal that learns an agent exists — at no cost in authority, since it already holds create/update on `secret/data/swarm/agents/*`. It still cannot collide with `hive-<hive>`: that needs a hive named `agent-<agent>`, and a hive name may not contain `agent` (nix/reserved-hive-fragments.nix, the reservation the queue's `hive-<name>-agent` client ids already lean on). A test spells that collision out and fails if the prefix moves somewhere that guard does not cover. Rendering only. Nothing mints a leaf, writes a role or delivers a certificate yet, and nothing calls either new function — because nothing at the module layer knows which agents exist: hive-c0re creates them at runtime and renders them into its own meta flake, which is why even the gateway's per-agent vhosts are a generated conf rather than nix. Issuance therefore has to be runtime, and that design is the operator's to approve before it is built. Refs #4386
86 lines
3.5 KiB
Rust
86 lines
3.5 KiB
Rust
//! The swarm's secret-store client: where a credential lives, and how both ends
|
|
//! reach it.
|
|
//!
|
|
//! The HTTP is [`vaultrs`]'s job. What this crate owns is the *agreements* —
|
|
//! 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`]). 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
|
|
//! agent's, which are two documents because they are two shapes of grant rather
|
|
//! than one with a name in it. It belongs here rather than in the controller
|
|
//! because the grant and the path are one statement — spelled differently they
|
|
//! produce a 403 that names neither.
|
|
//!
|
|
//! [`client`] is deliberately ignorant of all of it: it moves whatever type a
|
|
//! caller names, so a second kind of secret is a new module beside [`matrix`]
|
|
//! and not another field on a struct shared with it.
|
|
|
|
pub mod client;
|
|
pub mod matrix;
|
|
pub mod path;
|
|
pub mod policy;
|
|
pub mod queue;
|
|
|
|
pub use client::SecretStore;
|
|
|
|
/// What can go wrong between "we have a client certificate" and "we have the
|
|
/// credential".
|
|
#[derive(Debug, thiserror::Error)]
|
|
pub enum Error {
|
|
/// A name that would have addressed something other than what the caller
|
|
/// meant. See [`path`].
|
|
#[error("{kind} name {value:?} is not a single path segment of [A-Za-z0-9_-]")]
|
|
PathSegment {
|
|
/// Which name was rejected. A principal's kind in the singular
|
|
/// (`agent`, `hive`, `service`, `controller`) when the name addresses
|
|
/// one, or what the name is to the secret otherwise — `account`, for
|
|
/// a matrix credential.
|
|
kind: &'static str,
|
|
/// The offending value, quoted in the message because the caller
|
|
/// usually got it from config and needs to see which one.
|
|
value: String,
|
|
},
|
|
|
|
/// A variable the store's address or identity comes from is unset or
|
|
/// empty. Named rather than defaulted: a wrong store address fails much
|
|
/// later and much less clearly than a missing one.
|
|
#[error("{0} is unset or empty")]
|
|
MissingEnv(&'static str),
|
|
|
|
/// A client-certificate file named by the environment could not be read.
|
|
#[error("reading {path} (from {var}): {source}")]
|
|
Identity {
|
|
/// The variable that named the file.
|
|
var: &'static str,
|
|
/// The path it named.
|
|
path: String,
|
|
/// The underlying IO failure.
|
|
source: std::io::Error,
|
|
},
|
|
|
|
/// The address would not parse into a URL the client can use.
|
|
#[error("the store's settings are unusable: {0}")]
|
|
Settings(String),
|
|
|
|
/// The store refused us, was unreachable, or answered something we could
|
|
/// not parse.
|
|
#[error(transparent)]
|
|
Vault(#[from] Box<vaultrs::error::ClientError>),
|
|
|
|
/// The client certificate and key did not form a usable identity, or the
|
|
/// CA bundle did not parse.
|
|
#[error("building the TLS identity: {0}")]
|
|
Tls(#[source] reqwest::Error),
|
|
}
|
|
|
|
impl From<vaultrs::error::ClientError> for Error {
|
|
fn from(e: vaultrs::error::ClientError) -> Self {
|
|
// Boxed because `ClientError` is large enough that carrying it inline
|
|
// makes every `Result` in the crate pay for the rare arm.
|
|
Self::Vault(Box::new(e))
|
|
}
|
|
}
|