1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
//! sysctl kernel-parameter actions.
//!
//! `GetSysctl` is a read-only query of one key (or the full table); `SetSysctl`
//! changes a kernel parameter at runtime **and** persists it for the next boot.
//!
//! The read runs `sysctl` directly (the daemon is root). The write goes through
//! the root-owned helper `/usr/lib/sysknife/sysctl-edit`, which applies the
//! value with `sysctl -w` and rewrites the `/etc/sysctl.d/60-sysknife.conf`
//! drop-in idempotently — a narrow sudoers grant, not a bare `sysctl` grant, so
//! the daemon cannot use `sysctl -p <arbitrary-file>` to load an attacker file.
use super::{command_mechanism, ActionSpec};
use sysknife_types::RiskLevel;
pub fn specs() -> Vec<ActionSpec> {
vec![
get_sysctl(Some("net.ipv4.ip_forward")),
set_sysctl("net.ipv4.ip_forward", "1"),
]
}
/// Read one kernel parameter (`sysctl -- <key>`) or, when `key` is `None`, the
/// entire table (`sysctl -a`). Read-only.
///
/// The `--` guard means a key can never be reparsed as an option even though
/// the validator already forbids a leading dash.
pub fn get_sysctl(key: Option<&str>) -> ActionSpec {
let args = match key {
Some(k) => vec!["--".to_string(), k.to_string()],
None => vec!["-a".to_string()],
};
ActionSpec {
action_name: "GetSysctl",
mechanism: command_mechanism("sysctl", args),
risk_level: RiskLevel::Low,
reboot_required: false,
rollback_available: false,
}
}
/// Set and persist a kernel parameter via the scoped helper.
pub fn set_sysctl(key: &str, value: &str) -> ActionSpec {
ActionSpec {
action_name: "SetSysctl",
mechanism: command_mechanism(
"sudo",
[
"/usr/lib/sysknife/sysctl-edit",
"--key",
key,
"--value",
value,
],
),
risk_level: RiskLevel::High,
reboot_required: false,
rollback_available: false,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::actions::ActionMechanism;
fn args_of(spec: &ActionSpec) -> (&'static str, Vec<String>) {
match &spec.mechanism {
ActionMechanism::Command { program, args } => (program, args.clone()),
other => panic!("expected Command, got {other:?}"),
}
}
#[test]
fn get_one_key_uses_dash_dash_guard() {
let (program, args) = args_of(&get_sysctl(Some("net.ipv4.ip_forward")));
assert_eq!(program, "sysctl");
assert_eq!(args, vec!["--", "net.ipv4.ip_forward"]);
}
#[test]
fn get_all_uses_minus_a() {
let (_, args) = args_of(&get_sysctl(None));
assert_eq!(args, vec!["-a"]);
}
#[test]
fn set_delegates_to_scoped_helper() {
let spec = set_sysctl("vm.swappiness", "10");
let (program, args) = args_of(&spec);
assert_eq!(program, "sudo");
assert_eq!(
args,
vec![
"/usr/lib/sysknife/sysctl-edit",
"--key",
"vm.swappiness",
"--value",
"10"
]
);
assert_eq!(spec.risk_level, RiskLevel::High);
}
}