Watch
0
0
Fork
You've already forked hyperhive
0

bring existing agent cert roles to the 1h token ttl on controller start

This commit is contained in:
atlas 2026-10-29 13:47:32 +01:00 • committed by mara
commit 76de27fc97

View file

@ -8,7 +8,10 @@
use std::future::Future;
use swarm_secret_client::{Error, SecretStore, client::EntityRef};
use swarm_secret_client::{
Error, SecretStore,
client::{CertRoleTokenTtls, EntityRef},
};
/// The cert-auth role the controller logs in under.
///
@ -48,6 +51,20 @@ pub(crate) trait AccessStore {
policies: &[&str],
) -> impl Future<Output = Result<(), Error>> + Send;
/// As [`SecretStore::cert_role_token_ttls`].
fn cert_role_token_ttls(
&self,
mount: &str,
name: &str,
) -> impl Future<Output = Result<Option<CertRoleTokenTtls>, Error>> + Send;
/// As [`SecretStore::set_cert_role_token_ttls`].
fn set_cert_role_token_ttls(
&self,
mount: &str,
name: &str,
) -> impl Future<Output = Result<(), Error>> + Send;
/// As [`SecretStore::auth_mount_accessor`].
fn auth_mount_accessor(
&self,
@ -99,6 +116,18 @@ impl AccessStore for SecretStore {
SecretStore::write_cert_role(self, mount, name, certificate, common_name, policies).await
}
async fn cert_role_token_ttls(
&self,
mount: &str,
name: &str,
) -> Result<Option<CertRoleTokenTtls>, Error> {
SecretStore::cert_role_token_ttls(self, mount, name).await
}
async fn set_cert_role_token_ttls(&self, mount: &str, name: &str) -> Result<(), Error> {
SecretStore::set_cert_role_token_ttls(self, mount, name).await
}
async fn auth_mount_accessor(&self, mount: &str) -> Result<String, Error> {
SecretStore::auth_mount_accessor(self, mount).await
}
@ -137,16 +166,35 @@ impl AccessStore for SecretStore {
/// outcome of the access calls.
#[cfg(test)]
pub(crate) mod fake {
use std::sync::Mutex;
use std::{collections::BTreeMap, sync::Mutex};
use swarm_secret_client::{Error, client::EntityRef};
use swarm_secret_client::{
Error,
client::{CERT_ROLE_TOKEN_TTL_SECS, CertRoleTokenTtls, EntityRef},
};
use super::AccessStore;
/// A cert-auth role as the fake keeps it: what a write of the role sets,
/// and the lifetimes a write of only those can change.
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct FakeRole {
pub(crate) certificate: String,
pub(crate) common_name: String,
pub(crate) policies: Vec<String>,
pub(crate) ttls: CertRoleTokenTtls,
}
/// What the fake answers; the default is a store with a readable cert mount
/// and no entities, which accepts every write.
#[derive(Default)]
pub(crate) struct FakeAccess {
/// The cert-auth roles, by name.
roles: Mutex<BTreeMap<String, FakeRole>>,
/// Roles whose lifetimes cannot be read.
unreadable_roles: Vec<String>,
/// Roles whose lifetimes cannot be written.
unwritable_roles: Vec<String>,
/// The accessor read fails.
no_accessor: bool,
/// Every policy write is refused.
@ -182,6 +230,32 @@ pub(crate) mod fake {
}
}
/// A store that holds `role` under `name`.
pub(crate) fn with_role(self, name: &str, role: FakeRole) -> Self {
self.roles
.lock()
.expect("not poisoned")
.insert(name.to_owned(), role);
self
}
/// A store that cannot read the lifetimes of the role `name`.
pub(crate) fn with_unreadable_role(mut self, name: &str) -> Self {
self.unreadable_roles.push(name.to_owned());
self
}
/// A store that refuses to write the lifetimes of the role `name`.
pub(crate) fn with_unwritable_role(mut self, name: &str) -> Self {
self.unwritable_roles.push(name.to_owned());
self
}
/// The role `name` as the store holds it now.
pub(crate) fn role(&self, name: &str) -> Option<FakeRole> {
self.roles.lock().expect("not poisoned").get(name).cloned()
}
/// The calls so far, as `verb name` strings, in order.
pub(crate) fn calls(&self) -> Vec<String> {
self.calls.lock().expect("not poisoned").clone()
@ -217,11 +291,50 @@ pub(crate) mod fake {
&self,
_mount: &str,
name: &str,
_certificate: &str,
_common_name: &str,
certificate: &str,
common_name: &str,
policies: &[&str],
) -> Result<(), Error> {
self.log(format!("write_cert_role {name} {}", policies.join(",")));
self.roles.lock().expect("not poisoned").insert(
name.to_owned(),
FakeRole {
certificate: certificate.to_owned(),
common_name: common_name.to_owned(),
policies: policies.iter().map(|p| (*p).to_owned()).collect(),
ttls: CertRoleTokenTtls {
token_ttl: CERT_ROLE_TOKEN_TTL_SECS,
token_max_ttl: CERT_ROLE_TOKEN_TTL_SECS,
},
},
);
Ok(())
}
async fn cert_role_token_ttls(
&self,
_mount: &str,
name: &str,
) -> Result<Option<CertRoleTokenTtls>, Error> {
self.log(format!("cert_role_token_ttls {name}"));
if self.unreadable_roles.iter().any(|r| r == name) {
return Err(Self::denied());
}
Ok(self.role(name).map(|r| r.ttls))
}
async fn set_cert_role_token_ttls(&self, _mount: &str, name: &str) -> Result<(), Error> {
self.log(format!("set_cert_role_token_ttls {name}"));
if self.unwritable_roles.iter().any(|r| r == name) {
return Err(Self::denied());
}
// Only the lifetimes: the store keeps the fields a write omits.
if let Some(role) = self.roles.lock().expect("not poisoned").get_mut(name) {
role.ttls = CertRoleTokenTtls {
token_ttl: CERT_ROLE_TOKEN_TTL_SECS,
token_max_ttl: CERT_ROLE_TOKEN_TTL_SECS,
};
}
Ok(())
}