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 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
82pub 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 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
152pub 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
224pub 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 let _ = object;
264 diagnostics
265 }
266}
267
268pub 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 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}