1use super::SPIFFEID_SEPARATOR;
2use anyhow::{anyhow, Result};
3use serde::{
4 de::{Unexpected, Visitor},
5 Deserialize, Deserializer, Serialize, Serializer,
6};
7use std::collections::BTreeMap;
8use std::sync::atomic::Ordering;
9use std::fmt;
10use url::Url;
11use x509_parser::extensions::GeneralName;
12
13#[allow(clippy::upper_case_acronyms)]
14#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
15pub struct SpiffeID {
16 trust_domain: String,
17 components: BTreeMap<String, String>,
18}
19
20#[allow(clippy::upper_case_acronyms)]
21#[derive(Debug, Clone, PartialEq)]
22pub struct SpiffeIDMatcher {
23 trust_domain: String,
24 components: BTreeMap<String, Option<String>>,
25}
26
27impl Serialize for SpiffeIDMatcher {
28 fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
29 let str_value: String = self.to_string();
30 serializer.serialize_str(&*str_value)
31 }
32}
33
34impl Serialize for SpiffeID {
35 fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
36 let str_value: String = self.to_string();
37 serializer.serialize_str(&*str_value)
38 }
39}
40
41struct SpiffeVisitor;
42impl<'de> Visitor<'de> for SpiffeVisitor {
43 type Value = ();
44 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
45 write!(formatter, "a valid spiffe url",)
46 }
47}
48
49impl<'de> Deserialize<'de> for SpiffeIDMatcher {
50 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> std::result::Result<Self, D::Error> {
51 let url = Url::deserialize(deserializer)?;
52 match SpiffeID::new(url) {
53 Ok(x) => Ok(x.into()),
54 Err(_) => Err(serde::de::Error::invalid_value(
55 Unexpected::Str("url"),
56 &SpiffeVisitor,
57 )),
58 }
59 }
60}
61
62impl<'de> Deserialize<'de> for SpiffeID {
63 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> std::result::Result<Self, D::Error> {
64 let url = Url::deserialize(deserializer)?;
65 SpiffeID::new(url)
66 .map_err(|_| serde::de::Error::invalid_value(Unexpected::Str("url"), &SpiffeVisitor))
67 }
68}
69
70impl SpiffeID {
71 pub fn raw_from_x509_der(x509: &[u8]) -> Result<String> {
72 let (_, cert) = x509_parser::parse_x509_certificate(x509)?;
73 let (_, subjects) = cert
74 .tbs_certificate
75 .subject_alternative_name()
76 .ok_or_else(|| anyhow!("could not find subjectAlternativeName in certificate"))?;
77 for subject in subjects.general_names.iter() {
78 match subject {
79 GeneralName::DNSName(uri) | GeneralName::URI(uri)
80 if uri.starts_with("spiffe://") =>
81 {
82 return Ok(uri.to_string());
83 }
84 _ => continue,
85 }
86 }
87 Err(anyhow!(
88 "could not find SpiffeID in subjectAlternativeName of certificate"
89 ))
90 }
91
92 pub fn from_x509_der(x509: &[u8]) -> Result<SpiffeID> {
93 let raw = Self::raw_from_x509_der(x509)?;
94 let url = Url::parse(&raw)?;
95 SpiffeID::new(url)
96 }
97
98 pub fn new(url: Url) -> Result<SpiffeID> {
99 if url.scheme() != "spiffe" {
100 return Err(anyhow!(
101 "invalid non-spiffe scheme in url: {}",
102 url.scheme()
103 ));
104 }
105 if url.username() != "" || url.password().is_some() {
106 let mut url_sanitized = url;
107 url_sanitized.set_username("<sanitized>").ok();
108 url_sanitized.set_password(Some("<sanitized>")).ok();
109 return Err(anyhow!(
110 "cannot specify credentials in spiffe url: {:?}",
111 url_sanitized
112 ));
113 }
114 if url.query().is_some() {
115 return Err(anyhow!("cannot specify query in spiffe url: {:?}", url));
116 }
117 if url.port().is_some() {
118 return Err(anyhow!("cannot specify port in spiffe url: {:?}", url));
119 }
120 if url.fragment().is_some() {
121 return Err(anyhow!("cannot specify fragment in spiffe url: {:?}", url));
122 }
123 if url.host_str().is_none() {
124 return Err(anyhow!(
125 "no trust_domain specified for spiffe url: {:?}",
126 url
127 ));
128 }
129 let trust_domain = url.host_str().unwrap();
130 if trust_domain.len() > 255 {
131 return Err(anyhow!("overlength trust domain (>255 bytes): {:?}", url));
132 }
133 let segments = url
134 .path_segments()
135 .map(|x| x.collect::<Vec<&str>>())
136 .unwrap_or_default();
137 let separator = SPIFFEID_SEPARATOR.load(Ordering::SeqCst);
138 let mut components: BTreeMap<String, String> = BTreeMap::new();
139 for segment in segments {
140 if segment.is_empty() {
141 continue;
142 }
143 let split_index = segment.find(separator as char);
144 if split_index.is_none() {
145 return Err(anyhow!("malformed component in spiffe url: '{}'", segment));
146 }
147 let split_index = split_index.unwrap();
148 let name = &segment[0..split_index];
149 let value = &segment[split_index + 1..];
150 let current_value = components.get(name);
151 if current_value.is_some() {
152 return Err(anyhow!("invalid reset component in spiffe url: '{}'", name));
153 }
154 components.insert(name.to_string(), value.to_string());
155 }
156
157 let total_len = trust_domain.len()
159 + 9 + components
161 .iter()
162 .fold(0, |acc, (k, v)| acc + k.len() + v.len() + 2); if total_len > 2048 {
164 return Err(anyhow!("spiffe id too long! {} > 2048", total_len));
165 }
166
167 Ok(SpiffeID {
168 trust_domain: trust_domain.to_string(),
169 components,
170 })
171 }
172
173 pub fn get_component<'a>(&'a self, name: &str) -> Option<&'a str> {
174 self.components.get(name).map(|x| &**x)
175 }
176
177 pub fn get_components(&self) -> &BTreeMap<String, String> {
178 &self.components
179 }
180
181 pub fn get_trust_domain(&self) -> &str {
182 &self.trust_domain
183 }
184}
185
186impl fmt::Display for SpiffeID {
187 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
188 write!(
189 f,
190 "spiffe://{}/{}",
191 self.trust_domain,
192 self.components
193 .iter()
194 .map(|(name, value)| format!("{}{}{}", name, SPIFFEID_SEPARATOR.load(Ordering::SeqCst) as char ,value))
195 .collect::<Vec<String>>()
196 .join("/")
197 )
198 }
199}
200
201impl fmt::Display for SpiffeIDMatcher {
202 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203 write!(
204 f,
205 "spiffe://{}/{}",
206 self.trust_domain,
207 self.components
208 .iter()
209 .map(|(name, value)| format!(
210 "{}{}{}",
211 name,
212 SPIFFEID_SEPARATOR.load(Ordering::SeqCst) as char,
213 value.as_ref().map(|x| &**x).unwrap_or("*")
214 ))
215 .collect::<Vec<String>>()
216 .join("/")
217 )
218 }
219}
220
221impl From<SpiffeID> for SpiffeIDMatcher {
222 fn from(spiffe_id: SpiffeID) -> SpiffeIDMatcher {
223 SpiffeIDMatcher {
224 trust_domain: spiffe_id.trust_domain,
225 components: spiffe_id
226 .components
227 .into_iter()
228 .map(|(key, value)| {
229 let value = if value == "*" { None } else { Some(value) };
230 (key, value)
231 })
232 .collect(),
233 }
234 }
235}
236
237impl SpiffeIDMatcher {
238 #[allow(dead_code)] pub fn new(url: Url) -> Result<SpiffeIDMatcher> {
240 SpiffeID::new(url).map(|x| x.into())
241 }
242
243 pub fn matches(&self, id: &SpiffeID) -> bool {
245 let mut id_iter = id.components.iter();
246 for (name, value) in self.components.iter() {
247 if value.is_none() {
248 continue;
249 }
250 let value = value.as_ref().unwrap();
251 let id_value = loop {
252 if let Some((id_name, id_value)) = id_iter.next() {
253 if id_name != name {
254 continue;
255 }
256 break id_value;
257 }
258 return false;
259 };
260 if value != id_value {
261 return false;
262 }
263 }
264 true
265 }
266
267 pub fn get_component<'a>(&'a self, name: &str) -> Option<Option<&'a str>> {
268 self.components.get(name).map(|x| x.as_ref().map(|x| &**x))
269 }
270
271 pub fn get_components(&self) -> &BTreeMap<String, Option<String>> {
272 &self.components
273 }
274
275 pub fn get_trust_domain(&self) -> &str {
276 &self.trust_domain
277 }
278}
279
280#[cfg(test)]
281mod tests {
282 use super::*;
283 use crate::set_spiffe_separator;
284
285 #[test]
286 fn test_spiffe_matcher_basic() -> Result<()> {
287 SpiffeIDMatcher::new(Url::parse(
288 "spiffe://spiffe-test/ns:tce/r:us/vdc:useast2a/id:security.platform.kms_client",
289 )?)?;
290 Ok(())
291 }
292
293 #[test]
294 fn test_spiffe_matcher_wildcard() -> Result<()> {
295 let matcher = SpiffeIDMatcher::new(Url::parse(
296 "spiffe://spiffe-test/ns:*/r:us/vdc:*/id:security.platform.kms_client",
297 )?)?;
298 assert_eq!(matcher.components.get("ns").unwrap(), &None::<String>);
299 assert_eq!(matcher.components.get("r").unwrap().as_ref().unwrap(), "us");
300 assert_eq!(matcher.components.get("vdc").unwrap(), &None::<String>);
301 Ok(())
302 }
303
304 #[test]
305 fn test_spiffe_matcher_serialization() -> Result<()> {
306 let spiffe_matcher = SpiffeIDMatcher::new(Url::parse(
307 "spiffe://spiffe-test/ns:tce/r:us/vdc:*/id:security.platform.kms_client",
308 )?)?;
309 let serialized = serde_json::to_string(&spiffe_matcher)?;
310 let deserialized: SpiffeIDMatcher = serde_json::from_str(&serialized)?;
311 assert_eq!(spiffe_matcher, deserialized);
312 Ok(())
313 }
314
315 #[test]
316 fn test_spiffe_matcher_matching() -> Result<()> {
317 let spiffe_matcher = SpiffeIDMatcher::new(Url::parse(
318 "spiffe://spiffe-test/ns:tce/r:us/vdc:*/id:security.platform.kms_client",
319 )?)?;
320 let test_spiffe = SpiffeID::new(Url::parse("spiffe://spiffe-test/ns:tce/r:us/vdc:a_vdc/new:test/id:security.platform.kms_client").unwrap()).unwrap();
321 assert!(spiffe_matcher.matches(&test_spiffe));
322 let test_spiffe = SpiffeID::new(
323 Url::parse(
324 "spiffe://spiffe-test/ns:tce/r:us/vdc:a_vdc/id:security.platform.kms_client",
325 )
326 .unwrap(),
327 )
328 .unwrap();
329 assert!(spiffe_matcher.matches(&test_spiffe));
330 let test_spiffe = SpiffeID::new(
331 Url::parse(
332 "spiffe://spiffe-test/r:us/vdc:a_vdc/id:security.platform.kms_client/ns:tce",
333 )
334 .unwrap(),
335 )
336 .unwrap();
337 assert!(spiffe_matcher.matches(&test_spiffe));
338 let test_spiffe = SpiffeID::new(
339 Url::parse(
340 "spiffe://spiffe-test/ns:tce/r:us/vdc:another_vdc/id:security.platform.kms_client",
341 )
342 .unwrap(),
343 )
344 .unwrap();
345 assert!(spiffe_matcher.matches(&test_spiffe));
346 let spiffe_matcher = SpiffeIDMatcher::new(Url::parse(
347 "spiffe://spiffe-test/ns:tce/r:us/vdc:d/test:*/id:security.platform.kms_client",
348 )?)?;
349 let test_spiffe = SpiffeID::new(
350 Url::parse("spiffe://spiffe-test/ns:tce/r:us/vdc:d/id:security.platform.kms_client")
351 .unwrap(),
352 )
353 .unwrap();
354 assert!(spiffe_matcher.matches(&test_spiffe));
355 let spiffe_matcher = SpiffeIDMatcher::new(Url::parse(
356 "spiffe://spiffe-test/ns:tce/r:/vdc:*/id:security.platform.kms_client",
357 )?)?;
358 let test_spiffe = SpiffeID::new(
359 Url::parse("spiffe://spiffe-test/ns:tce/r:/vdc:a_vdc/id:security.platform.kms_client")
360 .unwrap(),
361 )
362 .unwrap();
363 assert!(spiffe_matcher.matches(&test_spiffe));
364
365 Ok(())
366 }
367
368 #[test]
369 fn test_spiffe_matcher_not_matching() -> Result<()> {
370 let spiffe_matcher = SpiffeIDMatcher::new(Url::parse(
371 "spiffe://spiffe-test/ns:tce/r:us/vdc:*/id:security.platform.kms_client",
372 )?)?;
373 let test_spiffe = SpiffeID::new(
374 Url::parse(
375 "spiffe://spiffe-test/ns:tce/r:not_us/vdc:a_vdc/id:security.platform.kms_client",
376 )
377 .unwrap(),
378 )
379 .unwrap();
380 assert!(!spiffe_matcher.matches(&test_spiffe));
381 let test_spiffe = SpiffeID::new(
382 Url::parse("spiffe://spiffe-test/ns:tce/r:/vdc:a_vdc/id:security.platform.kms_client")
383 .unwrap(),
384 )
385 .unwrap();
386 assert!(!spiffe_matcher.matches(&test_spiffe));
387 let test_spiffe = SpiffeID::new(Url::parse("spiffe://spiffe-test/ns:tce/r:not_us/vdc:another_vdc/id:security.platform.kms_client").unwrap()).unwrap();
388 assert!(!spiffe_matcher.matches(&test_spiffe));
389 let spiffe_matcher = SpiffeIDMatcher::new(Url::parse(
390 "spiffe://spiffe-test/ns:tce/r:/vdc:*/id:security.platform.kms_client",
391 )?)?;
392 let test_spiffe = SpiffeID::new(
393 Url::parse(
394 "spiffe://spiffe-test/ns:tce/r:not_us/vdc:a_vdc/id:security.platform.kms_client",
395 )
396 .unwrap(),
397 )
398 .unwrap();
399 assert!(!spiffe_matcher.matches(&test_spiffe));
400 let test_spiffe = SpiffeID::new(Url::parse("spiffe://spiffe-test/ns:tce/r:not_us/vdc:another_vdc/id:security.platform.kms_client").unwrap()).unwrap();
401 assert!(!spiffe_matcher.matches(&test_spiffe));
402 Ok(())
403 }
404
405 #[test]
406 fn test_spiffe_matcher_not_matching_extra_field() -> Result<()> {
407 let spiffe_matcher = SpiffeIDMatcher::new(Url::parse(
408 "spiffe://spiffe-test/ns:tce/r:us/vdc:*/id:security.platform.kms_client/new:test",
409 )?)?;
410 let test_spiffe = SpiffeID::new(
411 Url::parse(
412 "spiffe://spiffe-test/ns:tce/r:us/vdc:a_vdc/id:security.platform.kms_client",
413 )
414 .unwrap(),
415 )
416 .unwrap();
417 assert!(!spiffe_matcher.matches(&test_spiffe));
418 let test_spiffe = SpiffeID::new(
419 Url::parse(
420 "spiffe://spiffe-test/ns:tce/r:us/vdc:another_vdc/id:security.platform.kms_client",
421 )
422 .unwrap(),
423 )
424 .unwrap();
425 assert!(!spiffe_matcher.matches(&test_spiffe));
426 Ok(())
427 }
428
429 #[test]
430 fn test_spiffe_matcher_matching_wildcard() -> Result<()> {
431 set_spiffe_separator(":").unwrap();
432 let spiffe_matcher = SpiffeIDMatcher::new(Url::parse(
433 "spiffe://spiffe-test/ns:*/r:us/vdc:d/id:security.platform.kms_client",
434 )?)?;
435 let test_spiffe = SpiffeID::new(
436 Url::parse("spiffe://spiffe-test/ns:tce/r:us/vdc:d/id:security.platform.kms_client")
437 .unwrap(),
438 )
439 .unwrap();
440 assert!(spiffe_matcher.matches(&test_spiffe));
441 let spiffe_matcher = SpiffeIDMatcher::new(Url::parse(
442 "spiffe://spiffe-test/ns:tce/r:us/vdc:d/id:*/new:test",
443 )?)?;
444 let test_spiffe = SpiffeID::new(
445 Url::parse("spiffe://spiffe-test/ns:tce/r:us/vdc:d/id:security.platform.kms_client")
446 .unwrap(),
447 )
448 .unwrap();
449 assert!(!spiffe_matcher.matches(&test_spiffe));
450 Ok(())
451 }
452
453 #[test]
454 fn test_spiffe_basic() -> Result<()> {
455 SpiffeID::new(Url::parse(
456 "spiffe://spiffe-test/ns:tce/r:us/vdc:useast2a/id:security.platform.kms_client",
457 )?)?;
458 Ok(())
459 }
460
461 #[test]
462 fn test_long_trust_domain() -> Result<()> {
463 let trust_domain = vec!['a' as u8; 255];
464 let trust_domain = std::str::from_utf8(&trust_domain).unwrap();
465 let test_id = "spiffe://".to_string() + trust_domain;
466 SpiffeID::new(Url::parse(&test_id)?)?;
467
468 let trust_domain = vec!['a' as u8; 256];
469 let trust_domain = std::str::from_utf8(&trust_domain).unwrap();
470 let test_id = "spiffe://".to_string() + trust_domain;
471 SpiffeID::new(Url::parse(&test_id)?).unwrap_err();
472
473 Ok(())
474 }
475
476 #[test]
477 fn test_long_spiffe_id() -> Result<()> {
478 let trust_domain = vec!['x' as u8; 37];
479 let trust_domain = std::str::from_utf8(&trust_domain).unwrap();
480 let key = vec!['a' as u8; 1000];
481 let key = std::str::from_utf8(&key).unwrap();
482 let value = vec!['b' as u8; 1000];
483 let value = std::str::from_utf8(&value).unwrap();
484 let test_id = format!("spiffe://{}/{}:{}", trust_domain, key, value);
485 SpiffeID::new(Url::parse(&test_id)?)?;
486
487 let value = vec!['b' as u8; 1001];
488 let value = std::str::from_utf8(&value).unwrap();
489 let test_id = format!("spiffe://{}/{}:{}", trust_domain, key, value);
490 SpiffeID::new(Url::parse(&test_id)?).unwrap_err();
491 Ok(())
492 }
493
494 #[test]
495 fn test_spiffe_trailing_slash() -> Result<()> {
496 SpiffeID::new(Url::parse(
497 "spiffe://spiffe-test/ns:tce/r:us/vdc:useast2a/id:security.platform.kms_client/",
498 )?)?;
499 Ok(())
500 }
501
502 #[test]
503 fn test_spiffe_double_slash() -> Result<()> {
504 SpiffeID::new(Url::parse(
505 "spiffe://spiffe-test/ns:tce/r:us//vdc:useast2a/id:security.platform.kms_client",
506 )?)?;
507 Ok(())
508 }
509
510 #[test]
511 fn test_spiffe_component_new() -> Result<()> {
512 SpiffeID::new(Url::parse(
513 "spiffe://spiffe-test/t:y/ns:tce/r:us/vdc:useast2a/id:security.platform.kms_client",
514 )?)?;
515 Ok(())
516 }
517
518 #[test]
519 fn test_spiffe_scheme() -> Result<()> {
520 SpiffeID::new(Url::parse(
521 "http://spiffe-test/ns:tce/r:us/vdc:useast2a/id:security.platform.kms_client",
522 )?)
523 .unwrap_err();
524 Ok(())
525 }
526
527 #[test]
528 fn test_spiffe_credentials() -> Result<()> {
529 let error = SpiffeID::new(Url::parse("spiffe://uSeRnAmE:pAsSwOrD@spiffe-test/ns:tce/r:us/vdc:useast2a/id:security.platform.kms_client")?).unwrap_err();
530 let error = format!("{:?}", error);
531 assert!(!error.contains("uSeRnAmE"));
532 assert!(!error.contains("pAsSwOrD"));
533 Ok(())
534 }
535
536 #[test]
537 fn test_spiffe_query() -> Result<()> {
538 SpiffeID::new(Url::parse(
539 "spiffe://spiffe-test/ns:tce/r:us/vdc:useast2a/id:security.platform.kms_client?x=y",
540 )?)
541 .unwrap_err();
542 Ok(())
543 }
544
545 #[test]
546 fn test_spiffe_port() -> Result<()> {
547 SpiffeID::new(Url::parse(
548 "spiffe://spiffe-test:8080/ns:tce/r:us/vdc:useast2a/id:security.platform.kms_client",
549 )?)
550 .unwrap_err();
551 Ok(())
552 }
553
554 #[test]
555 fn test_spiffe_fragment() -> Result<()> {
556 SpiffeID::new(Url::parse("spiffe://spiffe-test/ns:tce/r:us/vdc:useast2a/id:security.platform.kms_client#fragment")?).unwrap_err();
557 Ok(())
558 }
559
560 #[test]
561 fn test_spiffe_host() -> Result<()> {
562 SpiffeID::new(Url::parse(
563 "spiffe:/ns:tce/r:us/vdc:useast2a/id:security.platform.kms_client",
564 )?)
565 .unwrap_err();
566 Ok(())
567 }
568
569 #[test]
570 fn test_spiffe_component_malformed() -> Result<()> {
571 SpiffeID::new(Url::parse(
572 "spiffe://spiffe-test/nstce/r:us/vdc:useast2a/id:security.platform.kms_client",
573 )?)
574 .unwrap_err();
575 Ok(())
576 }
577
578 #[test]
579 fn test_spiffe_component_reset() -> Result<()> {
580 SpiffeID::new(Url::parse(
581 "spiffe://spiffe-test/ns:tce/ns:tce/r:us/vdc:useast2a/id:security.platform.kms_client",
582 )?)
583 .unwrap_err();
584 Ok(())
585 }
586
587 #[test]
588 fn test_spiffe_component_reset_with_separator() -> Result<()> {
589 set_spiffe_separator("_").unwrap();
590 SpiffeID::new(Url::parse(
591 "spiffe://spiffe-test/ns_tce/ns_tce/r_cn/vdc_boe/id_security.platform.kms_client",
592 )?)
593 .unwrap_err();
594 set_spiffe_separator(":").unwrap();
595 Ok(())
596 }
597 #[test]
598 fn test_spiffe_component_reset_with_invalid_separator() -> Result<()> {
599 assert!(!set_spiffe_separator("!").is_ok());
600 SpiffeID::new(Url::parse(
601 "spiffe://spiffe-test/ns:tce/ns:tce/r:cn/vdc:boe/id:security.platform.kms_client",
602 )?)
603 .unwrap_err();
604 Ok(())
605 }
606}