VRRP HMAC Authentication¶
Strongly recommended for unicast
VRRP over unicast carries no authentication of its own. The TTL guard that
protects multicast falls away once adverts cross a router, so an off-segment
attacker can spoof an advert that demotes your master or forces an election.
We strongly encourage every unicast deployment to enable auth_hmac, and the
rest of this page shows how.
VRRP authenticates an advert by trusting the hop limit. An active router sends every advert with an IPv4 TTL or IPv6 hop limit of 255, and a receiver drops anything that arrives lower, which proves the packet never crossed a router. That guard is the Generalized TTL Security Mechanism, and it is the only protection VRRPv3 carries. It holds only as long as adverts stay on one link.
Unicast breaks that assumption. When Keepalived runs in a cloud or overlay network where multicast is unavailable, adverts travel as routed unicast to a configured peer list, they cross hops, and they arrive well below 255. The receiver has to relax the check to accept them, and once it does, nothing stops an off-segment attacker. Anyone who can land a spoofed packet at the receiver injects a higher priority advert to demote the master, or a priority 0 advert to force an election, and captured adverts replay just as easily.
Routed / overlay: no multicast, TTL decremented en route
ACTIVE --- advert (VRID 51, prio 200) ---> BACKUP
198.51.100.10 192.0.2.10
^
| spoofed advert: src = 192.0.2.10 (a peer), prio 255
ATTACKER
203.0.113.9
The auth_hmac extension closes that gap. It appends a trailer to each advert
carrying an HMAC-SHA256 tag truncated to 128 bits and a time-based sequence
number. The tag proves the advert came from a holder of the shared key and was
not altered, the sequence number proves it is fresh, and both behave the same
for VRRPv2 and v3, IPv4 and IPv6, unicast and multicast. It encrypts nothing,
because adverts are not secret, and a captured advert is useless once it leaves
the freshness window. The legacy authentication block never offered this. Its
cleartext password travels in the open, which is why RFC 3768 removed it, and
the IPSEC-AH option only ever existed for VRRPv2.
Specification
The wire format, the pseudo-header bound into the MAC, and the full threat model are specified in the IETF Internet-Draft referenced at the bottom of this page.
Minimal configuration¶
The extension links OpenSSL and rides on the same build flag that enabled the
legacy authentication block. Every node in the virtual router shares the same
keys under the same key ids, since the construction is symmetric and
provisioning the key material to each node is your responsibility. With no
mode set the block enforces, so a node rejects any advert that lacks a valid
trailer. To bring the extension up on a running cluster without dropping
adverts, start in permissive mode while you roll out, as described below.
The two nodes carry a near-mirror configuration, the active on 198.51.100.10
and the backup on 192.0.2.10. Only the state, the priority and the unicast
addresses differ between them.
vrrp_instance VI_1 {
state MASTER
interface eth0
virtual_router_id 51
priority 200
advert_int 1
unicast_src_ip 198.51.100.10
unicast_peer {
192.0.2.10
}
auth_hmac {
key 1 hex:000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f
active_key 1
anti_replay time
time_window 5
}
virtual_ipaddress {
192.0.2.100/32
}
}
vrrp_instance VI_1 {
state BACKUP
interface eth0
virtual_router_id 51
priority 100
advert_int 1
unicast_src_ip 192.0.2.10
unicast_peer {
198.51.100.10
}
auth_hmac {
key 1 hex:000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f
active_key 1
anti_replay time
time_window 5
}
virtual_ipaddress {
192.0.2.100/32
}
}
The auth_hmac block is identical on both nodes, as it is on every node in the
virtual router. key defines a signing key by id, active_key selects the id
used when sending, and a receiver verifies an incoming advert against whichever
configured key its key_id names. auth_hmac and the legacy authentication
block are mutually exclusive, and Keepalived drops the legacy one when both
appear.
Generating and sourcing keys¶
A key is 32 to 64 bytes, and 32 random bytes is the right default because it keeps a 128 bit margin even against a quantum search. Generate one with:
Carry the value inline as a hex: string or an ASCII passphrase, and the
secret then lives in keepalived.conf. To keep it out of the configuration,
read it from a file instead, where the file holds the same hex: line or a raw
ASCII key:
Keepalived warns when that file is readable by group or others, so keep it
root owned and mode 0600.
Keys from systemd credentials¶
The strongest way to source a key pairs file: with a systemd encrypted
credential, which seals the key to the TPM or host key and never writes it to
disk in the clear. Provision the credential once, sealed and readable only by
root:
umask 077
install -d /etc/keepalived/creds
printf 'hex:%s' "$(openssl rand -hex 32)" > /run/vrrp51.plain
systemd-creds encrypt --name=vrrp51 \
/run/vrrp51.plain /etc/keepalived/creds/vrrp51.cred
shred -u /run/vrrp51.plain
Then add a unit drop-in so systemd decrypts it at start, installed as
/etc/systemd/system/keepalived.service.d/auth_hmac.conf:
After systemctl daemon-reload && systemctl restart keepalived, systemd
decrypts the credential, exposes it read only to root on a private tmpfs at
/run/credentials/keepalived.service, drops it when the unit stops, and points
$CREDENTIALS_DIRECTORY there. Reference it from keepalived.conf with the
${_ENV ...} expansion, where the credential name is the last path component:
Without a TPM or host key, swap LoadCredentialEncrypted for LoadCredential
with a plaintext key file, which still lands on the same root only tmpfs, just
not encrypted at rest.
To carry several keys, for instance across a rotation, give each key id its own credential. Provision each one as above, then list them in the drop-in with a numbered scheme:
[Service]
LoadCredentialEncrypted=vrrp51_1:/etc/keepalived/creds/vrrp51_1.cred
LoadCredentialEncrypted=vrrp51_2:/etc/keepalived/creds/vrrp51_2.cred
systemd writes one file per credential under $CREDENTIALS_DIRECTORY, so each
key id reads from its own:
auth_hmac {
key 1 file:${_ENV CREDENTIALS_DIRECTORY}/vrrp51_1
key 2 file:${_ENV CREDENTIALS_DIRECTORY}/vrrp51_2
active_key 1
}
A numbered unit line per key works, but you edit the unit on every rotation. To
avoid that, keep the encrypted blobs in the system credential store and import
the whole set with one glob. Encrypt each key into /etc/credstore.encrypted/,
where the filename is the credential name:
printf 'hex:%s' "$(openssl rand -hex 32)" \
| systemd-creds encrypt --name=vrrp51_1 - /etc/credstore.encrypted/vrrp51_1
A single directive then imports every match, while the key lines stay exactly
as above:
Adding a key later is one more systemd-creds encrypt into the store plus a
key line, with no unit edit, since the glob already covers it.
ImportCredential= needs systemd 254 or newer. A ready to install drop-in with
these variants ships under doc/samples/.
Replay protection¶
anti_replay time, the default, enforces a freshness window and a per-sender
monotonic sequence, so it needs the nodes to agree on wall clock time within
time_window seconds. The window accepts 1 to 300 seconds and defaults to
max(3 x advert_int, 5), and a small window such as 5 seconds is a good start
when NTP is healthy. anti_replay monotonic drops the clock dependency and only
asks each sender's sequence to keep growing, which suits deployments that cannot
guarantee synchronized clocks, at the cost of the freshness guarantee.
Rolling out without breaking the cluster¶
A Keepalived that does not understand the trailer drops any advert that carries one, because the receive path checks the exact length and fails closed. Sequence the migration so no node sends a signed advert to a node that cannot yet accept it.
- Upgrade the binary on every node first, with no configuration change, so nothing signs and the cluster behaves as before.
- Add the
auth_hmacblock inmode permissiveeverywhere, then reload the nodes in a tight sweep. A reloaded node signs and, being permissive, still accepts unsigned adverts from peers not yet reloaded. Keep the sweep well under the master down interval, since a not yet reloaded peer drops the signed adverts and a long sweep risks a needless election. - Confirm every node now sends and verifies trailers. Authenticated adverts flow both ways, though an unsigned injected advert is still accepted while you stay permissive.
- Switch
mode enforceeverywhere and reload again. Every peer already signs, so nothing drops, and from now on an advert without a valid trailer is rejected.
strict_mode requires enforce, so finish the migration before turning strict
mode on.
Rotating keys¶
Because a receiver accepts any configured key id, rotation is make before break.
Add the new key under a fresh id on every node while keeping the old
active_key, then reload so each node verifies both ids while still signing
with the old one:
Move active_key 2 on every node and reload, so senders switch to the new key
that receivers already accept, then drop the retired key and reload once more.
Verifying the rollout¶
Keepalived logs and counts each authentication outcome per instance, rate limited, so you can watch the migration and spot trouble. A missing trailer is informational in permissive mode and means a node is not signing once you enforce. An invalid MAC points at a key mismatch or a forgery attempt. A stale trailer is reported apart from an invalid MAC, so you can tell clock drift from an attack, and a clock skew warning fires on accepted adverts once the skew passes half the window, which is the early signal to fix NTP before adverts start failing.
Configuration reference¶
The keepalived.conf(5) man page carries
the same auth_hmac block in its VRRP instance example as the canonical
reference. The keywords are:
| Keyword | Values | Default | Notes |
|---|---|---|---|
key <id> <value> |
id 1 to 255; value hex:..., an ASCII string, or file:<path> |
none | 32 to 64 bytes, define several for rotation |
active_key <id> |
1 to 255 | none | the id used when signing |
anti_replay |
time or monotonic |
time |
time needs synced clocks |
time_window <sec> |
1 to 300 | max(3 x advert_int, 5) |
freshness window, time mode only |
mode |
enforce or permissive |
enforce |
start permissive to migrate |
Security notes¶
The construction is symmetric with no key exchange on the wire, so it has no exposure to Shor's algorithm and stays post-quantum resistant by design. A quantum search only halves the effective key strength, which is the reason for the 32 byte minimum, since it preserves a 128 bit margin. The MAC binds the address family, VRRP version, virtual router id, and source address into the digest, so an attacker cannot splice a valid trailer onto a different instance or a different sender, while the IP header stays out of the MAC, which keeps the design clean across IPv4, IPv6, and the unicast TTL handling. The trailer adds 28 bytes to each advert, and Keepalived reserves that room when it decides how many virtual addresses fit in a packet, so on an instance whose address list sits near the interface MTU a few addresses move to the excluded set rather than overflowing the packet.
IETF draft¶
The on-the-wire format and the security analysis are specified in an IETF
Internet-Draft, An HMAC Authentication Extension for the Virtual Router
Redundancy Protocol (VRRP) (draft-cassen-vrrp-auth-hmac), by A. Cassen and
Q. Armitage. The link will point at the IETF Datatracker once the draft is
published.