Skip to main content
RunBook Academy

VyOSLIII · Security HardeningSecurity

Router hardening checklist — configuration review, OOB management, audit log

Advanced⏱ ~22 minshow configuration commandsshow configuration commands | matchshow system commitcompareshow firewall ipv4 input filtershow ip bgp summaryshow log

What you'll learn

  • Walk the production router hardening checklist for a VyOS 1.5 LTS router
  • Verify every peer has authentication, every interface has a description, every CoPP rule is in place
  • Establish an out-of-band management path (serial console, IPMI, dedicated VLAN)
  • Configure the audit log to record configuration changes, authentication attempts, and system events

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)

Not yet marked complete on this device.

A router’s security is the sum of every choice the operator has made since the last install. A configuration that started secure (no default credentials, CoPP in place, every peer has authentication) drifts over time: a new BGP peer is added without authentication, a CoPP rule is removed for a debugging session and never put back, a default user is left enabled. The hardening checklist is the operator’s discipline to catch the drift.

The production hardening checklist has three sections:

  1. Configuration review — verify every security control is still in place. This is a manual review of the running configuration, not an automated scan.
  2. Out-of-band management — establish a path to the router that does not depend on the production network. The OOB path is what the operator uses when the production network is down or compromised.
  3. Audit log — record every configuration change, authentication attempt, and system event. The audit log is the operator’s defence against the post- incident review question “what happened?”.

This lesson walks the checklist, explains the rationale for each item, and shows the production commands to verify the control.

The configuration review

The configuration review is a manual walk of the running configuration. The operator checks every security control against the checklist:

# Every BGP peer, then the peers that carry a key
show configuration commands | match "protocols bgp neighbor"
show configuration commands | match "protocols bgp neighbor .* password"

# OSPF authentication is per-interface on 1.5
show configuration commands | match "protocols ospf interface"
show configuration commands | match "protocols ospf .* authentication"

# Control-plane rate limiting lives in the input chain
show configuration commands | match "firewall ipv4 input filter"
show configuration commands | match "firewall ipv4 input filter .* limit"

# SSH and the local accounts
show configuration commands | match "service ssh"
show configuration commands | match "system login"

# The firewall as a whole, and the base hooks specifically
show configuration commands | match "firewall"
show configuration commands | match "firewall ipv4 (input|forward|output) filter"

The output of these commands is the operator’s evidence. show configuration commands flattens the running configuration into one set command per line, and match is a regular expression against that flat text — so the two-line pattern above is deliberate. The first line enumerates the objects, the second enumerates the objects that carry the control, and the review is the subtraction. Nine neighbours and six passwords is a finding; you do not get that finding from either line on its own.

flowchart TB
  R["Running configuration"] --> C1{"Every BGP peer<br/>carries a password?"}
  C1 -->|no| F1["FAIL: peer without TCP-MD5"]
  C1 -->|yes| C2{"Every OSPF interface<br/>authenticated?"}
  C2 -->|no| F2["FAIL: unauthenticated adjacency"]
  C2 -->|yes| C3{"Input chain rate-limits<br/>the control protocols?"}
  C3 -->|no| F3["FAIL: no control-plane limit"]
  C3 -->|yes| C4{"SSH hardened?"}
  C4 -->|no| F4["FAIL: harden SSH"]
  C4 -->|yes| C5{"Base hooks default-drop,<br/>named sets jumped to?"}
  C5 -->|no| F5["FAIL: filter absent or inert"]
  C5 -->|yes| PASS["Controls present<br/>- now read them"]

The diagram shows the review sequence. Each check is a branch: a missing control is a failure that the operator must remediate. A configuration that passes all checks is ready for the audit log review.

Checklist item 1: every BGP peer has authentication

show configuration commands | match "protocols bgp neighbor" | match "remote-as"
show configuration commands | match "protocols bgp neighbor" | match "password"
show configuration commands | match "protocols bgp neighbor" | match "ttl-security"

The first line is the peer inventory — one remote-as line per configured neighbour. The second is the peers that carry TCP-MD5. The third is the peers that carry GTSM. Any neighbour present in the first list and absent from the second is the finding.

On VyOS 1.5 the peer stanza sits directly under protocols bgp, not under the ASN:

set protocols bgp system-as 64512
set protocols bgp neighbor 10.0.0.5 remote-as 64513
set protocols bgp neighbor 10.0.0.5 password COORDINATE-OUT-OF-BAND
set protocols bgp neighbor 10.0.0.5 ttl-security hops 1

The local ASN moved to system-as in VyOS 1.4 and the neighbours moved out from under it. A checklist written against protocols bgp 64512 neighbor ... matches nothing on a 1.5 router — and an empty result from a review command reads exactly like a missing control, which is the worst way for a checklist to fail.

A typical BGP configuration has 5-50 neighbours; the review can be done by hand in 10-15 minutes. For a larger deployment, show configuration commands is line-oriented text, so the subtraction is a comm or an awk away — but keep the output of both lists in the change ticket, not just the verdict.

Checklist item 2: every OSPF interface has authentication

show configuration commands | match "protocols ospf interface"
show configuration commands | match "protocols ospf" | match "authentication"
show configuration commands | match "protocols ospf interface" | match "passive"

For every OSPF interface, the operator verifies that authentication is configured. On VyOS 1.5 the per-interface OSPF settings live under the protocol, not under the interface:

set protocols ospf interface eth1 authentication md5 key-id 1 md5-key COORDINATE-OUT-OF-BAND

The third grep is the one operators forget. An interface that faces a customer or a user VLAN and is not passive is running OSPF at anyone who plugs in, and authentication limits the damage rather than removing it: a passive interface still advertises the prefix and never forms an adjacency, which is the control you actually wanted. Read the two lists together — an interface that is neither authenticated nor passive is the finding.

Checklist item 3: the control plane is rate-limited

show configuration commands | match "firewall ipv4 input filter"
show configuration commands | match "firewall ipv4 input filter" | match "limit"
show configuration commands | match "firewall ipv6 input filter" | match "limit"

The mechanism is a limit rate and limit burst on the rule that accepts each class, so the accept keeps working at the legitimate rate and the excess falls through to whatever follows:

set firewall group address-group BGP-PEERS address '10.0.0.5'
set firewall group address-group BGP-PEERS address '10.0.0.9'

set firewall ipv4 input filter default-action 'drop'

set firewall ipv4 input filter rule 20 action 'accept'
set firewall ipv4 input filter rule 20 description 'SSH from management, rate limited'
set firewall ipv4 input filter rule 20 protocol 'tcp'
set firewall ipv4 input filter rule 20 destination port '22'
set firewall ipv4 input filter rule 20 source address '10.99.0.0/24'
set firewall ipv4 input filter rule 20 state 'new'
set firewall ipv4 input filter rule 20 limit rate '4/minute'
set firewall ipv4 input filter rule 20 limit burst '8'

set firewall ipv4 input filter rule 30 action 'accept'
set firewall ipv4 input filter rule 30 description 'BGP from configured peers only'
set firewall ipv4 input filter rule 30 protocol 'tcp'
set firewall ipv4 input filter rule 30 destination port '179'
set firewall ipv4 input filter rule 30 source group address-group BGP-PEERS

set firewall ipv4 input filter rule 40 action 'accept'
set firewall ipv4 input filter rule 40 description 'ICMP to the router, rate limited'
set firewall ipv4 input filter rule 40 protocol 'icmp'
set firewall ipv4 input filter rule 40 limit rate '10/second'
set firewall ipv4 input filter rule 40 limit burst '20'

Two of those rules are rate limited and one is not, and that is the judgement the checklist is really reviewing. limit on the SSH rule bounds a password-guessing rate without bounding an established session, because state new restricts the rule to the handshake. limit on ICMP bounds a flood. Putting a limit on BGP would be a mistake: the legitimate rate of a full-table convergence is not something you can guess, and the consequence of guessing low is that you drop your own routing. BGP is protected by who, not by how fast — a source address group holding exactly the configured peers, which is the narrowest control available and the one to verify.

Checklist item 4: SSH is hardened

show configuration commands | match "service ssh"
show configuration commands | match "system login user"
show configuration commands | match "system login user" | match "public-keys"

The controls the 1.5 service ssh tree actually exposes, and what each one is for:

set service ssh port '2222'
set service ssh listen-address '10.99.0.1'
set service ssh password-authentication 'disable'
set service ssh root-login 'disable'
set service ssh ciphers ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com']
set service ssh key-exchange ['curve25519-sha256', 'diffie-hellman-group18-sha512']
set service ssh mac ['hmac-sha2-512-etm@openssh.com', 'hmac-sha2-256-etm@openssh.com']

The review reads the third grep against the second: every account in system login user that has no public-keys entry is an account that cannot log in once password-authentication is disable, which is either the control working or a lockout waiting to happen, and the checklist is the place to find out which.

Checklist item 5: the firewall is in place and reachable

show configuration commands | match "firewall ipv4 input filter default-action"
show configuration commands | match "firewall ipv4 forward filter default-action"
show configuration commands | match "firewall ipv4 name"
show configuration commands | match "jump-target"
show firewall ipv4 input filter

The operator verifies at least:

  • default-action 'drop' on the base hooks. input filter is traffic to the router, forward filter is traffic through it, output filter is traffic the router originates. A default of accept on either of the first two makes every rule below it advisory.
  • An established / related rule near the top of each chain. On 1.5 these are separate values of state (state established, state related), not an enable leaf under a state node — the 1.3 form does not commit.
  • A rule for each routing protocol you actually run, scoped by source where the peers are static. BGP is TCP 179 in the input chain; OSPF is protocol ospf; both are addressed to the router, so neither belongs in forward.
  • A rule for management, scoped to the management prefix as in item 3.

The fourth grep is the one that catches the failure this item exists for. A firewall ipv4 name <SET> rule set is inert on its own: nothing in it is evaluated until a base hook contains a rule with action jump and a jump-target naming it. A router can carry a beautifully written 40-rule set that has never seen a packet, and the configuration review will show it as present. show firewall ipv4 input filter closes that gap by printing the live counters — a chain where every counter is zero is either a very quiet router or a chain nothing reaches.

Out-of-band management

The OOB management path is what the operator uses when the production network is down or compromised. Three mechanisms are typical:

  1. Serial console — a physical serial port on the router that connects to a terminal server. The terminal server is on a separate physical network (often a different ISP) and is reachable when the production network is down.
  2. IPMI / iLO / iDRAC — a dedicated out-of-band management interface on the server (not a router feature, but applicable to virtualised routers). The IPMI interface is on a separate physical network.
  3. Dedicated management VLAN — a separate VLAN carried over a separate physical path to the router. The management VLAN carries SSH and HTTPS only.
flowchart LR
  OP["Operator<br/>workstation"] -->|"terminal server"| OOB["OOB network"]
  OOB -->|"serial / IPMI"| R["Router"]
  OP -->|"SSH"| PROD["Production network"]
  PROD --> R

The diagram shows the two paths: the production path through SSH over the production network, and the OOB path through the terminal server. The OOB path is the operator’s lifeline when the production network is down.

# A dedicated management interface, in its own VRF
set vrf name mgmt table '100'
set interfaces ethernet eth1 address '10.99.0.1/24'
set interfaces ethernet eth1 description 'OOB management'
set interfaces ethernet eth1 vrf 'mgmt'

# Run the SSH daemon inside that VRF
set service ssh vrf 'mgmt'
set service ssh password-authentication 'disable'

# One named account per human, with a key
set system login user netops full-name 'NetOps on-call'
set system login user netops authentication public-keys oncall@jump type 'ssh-ed25519'
set system login user netops authentication public-keys oncall@jump key 'AAAAC3NzaC1lZDI1NTE5AAAAIExampleKeyMaterialGoesHere'

The management interface is on a separate network (10.99.0.0/24) with its own routing table, so a route leaked or injected in the default table cannot pull management traffic onto the production path.

The account carries a key rather than a password, which is what makes password-authentication 'disable' survivable. Where a password is unavoidable, the node is set system login user netops authentication plaintext-password 'value'. VyOS hashes it during commit and the running configuration afterwards shows encrypted-password with a $6$ hash, so the cleartext exists only in your terminal scrollback — which is its own reason to prefer the key.

The audit log

The audit log is the operator’s defence against the post-incident question “what happened?”. The log records every configuration change, every authentication attempt, and every system event:

The audit trail has two halves on VyOS, and a checklist that only reviews one of them has a hole in it.

Events go to syslog. The router ships them to a collector on the OOB network:

set system syslog host '10.99.0.100' facility all level 'info'
set system syslog console facility all level 'warning'

facility all level 'info' on the remote is the volume you want off the box. level 'warning' on the console is deliberate and not a typo: an info-level console floods the serial line with routine chatter, and the serial line is the one path you will be reading during the incident when everything else has failed. Part XLVIII covers the syslog tree, including the TCP and TLS transports, in detail.

Configuration does not go to syslog. VyOS keeps a commit archive on the box and can ship a copy of every committed configuration off it:

set system config-management commit-revisions '100'
set system config-management commit-archive location 'scp://archive@10.99.0.100:/srv/router-configs'

Every successful commit writes a revision locally and pushes a copy to that location. This is the half that answers “what changed”, because it is the only half that contains the configuration itself.

The audit trail therefore captures:

  • Configuration changes — the commit archive holds one full configuration per commit; show system commit lists the revisions with the user and the timestamp, and compare <revA> <revB> produces the diff between any two of them. The diff is computed on demand from the stored revisions, not recorded at commit time.
  • Authentication attempts — every SSH login attempt is logged by the daemon with the source address, the username, and the outcome, and reaches the collector through syslog.
  • System events — daemon restarts, interface transitions, routing protocol state changes, also through syslog.

Both destinations sit on the OOB network, so both survive a compromise of the production path.

Failure modes

Checklist not run

The operator does not run the checklist. The configuration drifts over time: a new BGP peer is added without authentication, a CoPP rule is removed for debugging. Six months later, an attacker compromises the new peer and uses the missing authentication to inject routes.

Diagnostic: the checklist is a manual review; the absence of a review is the failure.

Fix: schedule the checklist review on a recurring calendar event (quarterly is typical). The review is a manual walk of the checklist items with the operator verifying each control.

OOB network down

The OOB network has its own ISP and its own switches. The ISP goes down; the terminal server is unreachable. The operator has no path to the router except the production network.

Diagnostic: the operator cannot SSH to the terminal server. The terminal server’s status page is offline.

Fix: the OOB network is a single point of failure. Defensive idiom: dual-ISP OOB network with automatic failover, or a cellular modem as a tertiary path.

Audit trail never leaves the box

The operator configures logging and the commit archive and stops there. Both are files on the router’s disk, and an attacker with configuration access owns both.

Diagnostic: show configuration commands | match "system syslog host" returns nothing, and so does show configuration commands | match "commit-archive". Two empty results, two halves of the audit trail that exist only where the incident can reach them.

Fix: a remote syslog host and a commit-archive location, both on the OOB network. Note that these are two separate findings with two separate fixes — configuring the syslog host and calling the item done leaves the configuration history unprotected, which is the half that answers the question you will actually be asked.

Default credentials left in place

The operator installs VyOS and does not change the default vyos user password. An attacker scans the Internet for VyOS routers with the default password; the operator’s router is found.

Diagnostic: show configuration commands | match "system login user". An account whose only authentication line is an encrypted-password is a password account; whether that password is the installer default is not something the configuration can tell you, which is why this item is a rotation rather than a check.

Fix: give the account a key (system login user <name> authentication public-keys), set service ssh password-authentication 'disable' once the key is proven from a second session, and rotate the password anyway so the console path is not the default either. Where a RADIUS or TACACS+ backend exists (system login radius / system login tacacs), the local account becomes the break-glass credential — keep exactly one, document where its password is held, and set system login user <name> disable the rest rather than deleting them, so the removal is visible in the commit archive.

Rollback

A hardening change is reversible through the standard VyOS mechanisms:

  • rollback N reverts to stored revision N. On 1.5 this loads the revision into the candidate configuration and commits it, so it is a change like any other — and it reverts everything, not just the item you regret.
  • delete service ssh listen-address <ip> and commit to remove a listen-address restriction.
  • delete system syslog host <ip> and commit to remove a remote syslog destination.
  • delete system login user <user> disable re-enables a suspended account; delete system login user <user> removes it outright.
  • commit-confirm 5 before any of the firewall or SSH items, every time. Each of them can end the session you are typing into, and the OOB path exists precisely because that happens.

The VyOS commit validator checks syntax and internal consistency. It has no opinion about whether a BGP peer has a password or whether the input chain defaults to drop, so nothing about a clean commit says the controls survived the change. That gap is the whole reason this checklist is a human walk on a calendar.

Production discipline

Cross-course references

  • LIII-VyOS-Security (this part) covers the authentication, CoPP, and prefix-filtering mechanisms that the checklist reviews.
  • XLVII-VyOS-MgmtHardening covers the management-plane hardening (SSH, login users, PKI) in detail.
  • XLVIII-VyOS-LoggingSyslog covers the logging and remote syslog configuration in detail.
  • vyos-xxiv-04-bgp-authentication covers why password is the mechanism the BGP tree exposes and TCP-AO is not.
  • vyos-iv-05-post-install-hardening covers the first-boot version of these controls on a router that has just been installed.
  • The CIS Benchmarks for VyOS provide a community-driven hardening checklist that complements this lesson.

Quiz

Knowledge check · 4 questions

  1. Q1. What is the operator's defence against the post-incident question "what changed?"?

  2. Q2. A router hardening checklist is best run as an automated scan that fires once at install time.

  3. Q3. R1 is a production edge router. The operator runs the hardening checklist and finds that BGP neighbour 10.0.0.5 (added by a colleague two months ago) does not have authentication configured. The colleague's commit message in the audit log says "added BGP peer for new upstream". What is the security impact and what is the fix?

    R1 has a BGP neighbour 10.0.0.5 that does not have authentication. The neighbour was added two months ago. The colleague's commit message is the only evidence of the addition.

  4. Q4. R1's audit log is shipped to a remote syslog server on the production network. The production network is compromised: an attacker has gained access to the management plane and can read the syslog traffic. The operator wants to ensure the audit log is preserved even when the production network is compromised. What is the fix?

    R1's remote syslog is on the production network. An attacker who has compromised the production network can read the syslog traffic and may be able to manipulate it. The operator wants the audit log to be on a network the attacker cannot reach.

Passing score: 75%. Answers are checked in this browser.