Represent SSH distribution sync as a resource log

This commit is contained in:
Eric Wendland 2026-05-22 16:51:30 +02:00
commit 42d7301bdb
6 changed files with 247 additions and 52 deletions

View file

@ -220,6 +220,79 @@ pub struct SshRevocationEntry {
pub provenance: Option<SshRecordProvenance>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "kebab-case")]
pub enum SshDistributionLogEntry {
CertRequest {
entry_id: String,
resource: String,
timestamp: UnixMillis,
request: SshCertRequest,
},
Certificate {
entry_id: String,
resource: String,
timestamp: UnixMillis,
certificate: SshCertificateRecord,
},
Revocation {
entry_id: String,
resource: String,
timestamp: UnixMillis,
revocation: SshRevocationEntry,
},
}
impl SshDistributionLogEntry {
#[must_use]
pub fn cert_request(request: SshCertRequest) -> Self {
Self::CertRequest {
entry_id: format!("ssh-log:cert-request:{}", request.id),
resource: "resource:ssh:certs".to_owned(),
timestamp: request.created_at,
request,
}
}
#[must_use]
pub fn certificate(certificate: SshCertificateRecord) -> Self {
Self::Certificate {
entry_id: format!("ssh-log:certificate:{}", certificate.id),
resource: "resource:ssh:certs".to_owned(),
timestamp: certificate.imported_at,
certificate,
}
}
#[must_use]
pub fn revocation(revocation: SshRevocationEntry) -> Self {
Self::Revocation {
entry_id: format!("ssh-log:revocation:{}", revocation.id),
resource: "resource:ssh:revocations".to_owned(),
timestamp: revocation.created_at,
revocation,
}
}
#[must_use]
pub fn timestamp(&self) -> UnixMillis {
match self {
Self::CertRequest { timestamp, .. }
| Self::Certificate { timestamp, .. }
| Self::Revocation { timestamp, .. } => *timestamp,
}
}
#[must_use]
pub fn entry_id(&self) -> &str {
match self {
Self::CertRequest { entry_id, .. }
| Self::Certificate { entry_id, .. }
| Self::Revocation { entry_id, .. } => entry_id,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct SshCertRequestSigningPayload {
pub id: SshCertRequestId,
@ -547,6 +620,31 @@ mod tests {
assert_eq!(decoded, request);
}
#[test]
fn ssh_distribution_log_entries_roundtrip_with_stable_ids() {
let request = SshCertRequest {
id: "ssh-cert-request:1".into(),
requester_node: "node:laptop".into(),
public_key: "ssh-ed25519 AAAA test".to_owned(),
public_key_fingerprint: ssh_public_key_fingerprint("ssh-ed25519 AAAA test"),
cert_kind: SshCertKind::User,
principals: vec!["eric".to_owned()],
requested_validity: Some("+52w".to_owned()),
renewal_of: None,
reason: Some("bootstrap".to_owned()),
status: SshCertRequestStatus::Pending,
created_at: UnixMillis(10),
provenance: None,
};
let entry = SshDistributionLogEntry::cert_request(request);
assert_eq!(entry.entry_id(), "ssh-log:cert-request:ssh-cert-request:1");
assert_eq!(entry.timestamp(), UnixMillis(10));
let json = serde_json::to_string(&entry).expect("json");
let decoded: SshDistributionLogEntry = serde_json::from_str(&json).expect("decode");
assert_eq!(decoded, entry);
}
#[test]
fn sign_command_includes_host_flag_for_host_certs() {
let request = SshCertRequest {