<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Berik Ashimov</title>
    <description>The latest articles on DEV Community by Berik Ashimov (@ashimov).</description>
    <link>https://dev.to/ashimov</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F985440%2Fdf4ff69f-13bb-4ebe-b054-c238dbafa68c.jpeg</url>
      <title>DEV Community: Berik Ashimov</title>
      <link>https://dev.to/ashimov</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ashimov"/>
    <language>en</language>
    <item>
      <title>BGP Edge Hygiene at a PCI-Regulated Fintech: IRR + RPKI in Production</title>
      <dc:creator>Berik Ashimov</dc:creator>
      <pubDate>Fri, 15 May 2026 17:23:20 +0000</pubDate>
      <link>https://dev.to/ashimov/bgp-edge-hygiene-at-a-pci-regulated-fintech-irr-rpki-in-production-3gm4</link>
      <guid>https://dev.to/ashimov/bgp-edge-hygiene-at-a-pci-regulated-fintech-irr-rpki-in-production-3gm4</guid>
      <description>&lt;p&gt;A single hijacked prefix can route a chunk of payment traffic into a stranger's network for half an hour before anyone notices. For a payment provider, that is not a routing incident. It is a regulatory event, an exposed-traffic incident, and an auditor knocking on Monday morning.&lt;/p&gt;

&lt;p&gt;This post walks through the BGP edge hygiene we ran in production at a national fintech: what we filtered, how we automated it, what broke, and a copy-paste checklist at the end.&lt;/p&gt;

&lt;h2&gt;
  
  
  The threat model in 200 words
&lt;/h2&gt;

&lt;p&gt;If you run a public-facing AS, the internet routing system trusts you and your peers to announce only what you should announce. That trust is not enforced by default. Five classes of problem will hurt you:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Route hijacks&lt;/strong&gt; where a remote AS originates your prefix and pulls traffic away.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Route leaks&lt;/strong&gt; where a transit customer accidentally re-announces full tables to a peer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sub-prefix hijacks&lt;/strong&gt;, more-specific announcements that win longest-prefix-match.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;BGP optimizer leaks&lt;/strong&gt;, a known class of incident where a vendor box generates synthetic more-specifics and a misconfigured peer re-advertises them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Static fat-fingers from your own ops team&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For a payment provider the consequences are not just availability. They include confidentiality risk on traffic that touches the cardholder data environment, PCI DSS scope expansion, and customer trust damage that lasts longer than any outage.&lt;/p&gt;

&lt;p&gt;We treated the BGP edge as a perimeter control, not a routing function. That framing pulled the security team, the compliance team, and the network team into the same review cycle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Five layers of edge hygiene
&lt;/h2&gt;

&lt;p&gt;We layered five filters on every external eBGP session. Each one alone is insufficient. Together they cut routing-related incidents to near zero over the year we measured.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 1: max-prefix per session
&lt;/h3&gt;

&lt;p&gt;The cheapest, most effective control. If a peer accidentally leaks full tables to us, we want to tear the session down, not crash the box.&lt;/p&gt;

&lt;p&gt;Cisco IOS-XR:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cisco_ios"&gt;&lt;code&gt;&lt;span class="kt"&gt;router bgp&lt;/span&gt;&lt;span class="nf"&gt; 65000&lt;/span&gt;
&lt;span class="k"&gt; neighbor&lt;/span&gt; &lt;span class="m"&gt;203.0.113.1&lt;/span&gt;
&lt;span class="k"&gt;  remote-as&lt;/span&gt; 64500
&lt;span class="k"&gt;  address-family&lt;/span&gt; ipv4 unicast
&lt;span class="k"&gt;   maximum-prefix&lt;/span&gt; 5000 80 restart 5
&lt;span class="k"&gt;  !&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Junos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;protocols&lt;/span&gt; {
    &lt;span class="n"&gt;bgp&lt;/span&gt; {
        &lt;span class="n"&gt;group&lt;/span&gt; &lt;span class="n"&gt;EBGP&lt;/span&gt;-&lt;span class="n"&gt;PEERS&lt;/span&gt; {
            &lt;span class="n"&gt;neighbor&lt;/span&gt; &lt;span class="m"&gt;203&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;.&lt;span class="m"&gt;113&lt;/span&gt;.&lt;span class="m"&gt;1&lt;/span&gt; {
                &lt;span class="n"&gt;family&lt;/span&gt; &lt;span class="n"&gt;inet&lt;/span&gt; {
                    &lt;span class="n"&gt;unicast&lt;/span&gt; {
                        &lt;span class="n"&gt;prefix&lt;/span&gt;-&lt;span class="n"&gt;limit&lt;/span&gt; {
                            &lt;span class="n"&gt;maximum&lt;/span&gt; &lt;span class="m"&gt;5000&lt;/span&gt;;
                            &lt;span class="n"&gt;teardown&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt; &lt;span class="n"&gt;idle&lt;/span&gt;-&lt;span class="n"&gt;timeout&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;;
                        }
                    }
                }
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The 80% warning threshold is the important part. You want a syslog message before you go down, not after.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 2: AS-path filters
&lt;/h3&gt;

&lt;p&gt;Reject any prefix whose AS-path contains your own ASN. This catches the surprisingly common case where a peer somewhere has a stale route through your AS and tries to send it back to you.&lt;/p&gt;

&lt;p&gt;Junos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;policy&lt;/span&gt;-&lt;span class="n"&gt;options&lt;/span&gt; {
    &lt;span class="n"&gt;as&lt;/span&gt;-&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="n"&gt;OWN&lt;/span&gt;-&lt;span class="n"&gt;AS&lt;/span&gt;-&lt;span class="n"&gt;IN&lt;/span&gt;-&lt;span class="n"&gt;PATH&lt;/span&gt; &lt;span class="s2"&gt;".* 65000 .*"&lt;/span&gt;;
    &lt;span class="n"&gt;policy&lt;/span&gt;-&lt;span class="n"&gt;statement&lt;/span&gt; &lt;span class="n"&gt;DENY&lt;/span&gt;-&lt;span class="n"&gt;OWN&lt;/span&gt;-&lt;span class="n"&gt;AS&lt;/span&gt; {
        &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;as&lt;/span&gt;-&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="n"&gt;OWN&lt;/span&gt;-&lt;span class="n"&gt;AS&lt;/span&gt;-&lt;span class="n"&gt;IN&lt;/span&gt;-&lt;span class="n"&gt;PATH&lt;/span&gt;;
        &lt;span class="n"&gt;then&lt;/span&gt; &lt;span class="n"&gt;reject&lt;/span&gt;;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also reject paths containing private ASNs (64512 to 65534 and 4200000000 to 4294967294) on peering sessions where they have no business appearing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 3: IRR-based prefix filters
&lt;/h3&gt;

&lt;p&gt;For every peer that is not a Tier 1 transit, build a per-peer prefix filter from their published &lt;code&gt;as-set&lt;/code&gt; in IRR. If a prefix is not in their published origin set, drop it.&lt;/p&gt;

&lt;p&gt;Generate with &lt;code&gt;bgpq4&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bgpq4 &lt;span class="nt"&gt;-h&lt;/span&gt; whois.radb.net &lt;span class="nt"&gt;-4&lt;/span&gt; &lt;span class="nt"&gt;-A&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; PEER-EXAMPLE-IN AS-EXAMPLE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This produces a prefix-list ready to paste into Cisco IOS-XR or Junos config. Pin to a specific IRR source (RADb, RIPE, ARIN) per peer to avoid junk from less-trusted databases.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 4: RPKI Route Origin Validation
&lt;/h3&gt;

&lt;p&gt;RPKI ROV is the only one of these five layers that gives you cryptographic origin authentication. Configure a local validator (Routinator, rpki-client, or OctoRPKI), feed it to the routers, and drop &lt;code&gt;invalid&lt;/code&gt; at the edge.&lt;/p&gt;

&lt;p&gt;Cisco IOS-XR:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cisco_ios"&gt;&lt;code&gt;&lt;span class="kt"&gt;router bgp&lt;/span&gt;&lt;span class="nf"&gt; 65000&lt;/span&gt;
&lt;span class="k"&gt; rpki&lt;/span&gt; server &lt;span class="m"&gt;192.0.2.10&lt;/span&gt;
&lt;span class="k"&gt;  transport&lt;/span&gt; tcp port 3323
&lt;span class="k"&gt;  refresh-time&lt;/span&gt; 600
&lt;span class="k"&gt; !&lt;/span&gt;
&lt;span class="k"&gt; address-family&lt;/span&gt; ipv4 unicast
&lt;span class="k"&gt;  bgp&lt;/span&gt; bestpath origin-as use validity
&lt;span class="k"&gt; !&lt;/span&gt;

&lt;span class="k"&gt;route-policy&lt;/span&gt; RPKI-DROP-INVALID
&lt;span class="k"&gt;  if&lt;/span&gt; validation-state is invalid then
&lt;span class="k"&gt;    drop&lt;/span&gt;
&lt;span class="k"&gt;  else&lt;/span&gt;
&lt;span class="k"&gt;    pass&lt;/span&gt;
&lt;span class="k"&gt;  endif&lt;/span&gt;
&lt;span class="k"&gt;end-policy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apply the policy &lt;code&gt;in&lt;/code&gt; on every eBGP session. Do not "prefer valid", drop invalid. Halfway measures here are how partial hijacks slip through.&lt;/p&gt;

&lt;p&gt;Run at least two validators. We ran three (Routinator + rpki-client + a vendor appliance) and reconciled them in monitoring. If one validator goes stale, you do not want it silently flipping prefixes to NotFound.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 5: bogons, reserved, and martians
&lt;/h3&gt;

&lt;p&gt;Drop RFC1918, RFC6598 (100.64.0.0/10), documentation prefixes (192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24), default route (unless you genuinely accept it), and anything with a prefix length above your accepted maximum (typically /24 for IPv4 and /48 for IPv6).&lt;/p&gt;

&lt;p&gt;This catches misconfigurations from your peers more often than malice. It is also what auditors look for in PCI DSS section 1 reviews.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automating it with Ansible
&lt;/h2&gt;

&lt;p&gt;Manual prefix-list updates do not scale past a handful of peers and they rot fast. IRR data changes daily. We built a small Ansible role that does the boring part.&lt;/p&gt;

&lt;p&gt;Pipeline:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pull each peer's &lt;code&gt;as-set&lt;/code&gt; from RADb or RIPE using &lt;code&gt;bgpq4&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Render the per-peer prefix-list with a Jinja2 template.&lt;/li&gt;
&lt;li&gt;Diff against the running config. If the delta exceeds a threshold (say, plus or minus 10%), flag for human review instead of pushing.&lt;/li&gt;
&lt;li&gt;Push via Netmiko or NAPALM, with &lt;code&gt;commit confirm&lt;/code&gt; rollback on Junos and a &lt;code&gt;commit replace&lt;/code&gt; with diff preview on IOS-XR.&lt;/li&gt;
&lt;li&gt;Re-run nightly via cron. IRR data drifts continuously.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Minimal Ansible task:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Generate prefix-list for peer&lt;/span&gt;
  &lt;span class="na"&gt;ansible.builtin.command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="s"&gt;bgpq4 -h whois.radb.net -4 -A&lt;/span&gt;
    &lt;span class="s"&gt;-l "{{ peer.name }}-IN"&lt;/span&gt;
    &lt;span class="s"&gt;{{ peer.as_set }}&lt;/span&gt;
  &lt;span class="na"&gt;register&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prefix_list&lt;/span&gt;
  &lt;span class="na"&gt;changed_when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Render config snippet&lt;/span&gt;
  &lt;span class="na"&gt;ansible.builtin.template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;peer_filter.j2&lt;/span&gt;
    &lt;span class="na"&gt;dest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/var/network-configs/{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;peer.name&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}.conf"&lt;/span&gt;
  &lt;span class="na"&gt;vars&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;prefix_list_body&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;prefix_list.stdout&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Sanity check delta&lt;/span&gt;
  &lt;span class="na"&gt;ansible.builtin.script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;scripts/diff_check.py "{{ peer.name }}.conf"&lt;/span&gt;
  &lt;span class="na"&gt;register&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;delta&lt;/span&gt;
  &lt;span class="na"&gt;failed_when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;delta.rc not in [0, 2]&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Apply config&lt;/span&gt;
  &lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;delta.rc == &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
  &lt;span class="na"&gt;junipernetworks.junos.junos_config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/var/network-configs/{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;peer.name&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}.conf"&lt;/span&gt;
    &lt;span class="na"&gt;comment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;auto-update&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;prefix-list&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;peer.name&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
    &lt;span class="na"&gt;confirm&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;confirm: 5&lt;/code&gt; on Junos is the safety net. If the commit breaks the session and you cannot re-commit within 5 minutes, the box rolls back automatically.&lt;/p&gt;

&lt;p&gt;Store every generated config in git. When something goes sideways at 03:00, you want to see what changed in the last 24 hours, not guess.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three things production taught us
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Lesson 1: max-prefix saves you from your peers
&lt;/h3&gt;

&lt;p&gt;A regional peer added a new aggregate to their &lt;code&gt;as-set&lt;/code&gt; in IRR. Our nightly job picked it up and added it to the filter. The following morning their upstream withdrew the route entirely. Our session would have accepted the larger filter and idled around the new boundary. The 80% warning threshold on max-prefix fired an alert hours before the session reached the actual limit. We caught the IRR drift before anyone noticed at the routing layer.&lt;/p&gt;

&lt;p&gt;The lesson: IRR data is necessary but not authoritative. Always wrap it in a max-prefix that reflects the peer's actual size plus some headroom, not their theoretical max.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lesson 2: "prefer valid" is not "drop invalid"
&lt;/h3&gt;

&lt;p&gt;Early in the rollout we configured RPKI to prefer valid over invalid rather than dropping invalid outright. The reasoning seemed sound at the time: do not break things on day one.&lt;/p&gt;

&lt;p&gt;Then a partial hijack happened. A misconfigured AS announced an invalid origin for a /24 that overlapped a covered /22. "Prefer valid" picked the valid /22, but the more-specific /24 still won longest-prefix match because no valid /24 existed in the BGP table. Traffic for that /24 went to the hijacker for about 12 minutes.&lt;/p&gt;

&lt;p&gt;We flipped to drop-invalid on every session that night. We lost a small handful of legitimate prefixes whose ROAs were misconfigured by their owners. We sent them email. They fixed it. Net incident count went down, not up.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lesson 3: pre-commit diff validation catches typos
&lt;/h3&gt;

&lt;p&gt;An AS-path regex update almost dropped a legitimate transit prefix because the regex matched too aggressively. The Ansible diff-check script flagged a delta of minus 1,200 prefixes against the previous run. We caught it before push.&lt;/p&gt;

&lt;p&gt;If your automation does not have a "this change looks too big, ask a human" gate, build one. The cost is one extra step per change. The benefit is not paging your team at 02:00 because a regex ate the internet.&lt;/p&gt;

&lt;h2&gt;
  
  
  How this maps to PCI DSS
&lt;/h2&gt;

&lt;p&gt;If you operate in a PCI environment, the BGP edge is in scope, even if you sometimes argue otherwise. The relevant controls map cleanly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Requirement 1.2 (network segmentation):&lt;/strong&gt; A clean edge defines your perimeter. RPKI drop-invalid is the cleanest possible argument that you only accept authenticated origins.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Requirement 10 (logging):&lt;/strong&gt; Every RPKI validation state transition, every max-prefix warning, and every prefix-list change must reach your SIEM. Auditors will ask.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Requirement 11.4 (intrusion detection):&lt;/strong&gt; Anomalous prefix-count deltas, sudden session flaps, and unexpected origin AS changes are network IDS signals. Wire them to alerting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Requirement 6.4 (change management):&lt;/strong&gt; Git-backed configs, pre-commit diff validation, and commit-confirm rollback are change management. Show the auditor the commit log and the rollback playbook.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Done right, network hygiene becomes compliance-by-design, not a separate workstream.&lt;/p&gt;

&lt;h2&gt;
  
  
  The take-home checklist
&lt;/h2&gt;

&lt;p&gt;If you are setting this up in your own environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ ] max-prefix per eBGP session, with 80% warning threshold
[ ] AS-path filter rejecting your own ASN in the path
[ ] AS-path filter rejecting private ASNs on public sessions
[ ] IRR prefix filter per non-transit peer, regenerated daily
[ ] RPKI ROV with drop-invalid (not prefer-valid)
[ ] At least two RPKI validators, reconciled in monitoring
[ ] Bogon and reserved prefix drops at the edge
[ ] Maximum prefix length filter (typically /24 v4, /48 v6)
[ ] Pre-commit diff validation with a "too big" abort
[ ] commit-confirm or equivalent rollback on push
[ ] Git-backed configs with audit log
[ ] Logging RPKI state transitions to SIEM
[ ] Alerting on prefix-count delta over 10 percent
[ ] Quarterly review of every peer as-set
[ ] Runbook for sudden BGP session flap
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you implement the first six items, you have removed the largest sources of BGP edge risk for your AS. Everything else is operational polish.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final note
&lt;/h2&gt;

&lt;p&gt;None of this is novel work. IRR has been around since the 1990s, RPKI since the early 2010s. What is novel is that for most payment providers, fintechs, and regulated networks, this is still not the default configuration. The gap between "everyone should be doing this" and "everyone is doing this" is where edge incidents come from.&lt;/p&gt;

&lt;p&gt;If your AS is announced on the public internet and you cannot tick off most of the checklist above, you have homework.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I write about production network reliability, BGP edge security, and infrastructure automation. Find me on &lt;a href="https://www.linkedin.com/in/berik-ashimov" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; or reach out at &lt;a href="mailto:berik@ashimov.com"&gt;berik@ashimov.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>networking</category>
      <category>devops</category>
      <category>security</category>
      <category>sre</category>
    </item>
  </channel>
</rss>
