1use serde::{Deserialize, Serialize};
12use thiserror::Error;
13
14#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
17pub struct MeshIdentity {
18 pub workload_spiffe_id: String,
19 pub attestation: AttestationToken,
20}
21
22impl MeshIdentity {
23 pub fn new(spiffe_id: impl Into<String>, token: AttestationToken) -> Self {
24 Self {
25 workload_spiffe_id: spiffe_id.into(),
26 attestation: token,
27 }
28 }
29
30 pub fn split_spiffe(&self) -> Option<(&str, &str)> {
33 let rest = self.workload_spiffe_id.strip_prefix("spiffe://")?;
34 let slash = rest.find('/')?;
35 Some((&rest[..slash], &rest[slash + 1..]))
36 }
37
38 pub fn trust_domain(&self) -> Option<&str> {
39 self.split_spiffe().map(|(td, _)| td)
40 }
41}
42
43#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
48pub struct AttestationToken {
49 pub raw: Vec<u8>,
50 pub kid: String,
53}
54
55impl AttestationToken {
56 pub fn new(raw: impl Into<Vec<u8>>, kid: impl Into<String>) -> Self {
57 Self {
58 raw: raw.into(),
59 kid: kid.into(),
60 }
61 }
62}
63
64#[derive(Debug, Error, PartialEq)]
65pub enum IdentityError {
66 #[error("malformed SPIFFE ID: {0:?}")]
67 MalformedSpiffe(String),
68 #[error("attestation token is empty — refuse to authorize against an empty token")]
69 EmptyToken,
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75
76 #[test]
77 fn split_spiffe_extracts_components() {
78 let id = MeshIdentity::new(
79 "spiffe://prod.mnemo.io/agent/runner-42",
80 AttestationToken::new(vec![1, 2, 3], "k1"),
81 );
82 assert_eq!(
83 id.split_spiffe(),
84 Some(("prod.mnemo.io", "agent/runner-42"))
85 );
86 assert_eq!(id.trust_domain(), Some("prod.mnemo.io"));
87 }
88
89 #[test]
90 fn malformed_spiffe_returns_none() {
91 let id = MeshIdentity::new("not-a-spiffe-id", AttestationToken::new(vec![1], "k"));
92 assert!(id.split_spiffe().is_none());
93 assert!(id.trust_domain().is_none());
94 }
95
96 #[test]
97 fn malformed_no_workload_path_returns_none() {
98 let id = MeshIdentity::new(
99 "spiffe://only-trust-domain",
100 AttestationToken::new(vec![1], "k"),
101 );
102 assert!(id.split_spiffe().is_none());
103 }
104}