// STATUS — OPERATIONAL · rev 2026-06-12 · LIVING BUILD
// IN PLAIN TERMS — A hardened workstation isn't paranoia, it's practice: if you're going to
advise organisations on security controls, you should be able to run them on your own machine
first. The disk is encrypted so a stolen laptop is just scrap metal; the key to open it is a
physical token you have to touch; and every choice here has a written reason — and a written
list of what it still doesn't protect against.
// Overview — Hardened Fedora workstation built to a defence-in-depth security model.
LUKS2 full-disk encryption requires physical hardware key presence for unlock. If the key is
unavailable, the machine can still be unlocked remotely over SSH from a GrapheneOS mobile
platform — without the initramfs ever trusting a password over the network. WireGuard routes
all external traffic; AdGuard Home filters all DNS to an encrypted upstream before it leaves
the host.
// Living Build — An actively maintained workstation build. The core hardware values were
captured and verified on 2026-06-11; SELinux/seccomp status has since been measured and
resolved (2026-06-12). Remaining honest gaps (unlock-latency benchmark; UEFI Secure Boot) are
labelled as such below — an honest gap beats invented completeness.
Threat Model
The build addresses a specific threat model for a security practitioner whose workstation
stores research, tooling, and keys worth protecting against physical access, supply-chain
key compromise, and remote theft. Each component was chosen because it eliminates a
specific attack path — not because it adds capability.
| Threat | Control |
|---|---|
| Offline disk clone and brute-force | LUKS2 aes-xts-plain64/512-bit; Argon2id passphrase slot; hardware key required to unlock |
| Primary hardware key lost or seized | Backup Nitrokey keyslot; passphrase emergency fallback |
| Hardware key cloned without touch | Touch-only FIDO2 enrollment; Nitrokey 3A NFC requires physical presence |
| Remote workstation inaccessible for unlock | dracut-sshd: SSH into the initramfs over the pre-boot network |
| DNS traffic leakage and exfiltration | AdGuard Home → Quad9 DoH over the tunnel; no plaintext query leaves the host |
| Traffic interception and geolocation | WireGuard full-tunnel routing on all external traffic |
Components
LUKS2 Full-Disk Encryption
Encrypted volume: LUKS2 container UUID 6cbc50ba-6f8a-4932-abfc-f2d0504a29b3, mapped as
luks-6cbc50ba-…, cipher aes-xts-plain64, 512-bit key. Three keyslots: Slot 0 (passphrase,
Argon2id — emergency/recovery fallback, stored offline), Slot 1 (primary Nitrokey 3A NFC,
PBKDF2/SHA-512 — daily driver, touch required), Slot 2 (backup Nitrokey 3A NFC — kept offline).
The FIDO2 slots derive their key with the Nitrokey credential; the passphrase is never sent to the
key. crypttab references the volume by UUID with discard,x-initrd.attach,fido2-device=auto.
Why this slot design: the three slots split availability from security. Losing the
primary Nitrokey is an inconvenience (activate the offline backup), not a lockout — and no
single artefact unlocks the disk without either physical key presence or the offline
passphrase. Argon2id on the passphrase slot is deliberate: it is memory-hard, so an attacker
who clones the disk cannot parallelise guessing on GPUs/ASICs the way they could against a
fast KDF.
Nitrokey 3A NFC — FIDO2 Hardware Key
Hardware: Nitrokey 3A NFC · Firmware: 1.8.3 · Two tokens enrolled · Enrollment mode:
touch-only (fido2-up-required=true) with no clientPin (fido2-clientPin-required=false) —
physical presence is required for every unlock event, but no PIN is involved. clientPin is not
supported on this firmware; the touch-only constraint is intentional: a key activatable remotely
would defeat the physical-presence guarantee.
dracut-sshd — Remote Unlock via SSH
The initramfs is built with dracut-sshd (v0.7.1-5.fc44), plus the systemd-networkd and
fido2 modules, so wired networking comes up pre-boot (rd.neednet=1) and a Nitrokey touch can
satisfy the LUKS prompt directly. Unlock flow: machine reaches the initramfs SSH listener → the
GrapheneOS handset (Termux) connects over Tailscale (Tailscale runs on the client, not in the
initramfs — reachability comes from the LAN the pre-boot interface sits on being exposed over the
tailnet) → the embedded ed25519 public key authenticates (host key pinned on the client) →
systemd-tty-ask-password-agent drives the unlock, a Nitrokey present at the machine is touched,
and boot continues. Built on Fedora 44, kernel 7.0.11-200.fc44.x86_64.
Why this design: the unlock secret is only ever carried inside an encrypted SSH session
from a trusted, hardware-attested device — the initramfs never trusts a password over the
network. Completing the unlock requires the physical GrapheneOS handset and its Termux key
file (two separate things-you-have); an attacker with network access but not the handset
cannot complete the unlock, and the pinned host key defeats MITM substitution of the pre-boot
listener.
WireGuard VPN — wg-a and wg-b
Two NetworkManager tunnels: wg-a (endpoint 192.0.2.1:51820) and
wg-b (192.0.2.2:51820), each with full-tunnel AllowedIPs (0.0.0.0/0, ::/0),
interface address 192.0.2.3/32, and a 25-second keepalive. Activation is manual
(autoconnect=false). The named-tunnel model encodes the endpoint region for multi-tunnel
readability. Kill-switch — honest description: the protection is route-based, not a separate
fail-closed rule — while a tunnel is up, the full-tunnel AllowedIPs makes it the default route, so
traffic is tunnel-bound; there is no nftables/dispatcher rule that drops traffic if the interface
goes down (a hard fail-closed kill-switch is planned).
AdGuard Home DNS Filtering
AdGuard Home (install path /opt/AdGuardHome/) is the system resolver, listening on *:53
(pid 1452). Its only upstream is Quad9 over DNS-over-HTTPS (https://dns10.quad9.net/dns-query,
bootstrap 9.9.9.10 / 149.112.112.10), so every query leaves already encrypted and egresses
through the active WireGuard tunnel. Block list: AdGuard DNS filter. systemd-resolved forwards
all queries to 127.0.0.1 (stub listener off) so AdGuard owns port 53. DNS chain: application →
systemd-resolved (127.0.0.1) → AdGuard Home (:53) → Quad9 DoH → active WireGuard tunnel. The
no-plaintext-egress property rests on the DoH-over-tunnel upstream, not on a loopback bind. (The VPN
provider also pushes 10.2.0.1, but AdGuard overrides it — it is not the effective upstream.)
OpenRGB — Peripheral Configuration
Razer Huntsman V2 (keyboard), Razer Basilisk (mouse), 6× Corsair fans via Commander Pro.
OpenRGB provides vendor-agnostic RGB control without vendor cloud daemons (Razer Synapse,
iCUE). No vendor software installed. Commander Pro detected as USB HID device.
Security Architecture — Defence in Depth
| Layer | Control | Threat addressed |
|---|---|---|
| Physical | Nitrokey 3A NFC FIDO2, touch-only enrollment (fw 1.8.3, no clientPin) | Remote/unattended hardware-key activation |
| Disk | LUKS2 aes-xts-plain64/512-bit (UUID 6cbc50ba-…), Argon2id passphrase slot |
Offline disk clone and brute-force key derivation |
| Boot | dracut-sshd (v0.7.1-5.fc44) pre-boot SSH with pinned ed25519 host key | Unattended unlock; MITM key substitution during unlock |
| Key Custody | Primary Nitrokey, backup Nitrokey offline, offline emergency passphrase | Single point of key failure; primary key loss or seizure |
| Network | WireGuard wg-a / wg-b (NetworkManager, manual), full-tunnel routing |
Traffic interception, geolocation, leakage if tunnel drops |
| DNS | AdGuard Home on *:53 → Quad9 DoH over the active tunnel; systemd-resolved forwards to it |
Plaintext DNS leakage; tracker and malicious domain resolution |
Host Hardening — MAC, seccomp, boot integrity
Above the disk and network layers, the host runs SELinux in enforcing/targeted mode
(deliberately not disabled), with seccomp BPF filtering compiled into the kernel
(CONFIG_SECCOMP_FILTER=y) for systemd service sandboxing. Two controls are documented as
honest gaps rather than hidden: Yama ptrace_scope is at the Fedora default 0
(tightening to 1 is planned), and UEFI Secure Boot is currently disabled / in Setup Mode
— the build's unlock-integrity guarantee rests on LUKS2 + touch-only FIDO2 + a pinned
initramfs host key, not on Secure Boot. The current build also does not bind the LUKS unlock
to TPM PCR measurements; Secure Boot enrolment + TPM2 PCR sealing are tracked as FUTURE WORK
to close the evil-maid gap.
Hardening Checklist — Component Status
| Status | Control | Protects against |
|---|---|---|
| Verified | LUKS2 aes-xts-plain64/512-bit, Argon2id + 2× FIDO2 — 3 keyslots active |
Offline disk clone and brute-force key derivation; memory-hard KDF raises GPU/ASIC guessing cost; keyslot custody removes single point of key failure |
| Verified | Nitrokey 3A NFC FIDO2 — 2 tokens, firmware 1.8.3, touch-only, no clientPin | Hardware-key activation without physical presence; touch required for every unlock event |
| Verified | dracut-sshd remote unlock — v0.7.1-5.fc44; systemd-networkd + fido2 modules | Headless machine impossible to unlock remotely; MITM key substitution (host key pinned); passphrase never sent over the network |
| Verified | WireGuard wg-a + wg-b — NetworkManager, manual, full-tunnel |
Traffic interception and geolocation; route-based kill-switch while the tunnel is up |
| Verified | AdGuard Home DNS filtering — Quad9 DoH upstream, *:53, AdGuard DNS filter |
Plaintext DNS leakage; tracker / malicious-domain resolution |
| Verified | systemd-resolved → 127.0.0.1 (stub listener off) | Applications bypassing the DNS filter |
| Verified | Build platform — Fedora 44, kernel 7.0.11-200.fc44.x86_64 | Reproducibility / auditability of the build |
| Verified | OpenRGB (Razer + Corsair) — vendor daemons absent (udev 60-openrgb.rules) |
Vendor cloud-daemon telemetry; unnecessary supply-chain surface |
| Verified | SELinux — Enforcing, targeted policy (measured, not assumed) |
Post-exploitation lateral movement; mandatory access control above DAC |
| Verified | seccomp — CONFIG_SECCOMP_FILTER=y (systemd service sandboxing) |
Kernel attack surface from compromised services |
| GAP | UEFI Secure Boot — disabled / Setup Mode (honest gap, FUTURE WORK) | Boot-chain integrity; planned with TPM2 PCR sealing to close the evil-maid gap |
| PENDING | LUKS2 unlock-latency benchmark — hardware key vs passphrase | A usability data point to be measured, not estimated |
PENDING / GAP — labelled honestly rather than hidden (see the repo's
MANUAL_INPUTS.md
andhardening/os-hardening.md). An honest gap beats invented completeness.
Skills Demonstrated
| Skill | Evidence |
|---|---|
| Linux Hardening | LUKS2 (aes-xts-plain64/512-bit) with keyslot management and Argon2id passphrase slot |
| FIDO2 / Hardware Key Integration | Nitrokey 3A NFC enrolled as LUKS2 keyslots (touch-only, no clientPin); CTAP2 credential flow |
| Initramfs Engineering | dracut-sshd (v0.7.1-5.fc44) pre-boot SSH with systemd-networkd + fido2; pinned ed25519 key |
| Network Security | WireGuard full-tunnel routing via NetworkManager; named-tunnel model for multi-tunnel readability |
| DNS Security | AdGuard Home → Quad9 DoH over the tunnel; encrypted upstream, no plaintext egress |
| Defence in Depth | Each threat mapped to a compensating control across physical, disk, boot, network, and DNS layers |
| Operational Security | Vendor cloud daemons eliminated; RGB peripherals managed via OpenRGB without telemetry |
| Remote Operations | SSH-based unlock from GrapheneOS; IRONVEIL ↔ NULLBYTE integration for remote boot without physical presence |
Repository & Context
// GitHub — Full build documentation, hardening notes, and research references:
github.com/rootdrifter/ironveil — one repository
in the github.com/rootdrifter portfolio.
// Why this matters — IRONVEIL is the operational evidence behind a security-cleared
candidate: a workstation built with the key-custody, least-trust, and defence-in-depth
discipline expected in cleared environments. This reflects current UK government clearance obtained through employment in a
high-security environment.