Add resource secret epoch metadata

This commit is contained in:
Eric Wendland 2026-05-16 22:18:49 +02:00
commit a05112e6a2
12 changed files with 287 additions and 13 deletions

View file

@ -21,6 +21,7 @@ geth-iroh = { path = "../geth-iroh" }
geth-keychain = { path = "../geth-keychain" }
geth-kv = { path = "../geth-kv" }
geth-resource = { path = "../geth-resource" }
geth-secrets = { path = "../geth-secrets" }
geth-ssh-identity = { path = "../geth-ssh-identity" }
geth-store = { path = "../geth-store" }
geth-types = { path = "../geth-types" }

View file

@ -14,6 +14,7 @@ use geth_iroh::{EndpointStatus, GethIrohConfig, GethIrohEndpoint, GethRelayMode}
use geth_keychain::{KeychainOp, KeychainOpKind};
use geth_kv::{KvEntry, KvResource};
use geth_resource::ResourceDescriptor;
use geth_secrets::ResourceMasterSecret;
use geth_ssh_identity::{
SshCertApproval, SshCertKind, SshCertRequest, SshCertRequestStatus, SshCertificateRecord,
SshRevocationEntry, SshRevocationKind, build_ssh_cert_sign_command, cert_request_id,
@ -21,7 +22,8 @@ use geth_ssh_identity::{
};
use geth_store::{
Store, StoredAuthOp, StoredDbResource, StoredDocumentResource, StoredKeychainOp, StoredKvEntry,
StoredKvStore, StoredResource, StoredSshCertRequest, StoredSshCertificate, StoredSshRevocation,
StoredKvStore, StoredResource, StoredResourceSecret, StoredSshCertRequest,
StoredSshCertificate, StoredSshRevocation,
};
use geth_types::{
AuthOpId, Capability, KeyId, NodeId, PrincipalId, ResourceId, ResourceKind, ResourceName,
@ -67,6 +69,8 @@ pub enum NodeError {
InvalidDocumentName(String),
#[error("document not found: {0}")]
DocumentNotFound(String),
#[error("resource not found: {0}")]
ResourceNotFound(String),
#[error("invalid ssh certificate kind: {0}")]
InvalidSshCertKind(String),
#[error("invalid ssh certificate request status: {0}")]
@ -335,6 +339,27 @@ pub fn handle_request(
nodes: view.nodes.len(),
}))
}
ControlRequest::SecretStatus => Ok(ControlResponse::SecretStatus {
secrets: store
.list_resource_secrets()?
.into_iter()
.map(resource_secret_from_stored)
.collect(),
}),
ControlRequest::SecretCreate { resource } => {
ensure_resource_exists(&store, &resource)?;
let secret = create_resource_secret(&store, &resource, 1)?;
Ok(ControlResponse::SecretCreated { secret })
}
ControlRequest::SecretRotate { resource } => {
ensure_resource_exists(&store, &resource)?;
let next_epoch = store
.latest_resource_secret(&resource)?
.map(|secret| secret.epoch + 1)
.unwrap_or(1);
let secret = create_resource_secret(&store, &resource, next_epoch)?;
Ok(ControlResponse::SecretCreated { secret })
}
ControlRequest::AuthExplain {
subject,
resource,
@ -764,6 +789,48 @@ fn document_resource_from_stored(stored: &StoredDocumentResource) -> DocumentRes
}
}
fn ensure_resource_exists(store: &Store, resource_id: &str) -> Result<(), NodeError> {
if store
.list_resources()?
.into_iter()
.any(|resource| resource.resource_id == resource_id)
{
Ok(())
} else {
Err(NodeError::ResourceNotFound(resource_id.to_owned()))
}
}
fn create_resource_secret(
store: &Store,
resource_id: &str,
epoch: u64,
) -> Result<ResourceMasterSecret, NodeError> {
let created_at = UnixMillis(geth_store::now_ms());
let secret_id = format!(
"secret:{}",
geth_crypto::blake3_hex(format!("{resource_id}\0{epoch}\0{}", created_at.0).as_bytes())
);
let stored = StoredResourceSecret {
secret_id,
resource_id: resource_id.to_owned(),
epoch,
status: "active".to_owned(),
created_at_ms: created_at.0,
};
store.insert_resource_secret(&stored)?;
Ok(resource_secret_from_stored(stored))
}
fn resource_secret_from_stored(stored: StoredResourceSecret) -> ResourceMasterSecret {
ResourceMasterSecret {
id: stored.secret_id.into(),
resource: stored.resource_id.into(),
epoch: stored.epoch,
created_at: UnixMillis(stored.created_at_ms),
}
}
fn store_auth_op(store: &Store, op: &AuthOp) -> Result<(), NodeError> {
store.insert_auth_op(&StoredAuthOp {
op_id: op.id.to_string(),