syncable_cli/analyzer/kubelint/templates/
hostnetwork.rs1use 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
8pub 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 && pod_spec.host_network == Some(true)
48 {
49 diagnostics.push(Diagnostic {
50 message: "Pod is configured to use the host's network namespace".to_string(),
51 remediation: Some(
52 "Remove hostNetwork: true unless absolutely necessary. \
53 Using host network grants access to all network interfaces on the host."
54 .to_string(),
55 ),
56 });
57 }
58
59 diagnostics
60 }
61}
62
63pub 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 && pod_spec.host_pid == Some(true)
103 {
104 diagnostics.push(Diagnostic {
105 message: "Pod is configured to use the host's PID namespace".to_string(),
106 remediation: Some(
107 "Remove hostPID: true unless absolutely necessary. \
108 Using host PID allows processes in the container to see and signal all \
109 processes on the host."
110 .to_string(),
111 ),
112 });
113 }
114
115 diagnostics
116 }
117}
118
119pub 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 && pod_spec.host_ipc == Some(true)
159 {
160 diagnostics.push(Diagnostic {
161 message: "Pod is configured to use the host's IPC namespace".to_string(),
162 remediation: Some(
163 "Remove hostIPC: true unless absolutely necessary. \
164 Using host IPC allows processes to communicate with all processes on the host."
165 .to_string(),
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}