syncable_cli/analyzer/kubelint/templates/
misc.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 SysctlsTemplate;
10
11impl Template for SysctlsTemplate {
12 fn key(&self) -> &str {
13 "sysctls"
14 }
15
16 fn human_name(&self) -> &str {
17 "Sysctls"
18 }
19
20 fn description(&self) -> &str {
21 "Checks for unsafe sysctl settings"
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(SysctlsCheck))
37 }
38}
39
40struct SysctlsCheck;
41
42impl CheckFunc for SysctlsCheck {
43 fn check(&self, object: &Object) -> Vec<Diagnostic> {
44 let mut diagnostics = Vec::new();
45
46 let unsafe_sysctls = [
48 "kernel.shm",
49 "kernel.msg",
50 "kernel.sem",
51 "fs.mqueue.",
52 "net.",
53 ];
54
55 if let Some(pod_spec) = extract::pod_spec::extract_pod_spec(&object.k8s_object)
56 && let Some(sc) = &pod_spec.security_context
57 {
58 for sysctl in &sc.sysctls {
59 let is_unsafe = unsafe_sysctls
60 .iter()
61 .any(|prefix| sysctl.name.starts_with(prefix));
62 if is_unsafe {
63 diagnostics.push(Diagnostic {
64 message: format!("Pod uses potentially unsafe sysctl '{}'", sysctl.name),
65 remediation: Some(
66 "Ensure this sysctl is allowed by the cluster's PodSecurityPolicy \
67 or PodSecurityStandard and is necessary for your workload."
68 .to_string(),
69 ),
70 });
71 }
72 }
73 }
74
75 diagnostics
76 }
77}
78
79pub struct DnsConfigOptionsTemplate;
81
82impl Template for DnsConfigOptionsTemplate {
83 fn key(&self) -> &str {
84 "dnsconfig-options"
85 }
86
87 fn human_name(&self) -> &str {
88 "DNS Config Options"
89 }
90
91 fn description(&self) -> &str {
92 "Checks DNS configuration options"
93 }
94
95 fn supported_object_kinds(&self) -> ObjectKindsDesc {
96 ObjectKindsDesc::default()
97 }
98
99 fn parameters(&self) -> Vec<ParameterDesc> {
100 Vec::new()
101 }
102
103 fn instantiate(
104 &self,
105 _params: &serde_yaml::Value,
106 ) -> Result<Box<dyn CheckFunc>, TemplateError> {
107 Ok(Box::new(DnsConfigOptionsCheck))
108 }
109}
110
111struct DnsConfigOptionsCheck;
112
113impl CheckFunc for DnsConfigOptionsCheck {
114 fn check(&self, object: &Object) -> Vec<Diagnostic> {
115 let mut diagnostics = Vec::new();
116
117 if let Some(pod_spec) = extract::pod_spec::extract_pod_spec(&object.k8s_object)
118 && let Some(dns_config) = &pod_spec.dns_config
119 {
120 for option in &dns_config.options {
122 if let Some(name) = &option.name
123 && name == "ndots"
124 && let Some(value) = &option.value
125 && let Ok(ndots) = value.parse::<i32>()
126 && ndots > 5
127 {
128 diagnostics.push(Diagnostic {
129 message: format!(
130 "DNS ndots is set to {}, which may cause DNS lookup performance issues",
131 ndots
132 ),
133 remediation: Some(
134 "Consider lowering ndots to 2 or less for better DNS performance."
135 .to_string(),
136 ),
137 });
138 }
139 }
140 }
141
142 diagnostics
143 }
144}
145
146pub struct StartupPortTemplate;
148
149impl Template for StartupPortTemplate {
150 fn key(&self) -> &str {
151 "startup-port"
152 }
153
154 fn human_name(&self) -> &str {
155 "Startup Probe Port"
156 }
157
158 fn description(&self) -> &str {
159 "Validates that startup probe port matches an exposed container port"
160 }
161
162 fn supported_object_kinds(&self) -> ObjectKindsDesc {
163 ObjectKindsDesc::default()
164 }
165
166 fn parameters(&self) -> Vec<ParameterDesc> {
167 Vec::new()
168 }
169
170 fn instantiate(
171 &self,
172 _params: &serde_yaml::Value,
173 ) -> Result<Box<dyn CheckFunc>, TemplateError> {
174 Ok(Box::new(StartupPortCheck))
175 }
176}
177
178struct StartupPortCheck;
179
180impl CheckFunc for StartupPortCheck {
181 fn check(&self, object: &Object) -> Vec<Diagnostic> {
182 let mut diagnostics = Vec::new();
183
184 if let Some(pod_spec) = extract::pod_spec::extract_pod_spec(&object.k8s_object) {
185 for container in extract::container::containers(pod_spec) {
186 if let Some(probe) = &container.startup_probe {
187 let probe_port = probe
188 .http_get
189 .as_ref()
190 .map(|h| h.port)
191 .or_else(|| probe.tcp_socket.as_ref().map(|t| t.port));
192
193 if let Some(port_num) = probe_port {
194 let has_matching_port =
195 container.ports.iter().any(|p| p.container_port == port_num);
196
197 if !has_matching_port && !container.ports.is_empty() {
198 diagnostics.push(Diagnostic {
199 message: format!(
200 "Container '{}' startup probe uses port {} which is not exposed",
201 container.name, port_num
202 ),
203 remediation: Some(
204 "Ensure the startup probe port matches an exposed container port."
205 .to_string(),
206 ),
207 });
208 }
209 }
210 }
211 }
212 }
213
214 diagnostics
215 }
216}
217
218pub struct EnvVarValueFromTemplate;
220
221impl Template for EnvVarValueFromTemplate {
222 fn key(&self) -> &str {
223 "env-var-value-from"
224 }
225
226 fn human_name(&self) -> &str {
227 "Env Var Value From"
228 }
229
230 fn description(&self) -> &str {
231 "Checks environment variable valueFrom configurations"
232 }
233
234 fn supported_object_kinds(&self) -> ObjectKindsDesc {
235 ObjectKindsDesc::default()
236 }
237
238 fn parameters(&self) -> Vec<ParameterDesc> {
239 Vec::new()
240 }
241
242 fn instantiate(
243 &self,
244 _params: &serde_yaml::Value,
245 ) -> Result<Box<dyn CheckFunc>, TemplateError> {
246 Ok(Box::new(EnvVarValueFromCheck))
247 }
248}
249
250struct EnvVarValueFromCheck;
251
252impl CheckFunc for EnvVarValueFromCheck {
253 fn check(&self, object: &Object) -> Vec<Diagnostic> {
254 let diagnostics = Vec::new();
255 let _ = object;
258 diagnostics
259 }
260}
261
262pub struct TargetPortTemplate;
264
265impl Template for TargetPortTemplate {
266 fn key(&self) -> &str {
267 "target-port"
268 }
269
270 fn human_name(&self) -> &str {
271 "Target Port"
272 }
273
274 fn description(&self) -> &str {
275 "Checks Service targetPort references"
276 }
277
278 fn supported_object_kinds(&self) -> ObjectKindsDesc {
279 ObjectKindsDesc::new(&["Service"])
280 }
281
282 fn parameters(&self) -> Vec<ParameterDesc> {
283 Vec::new()
284 }
285
286 fn instantiate(
287 &self,
288 _params: &serde_yaml::Value,
289 ) -> Result<Box<dyn CheckFunc>, TemplateError> {
290 Ok(Box::new(TargetPortCheck))
291 }
292}
293
294struct TargetPortCheck;
295
296impl CheckFunc for TargetPortCheck {
297 fn check(&self, object: &Object) -> Vec<Diagnostic> {
298 let diagnostics = Vec::new();
299 let _ = object;
302 diagnostics
303 }
304}
305
306#[cfg(test)]
307mod tests {
308 use super::*;
309 use crate::analyzer::kubelint::parser::yaml::parse_yaml;
310
311 #[test]
312 fn test_sysctls_unsafe() {
313 let yaml = r#"
314apiVersion: apps/v1
315kind: Deployment
316metadata:
317 name: sysctl-deploy
318spec:
319 template:
320 spec:
321 securityContext:
322 sysctls:
323 - name: net.core.somaxconn
324 value: "1024"
325 containers:
326 - name: nginx
327 image: nginx:1.21.0
328"#;
329 let objects = parse_yaml(yaml).unwrap();
330 let check = SysctlsCheck;
331 let diagnostics = check.check(&objects[0]);
332 assert_eq!(diagnostics.len(), 1);
333 assert!(diagnostics[0].message.contains("net.core.somaxconn"));
334 }
335
336 #[test]
337 fn test_no_sysctls_ok() {
338 let yaml = r#"
339apiVersion: apps/v1
340kind: Deployment
341metadata:
342 name: no-sysctl-deploy
343spec:
344 template:
345 spec:
346 containers:
347 - name: nginx
348 image: nginx:1.21.0
349"#;
350 let objects = parse_yaml(yaml).unwrap();
351 let check = SysctlsCheck;
352 let diagnostics = check.check(&objects[0]);
353 assert!(diagnostics.is_empty());
354 }
355}