Config Generation¶
The agent turns a device config into the files OpenBSD reads: pf.conf and its
anchors, hostname.if files, unbound.conf, dhcpd.conf, dhcpleased.conf,
ifstated.conf, iked.conf, ospfd.conf, newsyslog.conf and a managed block
in root's crontab. This page covers the parts whose design is not obvious from
the output. See PF Generation for the ruleset itself.
Before anything is generated, config.Validate rejects configs that have no
valid output: group cycles, a block rule bound to a schedule, and the other
checks in internal/config/validate*.go.
Network objects¶
Named objects render as pf tables (host, network, group, FQDN) or
macros (port objects, services). A render-time index decides which
(internal/pf/objects.go):
type objectIndex struct {
tables map[string]bool
macros map[string]bool
services map[string]config.ServiceConfig
groupMembers map[string][]string
}
Resolution is a lookup against the index (resolveAddr, resolvePort),
with the literal value as the fallback. A host object named 10-camera resolves
to its table, not to the literal string.
Groups are flattened transitively to their leaf values (flattenGroup),
because pf tables cannot nest. This is why the UI shows what a group expands to
on the device, and why a cycle is rejected when it is saved.
Services supply protocol and destination port to any rule naming them. An explicit value on the rule wins, so a rule can narrow a service without redefining it.
Schedules¶
pf has no notion of time. Each schedule owns an anchor; the rules bound to it live in an anchor file that cron loads when the window opens and flushes when it closes. Outside the window the anchor is empty.
| Piece | Value |
|---|---|
| Anchor | sched_<name> (sanitised) |
| Anchor file | /etc/pf.anchors/sched_<name> |
| Open | pfctl -a sched_<name> -f /etc/pf.anchors/sched_<name> |
| Close | pfctl -a sched_<name> -F rules |
| Crontab block | between # BEGIN digilan-agent schedules and # END digilan-agent schedules; lines outside are preserved |
A window that crosses midnight closes on the following day. At apply time the agent loads every anchor whose window is open now and removes anchors of schedules that no longer exist.
A scheduled rule does not exist outside its window
A block bound to a schedule would stop blocking when the window closes,
the opposite of what an operator expects. Schedules therefore go on pass
rules, with the default deny covering the rest of the day; the agent's
validation refuses a scheduled block, and the UI says so at the point of
binding.
Macros are redeclared in each anchor file. An anchor loaded with
pfctl -a name -f file is parsed on its own and does not inherit macros from
the main ruleset. Without the redeclaration the anchor fails to parse and stays
empty, and an empty anchor fails silently.
DNS: unbound¶
internal/dnsfilter renders /var/unbound/etc/unbound.conf. Filtering blocks
at the resolver rather than through an intercepting proxy: no CA to distribute,
nothing breaks on certificate pinning, nothing decrypts customer traffic.
What it does not cover
A client with its own resolver, or using DNS-over-HTTPS, bypasses it. It is a policy control, not a containment boundary.
Views. Each filtering policy becomes an unbound view, and networks are bound
to it with access-control-view.
access-control-viewis a server-clause option. Outsideserver:it is a syntax error andunbound-checkconfrejects the whole file.- A view answers only what it lists unless
view-first: yesis set, which falls through to the server clause for everything else. The generator sets it.
When nothing is generated. Generate returns an empty string only when
filtering is off, no LAN uses the gateway as its resolver, and no service is
published. The caller then leaves unbound and its file as they are; it does not
write an empty config.
DHCP¶
internal/dhcp writes /etc/dhcpd.conf for the interfaces that serve DHCP,
including reservations, and drives dhcpd. internal/dhcpleased writes
/etc/dhcpleased.conf for the client side, so that only the intended uplink
installs a default route; see Uplink Failover.
Validating the output¶
Unit tests pin the generated text; they cannot tell whether OpenBSD accepts it.
cmd/gencheck writes every generated file for a representative config that
exercises every construct: hostname.<if>, pf.conf and the sched_*
anchors, unbound.conf (with published names), ospfd.conf, newsyslog.conf,
the detection scripts and rc.d scripts. It is not part of the agent binary.
dev/bench-check.sh runs gencheck, copies the output to the
development VM and runs the real parsers: pfctl -nf on
pf.conf and on each anchor, unbound-checkconf, ospfd -n, newsyslog -n,
ksh -n and rcctl check on rc scripts, and ifconfig on each hostname.if.
Every check is an explicit pass/fail, and the script fails on any of them. Run
it on any change to a generator.