tf_types/
bridge_spiffe.rs1use regex::Regex;
4
5use crate::bridges::{Bridge, BridgeError, BridgeKind};
6
7#[derive(Clone, Debug, PartialEq, Eq)]
8pub struct ParsedSpiffeId {
9 pub trust_domain: String,
10 pub path: String,
11 pub raw: String,
12}
13
14pub fn parse_spiffe_id(id: &str) -> Result<ParsedSpiffeId, BridgeError> {
15 if id.is_empty() {
16 return Err(BridgeError::InvalidInput("empty SPIFFE ID".into()));
17 }
18 let rest = id.strip_prefix("spiffe://").ok_or_else(|| {
19 BridgeError::InvalidInput(format!("SPIFFE ID must start with spiffe://, got {:?}", id))
20 })?;
21 let (trust_domain, path) = match rest.find('/') {
22 Some(i) => (&rest[..i], &rest[i + 1..]),
23 None => (rest, ""),
24 };
25 if trust_domain.is_empty() {
26 return Err(BridgeError::InvalidInput(
27 "SPIFFE ID has no trust domain".into(),
28 ));
29 }
30 if path.is_empty() {
31 return Err(BridgeError::InvalidInput("SPIFFE ID has no path".into()));
32 }
33 let re = Regex::new(r"^[A-Za-z0-9](?:[A-Za-z0-9.-]*[A-Za-z0-9])?$").unwrap();
34 if !re.is_match(trust_domain) {
35 return Err(BridgeError::InvalidInput(format!(
36 "SPIFFE trust domain is not DNS-like: {}",
37 trust_domain
38 )));
39 }
40 Ok(ParsedSpiffeId {
41 trust_domain: trust_domain.to_owned(),
42 path: path.to_owned(),
43 raw: id.to_owned(),
44 })
45}
46
47pub fn spiffe_to_actor_id(id: &str) -> Result<String, BridgeError> {
48 let parsed = parse_spiffe_id(id)?;
49 Ok(format!(
50 "tf:actor:service:{}/{}",
51 parsed.trust_domain, parsed.path
52 ))
53}
54
55pub fn actor_id_to_spiffe(actor_id: &str) -> Result<String, BridgeError> {
56 let re = Regex::new(r"^tf:actor:([^:]+):(.+)$").unwrap();
57 let caps = re
58 .captures(actor_id)
59 .ok_or_else(|| BridgeError::InvalidInput(format!("malformed actor URI: {}", actor_id)))?;
60 let type_segment = caps.get(1).unwrap().as_str();
61 let path_segment = caps.get(2).unwrap().as_str();
62 if type_segment != "service" {
63 return Err(BridgeError::Unsupported(format!(
64 "SPIFFE bridge only projects service actors, got {}",
65 type_segment
66 )));
67 }
68 let slash = path_segment.find('/').ok_or_else(|| {
69 BridgeError::InvalidInput(format!(
70 "service actor path must be <trust-domain>/<path>, got {}",
71 path_segment
72 ))
73 })?;
74 let trust_domain = &path_segment[..slash];
75 let tail = &path_segment[slash + 1..];
76 Ok(format!("spiffe://{}/{}", trust_domain, tail))
77}
78
79pub struct SpiffeBridge {
80 pub bridge_id: String,
81 pub trust_domain: String,
82}
83
84impl SpiffeBridge {
85 pub fn new(bridge_id: impl Into<String>, trust_domain: impl Into<String>) -> Self {
86 SpiffeBridge {
87 bridge_id: bridge_id.into(),
88 trust_domain: trust_domain.into(),
89 }
90 }
91
92 pub fn to_actor_id(&self, id: &str) -> Result<String, BridgeError> {
93 spiffe_to_actor_id(id)
94 }
95
96 pub fn to_spiffe(&self, actor_id: &str) -> Result<String, BridgeError> {
97 actor_id_to_spiffe(actor_id)
98 }
99}
100
101impl Bridge for SpiffeBridge {
102 fn bridge_id(&self) -> &str {
103 &self.bridge_id
104 }
105 fn kind(&self) -> BridgeKind {
106 BridgeKind::Spiffe
107 }
108 fn trust_domain(&self) -> &str {
109 &self.trust_domain
110 }
111}