syncable_cli/analyzer/kubelint/templates/
mod.rs1pub mod antiaffinity;
7pub mod capabilities;
8pub mod dangling;
9pub mod envvar;
10pub mod hostmounts;
11pub mod hostnetwork;
12pub mod latesttag;
13pub mod livenessprobe;
14pub mod misc;
15pub mod pdb;
16pub mod ports;
17pub mod privileged;
18pub mod privilegeescalation;
19pub mod rbac;
20pub mod readinessprobe;
21pub mod readonlyrootfs;
22pub mod replicas;
23pub mod requirements;
24pub mod runasnonroot;
25pub mod serviceaccount;
26pub mod unsafeprocmount;
27pub mod updateconfig;
28pub mod validation;
29
30use crate::analyzer::kubelint::context::Object;
31use crate::analyzer::kubelint::types::{Diagnostic, ObjectKindsDesc};
32use std::collections::HashMap;
33use std::sync::OnceLock;
34
35pub trait CheckFunc: Send + Sync {
37 fn check(&self, object: &Object) -> Vec<Diagnostic>;
39}
40
41#[derive(Debug, Clone)]
43pub struct ParameterDesc {
44 pub name: String,
46 pub description: String,
48 pub param_type: String,
50 pub required: bool,
52 pub default: Option<serde_yaml::Value>,
54}
55
56pub trait Template: Send + Sync {
58 fn key(&self) -> &str;
60
61 fn human_name(&self) -> &str;
63
64 fn description(&self) -> &str;
66
67 fn supported_object_kinds(&self) -> ObjectKindsDesc;
69
70 fn parameters(&self) -> Vec<ParameterDesc>;
72
73 fn instantiate(&self, params: &serde_yaml::Value) -> Result<Box<dyn CheckFunc>, TemplateError>;
75}
76
77#[derive(Debug, Clone)]
79pub enum TemplateError {
80 MissingParameter(String),
82 InvalidParameter(String),
84 UnknownTemplate(String),
86}
87
88impl std::fmt::Display for TemplateError {
89 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90 match self {
91 Self::MissingParameter(name) => write!(f, "Missing required parameter: {}", name),
92 Self::InvalidParameter(msg) => write!(f, "Invalid parameter: {}", msg),
93 Self::UnknownTemplate(key) => write!(f, "Unknown template: {}", key),
94 }
95 }
96}
97
98impl std::error::Error for TemplateError {}
99
100static REGISTRY: OnceLock<HashMap<String, Box<dyn Template>>> = OnceLock::new();
102
103pub fn registry() -> &'static HashMap<String, Box<dyn Template>> {
105 REGISTRY.get_or_init(|| {
106 let mut map: HashMap<String, Box<dyn Template>> = HashMap::new();
107
108 map.insert(
110 "privileged".to_string(),
111 Box::new(privileged::PrivilegedTemplate),
112 );
113 map.insert(
114 "privilege-escalation".to_string(),
115 Box::new(privilegeescalation::PrivilegeEscalationTemplate),
116 );
117 map.insert(
118 "run-as-non-root".to_string(),
119 Box::new(runasnonroot::RunAsNonRootTemplate),
120 );
121 map.insert(
122 "read-only-root-fs".to_string(),
123 Box::new(readonlyrootfs::ReadOnlyRootFsTemplate),
124 );
125 map.insert(
126 "latest-tag".to_string(),
127 Box::new(latesttag::LatestTagTemplate),
128 );
129 map.insert(
130 "liveness-probe".to_string(),
131 Box::new(livenessprobe::LivenessProbeTemplate),
132 );
133 map.insert(
134 "readiness-probe".to_string(),
135 Box::new(readinessprobe::ReadinessProbeTemplate),
136 );
137 map.insert(
138 "cpu-requirements".to_string(),
139 Box::new(requirements::CpuRequirementsTemplate),
140 );
141 map.insert(
142 "memory-requirements".to_string(),
143 Box::new(requirements::MemoryRequirementsTemplate),
144 );
145 map.insert(
146 "anti-affinity".to_string(),
147 Box::new(antiaffinity::AntiAffinityTemplate),
148 );
149 map.insert(
150 "drop-net-raw-capability".to_string(),
151 Box::new(capabilities::DropNetRawCapabilityTemplate),
152 );
153 map.insert(
154 "host-mounts".to_string(),
155 Box::new(hostmounts::HostMountsTemplate),
156 );
157 map.insert(
158 "writable-host-mount".to_string(),
159 Box::new(hostmounts::WritableHostMountTemplate),
160 );
161 map.insert(
162 "service-account".to_string(),
163 Box::new(serviceaccount::ServiceAccountTemplate),
164 );
165 map.insert(
166 "deprecated-service-account-field".to_string(),
167 Box::new(serviceaccount::DeprecatedServiceAccountFieldTemplate),
168 );
169 map.insert(
170 "rolling-update-strategy".to_string(),
171 Box::new(updateconfig::RollingUpdateStrategyTemplate),
172 );
173
174 map.insert(
176 "host-network".to_string(),
177 Box::new(hostnetwork::HostNetworkTemplate),
178 );
179 map.insert(
180 "host-pid".to_string(),
181 Box::new(hostnetwork::HostPIDTemplate),
182 );
183 map.insert(
184 "host-ipc".to_string(),
185 Box::new(hostnetwork::HostIPCTemplate),
186 );
187
188 map.insert("replicas".to_string(), Box::new(replicas::ReplicasTemplate));
190
191 map.insert(
193 "unsafe-proc-mount".to_string(),
194 Box::new(unsafeprocmount::UnsafeProcMountTemplate),
195 );
196
197 map.insert(
199 "env-var-secret".to_string(),
200 Box::new(envvar::EnvVarSecretTemplate),
201 );
202 map.insert(
203 "read-secret-from-env-var".to_string(),
204 Box::new(envvar::ReadSecretFromEnvVarTemplate),
205 );
206 map.insert(
207 "duplicate-env-var".to_string(),
208 Box::new(envvar::DuplicateEnvVarTemplate),
209 );
210
211 map.insert(
213 "privileged-ports".to_string(),
214 Box::new(ports::PrivilegedPortsTemplate),
215 );
216 map.insert("ssh-port".to_string(), Box::new(ports::SSHPortTemplate));
217 map.insert(
218 "liveness-port".to_string(),
219 Box::new(ports::LivenessPortTemplate),
220 );
221 map.insert(
222 "readiness-port".to_string(),
223 Box::new(ports::ReadinessPortTemplate),
224 );
225
226 map.insert(
228 "cluster-admin-role-binding".to_string(),
229 Box::new(rbac::ClusterAdminRoleBindingTemplate),
230 );
231 map.insert(
232 "wildcard-in-rules".to_string(),
233 Box::new(rbac::WildcardInRulesTemplate),
234 );
235 map.insert(
236 "access-to-secrets".to_string(),
237 Box::new(rbac::AccessToSecretsTemplate),
238 );
239 map.insert(
240 "access-to-create-pods".to_string(),
241 Box::new(rbac::AccessToCreatePodsTemplate),
242 );
243
244 map.insert(
246 "pdb-max-unavailable".to_string(),
247 Box::new(pdb::PdbMaxUnavailableTemplate),
248 );
249 map.insert(
250 "pdb-min-available".to_string(),
251 Box::new(pdb::PdbMinAvailableTemplate),
252 );
253 map.insert(
254 "pdb-unhealthy-pod-eviction-policy".to_string(),
255 Box::new(pdb::PdbUnhealthyPodEvictionPolicyTemplate),
256 );
257
258 map.insert(
260 "use-namespace".to_string(),
261 Box::new(validation::UseNamespaceTemplate),
262 );
263 map.insert(
264 "restart-policy".to_string(),
265 Box::new(validation::RestartPolicyTemplate),
266 );
267 map.insert(
268 "required-annotation".to_string(),
269 Box::new(validation::RequiredAnnotationTemplate),
270 );
271 map.insert(
272 "required-label".to_string(),
273 Box::new(validation::RequiredLabelTemplate),
274 );
275 map.insert(
276 "disallowed-gvk".to_string(),
277 Box::new(validation::DisallowedGVKTemplate),
278 );
279 map.insert(
280 "mismatching-selector".to_string(),
281 Box::new(validation::MismatchingSelectorTemplate),
282 );
283 map.insert(
284 "node-affinity".to_string(),
285 Box::new(validation::NodeAffinityTemplate),
286 );
287 map.insert(
288 "job-ttl-seconds-after-finished".to_string(),
289 Box::new(validation::JobTtlSecondsAfterFinishedTemplate),
290 );
291 map.insert(
292 "priority-class-name".to_string(),
293 Box::new(validation::PriorityClassNameTemplate),
294 );
295 map.insert(
296 "service-type".to_string(),
297 Box::new(validation::ServiceTypeTemplate),
298 );
299 map.insert(
300 "hpa-min-replicas".to_string(),
301 Box::new(validation::HpaMinReplicasTemplate),
302 );
303
304 map.insert("sysctls".to_string(), Box::new(misc::SysctlsTemplate));
306 map.insert(
307 "dnsconfig-options".to_string(),
308 Box::new(misc::DnsConfigOptionsTemplate),
309 );
310 map.insert(
311 "startup-port".to_string(),
312 Box::new(misc::StartupPortTemplate),
313 );
314 map.insert(
315 "env-var-value-from".to_string(),
316 Box::new(misc::EnvVarValueFromTemplate),
317 );
318 map.insert(
319 "target-port".to_string(),
320 Box::new(misc::TargetPortTemplate),
321 );
322
323 map.insert(
325 "dangling-service".to_string(),
326 Box::new(dangling::DanglingServiceTemplate),
327 );
328 map.insert(
329 "dangling-ingress".to_string(),
330 Box::new(dangling::DanglingIngressTemplate),
331 );
332 map.insert(
333 "dangling-hpa".to_string(),
334 Box::new(dangling::DanglingHpaTemplate),
335 );
336 map.insert(
337 "dangling-network-policy".to_string(),
338 Box::new(dangling::DanglingNetworkPolicyTemplate),
339 );
340 map.insert(
341 "dangling-network-policy-peer".to_string(),
342 Box::new(dangling::DanglingNetworkPolicyPeerTemplate),
343 );
344 map.insert(
345 "dangling-service-monitor".to_string(),
346 Box::new(dangling::DanglingServiceMonitorTemplate),
347 );
348 map.insert(
349 "non-existent-service-account".to_string(),
350 Box::new(dangling::NonExistentServiceAccountTemplate),
351 );
352 map.insert(
353 "non-isolated-pod".to_string(),
354 Box::new(dangling::NonIsolatedPodTemplate),
355 );
356 map.insert(
357 "scc-deny-privileged".to_string(),
358 Box::new(dangling::SccDenyPrivilegedTemplate),
359 );
360
361 map
362 })
363}
364
365pub fn get_template(key: &str) -> Option<&'static dyn Template> {
367 registry().get(key).map(|t| t.as_ref())
368}
369
370pub fn list_templates() -> Vec<&'static str> {
372 registry().keys().map(|s| s.as_str()).collect()
373}
374
375pub fn init_builtin_templates() {
378 let _ = registry();
379}
380
381#[cfg(test)]
382mod tests {
383 use super::*;
384
385 #[test]
386 fn test_registry_initialization() {
387 let reg = registry();
388 assert!(!reg.is_empty());
389 assert!(reg.contains_key("privileged"));
390 assert!(reg.contains_key("latest-tag"));
391 }
392
393 #[test]
394 fn test_get_template() {
395 let template = get_template("privileged");
396 assert!(template.is_some());
397 assert_eq!(template.unwrap().key(), "privileged");
398 }
399
400 #[test]
401 fn test_list_templates() {
402 let templates = list_templates();
403 assert!(templates.contains(&"privileged"));
404 assert!(templates.contains(&"latest-tag"));
405 }
406}