syncable_cli/analyzer/kubelint/templates/
unsafeprocmount.rs

1//! Unsafe proc mount detection template.
2
3use crate::analyzer::kubelint::context::Object;
4use crate::analyzer::kubelint::extract;
5use crate::analyzer::kubelint::templates::{CheckFunc, ParameterDesc, Template, TemplateError};
6use crate::analyzer::kubelint::types::{Diagnostic, ObjectKindsDesc};
7
8/// Template for detecting unsafe /proc mount settings.
9pub struct UnsafeProcMountTemplate;
10
11impl Template for UnsafeProcMountTemplate {
12    fn key(&self) -> &str {
13        "unsafe-proc-mount"
14    }
15
16    fn human_name(&self) -> &str {
17        "Unsafe Proc Mount"
18    }
19
20    fn description(&self) -> &str {
21        "Detects containers with unsafe /proc mount (procMount: Unmasked)"
22    }
23
24    fn supported_object_kinds(&self) -> ObjectKindsDesc {
25        ObjectKindsDesc::default()
26    }
27
28    fn parameters(&self) -> Vec<ParameterDesc> {
29        Vec::new()
30    }
31
32    fn instantiate(
33        &self,
34        _params: &serde_yaml::Value,
35    ) -> Result<Box<dyn CheckFunc>, TemplateError> {
36        Ok(Box::new(UnsafeProcMountCheck))
37    }
38}
39
40struct UnsafeProcMountCheck;
41
42impl CheckFunc for UnsafeProcMountCheck {
43    fn check(&self, object: &Object) -> Vec<Diagnostic> {
44        let mut diagnostics = Vec::new();
45
46        if let Some(pod_spec) = extract::pod_spec::extract_pod_spec(&object.k8s_object) {
47            for container in extract::container::all_containers(pod_spec) {
48                if let Some(sc) = &container.security_context
49                    && let Some(proc_mount) = &sc.proc_mount
50                    && proc_mount == "Unmasked"
51                {
52                    diagnostics.push(Diagnostic {
53                                message: format!(
54                                    "Container '{}' has unsafe /proc mount (procMount: Unmasked)",
55                                    container.name
56                                ),
57                                remediation: Some(
58                                    "Use the Default procMount type unless Unmasked is absolutely required. \
59                                     Unmasked proc mount exposes sensitive kernel information."
60                                        .to_string(),
61                                ),
62                            });
63                }
64            }
65        }
66
67        diagnostics
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74    use crate::analyzer::kubelint::parser::yaml::parse_yaml;
75
76    #[test]
77    fn test_unsafe_proc_mount_detected() {
78        let yaml = r#"
79apiVersion: apps/v1
80kind: Deployment
81metadata:
82  name: unsafe-procmount
83spec:
84  template:
85    spec:
86      containers:
87      - name: nginx
88        image: nginx:1.21.0
89        securityContext:
90          procMount: Unmasked
91"#;
92        let objects = parse_yaml(yaml).unwrap();
93        let check = UnsafeProcMountCheck;
94        let diagnostics = check.check(&objects[0]);
95        assert_eq!(diagnostics.len(), 1);
96        assert!(diagnostics[0].message.contains("Unmasked"));
97    }
98
99    #[test]
100    fn test_default_proc_mount_ok() {
101        let yaml = r#"
102apiVersion: apps/v1
103kind: Deployment
104metadata:
105  name: safe-procmount
106spec:
107  template:
108    spec:
109      containers:
110      - name: nginx
111        image: nginx:1.21.0
112        securityContext:
113          procMount: Default
114"#;
115        let objects = parse_yaml(yaml).unwrap();
116        let check = UnsafeProcMountCheck;
117        let diagnostics = check.check(&objects[0]);
118        assert!(diagnostics.is_empty());
119    }
120
121    #[test]
122    fn test_no_proc_mount_ok() {
123        let yaml = r#"
124apiVersion: apps/v1
125kind: Deployment
126metadata:
127  name: no-procmount
128spec:
129  template:
130    spec:
131      containers:
132      - name: nginx
133        image: nginx:1.21.0
134"#;
135        let objects = parse_yaml(yaml).unwrap();
136        let check = UnsafeProcMountCheck;
137        let diagnostics = check.check(&objects[0]);
138        assert!(diagnostics.is_empty());
139    }
140}