Watch
0
0
Fork
You've already forked hyperhive
0

hive-agent: order a stored login by when the store wrote it, not by its number

A delete removes the leaf's metadata, so the KV number restarts at 1. A login
made right after a delete, before the next poll, was version 1 again and
looked no newer than the recorded 1, so it never installed. The version file
now records the store's write time next to the number and a later write
installs whatever its number. A record from before the time was kept falls
back to the number and gets its time when the same number is read again.
This commit is contained in:
iris 2026-11-09 22:31:51 +01:00 • committed by mara
commit 4b1012056c

View file

@ -48,6 +48,19 @@ pub const CERT_ROLE_TOKEN_TTL_SECS: u64 = 3600;
/// this bound the call fails with [`Error::Vault`] instead.
const VAULT_REQUEST_TIMEOUT: Duration = Duration::from_secs(30);
/// The identity of one stored version of an object: its KV v2 number, and the
/// time the store wrote it.
///
/// The number restarts when the object is deleted outright; the time does not,
/// so a caller that must tell "a newer write" from "the same one again" across
/// a delete compares the time.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Version {
pub number: u64,
/// RFC 3339, as the store spells it.
pub created_time: String,
}
/// Where the store is and which identity we present to it.
///
/// Separate from the connect so the environment can be checked without one:
@ -234,18 +247,20 @@ impl SecretStore {
}
}
/// The newest version of the object at `path` and its KV v2 version
/// number, or `None` when nothing is stored there.
/// The newest version of the object at `path` and its KV v2 [`Version`], or
/// `None` when nothing is stored there.
///
/// One read, so the number is the one the value was stored as. Every
/// [`write`][Self::write] makes a larger one.
/// One read, so the version is the one the value was stored as. Every
/// [`write`][Self::write] makes a larger number, until the path is deleted
/// outright and its numbering starts again; the time it was written, which
/// the store stamps, keeps ordering across that.
///
/// # Errors
/// As [`read_optional`][Self::read_optional].
pub async fn read_versioned<T: DeserializeOwned>(
&self,
path: &str,
) -> Result<Option<(T, u64)>, Error> {
) -> Result<Option<(T, Version)>, Error> {
let endpoint = vaultrs::api::kv2::requests::ReadSecretRequest::builder()
.mount(MOUNT)
.path(path)
@ -255,7 +270,13 @@ impl SecretStore {
Ok(res) => {
let value = serde_json::from_value(res.data)
.map_err(|source| vaultrs::error::ClientError::JsonParseError { source })?;
Ok(Some((value, res.metadata.version)))
Ok(Some((
value,
Version {
number: res.metadata.version,
created_time: res.metadata.created_time,
},
)))
}
Err(vaultrs::error::ClientError::APIError { code: 404, .. }) => Ok(None),
Err(e) => Err(e.into()),