Skip to main content

sysknife_daemon/actions/
users.rs

1use super::{command_mechanism, ActionSpec};
2use sysknife_types::RiskLevel;
3
4pub fn specs() -> Vec<ActionSpec> {
5    vec![
6        list_users(),
7        list_groups(),
8        create_user("alice", Some("/bin/bash"), Some("/home/alice")),
9        delete_user("alice"),
10        add_user_to_group("alice", "wheel"),
11        remove_user_from_group("alice", "wheel"),
12        create_group("developers", false),
13        delete_group("developers"),
14        lock_user_account("alice"),
15        unlock_user_account("alice"),
16    ]
17}
18
19pub fn list_users() -> ActionSpec {
20    ActionSpec {
21        action_name: "ListUsers",
22        mechanism: command_mechanism("getent", ["passwd"]),
23        risk_level: RiskLevel::Low,
24        reboot_required: false,
25        rollback_available: false,
26    }
27}
28
29pub fn list_groups() -> ActionSpec {
30    ActionSpec {
31        action_name: "ListGroups",
32        mechanism: command_mechanism("getent", ["group"]),
33        risk_level: RiskLevel::Low,
34        reboot_required: false,
35        rollback_available: false,
36    }
37}
38
39pub fn create_user(username: &str, shell: Option<&str>, home: Option<&str>) -> ActionSpec {
40    let mut args = vec!["useradd".to_string(), "--create-home".to_string()];
41    if let Some(home) = home {
42        args.push("--home-dir".to_string());
43        args.push(home.to_string());
44    }
45    if let Some(shell) = shell {
46        args.push("--shell".to_string());
47        args.push(shell.to_string());
48    }
49    args.push(username.to_string());
50
51    ActionSpec {
52        action_name: "CreateUser",
53        mechanism: super::ActionMechanism::Command {
54            program: "sudo",
55            args,
56        },
57        risk_level: RiskLevel::High,
58        reboot_required: false,
59        rollback_available: false,
60    }
61}
62
63pub fn delete_user(username: &str) -> ActionSpec {
64    ActionSpec {
65        action_name: "DeleteUser",
66        mechanism: command_mechanism("sudo", ["userdel", username]),
67        // High risk: permanently removes login access — same class as SSH key
68        // removal and group membership changes.
69        risk_level: RiskLevel::High,
70        reboot_required: false,
71        rollback_available: false,
72    }
73}
74
75pub fn add_user_to_group(username: &str, group: &str) -> ActionSpec {
76    // On Fedora Atomic, system groups (wheel, adm, systemd-journal, etc.) live
77    // in the read-only /usr/lib/group OSTree layer. `usermod` only modifies
78    // /etc/group. If the group is absent from /etc/group, usermod silently
79    // succeeds without actually adding the user. Fix: copy the entry via
80    // `getent group` (which merges /usr/lib/group + /etc/group) if missing.
81    ActionSpec {
82        action_name: "AddUserToGroup",
83        mechanism: command_mechanism(
84            "sudo",
85            [
86                "/usr/lib/sysknife/action-steps",
87                "group-add",
88                username,
89                group,
90            ],
91        ),
92        // High risk: adding a user to a privileged group (e.g. `wheel`) grants
93        // sudo / sysknife-admin rights, constituting a privilege escalation if
94        // performed at lower than Admin level.
95        risk_level: RiskLevel::High,
96        reboot_required: false,
97        rollback_available: false,
98    }
99}
100
101pub fn remove_user_from_group(username: &str, group: &str) -> ActionSpec {
102    // Same Fedora Atomic group-layer issue as AddUserToGroup: `gpasswd` fails
103    // with "group does not exist in /etc/group" for system groups. Ensure the
104    // entry is present in /etc/group before deletion.
105    ActionSpec {
106        action_name: "RemoveUserFromGroup",
107        mechanism: command_mechanism(
108            "sudo",
109            [
110                "/usr/lib/sysknife/action-steps",
111                "group-remove",
112                username,
113                group,
114            ],
115        ),
116        // High risk: mirrors AddUserToGroup — removing from a privileged group
117        // is equally impactful and should require the same Admin authorization.
118        risk_level: RiskLevel::High,
119        reboot_required: false,
120        rollback_available: false,
121    }
122}
123
124/// Create a group (`sudo groupadd [--system] <group>`).
125///
126/// High risk: groups gate access to privileged resources, so creating one is a
127/// privilege-relevant operation on par with the rest of the user/group family.
128/// `system` selects a system group (GID from the system range) via `--system`.
129pub fn create_group(group: &str, system: bool) -> ActionSpec {
130    let mut args = vec!["groupadd".to_string()];
131    if system {
132        args.push("--system".to_string());
133    }
134    args.push(group.to_string());
135    ActionSpec {
136        action_name: "CreateGroup",
137        mechanism: super::ActionMechanism::Command {
138            program: "sudo",
139            args,
140        },
141        risk_level: RiskLevel::Medium,
142        reboot_required: false,
143        rollback_available: false,
144    }
145}
146
147/// Delete a group (`sudo groupdel <group>`).
148///
149/// High risk: irreversible; removing a group can strip access from every member
150/// and orphan file ownership. `groupdel` itself refuses to remove a user's
151/// primary group, which is a useful built-in guard.
152pub fn delete_group(group: &str) -> ActionSpec {
153    ActionSpec {
154        action_name: "DeleteGroup",
155        mechanism: command_mechanism("sudo", ["groupdel", group]),
156        risk_level: RiskLevel::High,
157        reboot_required: false,
158        rollback_available: false,
159    }
160}
161
162/// Lock a user account (`sudo usermod --lock <username>`).
163///
164/// High risk: disables password login for the account without deleting it
165/// (reversible via [`unlock_user_account`]). Note this does not terminate the
166/// user's existing sessions or disable SSH-key logins.
167pub fn lock_user_account(username: &str) -> ActionSpec {
168    ActionSpec {
169        action_name: "LockUserAccount",
170        mechanism: command_mechanism("sudo", ["usermod", "--lock", username]),
171        risk_level: RiskLevel::High,
172        reboot_required: false,
173        rollback_available: false,
174    }
175}
176
177/// Unlock a previously locked user account (`sudo usermod --unlock <username>`).
178///
179/// High risk: re-enables password login — an access-control change on par with
180/// the rest of the user/group family.
181pub fn unlock_user_account(username: &str) -> ActionSpec {
182    ActionSpec {
183        action_name: "UnlockUserAccount",
184        mechanism: command_mechanism("sudo", ["usermod", "--unlock", username]),
185        risk_level: RiskLevel::High,
186        reboot_required: false,
187        rollback_available: false,
188    }
189}