Harden FreeBSD guest /etc and activation diagnostics

This commit is contained in:
2026-04-02 22:45:34 +02:00
parent a04e650326
commit 901d0a8448
7 changed files with 392 additions and 7 deletions

View File

@@ -0,0 +1,210 @@
# Phase 12.2: tightened guest runtime diagnostics and reduced early-boot rough edges
Date: 2026-04-02
## Goal
The current Fruix FreeBSD guest already booted, reached the network, and ran Shepherd. The next hardening step was to make the guest easier to diagnose and to remove a few distracting runtime rough edges that were still coming from the prototype-style `/etc` handling.
The main targets were:
- reduce early-boot/login-class noise around `/etc/login.conf`
- ensure the FreeBSD password/login databases are created in writable guest state, not implicitly in store-backed paths
- add a clear activation log inside the guest
- teach the validation harnesses to assert the new behavior directly
## Root cause addressed
The old rootfs layout symlinked several generated `/etc` files directly to `/run/current-system/etc/...`, including:
- `/etc/login.conf`
- `/etc/master.passwd`
- `/etc/passwd`
- `/etc/group`
That worked for static lookup, but it was a poor fit for FreeBSD tools such as:
- `cap_mkdb`
- `pwd_mkdb`
- early login-class lookups
because those tools expect to work with regular files and adjacent generated database files such as:
- `/etc/login.conf.db`
- `/etc/pwd.db`
- `/etc/spwd.db`
With symlink-backed inputs, the system could still limp along, but it produced avoidable warnings and less clear runtime behavior.
## Implementation
### 1. Selected `/etc` files now become regular files in the guest rootfs
`modules/fruix/system/freebsd.scm` now materializes these files as regular files in the rootfs instead of symlinks:
- `/etc/passwd`
- `/etc/master.passwd`
- `/etc/group`
- `/etc/login.conf`
Other generated configuration files that do not need this treatment remain symlinked to `/run/current-system/...`.
This gives FreeBSD's database tools writable, regular inputs in the guest filesystem while keeping the current system closure as the source of truth.
### 2. Activation now refreshes those files from `/run/current-system/etc`
The generated activation script now refreshes the regular guest copies from the currently selected system closure before rebuilding databases.
That means rebuild/redeploy still works coherently even though these specific files are no longer left as symlinks in the booted guest.
### 3. Activation now records a guest-visible log
Activation now writes:
- `/var/log/fruix-activate.log`
with explicit markers such as:
- `fruix-activate:start`
- `fruix-activate:cap_mkdb=ok`
- `fruix-activate:pwd_mkdb=ok`
- `fruix-activate:done`
- exit status marker from the shell trap
This gives a direct guest-side indicator of whether activation actually completed.
### 4. Closure permissions were tightened slightly
The generated closure now explicitly sets:
- `etc/master.passwd` => `0600`
before the rootfs/image path copies it into the guest.
### 5. Validation harnesses were upgraded
#### Local image validation
`tests/system/run-phase8-system-image.sh` now asserts that the mounted image contains:
- `/etc/login.conf` as a regular file
- `/etc/master.passwd` as a regular file
#### VM validation
These harnesses now check for the new runtime behavior:
- `tests/system/run-phase9-xcpng-boot.sh`
- `tests/system/run-phase11-shepherd-pid1-xcpng.sh`
- `tests/system/run-phase11-shepherd-pid1-qemu.sh`
New checks include:
- `login_conf_kind=regular`
- `login_conf_db=present`
- `pwd_dbs=present`
- activation log contains `fruix-activate:done`
They also capture the activation log into metadata.
### 6. Small follow-up fix
The first activation-log implementation briefly used `touch`, which is not staged in the minimal guest userland. This was corrected by switching to shell redirection:
- `: >> "$logfile"`
so the activation path no longer depends on an extra utility for log-file creation.
## Validation
### Local image structure
Passing run:
- `PASS phase8-system-image`
- workdir: `/tmp/phase12-2-image-1775159011`
Key metadata:
```text
login_conf_kind=regular
master_passwd_kind=regular
```
### Local QEMU Shepherd PID 1
Passing run:
- `PASS phase11-shepherd-pid1-qemu`
- workdir: `/tmp/phase12-2b-qemu-1775161367`
Key metadata:
```text
activate_log=fruix-activate:start fruix-activate:cap_mkdb=ok fruix-activate:pwd_mkdb=ok fruix-activate:done fruix-activate:exit status=0
login_conf_kind=regular
login_conf_db=present
pwd_dbs=present
shepherd_pid=1
sshd_status=running
```
### Real VM, `freebsd-init+rc.d-shepherd`
Passing run:
- `PASS phase9-xcpng-boot`
- workdir: `/tmp/phase12-2b-phase9-1775161731`
Key metadata:
```text
activate_log=fruix-activate:start fruix-activate:cap_mkdb=ok fruix-activate:pwd_mkdb=ok fruix-activate:done fruix-activate:exit status=0
login_conf_kind=regular
login_conf_db=present
pwd_dbs=present
compat_prefix_shims=absent
guile_module_smoke=ok
shepherd_status=running
sshd_status=running
```
### Real VM, `shepherd-pid1`
Passing run:
- `PASS phase11-shepherd-pid1-xcpng`
- workdir: `/tmp/phase12-2b-phase11-1775162210`
Key metadata:
```text
activate_log=fruix-activate:start fruix-activate:cap_mkdb=ok fruix-activate:pwd_mkdb=ok fruix-activate:done fruix-activate:exit status=0
login_conf_kind=regular
login_conf_db=present
pwd_dbs=present
shepherd_pid=1
compat_prefix_shims=absent
guile_module_smoke=ok
sshd_status=running
```
## Assessment
This was a small but high-value hardening step.
The guest now behaves more like a real FreeBSD system in one of the places where a store-backed prototype can otherwise feel awkward: password/login database management and early `/etc` expectations.
The important result is not cosmetic; it is operational:
- activation success is now visible inside the guest
- the login/password database inputs live as regular guest files where FreeBSD expects them
- both validated boot modes still work locally and on the real XCP-ng VM
## Next recommended step
Proceed to Phase 12.3:
- make the host-staged FreeBSD base boundary explicit in the code/documentation model
- group the transitional host-copy base packages more clearly
- document the first intended replacement order for native world/kernel artifacts in `/frx/store`