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
This commit is contained in:
18
playbook/pxe-server-helpers/dnsmasq.service.d/nic-guard.conf
Normal file
18
playbook/pxe-server-helpers/dnsmasq.service.d/nic-guard.conf
Normal file
@@ -0,0 +1,18 @@
|
||||
# dnsmasq drop-in: refuse to start DHCP/PXE unless the imaging NIC is on
|
||||
# the isolated imaging network.
|
||||
#
|
||||
# Installed as /etc/systemd/system/dnsmasq.service.d/nic-guard.conf by
|
||||
# pxe_server_setup.yml. pxe-nic-check --guard listens on the imaging NIC
|
||||
# for 5 s and sends one DHCPDISCOVER (~6 s total), then exits:
|
||||
# 0 safe to serve (isolated, no link, or the check itself broke - it
|
||||
# fails open on bugs and says so loudly in the journal)
|
||||
# 1 DO NOT serve: a foreign DHCP server or a managed/corporate switch
|
||||
# (LLDP, CDP, 802.1X) is on the imaging NIC. dnsmasq does not start.
|
||||
#
|
||||
# Why did dnsmasq not start? journalctl -u dnsmasq -b | grep pxe-nic-check
|
||||
# Full picture: pxe-nic-check (or the webapp status page)
|
||||
# Guard does not run on "systemctl reload"; every start/restart runs it,
|
||||
# including the @reboot cron restart.
|
||||
|
||||
[Service]
|
||||
ExecStartPre=/usr/local/sbin/pxe-nic-check --guard
|
||||
1237
playbook/pxe-server-helpers/pxe-nic-check
Executable file
1237
playbook/pxe-server-helpers/pxe-nic-check
Executable file
File diff suppressed because it is too large
Load Diff
16
playbook/pxe-server-helpers/pxe-nic-check.service
Normal file
16
playbook/pxe-server-helpers/pxe-nic-check.service
Normal file
@@ -0,0 +1,16 @@
|
||||
# Periodic imaging-NIC watch (pxe-nic-check.timer runs this every 5 min).
|
||||
# Listens 60 s on each NIC, probes the imaging NIC for foreign DHCP, writes
|
||||
# /run/pxe-nic-status.json for the webapp, and STOPS dnsmasq if the imaging
|
||||
# NIC turns out to be on the GE network or next to another DHCP server.
|
||||
# It never starts dnsmasq: after fixing the cabling, start it by hand
|
||||
# (systemctl start dnsmasq), which re-runs the guard.
|
||||
|
||||
[Unit]
|
||||
Description=PXE NIC watch: keep dnsmasq off the GE corporate network
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/sbin/pxe-nic-check --watch
|
||||
TimeoutStartSec=180
|
||||
Nice=5
|
||||
11
playbook/pxe-server-helpers/pxe-nic-check.timer
Normal file
11
playbook/pxe-server-helpers/pxe-nic-check.timer
Normal file
@@ -0,0 +1,11 @@
|
||||
[Unit]
|
||||
Description=PXE NIC watch every 5 minutes
|
||||
|
||||
[Timer]
|
||||
OnBootSec=2min
|
||||
OnActiveSec=1min
|
||||
OnUnitActiveSec=5min
|
||||
AccuracySec=15s
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
@@ -55,6 +55,20 @@
|
||||
# _media/<pctype> links the base image's entries, so also look in the
|
||||
# image roots (on a rebuild the _media views may not exist yet).
|
||||
zscaler_ca_share_glob: "{{ samba_share }}/_media/*/Deploy/Applications/ZscalerCommercialCertificate-2048-SHA256.crt {{ samba_share }}/gea-*/Deploy/Applications/ZscalerCommercialCertificate-2048-SHA256.crt"
|
||||
# NIC guard (pxe-nic-check): dnsmasq must never serve DHCP/PXE on the GE
|
||||
# corporate network. Pin the two NICs by MAC (ip -br link); left blank,
|
||||
# the imaging NIC is the interface= of /etc/dnsmasq.d/pxe-nic-only.conf
|
||||
# or the holder of 172.16.9.1, and the GE NIC is the one with a DHCP
|
||||
# lease. A MAC of a bridge port resolves to the bridge (br-pxe).
|
||||
pxe_imaging_mac: ""
|
||||
pxe_ge_mac: ""
|
||||
# A foreign DHCP server handing out one of these domains (whole labels:
|
||||
# wjs.geaerospace.net matches, notgeaerospace.net does not) = GE network.
|
||||
pxe_corp_domain_suffixes: "geaerospace.net, ge.com"
|
||||
# Only if the IMAGING switch is itself managed and sends LLDP/CDP: its
|
||||
# LLDP system name / CDP device id / chassis MAC, comma separated, so
|
||||
# it does not count as a corporate switch. 802.1X always counts.
|
||||
pxe_nic_allowed_switches: ""
|
||||
image_types:
|
||||
- gea-standard
|
||||
- gea-engineer
|
||||
@@ -206,6 +220,77 @@
|
||||
# Script runs as root by default (dnsmasq --dhcp-scriptuser default).
|
||||
dhcp-script=/usr/local/sbin/pxe-dhcp-hook.sh
|
||||
|
||||
# --- NIC guard: keep dnsmasq off the GE corporate network ---
|
||||
# pxe-nic-check classifies each NIC from what it hears (LLDP/CDP/802.1X
|
||||
# from managed switches, foreign DHCP offers and their domain). The
|
||||
# dnsmasq drop-in runs it as ExecStartPre (--guard: dnsmasq refuses to
|
||||
# start if the imaging NIC is on the GE network or next to another DHCP
|
||||
# server); the timer re-checks every 5 min (--watch: stops dnsmasq) and
|
||||
# writes /run/pxe-nic-status.json for the webapp. Operator notes and
|
||||
# the floor tests: docs/pxe-nic-guard.md.
|
||||
- name: "NIC guard: deploy pxe-nic-check"
|
||||
copy:
|
||||
src: "{{ usb_mount }}/pxe-server-helpers/pxe-nic-check"
|
||||
dest: /usr/local/sbin/pxe-nic-check
|
||||
mode: '0755'
|
||||
|
||||
- name: "NIC guard: create /etc/pxe-server"
|
||||
file:
|
||||
path: /etc/pxe-server
|
||||
state: directory
|
||||
mode: '0755'
|
||||
|
||||
- name: "NIC guard: write /etc/pxe-server/nic.conf"
|
||||
copy:
|
||||
dest: /etc/pxe-server/nic.conf
|
||||
mode: '0644'
|
||||
content: |
|
||||
# Managed by pxe_server_setup.yml (vars pxe_imaging_mac, pxe_ge_mac,
|
||||
# pxe_corp_domain_suffixes). Read by /usr/local/sbin/pxe-nic-check.
|
||||
[nic]
|
||||
{% if pxe_imaging_mac %}
|
||||
imaging_mac = {{ pxe_imaging_mac | lower }}
|
||||
{% else %}
|
||||
# imaging_mac unset: falls back to interface= in
|
||||
# /etc/dnsmasq.d/pxe-nic-only.conf, then the holder of own_ip
|
||||
{% endif %}
|
||||
{% if pxe_ge_mac %}
|
||||
ge_mac = {{ pxe_ge_mac | lower }}
|
||||
{% else %}
|
||||
# ge_mac unset: falls back to the interface holding a DHCP lease
|
||||
{% endif %}
|
||||
corp_domain_suffixes = {{ pxe_corp_domain_suffixes }}
|
||||
own_ip = 172.16.9.1
|
||||
allowed_switches = {{ pxe_nic_allowed_switches }}
|
||||
|
||||
- name: "NIC guard: create dnsmasq drop-in directory"
|
||||
file:
|
||||
path: /etc/systemd/system/dnsmasq.service.d
|
||||
state: directory
|
||||
mode: '0755'
|
||||
|
||||
- name: "NIC guard: install dnsmasq ExecStartPre drop-in"
|
||||
copy:
|
||||
src: "{{ usb_mount }}/pxe-server-helpers/dnsmasq.service.d/nic-guard.conf"
|
||||
dest: /etc/systemd/system/dnsmasq.service.d/nic-guard.conf
|
||||
mode: '0644'
|
||||
|
||||
- name: "NIC guard: install watch service and timer"
|
||||
copy:
|
||||
src: "{{ usb_mount }}/pxe-server-helpers/{{ item }}"
|
||||
dest: "/etc/systemd/system/{{ item }}"
|
||||
mode: '0644'
|
||||
loop:
|
||||
- pxe-nic-check.service
|
||||
- pxe-nic-check.timer
|
||||
|
||||
- name: "NIC guard: enable watch timer"
|
||||
systemd:
|
||||
name: pxe-nic-check.timer
|
||||
state: started
|
||||
enabled: yes
|
||||
daemon_reload: yes
|
||||
|
||||
- name: "Create TFTP directory"
|
||||
file:
|
||||
path: "{{ tftp_dir }}"
|
||||
|
||||
Reference in New Issue
Block a user