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