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