diff --git a/deploy/ochenstarik-server-monitor-manager.sh b/deploy/ochenstarik-server-monitor-manager.sh index 4e25e61..af1e6f7 100755 --- a/deploy/ochenstarik-server-monitor-manager.sh +++ b/deploy/ochenstarik-server-monitor-manager.sh @@ -574,6 +574,7 @@ prepare_control_state() { chown -R "$CONTROL_USER:$CONTROL_USER" "$STATE_DIR/control" find "$STATE_DIR/control" -type d -exec chmod 0700 {} + find "$STATE_DIR/control" -type f -exec chmod 0600 {} + + repair_mesh_state_permissions } reverse_control_state_migration() { @@ -682,6 +683,22 @@ read_mesh_value() { awk -F '=' -v key="$key" '$1 == key { print substr($0, index($0, "=") + 1); exit }' "$ETC_DIR/mesh.env" } +ensure_mesh_state() { + install -d -m 0770 -o root -g "$CONTROL_USER" "$MESH_DIR" + touch "$MESH_DIR/nodes.tsv" + chown root:"$CONTROL_USER" "$MESH_DIR/nodes.tsv" + chmod 0660 "$MESH_DIR/nodes.tsv" +} + +repair_mesh_state_permissions() { + [[ -d "$MESH_DIR" ]] || return 0 + install -d -m 0770 -o root -g "$CONTROL_USER" "$MESH_DIR" + if [[ -e "$MESH_DIR/nodes.tsv" ]]; then + chown root:"$CONTROL_USER" "$MESH_DIR/nodes.tsv" + chmod 0660 "$MESH_DIR/nodes.tsv" + fi +} + render_hub_wireguard_config() { local private_key endpoint port node_id address public_key status private_key="$(cat "$WG_DIR/hub.key")" @@ -719,8 +736,10 @@ mesh_init() { fi [[ "$public_endpoint" =~ ^[A-Za-z0-9]([A-Za-z0-9.-]{0,251}[A-Za-z0-9])?$ ]] \ || fail "Invalid WireGuard public endpoint." + ensure_system_user "$CONTROL_USER" ensure_mesh_packages - install -d -m 0700 "$WG_DIR" "$MESH_DIR" /etc/wireguard + install -d -m 0700 -o root -g root "$WG_DIR" /etc/wireguard + ensure_mesh_state if [[ ! -f "$WG_DIR/hub.key" ]]; then umask 077 wg genkey >"$WG_DIR/hub.key" @@ -736,8 +755,6 @@ HUB_PUBLIC_KEY=$hub_public MESH_NETWORK=$MESH_NETWORK EOF chmod 0644 "$ETC_DIR/mesh.env" - touch "$MESH_DIR/nodes.tsv" - chmod 0600 "$MESH_DIR/nodes.tsv" printf '%s\n' 'net.ipv4.ip_forward=1' >"/etc/sysctl.d/90-ochenstarik-smm-mesh.conf" sysctl --system >/dev/null write_mesh_firewall @@ -761,9 +778,7 @@ EOF reserve_node_address() { local node_id="$1" existing host address - install -d -m 0700 "$MESH_DIR" - touch "$MESH_DIR/nodes.tsv" - chmod 0600 "$MESH_DIR/nodes.tsv" + ensure_mesh_state existing="$(awk -F '\t' -v node="$node_id" '$1 == node { print $2; exit }' "$MESH_DIR/nodes.tsv")" if [[ -n "$existing" ]]; then printf '%s\n' "$existing" @@ -1359,7 +1374,8 @@ add_mesh_peer() { awk -F '\t' -v OFS='\t' -v node="$node_id" -v address="$address" -v key="$public_key" \ '$1 == node { print node, address, key, "active"; found=1; next } { print } END { if (!found) exit 1 }' \ "$MESH_DIR/nodes.tsv" >"$tmp" || { rm -f -- "$tmp"; fail "Peer reservation is missing."; } - chmod 0600 "$tmp" + chown root:"$CONTROL_USER" "$tmp" + chmod 0660 "$tmp" mv -- "$tmp" "$MESH_DIR/nodes.tsv" render_hub_wireguard_config systemctl restart wg-quick@smm0.service diff --git a/deploy/ochenstarik-smm-control.service b/deploy/ochenstarik-smm-control.service index 82937fb..9d5e1a2 100644 --- a/deploy/ochenstarik-smm-control.service +++ b/deploy/ochenstarik-smm-control.service @@ -23,7 +23,7 @@ LockPersonality=true RestrictSUIDSGID=true RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 UMask=0077 -ReadWritePaths=/var/lib/ochenstarik-server-monitor-manager/control +ReadWritePaths=/var/lib/ochenstarik-server-monitor-manager/control /var/lib/ochenstarik-server-monitor-manager/mesh [Install] WantedBy=multi-user.target diff --git a/tests/bootstrap/test-bootstrap-contract.sh b/tests/bootstrap/test-bootstrap-contract.sh index 2b7405f..2a751c7 100755 --- a/tests/bootstrap/test-bootstrap-contract.sh +++ b/tests/bootstrap/test-bootstrap-contract.sh @@ -527,7 +527,18 @@ rm -rf "$role_fixture/lib/agent" rm -rf -- "$role_fixture" grep -Fq 'UMask=0077' "$root/deploy/ochenstarik-smm-control.service" -grep -Fq 'ReadWritePaths=/var/lib/ochenstarik-server-monitor-manager/control' "$root/deploy/ochenstarik-smm-control.service" +grep -Fq 'ReadWritePaths=/var/lib/ochenstarik-server-monitor-manager/control /var/lib/ochenstarik-server-monitor-manager/mesh' "$root/deploy/ochenstarik-smm-control.service" +grep -Fq 'install -d -m 0770 -o root -g "$CONTROL_USER" "$MESH_DIR"' "$bootstrap" +grep -Fq 'chown root:"$CONTROL_USER" "$MESH_DIR/nodes.tsv"' "$bootstrap" +grep -Fq 'chmod 0660 "$MESH_DIR/nodes.tsv"' "$bootstrap" +grep -Fq 'install -d -m 0700 -o root -g root "$WG_DIR" /etc/wireguard' "$bootstrap" +mesh_init_definition="$(extract_bootstrap_function mesh_init)" +grep -Fq ' ensure_system_user "$CONTROL_USER"' <<<"$mesh_init_definition" +[[ "$(grep -Fc ' ensure_mesh_state' "$bootstrap")" -eq 2 ]] +grep -Fq ' repair_mesh_state_permissions' <<<"$prepare_control_state_definition" +if [[ "$(uname -s)" != MINGW* ]] && command -v sudo >/dev/null 2>&1; then + bash "$root/tests/bootstrap/test-mesh-state-permissions.sh" +fi native_smoke="$root/tests/bootstrap/run-native-systemd-smoke.sh" grep -Fq 'node_code="$(sudo "$system_bootstrap" node-code smoke-node)"' "$native_smoke" grep -Fq 'export SMM_ENROLL_CODE="$node_code"' "$native_smoke" @@ -870,4 +881,3 @@ grep -Fq 'record_installed_version agent' "$bootstrap" || { } printf '%s\n' "BOOTSTRAP_CONTRACT=PASS" - diff --git a/tests/bootstrap/test-mesh-state-permissions.sh b/tests/bootstrap/test-mesh-state-permissions.sh new file mode 100644 index 0000000..b467cf7 --- /dev/null +++ b/tests/bootstrap/test-mesh-state-permissions.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +set -Eeuo pipefail +IFS=$'\n\t' + +root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)" +bootstrap="$root/deploy/ochenstarik-server-monitor-manager.sh" +fixture="$(mktemp -d -t smm-mesh-permissions.XXXXXXXX)" +test_user="$(id -un)" +test_group="$(id -gn)" +mesh_dir="$fixture/state/mesh" +wg_dir="$fixture/etc/wireguard" + +cleanup() { + sudo rm -rf -- "$fixture" +} +trap cleanup EXIT + +extract_function() { + local name="$1" + awk -v name="$name" ' + $0 == name "() {" { capture=1 } + capture { print } + capture && $0 == "}" { exit } + ' "$bootstrap" +} + +ensure_definition="$(extract_function ensure_mesh_state)" +repair_definition="$(extract_function repair_mesh_state_permissions)" +runner="$fixture/apply-permissions.sh" +{ + printf '%s\n%s\n' "$ensure_definition" "$repair_definition" + printf '%s\n' 'ensure_mesh_state' +} >"$runner" + +sudo env MESH_DIR="$mesh_dir" CONTROL_USER="$test_group" bash "$runner" +[[ "$(sudo stat -c '%a:%U:%G' "$mesh_dir")" == "770:root:$test_group" ]] +[[ "$(sudo stat -c '%a:%U:%G' "$mesh_dir/nodes.tsv")" == "660:root:$test_group" ]] + +printf '%s\n' $'fixture-node\t10.77.0.2\t-\treserved' >>"$mesh_dir/nodes.tsv" +grep -Fq 'fixture-node' "$mesh_dir/nodes.tsv" + +sudo install -d -m 0700 -o root -g root "$wg_dir" +printf '%s\n' 'private-hub-key' | sudo tee "$wg_dir/hub.key" >/dev/null +sudo chown root:root "$wg_dir/hub.key" +sudo chmod 0600 "$wg_dir/hub.key" +if sudo -u "$test_user" test -r "$wg_dir/hub.key"; then + printf '%s\n' 'Control-equivalent user can read the Hub private key' >&2 + exit 1 +fi + +sudo chmod 0700 "$mesh_dir" +sudo chmod 0600 "$mesh_dir/nodes.tsv" +{ + printf '%s\n%s\n' "$ensure_definition" "$repair_definition" + printf '%s\n' 'repair_mesh_state_permissions' +} >"$runner" +sudo env MESH_DIR="$mesh_dir" CONTROL_USER="$test_group" bash "$runner" +[[ "$(sudo stat -c '%a:%U:%G' "$mesh_dir")" == "770:root:$test_group" ]] +[[ "$(sudo stat -c '%a:%U:%G' "$mesh_dir/nodes.tsv")" == "660:root:$test_group" ]] + +printf '%s\n' 'MESH_STATE_PERMISSIONS=PASS'