spire_workload/
spiffe.rs

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        // check if total length is no more than 2048
158        let total_len = trust_domain.len()
159            + 9 // len of "spiffe://"
160            + components
161                .iter()
162                .fold(0, |acc, (k, v)| acc + k.len() + v.len() + 2); // + ':' '/'
163        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)] // TODO: remove
239    pub fn new(url: Url) -> Result<SpiffeIDMatcher> {
240        SpiffeID::new(url).map(|x| x.into())
241    }
242
243    // BHashMap is strictly ordered
244    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}