Add the audit timer, and the guard against naming a path twice
The timer exists because the rules it checks are edited on the hypervisor, in a console, possibly by someone not thinking about this VM at the time. A rule dragged below the DROPs or a Firewall checkbox cleared while debugging something else produces no symptom here at all: the box keeps working, it is simply no longer contained. Hourly means nobody has to remember. The service runs reach and not all. That is a security decision rather than a tidiness one: the radius report inventories credentials by path, and a timer would write that into the journal on a schedule, where it outlives the run and is readable by anything that later gets root. units.bats is not a straight copy. It read BIN_DIR out of install.sh, and this repository has none by design, so it now reads the one entry point. Copied unchanged it would have passed by reading a file that does not exist. Its first test also passed vacuously with no units present, because grep failing to open a file gives the same non-zero status the test was looking for; it now asserts there is something to check before checking it.
This commit is contained in:
@@ -0,0 +1,25 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Check that the hypervisor firewall is still applied to this VM
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
|
||||||
|
# `reach`, not `all`, and that is a security decision rather than a tidiness
|
||||||
|
# one. The radius report inventories credentials by path, and a timer running
|
||||||
|
# it would write that inventory into the journal on a schedule, where it
|
||||||
|
# outlives the session that produced it and is readable by anything that later
|
||||||
|
# gets root. The reachability half has no sensitive output at all, and it is
|
||||||
|
# also the only half that can change without anyone touching this VM.
|
||||||
|
ExecStart=@@BIN_DIR@@/dejima-devbox-audit reach
|
||||||
|
|
||||||
|
# The path above is a marker. bootstrap substitutes it and then checks that
|
||||||
|
# what it wrote is executable, because a unit naming a program that is not
|
||||||
|
# there fails with 203/EXEC on every start and says nothing anyone reads.
|
||||||
|
|
||||||
|
User=root
|
||||||
|
ProtectSystem=strict
|
||||||
|
ProtectHome=yes
|
||||||
|
PrivateTmp=yes
|
||||||
|
NoNewPrivileges=yes
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Re-check the hypervisor firewall periodically
|
||||||
|
|
||||||
|
[Timer]
|
||||||
|
# The rules this checks live on the hypervisor, where they are edited by hand
|
||||||
|
# in a console, by a person who may not be thinking about this VM at the time.
|
||||||
|
# A rule dragged below the DROPs, a security group detached, a Firewall
|
||||||
|
# checkbox cleared while debugging something else: none of those produce any
|
||||||
|
# symptom on this VM. The box keeps working. It is simply no longer contained.
|
||||||
|
#
|
||||||
|
# So the point of a schedule here is not freshness, it is that nobody has to
|
||||||
|
# remember. Hourly catches it the same day, and three TCP probes to your own
|
||||||
|
# gateway is not traffic worth noticing.
|
||||||
|
OnBootSec=5min
|
||||||
|
OnUnitActiveSec=1h
|
||||||
|
RandomizedDelaySec=5min
|
||||||
|
|
||||||
|
# Catch up after the VM has been off. A missed window is exactly when a change
|
||||||
|
# went in unobserved.
|
||||||
|
Persistent=true
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=timers.target
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
#!/usr/bin/env bats
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
# Copyright (C) 2026 Brad Anderson
|
||||||
|
#
|
||||||
|
# The systemd units, checked against where bootstrap actually puts things.
|
||||||
|
#
|
||||||
|
# These exist because the failure they guard has no symptom worth the name. A
|
||||||
|
# unit naming a program that is not there fails with 203/EXEC on every start,
|
||||||
|
# and for a timer that means the firewall silently stops being re-checked: no
|
||||||
|
# error anyone sees, nothing different on the box, and a host that looks
|
||||||
|
# configured while verifying nothing at all.
|
||||||
|
#
|
||||||
|
# In the sister repository a timer shipped that way and had never once worked
|
||||||
|
# on any host. The unit said /usr/local/bin and the installer used
|
||||||
|
# /usr/local/sbin, and nothing compared the two.
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
REPO="$(cd "$BATS_TEST_DIRNAME/.." && pwd)"
|
||||||
|
T="$BATS_TEST_TMPDIR"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "no unit hard-codes a path to a program bootstrap places" {
|
||||||
|
# Assert there is something to check first. Without this the test passes
|
||||||
|
# when systemd/ is empty or absent, because grep's own failure to open a
|
||||||
|
# file also produces a non-zero status - a green test asserting nothing.
|
||||||
|
units=("$REPO"/systemd/*.service)
|
||||||
|
[ -f "${units[0]}" ] || { echo "no units to check"; false; }
|
||||||
|
|
||||||
|
# The path belongs to bootstrap. Written down a second time it can only
|
||||||
|
# agree by luck, and in the sister repository it did not.
|
||||||
|
run grep -nE '^Exec(Start|Stop)=/(usr|opt|bin|sbin)' "${units[@]}"
|
||||||
|
[ "$status" -ne 0 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "every unit's ExecStart resolves to the directory bootstrap uses" {
|
||||||
|
# This repository has no install.sh by design, so the value is read from
|
||||||
|
# the one entry point instead. Copying this test unchanged would have made
|
||||||
|
# it pass by reading a file that does not exist.
|
||||||
|
bin=$(sed -n 's|^BIN_DIR=${BIN_DIR:-\(.*\)}|\1|p' "$REPO/bin/dejima-devbox-bootstrap")
|
||||||
|
[ -n "$bin" ]
|
||||||
|
for u in "$REPO"/systemd/*.service; do
|
||||||
|
exe=$(sed -n 's|^ExecStart=\([^ ]*\).*|\1|p' "$u" | head -1)
|
||||||
|
[ -n "$exe" ]
|
||||||
|
# After substitution it must name a program this repository ships.
|
||||||
|
resolved="${exe/@@BIN_DIR@@/$bin}"
|
||||||
|
[ "$resolved" != "$exe" ] || { echo "$u: ExecStart is not templated: $exe"; false; }
|
||||||
|
prog="${resolved##*/}"
|
||||||
|
[ -f "$REPO/bin/$prog" ] || { echo "$u: names $prog, which bin/ does not ship"; false; }
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "bootstrap substitutes the marker rather than copying it through" {
|
||||||
|
grep -q '@@BIN_DIR@@' "$REPO/bin/dejima-devbox-bootstrap"
|
||||||
|
# And refuses to finish if what it wrote is not executable.
|
||||||
|
grep -q 'which is not executable' "$REPO/bin/dejima-devbox-bootstrap"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user