syncable_cli/analyzer/kubelint/templates/
misc.rs

1//! Miscellaneous check 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 checking sysctls usage.
9pub 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        // Unsafe sysctls that require special permissions
47        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
79/// Template for checking DNS config options.
80pub 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            // Check for ndots setting that could cause performance issues
121            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
146/// Template for checking startup probe port.
147pub 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
218/// Template for checking env var valueFrom usage.
219pub 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        // This is a placeholder - the actual implementation would check
256        // for specific valueFrom misconfigurations
257        let _ = object;
258        diagnostics
259    }
260}
261
262/// Template for checking target port references.
263pub 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        // This check would need cross-resource validation to verify
300        // that targetPort references valid container ports
301        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}