syncable_cli/analyzer/hadolint/rules/
dl3002.rs1use crate::analyzer::hadolint::parser::instruction::Instruction;
7use crate::analyzer::hadolint::rules::{CustomRule, RuleState, custom_rule};
8use crate::analyzer::hadolint::shell::ParsedShell;
9use crate::analyzer::hadolint::types::Severity;
10
11pub fn rule()
12-> CustomRule<impl Fn(&mut RuleState, u32, &Instruction, Option<&ParsedShell>) + Send + Sync> {
13 custom_rule(
14 "DL3002",
15 Severity::Warning,
16 "Last USER should not be root",
17 |state, line, instr, _shell| {
18 match instr {
19 Instruction::From(_) => {
20 state.data.set_bool("is_root", true);
22 state.data.set_int("last_user_line", 0);
23 }
24 Instruction::User(user) => {
25 let is_root = user == "root" || user == "0" || user.starts_with("root:");
26 state.data.set_bool("is_root", is_root);
27 state.data.set_int("last_user_line", line as i64);
28 }
29 _ => {}
30 }
31 },
32 )
33}
34
35pub fn finalize(state: RuleState) -> Vec<crate::analyzer::hadolint::types::CheckFailure> {
38 let mut failures = state.failures;
39
40 if state.data.get_bool("is_root") {
42 let last_line = state.data.get_int("last_user_line");
43 if last_line > 0 {
44 failures.push(crate::analyzer::hadolint::types::CheckFailure::new(
45 "DL3002",
46 Severity::Warning,
47 "Last USER should not be root",
48 last_line as u32,
49 ));
50 }
51 }
52
53 failures
54}
55
56#[cfg(test)]
57mod tests {
58 use super::*;
59 use crate::analyzer::hadolint::parser::instruction::BaseImage;
60 use crate::analyzer::hadolint::rules::Rule;
61
62 #[test]
63 fn test_non_root_user() {
64 let rule = rule();
65 let mut state = RuleState::new();
66
67 let from = Instruction::From(BaseImage::new("ubuntu"));
68 let user = Instruction::User("appuser".to_string());
69
70 rule.check(&mut state, 1, &from, None);
71 rule.check(&mut state, 2, &user, None);
72
73 let failures = finalize(state);
74 assert!(failures.is_empty());
75 }
76
77 #[test]
78 fn test_root_user() {
79 let rule = rule();
80 let mut state = RuleState::new();
81
82 let from = Instruction::From(BaseImage::new("ubuntu"));
83 let user = Instruction::User("root".to_string());
84
85 rule.check(&mut state, 1, &from, None);
86 rule.check(&mut state, 2, &user, None);
87
88 let failures = finalize(state);
89 assert_eq!(failures.len(), 1);
90 assert_eq!(failures[0].code.as_str(), "DL3002");
91 }
92
93 #[test]
94 fn test_switch_from_root() {
95 let rule = rule();
96 let mut state = RuleState::new();
97
98 let from = Instruction::From(BaseImage::new("ubuntu"));
99 let user1 = Instruction::User("root".to_string());
100 let user2 = Instruction::User("appuser".to_string());
101
102 rule.check(&mut state, 1, &from, None);
103 rule.check(&mut state, 2, &user1, None);
104 rule.check(&mut state, 3, &user2, None);
105
106 let failures = finalize(state);
107 assert!(failures.is_empty());
108 }
109}