syncable_cli/analyzer/kubelint/templates/
hostnetwork.rs

1//! Host network/PID/IPC 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 pods using hostNetwork.
9pub struct HostNetworkTemplate;
10
11impl Template for HostNetworkTemplate {
12    fn key(&self) -> &str {
13        "host-network"
14    }
15
16    fn human_name(&self) -> &str {
17        "Host Network"
18    }
19
20    fn description(&self) -> &str {
21        "Detects pods using host network namespace"
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(HostNetworkCheck))
37    }
38}
39
40struct HostNetworkCheck;
41
42impl CheckFunc for HostNetworkCheck {
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            if pod_spec.host_network == Some(true) {
48                diagnostics.push(Diagnostic {
49                    message: "Pod is configured to use the host's network namespace".to_string(),
50                    remediation: Some(
51                        "Remove hostNetwork: true unless absolutely necessary. \
52                         Using host network grants access to all network interfaces on the host."
53                            .to_string(),
54                    ),
55                });
56            }
57        }
58
59        diagnostics
60    }
61}
62
63/// Template for detecting pods using hostPID.
64pub struct HostPIDTemplate;
65
66impl Template for HostPIDTemplate {
67    fn key(&self) -> &str {
68        "host-pid"
69    }
70
71    fn human_name(&self) -> &str {
72        "Host PID"
73    }
74
75    fn description(&self) -> &str {
76        "Detects pods using host PID namespace"
77    }
78
79    fn supported_object_kinds(&self) -> ObjectKindsDesc {
80        ObjectKindsDesc::default()
81    }
82
83    fn parameters(&self) -> Vec<ParameterDesc> {
84        Vec::new()
85    }
86
87    fn instantiate(
88        &self,
89        _params: &serde_yaml::Value,
90    ) -> Result<Box<dyn CheckFunc>, TemplateError> {
91        Ok(Box::new(HostPIDCheck))
92    }
93}
94
95struct HostPIDCheck;
96
97impl CheckFunc for HostPIDCheck {
98    fn check(&self, object: &Object) -> Vec<Diagnostic> {
99        let mut diagnostics = Vec::new();
100
101        if let Some(pod_spec) = extract::pod_spec::extract_pod_spec(&object.k8s_object) {
102            if pod_spec.host_pid == Some(true) {
103                diagnostics.push(Diagnostic {
104                    message: "Pod is configured to use the host's PID namespace".to_string(),
105                    remediation: Some(
106                        "Remove hostPID: true unless absolutely necessary. \
107                         Using host PID allows processes in the container to see and signal all \
108                         processes on the host."
109                            .to_string(),
110                    ),
111                });
112            }
113        }
114
115        diagnostics
116    }
117}
118
119/// Template for detecting pods using hostIPC.
120pub struct HostIPCTemplate;
121
122impl Template for HostIPCTemplate {
123    fn key(&self) -> &str {
124        "host-ipc"
125    }
126
127    fn human_name(&self) -> &str {
128        "Host IPC"
129    }
130
131    fn description(&self) -> &str {
132        "Detects pods using host IPC namespace"
133    }
134
135    fn supported_object_kinds(&self) -> ObjectKindsDesc {
136        ObjectKindsDesc::default()
137    }
138
139    fn parameters(&self) -> Vec<ParameterDesc> {
140        Vec::new()
141    }
142
143    fn instantiate(
144        &self,
145        _params: &serde_yaml::Value,
146    ) -> Result<Box<dyn CheckFunc>, TemplateError> {
147        Ok(Box::new(HostIPCCheck))
148    }
149}
150
151struct HostIPCCheck;
152
153impl CheckFunc for HostIPCCheck {
154    fn check(&self, object: &Object) -> Vec<Diagnostic> {
155        let mut diagnostics = Vec::new();
156
157        if let Some(pod_spec) = extract::pod_spec::extract_pod_spec(&object.k8s_object) {
158            if pod_spec.host_ipc == Some(true) {
159                diagnostics.push(Diagnostic {
160                    message: "Pod is configured to use the host's IPC namespace".to_string(),
161                    remediation: Some(
162                        "Remove hostIPC: true unless absolutely necessary. \
163                         Using host IPC allows processes to communicate with all processes on the host."
164                            .to_string(),
165                    ),
166                });
167            }
168        }
169
170        diagnostics
171    }
172}
173
174#[cfg(test)]
175mod tests {
176    use super::*;
177    use crate::analyzer::kubelint::parser::yaml::parse_yaml;
178
179    #[test]
180    fn test_host_network_detected() {
181        let yaml = r#"
182apiVersion: apps/v1
183kind: Deployment
184metadata:
185  name: host-net-deploy
186spec:
187  template:
188    spec:
189      hostNetwork: true
190      containers:
191      - name: nginx
192        image: nginx:1.21.0
193"#;
194        let objects = parse_yaml(yaml).unwrap();
195        let check = HostNetworkCheck;
196        let diagnostics = check.check(&objects[0]);
197        assert_eq!(diagnostics.len(), 1);
198        assert!(diagnostics[0].message.contains("host's network"));
199    }
200
201    #[test]
202    fn test_no_host_network_ok() {
203        let yaml = r#"
204apiVersion: apps/v1
205kind: Deployment
206metadata:
207  name: safe-deploy
208spec:
209  template:
210    spec:
211      containers:
212      - name: nginx
213        image: nginx:1.21.0
214"#;
215        let objects = parse_yaml(yaml).unwrap();
216        let check = HostNetworkCheck;
217        let diagnostics = check.check(&objects[0]);
218        assert!(diagnostics.is_empty());
219    }
220
221    #[test]
222    fn test_host_pid_detected() {
223        let yaml = r#"
224apiVersion: apps/v1
225kind: Deployment
226metadata:
227  name: host-pid-deploy
228spec:
229  template:
230    spec:
231      hostPID: true
232      containers:
233      - name: nginx
234        image: nginx:1.21.0
235"#;
236        let objects = parse_yaml(yaml).unwrap();
237        let check = HostPIDCheck;
238        let diagnostics = check.check(&objects[0]);
239        assert_eq!(diagnostics.len(), 1);
240        assert!(diagnostics[0].message.contains("PID namespace"));
241    }
242
243    #[test]
244    fn test_host_ipc_detected() {
245        let yaml = r#"
246apiVersion: apps/v1
247kind: Deployment
248metadata:
249  name: host-ipc-deploy
250spec:
251  template:
252    spec:
253      hostIPC: true
254      containers:
255      - name: nginx
256        image: nginx:1.21.0
257"#;
258        let objects = parse_yaml(yaml).unwrap();
259        let check = HostIPCCheck;
260        let diagnostics = check.check(&objects[0]);
261        assert_eq!(diagnostics.len(), 1);
262        assert!(diagnostics[0].message.contains("IPC namespace"));
263    }
264}