VyOSXLVII · Management Plane HardeningMgmtPlane
Source restrictions — listen-address, allow-client, and the firewall input chain
What you'll learn
- Restrict the SSH daemon's bind address with `listen-address` and understand what that does not do
- Filter management traffic by client address with the `firewall ipv4 input filter` chain
- Use `service https allow-client address` for the HTTP API, and know that `service ssh` has no equivalent
- Distinguish `dynamic-protection` (reactive blocking) from an allow-list
- Recognise the production failure modes where source restrictions lock out operators
Prerequisites
Verified against VyOS 1.5.x LTS (circinus) · VyOS 1.4.x (sagitta) — legacy · FRRouting 10.x (VyOS 1.5) · Linux kernel 6.6 LTS (VyOS 1.5 base) · strongSwan 5.9.x (IPsec) · WireGuard 1.0.x (kernel module + userspace tooling)
A hardened management plane restricts SSH, the HTTP API and every other management service to known source addresses. Operators reach for three mechanisms, and it matters enormously which of them actually filters on the client’s address:
listen-addressdecides which local address a daemon binds to. It says nothing about who may connect.- A service-level client allow-list decides which remote addresses the service will serve. On VyOS 1.5 this exists for the HTTPS/API service and does not exist for SSH.
- The firewall decides which packets reach the daemon at all. This is the only mechanism that filters SSH by source address on VyOS.
This lesson covers what each layer really does, where the gap is, and the production failure modes where over-restriction locks the operator out.
The three layers, honestly labelled
flowchart LR
A["Operator<br/>203.0.113.50"] -->|"TCP 22"| K["Kernel / nftables"]
B["Internet scanner<br/>arbitrary source"] -.->|"TCP 22"| K
K -->|"firewall ipv4 input filter<br/>matches source address"| D["sshd"]
K -.->|"no matching accept rule"| DROP["Dropped — client sees a timeout"]
D -->|"bound by listen-address"| S["Session offered"]
D -.->|"access-control deny user/group<br/>(identity, not address)"| REJ["Authentication refused"]
- Layer 1 —
listen-address. The daemon binds to one or more specific local addresses instead of every address on the box. This shrinks the attack surface to the interfaces that carry those addresses. It is not an allow-list: anyone who can route a packet to that address still reaches the daemon. - Layer 2 — service-level allow-list.
set service https allow-client addressrestricts the HTTP API.set service sshhas no such node — the SSH daemon’saccess-controlnode filters by user and group, not by address. - Layer 3 — the firewall.
set firewall ipv4 input filteris the base chain for traffic addressed to the router itself. This is where an SSH source allow-list is expressed on VyOS.
Each layer catches a different class of mistake, which is why the production pattern uses all of them. But only Layer 3 is a source-address filter for SSH, and building a design that assumes otherwise is how operators end up believing they have a restriction that does not exist.
Layer 1 — listen-address
configure
set service ssh listen-address 10.0.0.1
commit
save
The SSH daemon binds to 10.0.0.1 and nothing else. A scan of any other
address on the router finds no listener on port 22 — the kernel answers the SYN
with a RST because nothing is bound there.
The trade-off is reachability. The operator’s workstation must be able to route
to 10.0.0.1. If that address lives in a management VRF, the operator’s path
into the VRF has to exist before the change, not after it.
Layer 2 — the service-level allow-list, and the gap
For the HTTP API, VyOS 1.5 ships a real client allow-list:
configure
set service https listen-address 10.0.0.1
set service https allow-client address '203.0.113.50'
set service https allow-client address '203.0.113.0/24'
commit
save
For SSH there is no equivalent. The complete set of nodes under
set service ssh on VyOS 1.5 is: access-control, cipher,
client-keepalive-interval, disable-host-validation,
disable-password-authentication, dynamic-protection, fido,
hostkey-algorithm, key-exchange, listen-address, loglevel, mac,
port, pubkey-accepted-algorithm, rekey, trusted-user-ca and vrf.
None of them is a client-address allow-list.
configure
set service ssh dynamic-protection threshold 30
set service ssh dynamic-protection block-time 120
set service ssh dynamic-protection detect-time 1800
set service ssh dynamic-protection allow-from '203.0.113.0/24'
commit
save
That configuration blocks brute-force sources after they misbehave and promises never to block the operator’s own subnet. It is a useful control. It is not a restriction, and a design document that lists it as one is wrong.
Layer 3 — the firewall input chain
Traffic addressed to the router itself traverses the input hook. On VyOS 1.4
and 1.5 that is a first-class base chain; the per-interface
interfaces ethernet eth0 firewall local binding of 1.3 no longer exists.
configure
set firewall ipv4 input filter default-action 'drop'
set firewall ipv4 input filter default-log
set firewall ipv4 input filter rule 10 action 'accept'
set firewall ipv4 input filter rule 10 description 'Return traffic for router-originated sessions'
set firewall ipv4 input filter rule 10 state 'established'
set firewall ipv4 input filter rule 10 state 'related'
set firewall ipv4 input filter rule 20 action 'accept'
set firewall ipv4 input filter rule 20 description 'Management from operator subnet'
set firewall ipv4 input filter rule 20 inbound-interface name 'eth0'
set firewall ipv4 input filter rule 20 source address '203.0.113.0/24'
set firewall ipv4 input filter rule 20 protocol 'tcp'
set firewall ipv4 input filter rule 20 destination port '22,443'
set firewall ipv4 input filter rule 30 action 'accept'
set firewall ipv4 input filter rule 30 description 'Loopback'
set firewall ipv4 input filter rule 30 inbound-interface name 'lo'
commit
save
Rule 20 is the source restriction that the SSH daemon cannot express. Rule 10
is what keeps the router’s own outbound sessions — DNS, NTP, updates, BGP
sessions it initiated — working once the default action is drop. Rule 30
keeps loopback traffic alive, which several local services depend on.
If you prefer to keep the management rules in a named ruleset so they can be reviewed as a unit, create a custom chain and jump to it:
set firewall ipv4 name MGMT-INGRESS default-action 'drop'
set firewall ipv4 name MGMT-INGRESS default-log
set firewall ipv4 name MGMT-INGRESS rule 10 action 'accept'
set firewall ipv4 name MGMT-INGRESS rule 10 source address '203.0.113.0/24'
set firewall ipv4 name MGMT-INGRESS rule 10 protocol 'tcp'
set firewall ipv4 name MGMT-INGRESS rule 10 destination port '22,443'
set firewall ipv4 input filter rule 20 action 'jump'
set firewall ipv4 input filter rule 20 jump-target 'MGMT-INGRESS'
set firewall ipv4 input filter rule 20 inbound-interface name 'eth0'
A custom chain is only reachable from a base chain through a rule whose action
is jump and whose jump-target names it. A set firewall ipv4 name ...
ruleset with no jump pointing at it is inert — it will commit cleanly, appear in
show firewall, and filter nothing. That silent no-op is the most common
firewall mistake on 1.4/1.5, and it is a direct consequence of dropping the
per-interface binding.
The combined production pattern
configure
# Layer 1: the daemons bind only to the management address
set service ssh listen-address 10.0.0.1
set service ssh vrf mgmt
set service https listen-address 10.0.0.1
# Layer 2: the API has a client allow-list; SSH has none, so this
# line has no SSH counterpart. The firewall below covers SSH.
set service https allow-client address '203.0.113.0/24'
# Layer 2b: reactive brute-force protection, with the operator exempt
set service ssh dynamic-protection allow-from '203.0.113.0/24'
# Layer 3: the only source filter that applies to SSH
set firewall ipv4 input filter default-action 'drop'
set firewall ipv4 input filter default-log
set firewall ipv4 input filter rule 10 action 'accept'
set firewall ipv4 input filter rule 10 state 'established'
set firewall ipv4 input filter rule 10 state 'related'
set firewall ipv4 input filter rule 20 action 'accept'
set firewall ipv4 input filter rule 20 source address '203.0.113.0/24'
set firewall ipv4 input filter rule 20 protocol 'tcp'
set firewall ipv4 input filter rule 20 destination port '22,443'
set firewall ipv4 input filter rule 30 action 'accept'
set firewall ipv4 input filter rule 30 inbound-interface name 'lo'
commit-confirm 5
commit-confirm 5 is not decoration here. Every one of these lines can end the
session you are typing them into. If the change locks you out, the router rolls
back on its own after five minutes.
How the result is validated
show configuration commands | match 'service ssh'
show configuration commands | match 'service https'
show firewall ipv4 input filter
show firewall statistics
show log firewall
A working configuration shows:
sshdbound only to10.0.0.1— verifiable from the shell withss -ltnp | grep :22.- The input chain’s rule 20 counter advancing when the operator connects, and the default-action counter advancing when anything else tries.
- API requests from outside
203.0.113.0/24answered with HTTP 403 rather than dropped.
The rule counters are the evidence that matters. A rule whose counter never moves is either shadowed by an earlier rule or matching nothing, and in a management-plane ruleset both are worth investigating before you trust the restriction.
How it fails
The production failure modes a routing engineer must recognise:
- The named ruleset is never jumped to.
set firewall ipv4 name MGMT-INGRESSexists, commits, and does nothing, because no base-chain rule hasaction jumpandjump-target MGMT-INGRESS. The operator believes the management plane is restricted; it is wide open. default-action dropwith no established/related rule. The router’s own outbound sessions break: DNS resolution stops, NTP drifts,apthangs. The operator’s SSH session may survive (it is already in conntrack) while everything else quietly fails.- The operator’s subnet is missing from the input chain. The default action drops the operator’s SYN. The client sees a timeout, not a rejection.
- VPN egress address not in the rule. The operator connects through a corporate VPN whose egress address is not the office address in the rule. Same timeout, different cause.
listen-addressset to an address the operator cannot route to. The daemon is healthy and unreachable. Nothing in the firewall is wrong.allow-clientmistaken for a firewall. The API is restricted; SSH, SNMP and everything else on the box are not. A port scan still finds them.dynamic-protectionmistaken for an allow-list. Sources are only blocked after they fail repeatedly. It stops brute force; it does not stop a single well-aimed connection from an address you never intended to permit.- Source address changes. A DHCP renewal or a mobile network hand-off moves the operator to an address the rule does not cover. Prefer a subnet to a host address where the operator’s network makes that safe.
Rollback
The recovery from a locked-out configuration:
- Wrong firewall rule. Connect via OOB and correct or delete the rule, or
set
default-action accepton the input chain long enough to get back in. - Wrong
listen-address. Connect via OOB and delete the node; the daemon falls back to binding every address. - Wrong
allow-client. Connect via OOB or via SSH (the API allow-list does not affect SSH) and correct the address. dynamic-protectionblocked you. Wait outblock-time, or connect via OOB and add your prefix toallow-from.
rollback N followed by commit restores a previous revision, and
commit-confirm does the same automatically when the operator cannot confirm.
Production discipline
Cross-course references
XLVII-VyOS-MgmtPlane(vyos-xlvii-01-ssh-hardening,vyos-xlvii-02-api-auth,vyos-xlvii-04-oob-access) cover the rest of the management plane hardening.XXXVII-VyOS-Firewallcovers the firewall configuration in detail, including the base chains and the jump mechanism used here.LII-VyOS-Troubleshootcovers the diagnostic methodology for source-restriction failures.
Quiz
Knowledge check · 4 questions
Q1. On VyOS 1.5, which mechanism restricts SSH to a specific set of client source addresses?
Q2. A `set firewall ipv4 name MGMT-INGRESS` ruleset filters management traffic as soon as it is committed.
Q3. An operator sets `service https allow-client address '203.0.113.50'` and reports that the management plane is now restricted to the office address. A week later an SSH brute-force campaign from an unrelated network shows up in the auth log. What did the operator get wrong?
The router has `set service https allow-client address '203.0.113.50'` and no firewall input filter rules. The operator's change record claims 'management access restricted to office'. The auth log shows thousands of failed SSH password attempts from addresses outside 203.0.113.0/24, and the HTTP API is answering 403 to the same addresses.
Q4. After committing `set firewall ipv4 input filter default-action drop` with an accept rule for the operator's subnet, SSH still works but the router can no longer resolve DNS, NTP has stopped synchronising, and a BGP session has dropped. What is missing?
The input chain has default-action drop, default-log, and one accept rule matching the operator's subnet on TCP 22 and 443. The operator's existing SSH session survived the commit. `show log firewall` fills with drops whose source addresses are the DNS resolver, the NTP servers and the BGP peer.
Passing score: 75%. Answers are checked in this browser.