Files
dejima-devbox-hardener/docs/reference.md
branderson 6ce8c9e6c0
tests / runner-env (pull_request_target) Successful in 1m40s
tests / tests (pull_request_target) Successful in 1m54s
Report the addressing mode, and catch a lease that stopped renewing
The design spec asked for this and the first implementation left it out. It targets the worst failure in the whole design: a DHCP guest behind these rules renews through a path the firewall may be dropping, nothing breaks at boot because the first lease predates the rules, and the VM falls off the network up to a full lease time later looking nothing like a firewall rule.

Static is decided before any lease file is consulted, and that order is the correctness of the function rather than a detail. A host pinned by dejima-devbox-static-ip keeps the leases from whatever it did before, so letting those decide would tell every pinned host it needs rules it does not need. A setup matching neither is reported as unknown rather than assumed to be the friendlier answer.

No state is kept between runs. The spec's own suggestion was to record the expiry each run and watch whether it advances, which would have made this program start writing files for the first time. It does not need to: dhclient rewrites the lease file on every successful renewal, so the file's mtime against the renewal time it records answers the question in one pass. Past T1 with an mtime older than T1 means dhclient asked and got nothing back.

One test failed on a variable shadow rather than on logic: cmd_radius declares , which shadowed the fixture path the stub closed over, so the stub returned an empty string. The fixtures now use a name production code has no reason to pick, with a comment, because the symptom pointed at the parser and the cause was three functions away.
2026-08-10 00:17:47 -07:00

4.9 KiB

Reference

Detail that would clutter the README.

The three verdicts

dejima-devbox-audit reach probes the default gateway on 443, 80 and 22, and reports one of three things.

Verdict Means Exit
PASS Every probe timed out, and a positive control succeeded 0
FAIL Something answered: a handshake, or a refusal 1
INDETERMINATE Every probe timed out and the positive controls also failed 1

Why three and not two. A timeout means "no answer came back", which is the firewall working or the network being broken. Reading it as a pass is a false negative, and false negatives are the wrong direction here: the operator stops looking. So silence alone is never enough.

Why a refusal is a failure. A closed port still proves the packet reached a live host and something sent an answer back. Silently dropped is the only state that counts as contained.

Why not 53. The resolver exception may legitimately permit it, so a rule working exactly as designed would read as a failure.

Why the gateway. Its address is already in the routing table, so naming it writes down nothing an attacker on this box could not read anyway. It is guaranteed to be alive, which an arbitrary address is not. And it sits inside the ranges the firewall drops, so with the rules applied it must be unreachable as a destination while traffic through it is untouched.

The strongest case is free when your resolver is your gateway, which is common on home networks: one host answers on 53 and times out on 443, and no routing failure can produce that.

A known limit. bash's /dev/tcp cannot distinguish ECONNREFUSED from EHOSTUNREACH, so a rule that REJECTs rather than DROPs looks like a live host refusing and produces FAIL. That is the safe direction to be wrong in, and it is why the template specifies DROP.

The positive controls

Either one is sufficient:

  • the forge's name resolves, which exercises the resolver exception;
  • the forge answers on 443, which exercises layer 4 and the forge exception.

Requiring both would turn an unrelated outage into a report that gets ignored.

The three addressing modes

Mode Boundary cost Maintenance
Static in the guest None. Four DROPs plus the named exceptions, and no DHCP rules The address is asserted in two places
Reserved at the router Needs both halves of the DHCP pair One source of truth
Plain DHCP Same DHCP pair None, but the address moves

Static is the recommended default here, on the strength of being the only mode that needs no DHCP rules at all.

Plain DHCP is more workable here than in the sister project, because the inbound rules are scoped to client subnets rather than to this VM's own address, so a changed lease breaks nothing in the rule set.

What the audit reports about addressing

dejima-devbox-audit radius names the mode and, on DHCP, checks the lease.

Static is decided before any lease file is consulted, deliberately: a host pinned by dejima-devbox-static-ip keeps the leases from whatever it was doing before, and letting those decide would tell every pinned host it needs rules it does not need. A setup matching neither is reported as unknown rather than assumed to be the friendlier answer.

The lease check exists for the worst failure in this design. A DHCP guest behind these rules renews through a path the firewall may be dropping. Nothing breaks at boot, because the first lease was obtained before the rules mattered. The VM stops renewing and falls off the network up to a full lease time later, looking nothing like a firewall rule.

It is detected without keeping any state between runs. The obvious design records the expiry each run and watches whether it advances, which means this program starts writing files. It does not need to: dhclient rewrites the lease file on every successful renewal, so the file's own mtime against the renewal time it records answers the question in one pass. Past T1 with an mtime older than T1 means dhclient asked and got nothing back, which is exactly the shape of a dropped reply, and it is visible hours before the lease expires.

Configuration

/etc/dejima-devbox/config, one key:

forge_host=forge.example.invalid

That value must match the destination of the forge rules on the hypervisor. They are entered in two places and are silent when they disagree: the audit reports INDETERMINATE, which is correct and unhelpful.

Prefer a name over an address. Both work, but getent hosts on an address is a reverse lookup, and a forge without a PTR record fails it, so the resolution control quietly stops contributing.

There is no list of what this VM must not reach, and there should never be one. Such a list is a map of valuable targets kept on the machine this project assumes will be owned, describing topology not otherwise obtainable from here without loud scanning.