use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct MeshIdentity {
pub workload_spiffe_id: String,
pub attestation: AttestationToken,
}
impl MeshIdentity {
pub fn new(spiffe_id: impl Into<String>, token: AttestationToken) -> Self {
Self {
workload_spiffe_id: spiffe_id.into(),
attestation: token,
}
}
pub fn split_spiffe(&self) -> Option<(&str, &str)> {
let rest = self.workload_spiffe_id.strip_prefix("spiffe://")?;
let slash = rest.find('/')?;
Some((&rest[..slash], &rest[slash + 1..]))
}
pub fn trust_domain(&self) -> Option<&str> {
self.split_spiffe().map(|(td, _)| td)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AttestationToken {
pub raw: Vec<u8>,
pub kid: String,
}
impl AttestationToken {
pub fn new(raw: impl Into<Vec<u8>>, kid: impl Into<String>) -> Self {
Self {
raw: raw.into(),
kid: kid.into(),
}
}
}
#[derive(Debug, Error, PartialEq)]
pub enum IdentityError {
#[error("malformed SPIFFE ID: {0:?}")]
MalformedSpiffe(String),
#[error("attestation token is empty — refuse to authorize against an empty token")]
EmptyToken,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn split_spiffe_extracts_components() {
let id = MeshIdentity::new(
"spiffe://prod.mnemo.io/agent/runner-42",
AttestationToken::new(vec![1, 2, 3], "k1"),
);
assert_eq!(
id.split_spiffe(),
Some(("prod.mnemo.io", "agent/runner-42"))
);
assert_eq!(id.trust_domain(), Some("prod.mnemo.io"));
}
#[test]
fn malformed_spiffe_returns_none() {
let id = MeshIdentity::new("not-a-spiffe-id", AttestationToken::new(vec![1], "k"));
assert!(id.split_spiffe().is_none());
assert!(id.trust_domain().is_none());
}
#[test]
fn malformed_no_workload_path_returns_none() {
let id = MeshIdentity::new(
"spiffe://only-trust-domain",
AttestationToken::new(vec![1], "k"),
);
assert!(id.split_spiffe().is_none());
}
}