Add the hypervisor runbook, without a rollback cadence
Section 4 no longer automates anything. A CI runner is rebuilt on a timer because nothing on it is worth keeping; a devbox holds uncommitted work, so a rollback on a cadence destroys the thing the machine exists to hold. What replaces it is pushing more often, knowing the rebuild cost by having paid it once, and keeping credentials off the box so a rebuild is the whole of the recovery. The rules go from eight to ten. The forge gains tcp/22 because a devbox pushes over SSH, and inbound becomes two client-subnet lists rather than one management host, with the front door on the narrower of the two. That last one is not the intuitive way round, so it is argued rather than asserted: both services put a terminal in front of whoever reaches them, and they differ only in what stands in front, where a public key beats basic auth with no rate limiting. The from-inside verification probes the default gateway rather than another host on the LAN. Naming another host would write down something worth attacking on the machine this page assumes will be owned; the gateway is already in the routing table, is guaranteed alive, and must be unreachable as a destination while traffic through it is untouched. It also carries the note that silence proves nothing when nothing else is getting through, which is why the audit reports three outcomes.
This commit is contained in:
@@ -0,0 +1,457 @@
|
||||
# Provisioning a devbox on Proxmox
|
||||
|
||||
The node-side half: the VM itself and the firewall applied to it. Everything
|
||||
here is done in the Proxmox web console.
|
||||
|
||||
What happens *inside* the VM is
|
||||
[docs/building-a-devbox.md](../docs/building-a-devbox.md), which is the same on
|
||||
any hypervisor. Do this page first, then go there.
|
||||
|
||||
That split is deliberate, and so is the console-only rule. A script that runs
|
||||
`qm create` and writes into `/etc/pve/firewall/` is exactly the kind of
|
||||
artifact people paste into a root shell without reading, and the blast radius
|
||||
is the whole node, including every VM on it that has nothing to do with this
|
||||
one. The steps below are short enough to follow and specific enough to check.
|
||||
|
||||
Nothing on this page asks you to edit a file on the node. Where a console path
|
||||
and a file path both exist, this runbook takes the console path even when the
|
||||
file would be tidier, because a hand-edited file cannot be checked as you
|
||||
follow it and puts the hypervisor's configuration outside the tool's reach.
|
||||
|
||||
The in-VM half **is** a script:
|
||||
[`bin/dejima-devbox-bootstrap`](../bin/dejima-devbox-bootstrap).
|
||||
|
||||
---
|
||||
|
||||
## What you are building
|
||||
|
||||
A machine you cannot afford to lose, treated as one you might.
|
||||
|
||||
That sentence is the whole difference from a CI runner. A runner is built to be
|
||||
lost: it is rebuilt from a snapshot on a schedule, so anything that gets into it
|
||||
has until the next rollback and no longer. A devbox holds uncommitted work, so a
|
||||
rollback on a timer destroys the thing the machine exists to hold. Nothing
|
||||
reclaims this VM on a cadence.
|
||||
|
||||
What follows from that: containment here is the firewall on this page, plus
|
||||
keeping the box worth little, plus being able to rebuild it. There is no fourth
|
||||
thing, and no scheduled undo.
|
||||
|
||||
**The firewall goes on the node, not in the guest.** Root in the VM can flush
|
||||
any rule set it can see. It cannot reach one applied to its own tap device.
|
||||
|
||||
**dejima's own controls are not a second copy of this.** They bound what a
|
||||
session inside the container reaches, and they do that well: the container's
|
||||
egress is deny-by-default and it is given the Docker host's address rather than
|
||||
the host's subnet, so a session cannot walk your LAN. What they cannot bound is
|
||||
what the *VM* reaches, because they run inside it. That gap is what this page
|
||||
closes, and it is why this is worth doing even though dejima already firewalls
|
||||
itself.
|
||||
|
||||
**What this does not prevent.** Root on this VM. Nothing here tries to. What it
|
||||
does is make root on this VM worth less: no LAN to move onto, and a machine
|
||||
whose rebuild cost you have already paid once. See
|
||||
[docs/threat-model.md](../docs/threat-model.md#blast-radius-by-layer) for what
|
||||
each layer actually grants.
|
||||
|
||||
---
|
||||
|
||||
## 1. Check the datacenter firewall is on
|
||||
|
||||
**Do this first.** A per-VM firewall with the datacenter switch off is read
|
||||
and enforced nowhere, and nothing about that is visible from the VM — it looks
|
||||
exactly like a working setup.
|
||||
|
||||
**Datacenter → Firewall → Options → Firewall: `Yes`**
|
||||
|
||||
If you have to change it, check that nothing else on the node depended on
|
||||
having no firewall. The datacenter default policy is `ACCEPT` in and out, so
|
||||
turning it on does not by itself filter anything — but it is worth knowing
|
||||
before you flip it rather than after.
|
||||
|
||||
---
|
||||
|
||||
## 2. Create the VM
|
||||
|
||||
**Create VM**, and the values that matter:
|
||||
|
||||
| Tab | Setting | Value |
|
||||
| --- | --- | --- |
|
||||
| General | Name | `devbox`, or whatever you prefer |
|
||||
| OS | ISO | Debian netinst, current stable |
|
||||
| System | Qemu Agent | ✓ — and see the note below, the tick alone does nothing |
|
||||
| Disks | Size | 128 GB. A devbox accumulates checkouts, container images and build caches, and it is not rebuilt on a schedule to clear them |
|
||||
| CPU | Cores | 4 |
|
||||
| Memory | | 16384 MB. dejima builds its image and runs sessions; 8 GB works and is tight |
|
||||
| Network | Bridge | your normal bridge |
|
||||
| Network | **Firewall** | **✓ — this is the checkbox everything else depends on** |
|
||||
|
||||
The Firewall checkbox is easy to miss and easy to uncheck later without
|
||||
noticing. If it is off, the per-VM rules do not apply to the interface and the
|
||||
VM is simply on your LAN.
|
||||
|
||||
**Qemu Agent is two halves, and this tick is one of them.** It opens a
|
||||
virtio-serial channel on the host side; answering on it takes the
|
||||
`qemu-guest-agent` package inside the guest, which `dejima-devbox-bootstrap`
|
||||
installs and which tells you if this tick is missing. Leave it on: without the
|
||||
channel the agent has nothing to attach to, the Summary pane shows no address,
|
||||
`qm shutdown` falls back to hoping ACPI is acted on, and a snapshot of a
|
||||
running VM stays crash-consistent.
|
||||
|
||||
**What a moving address does *not* break.** Not the firewall. Every rule in the
|
||||
next section keys on a *destination*, except the two inbound rules, which key
|
||||
on *your* client subnets — and Proxmox applies the whole set to this VM's tap
|
||||
device, identified by VMID rather than by IP. Nothing anywhere names this VM by
|
||||
its own address, so a rebuilt VM that comes up on a different one is still
|
||||
fully firewalled.
|
||||
|
||||
That is a real difference from the sister project, where the inbound rule names
|
||||
a single management host. Scoping inbound to subnets rather than to one address
|
||||
means a changed lease breaks nothing in the rule set.
|
||||
|
||||
What it breaks is you finding the machine: `ssh <you>@<vm-ip>`, and every
|
||||
later visit. That is reason enough to pin it.
|
||||
|
||||
---
|
||||
|
||||
## 3. The firewall rules
|
||||
|
||||
Ten rules, built once as a **security group** and applied to each devbox. Even
|
||||
with one VM, build the group: hand-entered copies are two chances to get the
|
||||
ordering wrong and one of them to drift later.
|
||||
|
||||
This is the primary control. Everything else in this repository exists to
|
||||
verify it or to explain it.
|
||||
|
||||
### What each part is for
|
||||
|
||||
The outbound half is short and does the work. Four DROPs cover every private
|
||||
range, and above them sit exceptions for the two hosts this VM genuinely needs:
|
||||
your forge and your resolver. That is the bound this whole project states, and
|
||||
it does not change however long the inbound list gets.
|
||||
|
||||
The inbound half is the part that grows, because unlike a CI runner a devbox
|
||||
offers services you want to reach. Keep it legible rather than short: every
|
||||
subnet named here, every port stated, and no rule added anywhere else.
|
||||
|
||||
### Build the group once
|
||||
|
||||
**Datacenter → Firewall → Security Group → Create**, named
|
||||
`dejima-devbox-hardener`. Then **Add** these to it, in this order:
|
||||
|
||||
| # | Direction | Action | Protocol | Dest. port | Source / Dest | Why |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| 1 | in | ACCEPT | tcp | 22 | **Source:** admin subnet | SSH, from wherever you administer |
|
||||
| 2 | in | ACCEPT | tcp | front door | **Source:** front-door subnet | Remote access, narrower on purpose |
|
||||
| 3 | out | ACCEPT | tcp | 443 | **Dest:** forge IP | Clone, fetch and push over HTTPS |
|
||||
| 4 | out | ACCEPT | tcp | 22 | **Dest:** forge IP | Push over SSH |
|
||||
| 5 | out | ACCEPT | udp | 53 | **Dest:** nameserver IP | The forge's name is internal |
|
||||
| 6 | out | ACCEPT | tcp | 53 | **Dest:** nameserver IP | Same, over TCP |
|
||||
| 7 | out | DROP | — | — | **Dest:** `10.0.0.0/8` | |
|
||||
| 8 | out | DROP | — | — | **Dest:** `172.16.0.0/12` | |
|
||||
| 9 | out | DROP | — | — | **Dest:** `192.168.0.0/16` | The three that matter |
|
||||
| 10 | out | DROP | — | — | **Dest:** `169.254.0.0/16` | Link-local, incl. metadata services |
|
||||
|
||||
**Rules 1 and 2 are added once per client subnet.** Phones and laptops sit on
|
||||
different wireless networks often enough that assuming one is the wrong
|
||||
default. Add the rule again for each.
|
||||
|
||||
Do not widen the mask instead. A range chosen to span two subnets is not two
|
||||
subnets, it is every subnet between them, and it will keep being right for
|
||||
years before it is suddenly very wrong.
|
||||
|
||||
**Rule 4 is the one a CI runner never needed.** A devbox pushes over SSH. Miss
|
||||
it and `git push` hangs against rule 7 rather than failing, which reads as the
|
||||
forge being down.
|
||||
|
||||
#### Why the front door gets the narrower list
|
||||
|
||||
The intuition that SSH is the more dangerous of the two is backwards.
|
||||
|
||||
Both put a terminal in front of whoever reaches them, so they do not differ in
|
||||
capability. They differ in what stands in front. SSH with public-key auth has
|
||||
nothing to guess and nothing to stuff, and is about as attack-resistant as an
|
||||
exposed service gets. A web front door behind HTTP basic auth is a password,
|
||||
and nginx's basic auth brings no rate limiting, no lockout and no second factor
|
||||
unless they are added deliberately.
|
||||
|
||||
Same capability behind a weaker gate, so the front door is the higher-risk
|
||||
service and takes the smaller source range. Usage tends to land on that split
|
||||
anyway: you administer over SSH from whatever device is to hand, and you want
|
||||
the front door for a phone, because that is what it is for.
|
||||
|
||||
Two cheap improvements to the front door, since it is the weaker side by
|
||||
construction:
|
||||
|
||||
- `limit_req` on that nginx location, which supplies the rate limiting basic
|
||||
auth does not have;
|
||||
- a client certificate, which phones install as a profile and which brings that
|
||||
gate to roughly the strength of an SSH key.
|
||||
|
||||
#### What this list does not bound
|
||||
|
||||
Adding a client subnet widens **who can reach this VM**. It does not widen what
|
||||
this VM can reach, which is the bound rules 7 to 10 fix and the one this
|
||||
project exists to state. The two are different axes and conflating them leads
|
||||
to refusing a harmless convenience.
|
||||
|
||||
What the inbound list does affect is the attack surface of the front door
|
||||
itself. So the number worth knowing is not how many subnets are listed but how
|
||||
many addresses they cover, which is what `dejima-devbox-audit` reports.
|
||||
|
||||
#### Two more, if the VM gets its address by DHCP
|
||||
|
||||
Skip both if you pinned the address in the guest with `dejima-devbox-static-ip`.
|
||||
Add both if you did not, including when the address is reserved for you on the
|
||||
router: reserved or not, the guest still speaks DHCP.
|
||||
|
||||
| # | Direction | Action | Protocol | Dest. port | Source / Dest | Why |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| 2a | in | ACCEPT | udp | 68 | — | Let the lease reply back in |
|
||||
| 6a | out | ACCEPT | udp | 67 | — | Let the lease request out |
|
||||
|
||||
Place `2a` with the other inbound rules and `6a` above the DROPs, as the
|
||||
numbering suggests. Both name a port and no host, which makes them narrower
|
||||
than an exception naming your DHCP server: they add no reachable host to the
|
||||
list above.
|
||||
|
||||
**Both or neither.** Rule `6a` alone gets the request out and the VM still
|
||||
never renews, because the reply arrives from the server's address to the VM's,
|
||||
which is not the tuple the request created. Connection tracking is unlikely to
|
||||
match that against `Input Policy: DROP`, so `2a` is what actually lets the
|
||||
lease complete.
|
||||
|
||||
Do not count on rebinding to save you. At T2 the client stops unicasting and
|
||||
broadcasts to `255.255.255.255`, which none of the DROPs match, so the request
|
||||
does leave without rule `6a` — but the reply still needs `2a`. Half a working
|
||||
path renews nothing.
|
||||
|
||||
This failure is slow and does not look like a firewall. Nothing breaks at
|
||||
reboot or at first boot. The VM simply stops renewing, and somewhere up to a
|
||||
full lease time later it drops off the network.
|
||||
|
||||
Rule `2a` lets anything on the segment send to the client port, which is a
|
||||
spoofed-reply surface. It is close to free: DHCP on a LAN is trust-on-first-use
|
||||
for every host on that segment whatever the firewall does, so permitting the
|
||||
reply does not create that exposure. Not using DHCP is still narrower, and
|
||||
`dejima-devbox-static-ip` exists for it.
|
||||
|
||||
### Then apply it, per VM
|
||||
|
||||
A security group carries **rules and nothing else**. The policies are not part
|
||||
of it, so each VM still needs its own:
|
||||
|
||||
**VM → Firewall → Options**
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| Firewall | `Yes` |
|
||||
| Input Policy | `DROP` |
|
||||
| Output Policy | `ACCEPT` |
|
||||
|
||||
Inbound denies by default — this VM offers no service anyone should reach, so
|
||||
the exceptions are short and knowable. Outbound accepts by default, and the LAN
|
||||
is denied explicitly by the group.
|
||||
|
||||
That asymmetry is deliberate, and it is worth resisting the obvious-looking
|
||||
alternative: deny outbound too, then allow tcp/80, tcp/443 and udp/123. That
|
||||
breaks things — apt during a first install, and anything else needing a port
|
||||
nobody listed — and each breakage presents as a network fault rather than as a
|
||||
firewall rule. It also buys almost nothing: this VM is assumed to be owned by
|
||||
an attacker with root, and the guarantee being made is *reaches the internet,
|
||||
not your network*, not *reaches only three ports*. Anyone who owns the box
|
||||
tunnels over 443 like everybody else.
|
||||
|
||||
Deny-by-default earns its keep where the protected set is enumerable, which is
|
||||
the LAN. The internet is not, and treating it as though it were gets you a rule
|
||||
set that looks strict and only gets in the way.
|
||||
|
||||
Then **VM → Firewall → Add → Security Group**, and select
|
||||
`dejima-devbox-hardener`. That single entry is the whole rule set, in the order the
|
||||
group defines, and it is the only firewall rule the VM needs.
|
||||
|
||||
Adding another devbox later is that one step. The ordering below was got right
|
||||
once, in one place, and cannot drift between hosts.
|
||||
|
||||
**Order matters within the group, and it is the one thing here that is easy to
|
||||
get wrong.**
|
||||
Rules are evaluated top to bottom and the first match wins. Your forge and
|
||||
your resolver are *inside* the ranges rules 5–8 drop, so if either pinhole
|
||||
ends up below them it is dead and the VM cannot reach the forge or resolve
|
||||
anything. The console appends new rules at the bottom, so adding them in the
|
||||
order above is all it takes — but check the list afterwards, and drag rows if
|
||||
they are out of sequence.
|
||||
|
||||
Rules 2–4 are the entire remaining LAN exposure, and they are why "no LAN
|
||||
access" is not literally true. Both destinations are pinned to one host and
|
||||
one port.
|
||||
|
||||
Nothing follows rule 8. The public internet is reached by the default policy.
|
||||
|
||||
### Values that must agree
|
||||
|
||||
Each of these is entered in two places, and each is silent when the halves
|
||||
disagree. Read the real value off the machine rather than recalling it. That is
|
||||
the whole of the advice, and every one of them is easy to get wrong from
|
||||
memory.
|
||||
|
||||
| This | Must equal this | Read the truth from |
|
||||
| --- | --- | --- |
|
||||
| Rules 5–6's destination | The VM's **nameserver** | `grep nameserver /etc/resolv.conf` on the VM |
|
||||
| Rules 3–4's destination | The forge's address | `getent hosts <forge-hostname>` |
|
||||
| Rule 1's source | The subnets you administer from | your own devices |
|
||||
| Rule 2's source | The subnets a phone is on | your own devices |
|
||||
| The forge's hostname | `forge_host` in `/etc/dejima-devbox/config` | `dejima-devbox-bootstrap` asks and writes it |
|
||||
|
||||
**Rules 5–6 want the nameserver, not the gateway.** They are the same address
|
||||
on plenty of networks, which is exactly what makes this worth saying: using
|
||||
the gateway works for some people and, for everyone else, blocks DNS while
|
||||
leaving every other check passing. `ip route` gives you the gateway;
|
||||
`/etc/resolv.conf` gives you the nameserver, and it is the second one that
|
||||
belongs in these rules.
|
||||
|
||||
**The forge pair is the one the audit depends on.** `dejima-devbox-audit` uses
|
||||
the forge as its positive control: if the forge answers, packets are moving,
|
||||
and a timeout to the gateway means the firewall rather than a broken network.
|
||||
Point rules 3–4 at one address and `forge_host` at a name that resolves
|
||||
elsewhere and the audit reports INDETERMINATE, which is correct but unhelpful.
|
||||
|
||||
### Check it, rather than assuming it
|
||||
|
||||
On the node, with the VM **running** — Proxmox builds these chains around the
|
||||
VM's tap device, so a stopped VM has none and their absence would tell you
|
||||
nothing:
|
||||
|
||||
```sh
|
||||
# what is actually loaded right now
|
||||
sudo iptables -S | grep "tap<vmid>i0"
|
||||
|
||||
# what Proxmox would generate from the current config
|
||||
sudo pve-firewall compile | grep -B2 -A20 "tap<vmid>i0"
|
||||
```
|
||||
|
||||
**What you are looking for.** Proxmox names the per-VM chains after the tap
|
||||
interface — `tap<vmid>i0-IN` and `tap<vmid>i0-OUT` for the first NIC — and
|
||||
your rules appear inside them, in the order you added them. In the `-OUT`
|
||||
chain that means the four pinhole ACCEPTs first, then the four LAN DROPs,
|
||||
then whatever Proxmox appends for the policy.
|
||||
|
||||
**What each failure looks like.**
|
||||
|
||||
| What you see | What it means |
|
||||
| --- | --- |
|
||||
| No `tap<vmid>i0` chains at all | The firewall is not applied to this VM. The NIC's Firewall checkbox, the VM's Firewall option, or the datacenter switch — check all three, in that order |
|
||||
| Chains exist but hold none of your rules | The rules are on a different VM id, or were added at datacenter or node level rather than to this VM |
|
||||
| A LAN DROP above a pinhole ACCEPT | The ordering trap. The VM will not reach the forge or resolve anything; drag the pinholes above the DROPs |
|
||||
| Rules present and correct, DNS still failing | The address in rules 5–6 is not the VM's nameserver. The gateway is the usual wrong answer — see "Values that must agree" |
|
||||
|
||||
`pve-firewall status` is the quicker sanity check of whether the service is
|
||||
running and enabled at all, but it says nothing about *these* rules — it is
|
||||
the datacenter switch it reports on, not your ten lines.
|
||||
|
||||
**Both commands describe configuration.** They are worth reading, and they are
|
||||
not proof: a rule that is loaded and a rule that does what you meant are
|
||||
different claims. The checks from inside the VM below are the ones that test
|
||||
behaviour, which is why they follow rather than replace this.
|
||||
|
||||
and from inside the VM, once it is up. Three of these must succeed and one
|
||||
must fail, which is what makes the set diagnostic rather than reassuring:
|
||||
|
||||
```sh
|
||||
try() { timeout 5 bash -c "exec 3<>/dev/tcp/$1/$2" 2>/dev/null \
|
||||
&& echo " CONNECTED $1:$2" || echo " blocked $1:$2"; }
|
||||
|
||||
gw=$(ip -4 route show default | awk '{print $3; exit}')
|
||||
|
||||
try "$gw" 443 # must be blocked - your own first hop
|
||||
try <forge-ip> 443 # must connect
|
||||
try deb.debian.org 80 # must connect - the public internet
|
||||
getent hosts deb.debian.org && echo " resolved" || echo " DNS FAILED"
|
||||
```
|
||||
|
||||
Nothing here needs installing: `bash` opens a TCP connection on its own and
|
||||
`getent` is part of libc, which matters on a machine you have not finished
|
||||
setting up yet.
|
||||
|
||||
**The first probe targets your gateway, deliberately, and not some other host
|
||||
on your LAN.** Naming another host would mean writing down something worth
|
||||
attacking on the machine this page assumes will be owned. The gateway avoids
|
||||
that entirely: its address is already in the routing table, so it reveals
|
||||
nothing; it is guaranteed to be alive, which an arbitrary address is not; and
|
||||
it sits inside the ranges rules 7 to 10 drop, so with the rules applied it must
|
||||
be unreachable *as a destination* while traffic *through* it to the internet is
|
||||
untouched.
|
||||
|
||||
Read the four together, because the pattern says which thing is wrong:
|
||||
|
||||
- **First one connects** → the firewall is not in force at all. Check the
|
||||
Network device's Firewall checkbox and the datacenter switch.
|
||||
- **Second or third fails while the fourth works** → a pinhole is below the LAN
|
||||
DROPs. Check the rule order.
|
||||
- **Everything fails** → the VM has no working route or DNS, which is a
|
||||
networking problem rather than a firewall one. Note that this also makes the
|
||||
first probe's "blocked" meaningless: silence proves nothing when nothing is
|
||||
getting through. That ambiguity is exactly why `dejima-devbox-audit` reports
|
||||
three outcomes rather than two.
|
||||
|
||||
**Do this once by hand, then stop doing it by hand.**
|
||||
[`bin/dejima-devbox-audit`](../bin/dejima-devbox-audit) runs the same checks,
|
||||
tells the three outcomes apart properly, and a timer re-runs it. Rules get
|
||||
edited, and a rule widened by accident has no symptom on this VM at all.
|
||||
|
||||
---
|
||||
|
||||
## 4. Snapshots, and why there is no cadence
|
||||
|
||||
Take a snapshot before anything you would not want to undo by hand: a kernel
|
||||
upgrade, a change to the container runtime, the first run of new tooling.
|
||||
|
||||
**VM → Snapshots → Take Snapshot.** Leave *Include RAM* unchecked. You want a
|
||||
disk state you can return to, not a resumed process tree, and the RAM image
|
||||
costs the VM's whole memory in storage for nothing you will use.
|
||||
|
||||
### There is no scheduled rollback here, and that is the design
|
||||
|
||||
This is the single largest difference from a CI runner, and it is worth being
|
||||
explicit about because the sister project's runbook devotes a long section to
|
||||
automating exactly what this page refuses to do.
|
||||
|
||||
A runner is rebuilt on a timer because nothing on it is worth keeping. Its
|
||||
whole containment story is that anything which gets in has until the next
|
||||
rollback. A devbox holds uncommitted work, so a rollback on a timer destroys
|
||||
the thing the machine exists to hold. There is no cadence that is both safe for
|
||||
your work and useful against an intruder.
|
||||
|
||||
So containment here comes from §3 and from keeping the box worth little. Not
|
||||
from being able to throw it away.
|
||||
|
||||
### What replaces it
|
||||
|
||||
**Push more often than you would on a machine you trusted.** The forge is the
|
||||
only copy a rollback cannot eat, and it is also the only copy an intruder on
|
||||
this VM cannot quietly alter without it showing up as a push you did not make.
|
||||
|
||||
**Know your rebuild cost, by having paid it once.** On a box you cannot roll
|
||||
back, rebuild time is the real measure of containment: it is what "recover from
|
||||
a compromise" actually costs you. A rebuild you have never done is an estimate,
|
||||
and estimates of this are always low. Doing it once, deliberately, converts the
|
||||
number from a guess into a fact.
|
||||
|
||||
**Keep the credentials off it**, so the rebuild is the whole of the recovery
|
||||
and not the start of it. That part is in
|
||||
[docs/building-a-devbox.md](../docs/building-a-devbox.md), because it is guest
|
||||
work.
|
||||
|
||||
### Two hazards no rule covers
|
||||
|
||||
**Agent forwarding.** `ssh -A` into this VM lends it your keys for the life of
|
||||
the session, and this is a VM whose threat model says it may be owned. Put
|
||||
`ForwardAgent no` in your SSH config for this host, and reach anything else
|
||||
directly rather than through it. This is easy to leave on by accident, because
|
||||
it is often set globally and never thought about again.
|
||||
|
||||
**Exception creep.** A NAS today, a registry next month, a printer after that.
|
||||
Each is reasonable on its own and the set is nobody's decision. Every LAN
|
||||
exception belongs in §3's table and nowhere else, so the list can be read in
|
||||
one place and pruned in one place. A rule added directly to the VM rather than
|
||||
to the group is the one that gets forgotten.
|
||||
Reference in New Issue
Block a user