syncable_cli/analyzer/kubelint/templates/
hostmounts.rs

1//! Host mount detection templates.
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 host path mounts.
9pub struct HostMountsTemplate;
10
11impl Template for HostMountsTemplate {
12    fn key(&self) -> &str {
13        "host-mounts"
14    }
15
16    fn human_name(&self) -> &str {
17        "Host Mounts"
18    }
19
20    fn description(&self) -> &str {
21        "Detects containers with host path volume mounts"
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(HostMountsCheck))
37    }
38}
39
40struct HostMountsCheck;
41
42impl CheckFunc for HostMountsCheck {
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 volume in &pod_spec.volumes {
48                if let Some(host_path) = &volume.host_path {
49                    diagnostics.push(Diagnostic {
50                        message: format!(
51                            "Volume '{}' mounts host path '{}'",
52                            volume.name, host_path.path
53                        ),
54                        remediation: Some(
55                            "Avoid using hostPath volumes as they provide access to the host filesystem. \
56                             Use PersistentVolumeClaims or ConfigMaps instead.".to_string()
57                        ),
58                    });
59                }
60            }
61        }
62
63        diagnostics
64    }
65}
66
67/// Template for detecting writable host path mounts.
68pub struct WritableHostMountTemplate;
69
70impl Template for WritableHostMountTemplate {
71    fn key(&self) -> &str {
72        "writable-host-mount"
73    }
74
75    fn human_name(&self) -> &str {
76        "Writable Host Mount"
77    }
78
79    fn description(&self) -> &str {
80        "Detects containers with writable host path volume mounts"
81    }
82
83    fn supported_object_kinds(&self) -> ObjectKindsDesc {
84        ObjectKindsDesc::default()
85    }
86
87    fn parameters(&self) -> Vec<ParameterDesc> {
88        Vec::new()
89    }
90
91    fn instantiate(
92        &self,
93        _params: &serde_yaml::Value,
94    ) -> Result<Box<dyn CheckFunc>, TemplateError> {
95        Ok(Box::new(WritableHostMountCheck))
96    }
97}
98
99struct WritableHostMountCheck;
100
101impl CheckFunc for WritableHostMountCheck {
102    fn check(&self, object: &Object) -> Vec<Diagnostic> {
103        let mut diagnostics = Vec::new();
104
105        if let Some(pod_spec) = extract::pod_spec::extract_pod_spec(&object.k8s_object) {
106            // Find host path volumes
107            let host_volumes: std::collections::HashSet<_> = pod_spec
108                .volumes
109                .iter()
110                .filter(|v| v.host_path.is_some())
111                .map(|v| v.name.as_str())
112                .collect();
113
114            // Check each container's volume mounts
115            for container in extract::container::all_containers(pod_spec) {
116                for mount in &container.volume_mounts {
117                    if host_volumes.contains(mount.name.as_str()) {
118                        // Default is writable (readOnly: false)
119                        let is_writable = mount.read_only != Some(true);
120
121                        if is_writable {
122                            diagnostics.push(Diagnostic {
123                                message: format!(
124                                    "Container '{}' has writable host mount at '{}'",
125                                    container.name, mount.mount_path
126                                ),
127                                remediation: Some(
128                                    "Set volumeMounts.readOnly to true for host path mounts, \
129                                     or avoid using hostPath volumes entirely."
130                                        .to_string(),
131                                ),
132                            });
133                        }
134                    }
135                }
136            }
137        }
138
139        diagnostics
140    }
141}