Commit Graph

10 Commits

Author SHA1 Message Date
cproudlock
da6e62c084 Merge branch 'fix-build' into pxe-webapp-redesign 2026-10-15 14:38:48 -04:00
cproudlock
0b1625ed87 Build/playbook re-run safety: corp access, netplan rollback, first-boot retry
H1  corp_admin_cidrs (UFW 22/9009 on the corp NIC from these only; refuses
    when it would lock out the current SSH client) and corp_ssh_password_auth
    (sshd drop-in, passwords only from the imaging subnet). Defaults keep the
    current behaviour and print a loud warning.
H2  pxe-netplan-apply.sh: pre-flight refusal (pxe-MAC NIC holds the default
    route, a DHCP lease or the route to an SSH client), run from the USB
    before anything changes; post-apply checks (names, imaging IPv4, corp
    IPv4 back within 30 s, SSH route) and a 120 s systemd-run auto-revert.
H3  restore() puts the previous NIC names back (two-phase rename via
    temporary names, so swaps work) before generate + apply.
H4  a failed rename is fatal (restore + exit 1); the play drops the old
    any-interface UFW rules only after verifying both NICs and their
    per-interface SSH rules.
H5  first-boot checks the playbook exit code, retries at next boot (max 5),
    writes /var/lib/pxe-firstboot.failed and /etc/issue + /etc/motd banners.
M1  build-usb.sh verifies iPXE files against the playbook pins, every wheel
    against its own package's hashes, drops the unpinned pip fallback,
    requires --site-vars or --no-site-vars.
M2  webapp restarts when the code, wheels or unit changed.
M3  ignore-carrier on the imaging NIC; ip_nonlocal_bind kept for the
    apache/networkd boot race.
M4  menu suffix must match blancco_kver; missing kernel with no earlier
    build is fatal; a live menu with the staged Blancco test entry is not
    overwritten.
M5  build-proxmox-iso.sh is unsupported (hard exit); use build-usb --image.
M6  deploy-unattend.sh: password via SSHPASS env and sudo stdin only,
    %q-quoted remote args, sha256 check of the live file; lint-unattend.py
    reads PXE_SERVER/PXE_USER/PXE_PASS and uses sshpass -e.
M7  the playbook lints the unattend files before the force: yes copies
    (lint-unattend.py ships on CIDATA as scripts/); pre-commit ACMR.
M8  Blancco initramfs build: no ignore_errors, pipefail, trap cleanup.
Also: Send-ImagingStage.ps1 deploy, carrier hook install tasks
(pxe-nic-carrier, networkd-dispatcher), L1 imaging-active hold
(allow_disruptive) and no iPXE downgrade below an Updates-page install,
L2 iso-volid, L3 kver pin read from the playbook, L4 cp -rL, L5 refuse
>= 4 GiB WinPE files, L6 PIP_DIR wipe guard, L7 per-package wheel hashes,
no online fallback, L8 check-updates.py (apt_pkg/cached compare, all
components, exit 2 on no debs / bad answers).
2026-10-15 14:32:23 -04:00
cproudlock
21ff95af78 NIC guard: ignore client EAPOL, re-check on carrier change, SMB-only lease cleanup
- pxe-nic-check: count 802.1X only from an authenticator (EAPOL type 0
  with EAP Request/Success/Failure). iPXE sends EAPOL-Start on every NIC
  open and Windows sends Start/Response, so a PXE-booting bay made the
  verdict 'ge' and the watch stopped dnsmasq for the floor.
- pxe-nic-check: join the STP/PAE/LLDP/CDP/PVST+ multicast groups on
  every capture socket, so CDP-only ports are heard on a plain NIC.
- pxe-nic-check --watch: if dnsmasq was (re)started inside the window,
  re-check (~6 s) before stopping it; skip when already stopped.
  New --state-cmd and --print-imaging.
- pxe-nic-carrier (+ pxe-nic-carrier.service): networkd-dispatcher hook
  for the imaging NIC only. Carrier loss stops dnsmasq; carrier gain
  restarts it through its guard if it was running or the hook stopped it.
- pxe-dhcp-hook.sh: clear only SMB (tcp 445/139) conntrack entries and
  sockets, not all traffic for the client, which raced iPXE's first
  HTTP fetch.
- GetPxeScript.aspx: every boot line has a failure branch (iPXE 2.0
  accepts an unsigned EFI kernel and fails at boot under Secure Boot,
  which aborted the script).
- Tests: client EAPOL, packet_mreq packing, watch race, shell helpers on
  stub PATH, lab scenarios eapol-client, br-eapol-client, race, carrier.
2026-10-15 14:26:39 -04:00
cproudlock
ead3314f58 Two-NIC standard: pin imaging and GE NICs by MAC everywhere
Every server has an imaging NIC (static, the only one that serves
DHCP/TFTP/HTTP boot/SMB) and a GE corporate NIC (DHCP client, SSH and the
webapp on 9009). The playbook guessed the PXE NIC and the installer put
172.16.9.1/24 on every en* NIC, so a two-NIC box could answer on the GE
network.

- New per-site vars pxe_nic_mac / corp_nic_mac (required, from
  playbook/site-vars.yml or -e; template site-vars.example.yml), stable
  names pxe_nic_name / corp_nic_name (default pxe0 / corp0, overridable),
  pxe_server_ip, pxe_subnet, DHCP range. pxe_imaging_mac / pxe_ge_mac are
  derived from them. The play stops before touching anything if a MAC is
  unset, malformed, duplicated or not on the box (lists the box's NICs).
- Heuristic NIC detection and the single-NIC netplan task replaced by a
  two-NIC netplan template (match macaddress + set-name), installed by
  pxe-netplan-apply.sh: validate in a scratch root, back up, generate,
  rename, apply, restore the previous config on any failure. cloud-init
  network config disabled so it cannot rewrite it.
- dnsmasq: /etc/dnsmasq.d/pxe-nic-only.conf from the vars (interface=,
  except-interface=, no-dhcp-interface=); dnsmasq.conf IP/range from vars.
- UFW per interface: imaging NIC 22, 67/udp, 69/udp, 80, 4433, 445, 139,
  137/udp, 138/udp, 9009; GE NIC 22 and 9009 only. Old any-interface rules
  and the stale 9011 rule removed after the new ones are in.
- sysctl drop-in: IPv4/IPv6 forwarding off; ip_nonlocal_bind so Apache
  can bind the imaging IP before the address is up.
- Samba [global]: interfaces = 127.0.0.0/8 <imaging NIC>, bind interfaces
  only. Apache 80 and 4433 on the imaging IP only; 9009 stays on all (TODO:
  the webapp has no auth).
- autoinstall: DHCP only on en*, no static 172.16.9.1; apt geoip off and
  offline-install fallback now that the corp NIC may have a lease.
- Blancco UEFI/Secure Boot: blancco_kver (7.0.0-34-generic) drives the
  kernel, initramfs and modules builds instead of the running kernel;
  outputs vmlinuz-ubuntu-70, kexec-initrd-70.img, kmod-<kver>.tar.gz as the
  menu and blancco-init.sh expect; creates: guards keyed on the full kver.
  Ubuntu's shimx64.efi.signed published as blancco/shimx64.efi.
  download-packages.sh adds shim-signed, linux-generic-hwe-24.04 and the
  pinned linux-image/modules/modules-extra for BLANCCO_KVER.

Rendered with the live MACs and names, netplan generate output is
identical to the live config; pxe-nic-only.conf and the Samba binding
match live directive for directive.
2026-10-15 08:53:41 -04:00
cproudlock
689eebf9d1 Merge branch 'nic-guard' into pxe-webapp-redesign 2026-10-15 08:40:57 -04:00
cproudlock
d3a0f90ca1 Re-imaging the same PC: clear its stale SMB/conntrack state every time
A PC whose imaging failed and is re-imaged gets the same IP, and the
server still held state from the dead run, so net use failed (System
error 53). The April fix (18537ac) did not cover it:

- The dnsmasq hook only acted on 'add'/'del'. A re-image comes back to the
  existing lease, which dnsmasq reports as 'old', and only when the
  hostname/MAC changed. Now dnsmasq sets script-on-renewal, and the hook
  clears state on 'old' when the request comes from PXE firmware or iPXE
  (vendor class PXEClient), i.e. the machine just network-booted. A
  Windows lease renewal (MSFT 5.0) is left alone.
- The 1 h conntrack timeout never applied after a reboot: systemd-sysctl
  runs before ufw loads nf_conntrack, so the keys did not exist and the
  5-day default stayed. /etc/modules-load.d/pxe-conntrack.conf loads it
  first.
- smb-soft-reset.sh flushed neighbours on br-pxe, which no longer exists.

Live 2026-10-14. Hook tested on the server with an unused IP: network-boot
renewal clears, Windows renewal and no vendor class do not, add clears.
2026-10-14 17:19:33 -04:00
cproudlock
bc422649e9 NIC guard: dnsmasq never serves DHCP/PXE on the GE network
pxe-nic-check classifies each NIC from what it hears: LLDP/CDP/802.1X
from managed switches, foreign DHCP offers and their domain (one
DHCPDISCOVER on the imaging NIC; the GE NIC's networkd lease is read
instead), STP and ARP hosts for information. A kernel BPF filter keeps
bulk SMB traffic out of the capture loop; bridge ports are listened on
too, since LLDP and EAPOL stop at the port.

- dnsmasq drop-in runs --guard as ExecStartPre: exit 1 on a ge or
  foreign-dhcp imaging verdict, fail open (loudly) on bugs or no link
- pxe-nic-check.timer runs --watch every 5 min: stops dnsmasq on a bad
  imaging verdict, never starts it
- /run/pxe-nic-status.json (0644, atomic) for the webapp
- playbook installs it all and writes /etc/pxe-server/nic.conf from
  pxe_imaging_mac, pxe_ge_mac, pxe_corp_domain_suffixes,
  pxe_nic_allowed_switches
- unit tests plus a no-root network-namespace lab (veth pairs, a raw
  socket DHCP/LLDP peer and real dnsmasq); docs/pxe-nic-guard.md has the
  operator notes and the two floor tests
2026-10-14 17:18:53 -04:00
cproudlock
ce604adcda Renumber PXE LAN from 10.9.100.0/24 to 172.16.9.0/24
Single-site bay-stuck issue at WJ: GE Intune Report IP script filters
Get-NetIPAddress on StartsWith("10.") and posts everything matching
to the GE Tines webhook. Bays at WJ get the PXE LAN 10.9.100.x IP
captured and reported -> GE backend tags bays as on a non-corp 10.x
subnet -> dynamic group eligibility for SFLD policy never matches.
Other GE sites work because their PXE LANs aren't on 10.x at all.

Renumber PXE LAN to RFC1918 172.16.9.0/24 so the GE filter naturally
skips wired PXE addresses without any disable-NIC dance.

Server-side already in flight (netplan dual-bound, dnsmasq scope +
boot URL repointed, blancco preferences + grub.cfg + iPXE GetPxeScript
all sed'd to 172.16.9.1). This commit is the playbook / scripts /
docs side: 109 hits across 35 files sed'd in one shot.

After this lands + boot.wim is rebuilt + bays renumber off DHCP,
the 10.9.100.1 binding will be dropped from netplan as the final
cleanup step.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 16:30:32 -04:00
cproudlock
0292bc01ad Auto-flush stale SMB/conntrack state on DHCP lease, one-source PPKG model
Three changes that go together so a re-image never hits "System error 53":

1. dnsmasq dhcp-script hook (playbook/pxe-server-helpers/pxe-dhcp-hook.sh)
   Fires on every add/del lease event. Runs conntrack -D and ss -K for the
   client IP so any stale ESTABLISHED SMB session from a previous boot is
   cleared before the client reconnects. Runs as root (dnsmasq default).
   Wired into /etc/dnsmasq.conf via dhcp-script= directive in the playbook.

2. One-source PPKG (playbook/startnet.cmd + startnet-template.cmd)
   The 5 per-Office PPKG copies were bit-for-bit identical; only the
   filename differs because BPRT parses Office and Region out of the name.
   Store one source file (e.g. GCCH_Prod_SFLD_v4.11.ppkg) and construct
   the BPRT-tagged target filename at menu-selection time from variables:
     SOURCE_PPKG / PPKG_VER / PPKG_EXP / REGION / OFFICE
   copy /Y "Y:\ppkgs\%SOURCE_PPKG%" "W:\Enrollment\%PPKG%"
   Bumped PPKG_VER v4.10 -> v4.11 and PPKG_EXP 20260430 -> 20270430.
   Saves ~30G on disk per version.

3. run-enrollment.ps1 already committed in 5a9c3db uses provtool.exe
   directly (no PowerShell cmdlet 180s timeout). Included here because it
   is part of the same end-to-end PPKG path.
2026-04-15 09:03:16 -04:00
cproudlock
18537acbbc PXE server: fix WinPE re-image SMB connection loss
WinPE clients re-imaging the same machine hit "System error 53 -
network path not found" on the second attempt. systemctl restart smbd
did not help; only a full server power cycle cleared the state.

Root cause is kernel nf_conntrack: the default TCP ESTABLISHED timeout
is 5 days (432000s), so a session from the first WinPE run whose
client rebooted abnormally leaves an ASSURED ESTABLISHED entry that
ufw's state-tracking rules then mis-classify the new SYN against.

Fix applied in three layers:
- /etc/sysctl.d/99-pxe-conntrack.conf drops TCP ESTABLISHED timeout
  to 1 hour and shortens the half-closed states to 30s each.
- smb.conf gains socket options TCP_NODELAY SO_KEEPALIVE IPTOS_LOWDELAY
  plus keepalive = 30 and deadtime = 5. Active sessions refresh the
  conntrack timer every 30s via keepalives so they never age out;
  dead ones expire in an hour.
- /usr/local/sbin/smb-diag.sh snapshots kernel + Samba state for
  remote diagnosis; /usr/local/sbin/smb-soft-reset.sh walks a
  progressive recovery (nmbd/smbd restart, conntrack flush, arp
  flush, ss -K) as an alternative to power-cycling.

conntrack package added to download-packages.sh and playbook verify
list so the offline .deb bundle ships with it.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 13:00:43 -04:00