1use super::{command_mechanism, ActionSpec};
12use sysknife_types::RiskLevel;
13
14const HELPER: &str = "/usr/lib/sysknife/sudoers-edit";
15
16pub fn specs() -> Vec<ActionSpec> {
17 vec![
18 get_sudo_grants(),
19 grant_sudo_access("deploy-restart", "deploy", "/usr/bin/systemctl", None, true),
20 revoke_sudo_access("deploy-restart"),
21 ]
22}
23
24pub fn get_sudo_grants() -> ActionSpec {
29 ActionSpec {
30 action_name: "GetSudoGrants",
31 mechanism: command_mechanism(HELPER, ["--op", "list"]),
32 risk_level: RiskLevel::Low,
33 reboot_required: false,
34 rollback_available: false,
35 }
36}
37
38pub fn grant_sudo_access(
43 name: &str,
44 user: &str,
45 commands: &str,
46 runas: Option<&str>,
47 nopasswd: bool,
48) -> ActionSpec {
49 let mut args = vec![
50 HELPER.to_string(),
51 "--op".to_string(),
52 "grant".to_string(),
53 "--name".to_string(),
54 name.to_string(),
55 "--user".to_string(),
56 user.to_string(),
57 "--commands".to_string(),
58 commands.to_string(),
59 ];
60 if let Some(r) = runas {
61 args.push("--runas".to_string());
62 args.push(r.to_string());
63 }
64 if nopasswd {
65 args.push("--nopasswd".to_string());
66 }
67 ActionSpec {
68 action_name: "GrantSudoAccess",
69 mechanism: command_mechanism("sudo", args),
70 risk_level: RiskLevel::High,
71 reboot_required: false,
72 rollback_available: false,
73 }
74}
75
76pub fn revoke_sudo_access(name: &str) -> ActionSpec {
78 ActionSpec {
79 action_name: "RevokeSudoAccess",
80 mechanism: command_mechanism("sudo", [HELPER, "--op", "revoke", "--name", name]),
81 risk_level: RiskLevel::High,
82 reboot_required: false,
83 rollback_available: false,
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90 use crate::actions::ActionMechanism;
91
92 fn args_of(spec: &ActionSpec) -> (&'static str, Vec<String>) {
93 match &spec.mechanism {
94 ActionMechanism::Command { program, args } => (program, args.clone()),
95 other => panic!("expected Command, got {other:?}"),
96 }
97 }
98
99 #[test]
100 fn list_is_read_only_bare_helper() {
101 let (program, args) = args_of(&get_sudo_grants());
102 assert_eq!(program, HELPER);
103 assert_eq!(args, vec!["--op", "list"]);
104 assert_eq!(get_sudo_grants().risk_level, RiskLevel::Low);
105 }
106
107 #[test]
108 fn grant_builds_flags_including_optional_runas_and_nopasswd() {
109 let (program, args) = args_of(&grant_sudo_access(
110 "ci",
111 "ci",
112 "/usr/bin/systemctl",
113 Some("root"),
114 true,
115 ));
116 assert_eq!(program, "sudo");
117 assert_eq!(
118 args,
119 vec![
120 HELPER,
121 "--op",
122 "grant",
123 "--name",
124 "ci",
125 "--user",
126 "ci",
127 "--commands",
128 "/usr/bin/systemctl",
129 "--runas",
130 "root",
131 "--nopasswd"
132 ]
133 );
134 let (_, minimal) = args_of(&grant_sudo_access("ci", "ci", "ALL", None, false));
136 assert!(!minimal.iter().any(|a| a == "--runas" || a == "--nopasswd"));
137 assert_eq!(
138 grant_sudo_access("a", "b", "ALL", None, false).risk_level,
139 RiskLevel::High
140 );
141 }
142
143 #[test]
144 fn revoke_shape() {
145 let (program, args) = args_of(&revoke_sudo_access("ci"));
146 assert_eq!(program, "sudo");
147 assert_eq!(args, vec![HELPER, "--op", "revoke", "--name", "ci"]);
148 }
149}