mara, reviewing the previous commit: "the field is specific to matrix, why add it to the general struct". She is right, and the answer is that there was no general struct — `Credential` had one consumer, the crate's only path builder was `matrix_account`, and `value` is pinned by `glue-matrix-bao-token.nix`, a matrix unit. It was matrix's throughout, wearing a general name; adding `homeserver` is what made that visible. `client` now moves whatever type a caller names and decodes nothing itself. That is forwarding rather than machinery: `vaultrs::kv2::read`/`set` are already generic over the payload. The matrix agreement moves to its own module holding both halves — where a credential lives (`account_path`, was `path::matrix_account`) and what the object at that path holds. `path` keeps only what every path obeys, so a second kind of swarm secret becomes a module beside `matrix` rather than another optional field on a struct it shares. argus raised the same collision from the other direction on #4092: two mutually-exclusive `Option`s modelling one concept is the failure mode this forecloses. `checked_segment` stays public in `path`: hive-priv builds an on-disk path from the same names and must accept the same charset. Behaviour is unchanged. The compatibility properties move with the struct — `Option` is what lets a pre-`homeserver` stored object decode, and `skip_serializing_if` is what keeps a token-only object free of `"homeserver":null` for that nix reader. Refs #3726
74 lines
2.8 KiB
Rust
74 lines
2.8 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`]). Each of those is a thing the controller and a hive must say
|
|
//! identically, so it is said once here.
|
|
//!
|
|
//! [`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 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 — `agent` or `account`.
|
|
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))
|
|
}
|
|
}
|