Skip to main content
RunBook Academy

VyOSLIII · Security HardeningSecurity

RPKI — Route Origin Authorisation, validation cache, and route validation

Advanced⏱ ~28 minvyosvtyshshow rpki cache-connectionshow rpki prefix-tableshow rpki prefixshow bgp ipv4 unicast rpki

What you'll learn

  • Explain the RPKI trust hierarchy and the ROA structure
  • Configure an RPKI validator cache and connect the BGP speaker to it
  • Apply RPKI invalid drop as part of the leak firewall
  • Recognise the production failure modes where RPKI is unavailable or misconfigured

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.

RPKI (Resource Public Key Infrastructure) is the cryptographic complement to prefix-list filtering. Where the prefix-list says “this peer is allowed to advertise this cone”, RPKI says “this AS is authorised by the resource holder to originate this prefix”. A BGP route whose origin AS does not match an RPKI ROA is a hijack attempt or a misconfiguration that the prefix-list alone cannot detect.

On VyOS 1.5 LTS / FRR 10.x, RPKI is configured through a validator cache (an external service that fetches ROAs from the RPKI trust anchors) and the RTR protocol (the cache-to-router protocol that delivers the ROAs to the BGP speaker). The BGP speaker then validates every received route against the ROA set and marks the route as valid, invalid, or not-found.

This lesson covers the RPKI trust hierarchy, the ROA structure, the validator cache, the BGP route validation, and the production deployment pattern.

The RPKI trust hierarchy

RPKI is a hierarchy of cryptographic certificates that maps IP prefixes and AS numbers to the entities that hold them. The hierarchy mirrors the Internet’s allocation hierarchy — but it does not have a single root:

  • Each RIR operates its own trust anchor. RIPE NCC, ARIN, APNIC, LACNIC and AFRINIC each publish a self-signed trust anchor certificate covering the resources they administer. There are five roots, not one; IANA does not sign them. A validator is bootstrapped with five Trust Anchor Locator files (TALs), one per RIR, and a validator missing a TAL is simply blind to everything under it.
  • Local Internet Registries (ISPs, enterprises) hold the address space the RIR allocated to them. Each LIR’s resource certificate is signed by the parent RIR’s certificate.

The certificates are X.509 with the RFC 3779 resource extensions that carry the prefixes and ASNs. The tree is published in RFC 8182 (RRDP) and rsync repositories; validators fetch them periodically and rebuild the chain from each TAL downwards.

flowchart TB
  TAL1[RIPE NCC\ntrust anchor] --> LIR1[Local ISP\nresource certificate]
  TAL1 --> LIR2[Enterprise\nresource certificate]
  TAL2[ARIN\ntrust anchor]
  TAL3[APNIC\ntrust anchor]
  LIR1 --> ROA1[ROA\n192.168.0.0/16\nmax-length /24\norigin AS 65001]
  LIR1 --> ROA2[ROA\n192.169.0.0/16\nmax-length /16\norigin AS 65001]
  LIR2 --> ROA3[ROA\n10.0.0.0/8\nmax-length /16\norigin AS 65002]

The diagram shows three of the five independent trees. A ROA is a signed statement by the resource holder that a specific AS is authorised to originate a specific prefix with a maximum prefix length. Nothing above the RIR signs anything, which is why “who do I trust?” in RPKI is answered by which TALs your validator loaded, not by a single root key.

The ROA structure

A Route Origin Authorisation (ROA) is the leaf object in the RPKI tree. It contains:

  • AS number — the AS authorised to originate the prefix.
  • Prefix — the IP prefix (e.g. 192.168.0.0/16).
  • Maximum prefix length — the longest prefix the AS is authorised to advertise (e.g. /24 means the AS may advertise any sub-prefix up to /24 inclusive).

A ROA is signed by the LIR’s certificate. The signature proves that the LIR — the entity that holds the address space — authorises the AS to originate the prefix.

# Example ROA in JSON form (RPKI repository format)
{
  "asn": "AS65001",
  "prefix": "192.168.0.0/16",
  "maxLength": 24,
  "ta": "ripe"
}

The ROA says: AS 65001 is authorised to originate 192.168.0.0/16 with sub-prefixes up to /24. An advertisement of 192.168.1.0/25 (longer than /24) would be RPKI invalid; an advertisement of 192.168.1.0/24 is RPKI valid.

The validator cache and the RTR protocol

The BGP speaker does not fetch ROAs directly. The BGP speaker connects to a validator cache (a separate service) using the RPKI to Router Protocol (RTR, RFC 8210). The cache fetches the RPKI repositories, validates the certificate chain, and presents the ROAs to the BGP speaker.

flowchart LR
  R1["Local router\nFRR / VyOS"] -->|RTR\nTCP/3323| CACHE["RPKI cache\nRoutinator, rpki-client\n+ StayRTR, FORT"]
  CACHE -->|rsync\nHTTPS| REPO1["RIPE NCC\nrepository"]
  CACHE -->|rsync\nHTTPS| REPO2["ARIN\nrepository"]
  CACHE -->|rsync\nHTTPS| REPO3["APNIC\nrepository"]

RTR is a small binary protocol over plain TCP (RFC 8210 registers port 323; most implementations ship on 3323). The router asks for the current set, the cache answers with it and a serial number, and from then on the router polls for the delta since that serial rather than re-downloading everything. VyOS also supports running RTR over SSH: each cache takes an ssh username and an ssh key, with the key generated by generate ssh client-key /config/auth/id_rsa_rpki. That is the option to reach for when the cache is not on a trusted management segment.

A production deployment typically runs one or more validator caches on dedicated hosts. The caches fetch the RPKI repositories from the RIRs; the BGP speakers fetch the ROAs from the caches.

# VyOS 1.5 — connect to two RPKI caches
set protocols rpki cache 10.0.0.1 port '3323'
set protocols rpki cache 10.0.0.1 preference '1'
set protocols rpki cache 10.0.0.2 port '3323'
set protocols rpki cache 10.0.0.2 preference '2'

# Timers (these are the FRR defaults, shown so they can be reasoned about)
set protocols rpki polling-period '300'
set protocols rpki retry-interval '600'
set protocols rpki expire-interval '7200'

The configuration specifies one or more caches with a preference; lower is preferred. The BGP speaker connects to the most preferred cache that is reachable and falls back to the next.

Two notes on those numbers. RFC 8210 registers TCP/323 for RPKI-RTR, but Routinator, StayRTR and friends default to 3323 because that is above the privileged range and they do not run as root — check what your cache actually listens on rather than assuming the RFC port. And the three timers are the ones the operator has to hold in their head during an incident: polling-period is how often the router asks for updates, retry-interval is how long it waits before reconnecting after a failure, and expire-interval is how long the already-downloaded ROA set stays usable once the cache is gone. That last one is why a cache outage does not blind the router immediately.

BGP route validation

The BGP speaker validates every received route against the ROA set. The validation result is one of three states:

  • Valid — the route’s prefix and origin AS match a ROA, and the prefix length does not exceed the ROA’s max-length.
  • Invalid — at least one ROA exists for the prefix, but the route’s origin AS does not match, or the prefix length exceeds the ROA’s max-length.
  • Not-found — no ROA exists for the prefix.
flowchart LR
  R[Received route\n192.168.1.0/24\norigin AS 65001] --> V{Validate\nagainst ROAs}
  V --> ROA1[ROA:\n192.168.0.0/16\nmax /24\norigin AS 65001]
  ROA1 -->|match| VALID[Valid]
  R2[Received route\n192.168.1.0/24\norigin AS 65002] --> V
  V --> ROA2[ROA:\n192.168.0.0/16\nmax /24\norigin AS 65001]
  ROA2 -->|origin mismatch| INVALID[Invalid]
  R3[Received route\n10.0.0.0/8\norigin AS 65003] --> V
  V --> ROA3[No matching ROA]
  ROA3 -->|no ROA| NOTFOUND[Not found]

The production BGP configuration uses the validation state to filter incoming routes:

# Route-map that denies RPKI invalid
set policy route-map DROP-RPKI-INVALID rule 10 action 'deny'
set policy route-map DROP-RPKI-INVALID rule 10 match rpki 'invalid'

set policy route-map DROP-RPKI-INVALID rule 20 action 'permit'

# Apply it inbound on the peer, under the address family
set protocols bgp system-as '64512'
set protocols bgp neighbor 10.0.0.1 remote-as '65002'
set protocols bgp neighbor 10.0.0.1 address-family ipv4-unicast route-map import 'DROP-RPKI-INVALID'

The route-map denies routes whose RPKI status is invalid. Routes whose status is valid or not-found pass through rule 20.

The last line is where a pre-1.4 configuration breaks. On VyOS 1.4 and 1.5 the peer lives at set protocols bgp neighbor <address> rather than under the ASN, the local ASN is the system-as leaf, and the inbound policy is attached under the address family as route-map import <name>. There is no import-filter node in the tree at all — a configuration carrying one does not half-apply the policy, it fails to commit.

Production deployment

A production RPKI deployment has three components:

  1. Validator cache — a routinator or gortr installation that fetches the RPKI repositories and serves the ROA set over RTR. The cache is typically deployed on a dedicated host in the operator’s infrastructure.
  2. BGP speaker — every edge router that needs RPKI validation connects to the validator cache via RTR.
  3. ROA publication — the operator publishes ROAs for their own prefixes at the appropriate RIR’s RPKI portal (RIPE NCC’s myRIPE, ARIN’s online portal, etc.).

The defensive idiom: every edge router that connects to an upstream provider or to a customer cone runs RPKI validation. The operator who runs RPKI only on the aggregation router is one configuration error away from a hijacked prefix entering the local table.

Failure modes

RPKI cache unreachable

The BGP speaker cannot reach the validator cache. This is the failure mode with a delay fuse: nothing changes for up to expire-interval seconds, because the previously downloaded table is still being used. When it expires, every route becomes notfound, the deny invalid rule stops matching anything, and the router accepts hijacks it would have rejected an hour earlier. No BGP session goes down. No route disappears. The only symptom is a filter that no longer filters.

Diagnostic — three commands, in this order:

show rpki cache-connection
show rpki prefix-table
show bgp ipv4 unicast rpki invalid

The first says whether the RTR session to each configured cache is up. The second says whether there is still a payload table to validate against — an empty table with caches configured is the expired state. The third is the one to trend: a peer that was producing a steady trickle of invalid routes and now produces none has either cleaned up its act or lost validation, and it is almost never the former.

Fix: restore connectivity to the validator cache. The defensive idiom is two or more caches on different hosts and different network paths, plus an alert on show rpki cache-connection rather than on BGP state, because BGP state will not tell you.

RPKI invalid drop too aggressive

The operator enables RPKI invalid drop before all customer ROAs are published. A legitimate customer prefix without a ROA is marked not-found (not invalid); the import route-map passes it through. But the operator’s configuration accidentally drops not-found too, and the customer prefix is dropped.

Fix: read the route-map back and confirm that the only deny rule matches invalid. The rendered FRR view is the one that decides, so read it rather than the VyOS tree:

show configuration commands | match DROP-RPKI-INVALID
vtysh -c 'show route-map DROP-RPKI-INVALID'

A rule matching rpki notfound with action deny is the bug. So is a route-map with no final permit rule: FRR’s implicit deny at the end of a route-map filters everything that reached it, so a DROP-RPKI-INVALID that lost its rule 20 does not drop invalids, it drops the peer’s entire table.

ROA publication error

The operator publishes a ROA for 192.168.0.0/16 with max-length /16. The operator’s BGP speaker advertises 192.168.1.0/24 — which is a more specific prefix. The ROA’s max-length /16 does not authorise the /24; the /24 is marked invalid. The operator’s own prefix is dropped.

Diagnostic — ask the router what the payload table says about the prefix and origin pair, which is a direct answer rather than an inference:

show rpki prefix 192.168.1.0/24 65001
show bgp ipv4 unicast rpki invalid

show rpki prefix reports the covering ROA and the max-length it carries. If the covering entry says max-length 16 while the announcement is a /24, the arithmetic is the whole diagnosis. The second command lists what the local router is currently calling invalid; on a peer that already drops invalids, your prefix will be absent from show bgp ipv4 unicast entirely, which is the symptom your customer reports as “unreachable”.

Fix: re-publish the ROA with max-length /24. The defensive idiom: max-length should be set to the longest sub-prefix the operator intends to advertise. A max-length of /16 for a customer cone with /24 sub-prefixes is too restrictive.

A repository is stale or unfetchable

There is no unknown state in RTR. The cache sends the router validated ROA payloads — prefix, max-length, origin AS — and an object that fails validation is simply not in that set. Everything the router did not receive a payload for is notfound. That is the whole state machine, and it is worth holding on to, because it means every repository problem presents identically at the router: prefixes silently move from valid to notfound.

So when a CA’s repository goes unfetchable, or its manifest expires, or a publication point is mid-update and inconsistent, the router sees no error. It sees a smaller prefix table. Your own prefix can move from valid to notfound on every peer in the world without a single log line on your router.

Diagnostic: this one is not answered on the router. Read the validator’s own logs and metrics — Routinator, rpki-client and FORT all report per-repository fetch status and object counts — and compare the router’s show rpki prefix-table count against the validator’s VRP count. A router table that is much smaller than the validator’s set means the RTR session, not RPKI, is the problem.

Fix: usually to wait — publication points converge on their own — while monitoring the count rather than assuming. A validator whose fetch has been failing for hours is a page, not a graph to admire.

Rollback

An RPKI configuration change is reversible through the standard VyOS mechanisms:

# Remove one cache server
delete protocols rpki cache 10.0.0.1

# Remove the RPKI drop from one peer, leaving the session up
delete protocols bgp neighbor 10.0.0.1 address-family ipv4-unicast route-map import

# Or revert the whole change set
rollback 1

Note the shape of the second command: on 1.4 and 1.5 the peer is addressed as protocols bgp neighbor <ip> with no ASN in the path, and the policy hangs off the address family as route-map import. The pre-1.4 spelling with the ASN in the path, or with an import-filter node, does not delete anything on 1.5 — it fails.

Removing the import route-map is a policy change, not a session reset: FRR re-evaluates the peer’s stored routes and the previously dropped invalids reappear in the table within seconds. That makes it a genuinely fast escape hatch when RPKI is filtering something it should not, and also a fast way to accept a hijack, so it belongs in a change record either way.

The commit validator does not check that the cache is reachable — a configuration referencing a cache that has never answered commits perfectly happily. The only proof is show rpki cache-connection after the commit.

Production discipline

Cross-course references

  • LIII-VyOS-Security (vyos-liii-03-prefix-filtering, the previous lesson) covers the prefix-list / AS-path filtering that complements RPKI.
  • XXVIII-VyOS-BGPPrefixFilter (vyos-xxviii-04-filter-list) covers the BGP filter-list mechanism.
  • The RPKI trust hierarchy is documented in RFC 6480; the BGP origin validation is documented in RFC 6811; the RTR protocol is documented in RFC 8210.

Quiz

Knowledge check · 4 questions

  1. Q1. What does an RPKI ROA authorise?

  2. Q2. A BGP route whose prefix is covered by no ROA at all is marked RPKI `not-found`.

  3. Q3. R1 is connected to a validator cache at 10.0.0.1. The cache is unreachable due to a network partition. R1's RPKI validation state for every received route is now `not-found`. The operator configured RPKI invalid drop on the BGP neighbour 10.0.0.2 (AS 65002). What is the impact?

    R1's connection to the validator cache is down. R1's RPKI validation is not operational. The BGP neighbour has RPKI invalid drop configured.

  4. Q4. R1 originates 192.168.1.0/24 in AS 65001. The operator publishes a ROA for 192.168.0.0/16 with origin AS 65001 and max-length /16. After deploying RPKI invalid drop, R1's own prefix 192.168.1.0/24 is being dropped by peers running RPKI. What went wrong?

    R1 originates 192.168.1.0/24 in AS 65001. The operator published a ROA for 192.168.0.0/16 with origin AS 65001 and max-length /16. RPKI invalid drop is enabled. The /24 is being dropped by peers.

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