Summary
Add a mechanism to populate firewall groups (network-group, address-group, their IPv6 equivalents, interface-group) and policy prefix-lists (prefix-list, prefix-list6, access-list, access-list6) from another part of the live configuration — e.g. every configured BGP neighbor address, or every connected interface subnet — instead of requiring the operator to duplicate those values by hand and keep the copy in sync forever.
Motivation / real-world use case
We run a DFZ edge router (VyOS 1.5.1, FRR 10.5.2) with several firewall groups whose entire purpose is to mirror facts that already exist elsewhere in the same configuration:
set firewall group network-group IPv4_EBGP_HOSTS network '62.141.40.144/32' set firewall group network-group IPv4_EBGP_HOSTS network '62.141.40.145/32' set firewall group network-group IPv4_EBGP_HOSTS network '185.1.155.254/32' ...
Every one of these addresses is *also* present, verbatim, under protocols bgp neighbor <address> — that's the whole point of the group (only allow BGP/SSH/etc. from our actual configured peers). The group is not encoding any independent decision; it is a hand-maintained copy of information the router already has. Every time a peer is added, removed, or renumbered, both places have to be updated, in the same commit, correctly, or the group silently drifts from reality with no commit-time check that it's still complete. We run four routers with this same pattern, which quadruples the chance of one of them missing an update.
The same problem shows up with policy prefix-list/prefix-list6: e.g. a prefix-list meant to represent "every prefix we originate" or "every connected subnet on this router" is, again, just a manual copy of information already present under protocols bgp ... network, interfaces ..., or protocols static route.
What we actually want
A way to declare, on the group/list itself, that its membership should be derived from another config-tree path rather than entered manually - conceptually identical to Junos's policy-options prefix-list <name> apply-path "<config path>", which populates a prefix-list directly from another part of the configuration (e.g. every configured BGP neighbor address).
Current workaround (and why it's not ideal)
Manually enumerate every value in the group/prefix-list and maintain it in parallel with the config it's meant to reflect. Concretely:
- No commit-time check that the list is complete - a newly added BGP neighbor that isn't also added to the group silently loses whatever protection/permission the group was meant to grant.
- Has to be repeated per router and kept in sync across a fleet (four routers, same pattern, four chances to get it wrong).
- The group's own commit history stops communicating *why* an entry exists - it's just a flat list, disconnected from the config that actually motivates each entry.
Requested syntax (example)
set firewall group network-group IPv4_EBGP_HOSTS apply-path 'protocols bgp neighbor'
For config that's duplicated per-VRF (a very common shape - BGP, static routes, etc. all exist both under the default VRF and under vrf name <name> protocols ...), a wildcard segment to expand every instance of a tag node at that position:
set firewall group network-group IPv4_EBGP_HOSTS apply-path 'vrf name * protocols bgp neighbor'
Same option, same semantics, on a prefix-list:
set policy prefix-list OWN-ORIGINATED apply-path 'protocols bgp address-family ipv4-unicast network'
Why this matters beyond our specific case
This is a general pattern, not specific to our BGP-hosts example. We went through every "manually enumerated list" concept currently in VyOS (firewall.xml.in, policy.xml.in) and sorted them by whether they'd plausibly benefit:
| Resource | Fit | Why |
|---|---|---|
| firewall group network-group / ipv6-network-group | High | our motivating case - mirrors addresses/subnets that already exist elsewhere (BGP neighbors, interface addresses, static route targets) |
| firewall group address-group / ipv6-address-group | High | same, for single addresses |
| firewall group interface-group | Medium-high | e.g. "every interface belonging to VRF X", "every bond member" |
| firewall group mac-group | Medium | could mirror service dhcp-server ... static-mapping ... mac-address |
| policy prefix-list / prefix-list6 | High | "every network we originate", "every connected subnet" |
| policy access-list / access-list6 | High | same as prefix-list, older/simpler match syntax |
| firewall group domain-group | Low | no obvious existing config to mirror |
| firewall group port-group | Low | ports are rarely already a config fact elsewhere |
| policy as-path-list / community-list / extcommunity-list / large-community-list | Low | these encode routing *policy intent* (regexes/values chosen by the operator), not a copy of infrastructure that already exists elsewhere in config - there's no natural derivation source |
| policy route-map | Not applicable | a match/action chain, not a membership list |
Notably, VyOS already has a precedent for a group that isn't manually populated: firewall group remote-group fetches its membership from an external URL on an interval. apply-path is the same underlying idea - "this group's membership isn't typed in by hand, it's fetched from a source of truth" - just with the source being the router's own configuration instead of a remote HTTP(S) endpoint. This would not be a foreign concept to the project.
Architecturally, this looks achievable without a large new subsystem. firewall group membership is a single shared resource - not only firewall rules but also nat66, policy route, load-balancing wan, and system conntrack all reference these groups by name against the same underlying nftables set, so solving apply-path once at the group-definition layer (inside firewall.py's existing group-config assembly, before the nftables-defines.j2 template renders elements = { ... }) would benefit every one of those consumers automatically, with no per-consumer changes needed. policy prefix-list is a separate consumer with a different backend (FRR config, not nftables), but VyOS's Config class already exposes the two primitives such a resolver would need generically: list_nodes(path) (enumerate tag-node instances - e.g. every configured BGP neighbor address) and return_values(path) (read a multi-value leaf node). A small, shared, backend-agnostic path-resolver module - called independently by firewall.py and by policy.py, each merging the resolved values into the list they already assemble before rendering - would cover both without duplicating the path-walking logic.
Environment
- VyOS 1.5.1 (circinus), official generic ISO, FRR 10.5.2
- Verified against the vyos-1x source (rolling branch): python/vyos/config.py (list_nodes/return_values), data/templates/firewall/nftables-defines.j2 (group elements rendered from a plain Python list), interface-definitions/nat66.xml.in / policy_route.xml.in / load-balancing_wan.xml.in / system_conntrack.xml.in (all reference firewall group by name), interface-definitions/firewall.xml.in (remote-group as an existing precedent for non-manual group population), interface-definitions/policy.xml.in (prefix-list/access-list/as-path-list/community-list/extcommunity-list/large-community-list/route-map structure)