name the agent's entity before narrowing its role, and test hive provisioning against a fake store
This commit is contained in:
parent
139007e656
commit
09d89507a9
1 changed files with 535 additions and 67 deletions
|
|
@ -13,20 +13,22 @@
|
|||
//! is ever moved. Any other arrangement is a [`Plan::Conflict`]: nothing is
|
||||
//! written and an operator decides.
|
||||
//!
|
||||
//! Everything else here is best effort: a failure is logged at `warn` and the
|
||||
//! caller goes on. An agent whose entity is not named after it reads nothing,
|
||||
//! because the shared policy is the only one its role attaches; at mint the
|
||||
//! read-back fails, and on a start pass its next reads are refused.
|
||||
//! An agent whose entity is not named after it reads nothing under the shared
|
||||
//! policy, so what a failure costs depends on the caller. [`ensure_agent`],
|
||||
//! which runs before a cert-auth role is narrowed to that policy, returns the
|
||||
//! error and leaves the role as it was. [`ensure_agents`], the pass at every
|
||||
//! start, writes no role and so narrows nothing: it logs at `warn` and goes on.
|
||||
//! The grants are the `identity/` stanzas of `swarm-bao.nix`'s
|
||||
//! `controllerPolicyText`.
|
||||
|
||||
use anyhow::{Context, Result, bail};
|
||||
use swarm_secret_client::{
|
||||
SecretStore,
|
||||
client::{DEFAULT_CERT_MOUNT, EntityRef},
|
||||
policy,
|
||||
};
|
||||
|
||||
use crate::store::AccessStore;
|
||||
|
||||
/// What the store holds for one agent's entity.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Seen {
|
||||
|
|
@ -92,13 +94,13 @@ pub fn plan(agent: &str, seen: &Seen) -> Plan {
|
|||
|
||||
/// Make sure every agent in `agents` has its entity, logging what could not be
|
||||
/// done. Never fails: see the module header.
|
||||
pub async fn ensure_agents(store: &SecretStore, agents: &[String]) {
|
||||
pub async fn ensure_agents(store: &impl AccessStore, agents: &[String]) {
|
||||
let accessor = match store.auth_mount_accessor(DEFAULT_CERT_MOUNT).await {
|
||||
Ok(accessor) => accessor,
|
||||
Err(e) => {
|
||||
tracing::warn!(
|
||||
error = %e,
|
||||
"agent entities are not managed: the cert auth mount's accessor could not be read; an agent whose entity is not named after it cannot read its secrets"
|
||||
"agent entities are not managed: the cert auth mount's accessor could not be read; an agent whose role attaches the shared policy alone reads nothing until its entity is named after it"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
|
@ -120,13 +122,30 @@ pub async fn ensure_agents(store: &SecretStore, agents: &[String]) {
|
|||
);
|
||||
}
|
||||
|
||||
/// [`ensure_agents`] for one agent.
|
||||
pub async fn ensure_agent(store: &SecretStore, agent: &str) {
|
||||
ensure_agents(store, &[agent.to_owned()]).await;
|
||||
/// Make sure `agent` has its entity, or say why not.
|
||||
///
|
||||
/// For a caller that must not go on without it, such as the one that narrows a
|
||||
/// cert-auth role to the shared policy: an agent whose entity is not named after
|
||||
/// it reads nothing under that policy.
|
||||
///
|
||||
/// # Errors
|
||||
/// When the cert mount's accessor cannot be read, when a store call fails, and
|
||||
/// on a [`Plan::Conflict`], which [`ensure_agents`] only logs.
|
||||
pub async fn ensure_agent(store: &impl AccessStore, agent: &str) -> Result<()> {
|
||||
let accessor = store
|
||||
.auth_mount_accessor(DEFAULT_CERT_MOUNT)
|
||||
.await
|
||||
.context("reading the cert auth mount's accessor")?;
|
||||
match ensure_one(store, &accessor, agent).await? {
|
||||
Plan::Conflict { alias, entity_name } => bail!(
|
||||
"the alias {alias} is on the entity {entity_name}, which is not {agent}; nothing was changed"
|
||||
),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Observe, plan and apply for one agent.
|
||||
async fn ensure_one(store: &SecretStore, accessor: &str, agent: &str) -> Result<Plan> {
|
||||
async fn ensure_one(store: &impl AccessStore, accessor: &str, agent: &str) -> Result<Plan> {
|
||||
let alias = policy::agent_object_name(agent)?;
|
||||
let seen = Seen {
|
||||
by_alias: store
|
||||
|
|
@ -174,7 +193,7 @@ async fn ensure_one(store: &SecretStore, accessor: &str, agent: &str) -> Result<
|
|||
agent,
|
||||
alias,
|
||||
entity_name,
|
||||
"the agent's alias is on an entity that is not the agent's; nothing was changed and the agent cannot read its secrets until an operator resolves it"
|
||||
"the agent's alias is on an entity that is not the agent's; nothing was changed; under the shared policy alone the agent reads nothing until an operator resolves it"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue