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