fix: correct ASPA RFC citation and broken submission instructions
Reapplied against the current main (post src/features/ refactor), since the old aspa/ module was carried over unchanged and still had both bugs. Also caught two spots the stale local clone never had: public/index.html's title= attribute and the newer index-classic.html. ASPA has no RFC yet, still draft-ietf-sidrops-aspa-profile (object format) and draft-ietf-sidrops-aspa-verification (path validation), both in WG Last Call. "RFC 9582" is the ROA profile RFC, unrelated. The generated "submit via RIPE DB webupdates / auto-dbm@ripe.net" instructions were wrong too. ASPA objects are signed RPKI objects created through a RIR's hosted RPKI platform, not RPSL text pasted into the whois database. Replaced generateASPAObject/ generateRipeDbTemplate with summarizeASPAObject (review summary) and generateASPASubmissionGuide (per-RIR: status, where to actually create the object, source link). aspa_generate now takes an optional rir param instead of a RIPE-specific maintainer handle. Also corrected an overprecise APNIC launch date: APNIC's own blog doesn't give one, only a vague "since Nov 2025" for all three live RIRs collectively, which doesn't match RIPE's and ARIN's own more precise announcements.
This commit is contained in:
parent
aa43c68554
commit
5c1cb1652b
@ -511,7 +511,7 @@ a{color:var(--blue);text-decoration:none;transition:color .2s}a:hover{color:var(
|
||||
|
||||
<div style="background:var(--bg);border:1px solid var(--border);border-radius:10px;padding:1rem;display:flex;gap:.75rem;align-items:flex-start">
|
||||
<div style="width:36px;height:36px;border-radius:8px;background:rgba(255,158,100,.12);border:1px solid rgba(255,158,100,.2);display:flex;align-items:center;justify-content:center;flex-shrink:0;font-size:1rem">🔐</div>
|
||||
<div><div style="font-weight:700;font-size:.85rem;color:var(--orange)">ASPA (RFC 9582)</div><div style="font-size:.7rem;color:var(--muted);margin-top:.15rem">AS Provider Authorization from Cloudflare RPKI JSON feed. RFC-compliant upstream/downstream path verification with valley detection.</div><a href="https://www.ietf.org/archive/id/draft-ietf-sidrops-aspa-verification-14.html" target="_blank" style="font-size:.65rem;color:var(--blue)">IETF Draft-14</a></div>
|
||||
<div><div style="font-weight:700;font-size:.85rem;color:var(--orange)">ASPA (IETF draft, not yet an RFC)</div><div style="font-size:.7rem;color:var(--muted);margin-top:.15rem">AS Provider Authorization from Cloudflare RPKI JSON feed. Draft-compliant upstream/downstream path verification with valley detection.</div><a href="https://datatracker.ietf.org/doc/draft-ietf-sidrops-aspa-verification/" target="_blank" style="font-size:.65rem;color:var(--blue)">IETF Draft</a></div>
|
||||
</div>
|
||||
|
||||
<div style="background:var(--bg);border:1px solid var(--border);border-radius:10px;padding:1rem;display:flex;gap:.75rem;align-items:flex-start">
|
||||
|
||||
@ -733,7 +733,7 @@ a:hover{text-decoration:underline}
|
||||
<div class="verdict-body">
|
||||
<div class="verdict-label">ASPA</div>
|
||||
<div class="verdict-value valid">DEPLOYED</div>
|
||||
<div class="verdict-sub">RFC 9582 — provider set complete</div>
|
||||
<div class="verdict-sub">ASPA draft, provider set complete</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -880,7 +880,7 @@ a:hover{text-decoration:underline}
|
||||
<div class="health-mark pass">✓</div>
|
||||
<div>
|
||||
<div class="health-text">ASPA record present</div>
|
||||
<div class="health-sub">RFC 9582 provider authorisation</div>
|
||||
<div class="health-sub">ASPA draft provider authorisation</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="health-item">
|
||||
|
||||
@ -554,7 +554,7 @@ a{color:var(--blue);text-decoration:none;transition:color .2s}a:hover{color:var(
|
||||
|
||||
<div style="background:var(--bg);border:1px solid var(--border);border-radius:10px;padding:1rem;display:flex;gap:.75rem;align-items:flex-start">
|
||||
<div style="width:36px;height:36px;border-radius:8px;background:rgba(255,158,100,.12);border:1px solid rgba(255,158,100,.2);display:flex;align-items:center;justify-content:center;flex-shrink:0;font-size:1rem">🔐</div>
|
||||
<div><div style="font-weight:700;font-size:.85rem;color:var(--orange)">ASPA (RFC 9582)</div><div style="font-size:.7rem;color:var(--muted);margin-top:.15rem">AS Provider Authorization from Cloudflare RPKI JSON feed. RFC-compliant upstream/downstream path verification with valley detection.</div><a href="https://www.ietf.org/archive/id/draft-ietf-sidrops-aspa-verification-14.html" target="_blank" style="font-size:.65rem;color:var(--blue)">IETF Draft-14</a></div>
|
||||
<div><div style="font-weight:700;font-size:.85rem;color:var(--orange)">ASPA (IETF draft, not yet an RFC)</div><div style="font-size:.7rem;color:var(--muted);margin-top:.15rem">AS Provider Authorization from Cloudflare RPKI JSON feed. Draft-compliant upstream/downstream path verification with valley detection.</div><a href="https://datatracker.ietf.org/doc/draft-ietf-sidrops-aspa-verification/" target="_blank" style="font-size:.65rem;color:var(--blue)">IETF Draft</a></div>
|
||||
</div>
|
||||
|
||||
<div style="background:var(--bg);border:1px solid var(--border);border-radius:10px;padding:1rem;display:flex;gap:.75rem;align-items:flex-start">
|
||||
|
||||
@ -581,7 +581,7 @@ a:hover{color:var(--purple)}
|
||||
|
||||
<div style="background:var(--bg);border:1px solid var(--border);border-radius:0;padding:1rem;display:flex;gap:.75rem;align-items:flex-start">
|
||||
<div style="width:36px;height:36px;border-radius:0;background:rgba(180,83,9,.08);border:1px solid rgba(180,83,9,.2);display:flex;align-items:center;justify-content:center;flex-shrink:0;font-size:1rem">🔐</div>
|
||||
<div><div style="font-weight:700;font-size:.85rem;color:var(--orange);font-family:var(--body)">ASPA (RFC 9582)</div><div style="font-size:.7rem;color:var(--muted);margin-top:.15rem;font-family:var(--body)">AS Provider Authorization from Cloudflare RPKI JSON feed. RFC-compliant upstream/downstream path verification with valley detection.</div><a href="https://www.ietf.org/archive/id/draft-ietf-sidrops-aspa-verification-14.html" target="_blank" style="font-size:.65rem;color:var(--blue);font-family:var(--mono)">IETF Draft-14</a></div>
|
||||
<div><div style="font-weight:700;font-size:.85rem;color:var(--orange);font-family:var(--body)">ASPA (IETF draft, not yet an RFC)</div><div style="font-size:.7rem;color:var(--muted);margin-top:.15rem;font-family:var(--body)">AS Provider Authorization from Cloudflare RPKI JSON feed. Draft-compliant upstream/downstream path verification with valley detection.</div><a href="https://datatracker.ietf.org/doc/draft-ietf-sidrops-aspa-verification/" target="_blank" style="font-size:.65rem;color:var(--blue);font-family:var(--mono)">IETF Draft</a></div>
|
||||
</div>
|
||||
|
||||
<div style="background:var(--bg);border:1px solid var(--border);border-radius:0;padding:1rem;display:flex;gap:.75rem;align-items:flex-start">
|
||||
|
||||
@ -558,7 +558,7 @@ body.dark .card{border-top-color:#e8e4dc}
|
||||
</section>
|
||||
|
||||
<!-- ASPA Status -->
|
||||
<section class="card" id="aspaCard" title="ASPA (Autonomous System Provider Authorization) status — RFC 9582. Verifies whether this AS has published which providers are authorized to forward its routes, protecting against route leaks">
|
||||
<section class="card" id="aspaCard" title="ASPA (Autonomous System Provider Authorization) status. Still an IETF draft (draft-ietf-sidrops-aspa-profile), not yet an RFC. Verifies whether this AS has published which providers are authorized to forward its routes, protecting against route leaks">
|
||||
<div class="card-title">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 3H5a2 2 0 0 0-2 2v4m6-6h10a2 2 0 0 1 2 2v4M9 3v18m0 0h10a2 2 0 0 0 2-2v-4M9 21H5a2 2 0 0 1-2-2v-4"/></svg>
|
||||
ASPA Status
|
||||
@ -815,7 +815,7 @@ body.dark .card{border-top-color:#e8e4dc}
|
||||
|
||||
<div style="background:var(--bg);border:1px solid var(--border);border-radius:0;padding:1rem;display:flex;gap:.75rem;align-items:flex-start">
|
||||
<div style="width:36px;height:36px;border-radius:0;background:rgba(180,83,9,.08);border:1px solid rgba(180,83,9,.2);display:flex;align-items:center;justify-content:center;flex-shrink:0;font-size:1rem">🔐</div>
|
||||
<div><div style="font-weight:700;font-size:.85rem;color:var(--orange);font-family:var(--body)">ASPA (RFC 9582)</div><div style="font-size:.7rem;color:var(--muted);margin-top:.15rem;font-family:var(--body)">AS Provider Authorization from Cloudflare RPKI JSON feed. RFC-compliant upstream/downstream path verification with valley detection.</div><a href="https://www.ietf.org/archive/id/draft-ietf-sidrops-aspa-verification-14.html" target="_blank" style="font-size:.65rem;color:var(--blue);font-family:var(--mono)">IETF Draft-14</a></div>
|
||||
<div><div style="font-weight:700;font-size:.85rem;color:var(--orange);font-family:var(--body)">ASPA (IETF draft, not yet an RFC)</div><div style="font-size:.7rem;color:var(--muted);margin-top:.15rem;font-family:var(--body)">AS Provider Authorization from Cloudflare RPKI JSON feed. Draft-compliant upstream/downstream path verification with valley detection.</div><a href="https://datatracker.ietf.org/doc/draft-ietf-sidrops-aspa-verification/" target="_blank" style="font-size:.65rem;color:var(--blue);font-family:var(--mono)">IETF Draft</a></div>
|
||||
</div>
|
||||
|
||||
<div style="background:var(--bg);border:1px solid var(--border);border-radius:0;padding:1rem;display:flex;gap:.75rem;align-items:flex-start">
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
* and individual networks. Useful for tracking the rollout of ASPA
|
||||
* and identifying adoption gaps.
|
||||
*
|
||||
* @see https://www.rfc-editor.org/rfc/rfc9582
|
||||
* @see https://datatracker.ietf.org/doc/draft-ietf-sidrops-aspa-profile/ (object format, still an IETF draft, not yet an RFC)
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
|
||||
@ -1,11 +1,16 @@
|
||||
/**
|
||||
* @module aspa/generator
|
||||
* Auto-generate ASPA objects from BGP data.
|
||||
* Detect upstream providers from BGP data and produce an ASPA submission guide.
|
||||
*
|
||||
* Analyzes BGP path data to detect upstream provider relationships,
|
||||
* then generates ASPA objects in RIPE DB format for registration.
|
||||
* Analyzes BGP path data to detect upstream provider relationships, then
|
||||
* produces a plain-language provider list plus per-RIR instructions for
|
||||
* creating the actual ASPA object. Real ASPA objects are signed RPKI/CMS
|
||||
* objects created through your RIR's hosted RPKI platform (or a delegated
|
||||
* RPKI CA); they are not RPSL text and cannot be submitted through the
|
||||
* RIPE Database (whois) the way an aut-num or route object can. There is
|
||||
* no `aspa:` or `upstream:` attribute in the RIPE DB schema.
|
||||
*
|
||||
* @see https://www.rfc-editor.org/rfc/rfc9582
|
||||
* @see https://datatracker.ietf.org/doc/draft-ietf-sidrops-aspa-profile/ (object format, still an IETF draft, not yet an RFC)
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
@ -13,9 +18,9 @@
|
||||
* console.log(providers);
|
||||
* // [{ asn: 174, name: "Cogent", confidence: 0.95, pathCount: 42 }]
|
||||
*
|
||||
* const template = generateRipeDbTemplate(64501, providers, "MNT-EXAMPLE");
|
||||
* console.log(template);
|
||||
* // Ready-to-paste RIPE DB ASPA object
|
||||
* const guide = generateASPASubmissionGuide(64501, providers);
|
||||
* console.log(guide);
|
||||
* // Provider list + a link to the RIR's actual RPKI dashboard
|
||||
* ```
|
||||
*/
|
||||
|
||||
@ -138,56 +143,56 @@ export function detectProviders(
|
||||
});
|
||||
}
|
||||
|
||||
// ── ASPA Object Generation ──────────────────────────────
|
||||
// ── ASPA Provider Summary ────────────────────────────────
|
||||
|
||||
/**
|
||||
* Generate an ASPA object in RPSL text format.
|
||||
* Summarize detected providers in plain language, in the ASPA object's
|
||||
* logical field order (customerASID, then providers ascending by ASN,
|
||||
* per draft-ietf-sidrops-aspa-profile).
|
||||
*
|
||||
* Produces a human-readable ASPA object suitable for display or
|
||||
* manual registration. Includes comments explaining each field.
|
||||
* This is a human-readable summary for review, not a submittable object.
|
||||
* There is no text format you can paste into a registry to create an
|
||||
* ASPA object; it must be created through your RIR's RPKI platform.
|
||||
*
|
||||
* @param asn - The customer ASN
|
||||
* @param providers - Detected upstream providers
|
||||
* @returns RPSL-formatted ASPA object text
|
||||
* @returns Plain-language summary of the ASPA object's intended content
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const text = generateASPAObject(64501, [
|
||||
* const text = summarizeASPAObject(64501, [
|
||||
* { asn: 174, name: "Cogent", confidence: 0.95, pathCount: 42, afi: ["ipv4", "ipv6"] },
|
||||
* ]);
|
||||
* console.log(text);
|
||||
* // aut-num: AS64501
|
||||
* // aspa: AS64501
|
||||
* // upstream: AS174 # Cogent (confidence: 95%, seen in 42 paths)
|
||||
* // ...
|
||||
* // Customer AS64501 would authorize: AS174 (Cogent, confidence 95%, seen in 42 paths)
|
||||
* ```
|
||||
*/
|
||||
export function generateASPAObject(
|
||||
export function summarizeASPAObject(
|
||||
asn: number,
|
||||
providers: ReadonlyArray<Provider>
|
||||
): string {
|
||||
const lines: string[] = [
|
||||
`% ASPA object for AS${asn}`,
|
||||
`% Generated by PeerCortex on ${new Date().toISOString()}`,
|
||||
`% Based on BGP path analysis — review before submitting to your RIR`,
|
||||
`%`,
|
||||
`% ASPA (Autonomous System Provider Authorization) declares which ASNs`,
|
||||
`% are authorized upstream providers of this AS. This helps prevent`,
|
||||
`% route leaks by allowing RPKI validators to verify AS path legitimacy.`,
|
||||
`%`,
|
||||
`% Reference: RFC 9582 — Autonomous System Provider Authorization`,
|
||||
`# ASPA summary for AS${asn}`,
|
||||
`# Generated by PeerCortex on ${new Date().toISOString()}`,
|
||||
`# Based on BGP path analysis. Review before creating the real object.`,
|
||||
`#`,
|
||||
`# ASPA (Autonomous System Provider Authorization) declares which ASNs`,
|
||||
`# are authorized upstream providers of this AS. This helps prevent`,
|
||||
`# route leaks by allowing RPKI validators to verify AS path legitimacy.`,
|
||||
`#`,
|
||||
`# Reference: IETF draft-ietf-sidrops-aspa-profile (not yet an RFC).`,
|
||||
`# This is a summary for review only. It is NOT a submittable object.`,
|
||||
`# ASPA objects are signed RPKI objects created through your RIR's`,
|
||||
`# hosted RPKI platform, not RPSL text you paste into a registry.`,
|
||||
``,
|
||||
`aut-num: AS${asn}`,
|
||||
`aspa: AS${asn}`,
|
||||
`Customer: AS${asn}`,
|
||||
];
|
||||
|
||||
for (const provider of providers) {
|
||||
const sortedProviders = [...providers].sort((a, b) => a.asn - b.asn);
|
||||
for (const provider of sortedProviders) {
|
||||
const afiStr = provider.afi.join(", ");
|
||||
const comment = `# ${provider.name} (confidence: ${Math.round(provider.confidence * 100)}%, seen in ${provider.pathCount} paths)`;
|
||||
lines.push(`upstream: AS${provider.asn} ${comment}`);
|
||||
if (provider.afi.length === 1) {
|
||||
lines.push(` afi: ${afiStr}`);
|
||||
}
|
||||
const comment = `${provider.name} (confidence: ${Math.round(provider.confidence * 100)}%, seen in ${provider.pathCount} paths, ${afiStr})`;
|
||||
lines.push(` Provider: AS${provider.asn} # ${comment}`);
|
||||
}
|
||||
|
||||
lines.push(``);
|
||||
@ -195,80 +200,105 @@ export function generateASPAObject(
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
/** Which Regional Internet Registry an ASN belongs to, for RIR-specific guidance */
|
||||
export type RIR = "ripe" | "arin" | "apnic" | "lacnic" | "afrinic";
|
||||
|
||||
/** Per-RIR ASPA production status and the correct place to create the object */
|
||||
const RIR_ASPA_INFO: Record<
|
||||
RIR,
|
||||
{ readonly status: string; readonly howTo: string; readonly infoUrl: string }
|
||||
> = {
|
||||
ripe: {
|
||||
status: "Production since 15 Dec 2025",
|
||||
howTo: "RIPE NCC LIR Portal, RPKI Dashboard, ASPA section",
|
||||
infoUrl: "https://www.ripe.net/manage-ips-and-asns/resource-management/rpki/aspa/",
|
||||
},
|
||||
arin: {
|
||||
status: "Production since 20 Jan 2026",
|
||||
howTo: "ARIN Online, Routing Security menu, ASPA section",
|
||||
infoUrl: "https://www.arin.net/resources/manage/rpki/aspa/",
|
||||
},
|
||||
apnic: {
|
||||
status: "Production (exact launch date not published by APNIC; blog post from 9 Jul 2026 confirms support is live)",
|
||||
howTo: "MyAPNIC or the APNIC Registry API",
|
||||
infoUrl: "https://help.apnic.net/s/article/ASPA",
|
||||
},
|
||||
lacnic: {
|
||||
status: "Not yet in production. Committed for end of 2026",
|
||||
howTo: "Not yet available",
|
||||
infoUrl: "https://blog.lacnic.net/en/programa-rpki-nro-resumen/",
|
||||
},
|
||||
afrinic: {
|
||||
status: "Not yet in production, no committed date",
|
||||
howTo: "Not yet available",
|
||||
infoUrl: "https://blog.afrinic.net/nro-rpki-program-2025-in-review",
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate a complete RIPE DB template ready for submission.
|
||||
* Produce a submission guide for an ASN's detected providers.
|
||||
*
|
||||
* Produces a full RPSL object including maintainer, source, and
|
||||
* administrative fields required for RIPE DB submission.
|
||||
* This does NOT produce a pasteable object, because ASPA objects are
|
||||
* signed RPKI objects, not RPSL text. It produces the provider list to
|
||||
* review plus the correct place to actually create the object for the
|
||||
* given RIR.
|
||||
*
|
||||
* @param asn - The customer ASN
|
||||
* @param providers - Detected upstream providers
|
||||
* @param maintainer - RIPE DB maintainer handle (e.g., "MNT-EXAMPLE")
|
||||
* @returns Complete RIPE DB template text
|
||||
* @param rir - Which RIR this ASN is registered with
|
||||
* @returns Human-readable guide: provider list plus RIR-specific instructions
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const template = generateRipeDbTemplate(
|
||||
* const guide = generateASPASubmissionGuide(
|
||||
* 13335,
|
||||
* [{ asn: 174, name: "Cogent", confidence: 0.95, pathCount: 100, afi: ["ipv4", "ipv6"] }],
|
||||
* "MNT-CLOUDFLARE"
|
||||
* "ripe"
|
||||
* );
|
||||
* // Paste this into https://apps.db.ripe.net/db-web-ui/webupdates
|
||||
* ```
|
||||
*/
|
||||
export function generateRipeDbTemplate(
|
||||
export function generateASPASubmissionGuide(
|
||||
asn: number,
|
||||
providers: ReadonlyArray<Provider>,
|
||||
maintainer: string
|
||||
rir: RIR
|
||||
): string {
|
||||
const info = RIR_ASPA_INFO[rir];
|
||||
const lines: string[] = [
|
||||
`% ============================================================`,
|
||||
`% ASPA Object Template for AS${asn}`,
|
||||
`% Generated by PeerCortex — ${new Date().toISOString()}`,
|
||||
`% ============================================================`,
|
||||
`%`,
|
||||
`% INSTRUCTIONS:`,
|
||||
`% 1. Review the provider list below for accuracy`,
|
||||
`% 2. Remove any providers you no longer use`,
|
||||
`% 3. Add any providers that were not detected`,
|
||||
`% 4. Submit via: https://apps.db.ripe.net/db-web-ui/webupdates`,
|
||||
`% 5. Or via email to auto-dbm@ripe.net`,
|
||||
`%`,
|
||||
`% NOTE: ASPA objects are part of the RPKI framework.`,
|
||||
`% Your RIR must support ASPA object creation.`,
|
||||
`% Check with your RIR for current ASPA support status.`,
|
||||
`%`,
|
||||
`# ============================================================`,
|
||||
`# ASPA Submission Guide for AS${asn}`,
|
||||
`# Generated by PeerCortex, ${new Date().toISOString()}`,
|
||||
`# ============================================================`,
|
||||
`#`,
|
||||
`# ASPA support at your RIR (${rir.toUpperCase()}): ${info.status}`,
|
||||
`# Where to create the object: ${info.howTo}`,
|
||||
`# More info: ${info.infoUrl}`,
|
||||
`#`,
|
||||
`# ASPA objects are signed RPKI objects. There is no text format`,
|
||||
`# to paste into a registry database. Use the RIR platform above`,
|
||||
`# and enter the provider ASNs listed below.`,
|
||||
`#`,
|
||||
``,
|
||||
`Customer: AS${asn}`,
|
||||
];
|
||||
|
||||
// Build the main object
|
||||
lines.push(`aut-num: AS${asn}`);
|
||||
|
||||
for (const provider of providers) {
|
||||
// Only include high-confidence providers in the template
|
||||
if (provider.confidence >= 0.5) {
|
||||
const afiComment =
|
||||
provider.afi.length === 2
|
||||
? ""
|
||||
: ` # ${provider.afi[0]} only`;
|
||||
lines.push(
|
||||
`upstream: AS${provider.asn}${afiComment}`
|
||||
);
|
||||
}
|
||||
const highConfidence = providers.filter((p) => p.confidence >= 0.5);
|
||||
const sortedHigh = [...highConfidence].sort((a, b) => a.asn - b.asn);
|
||||
for (const provider of sortedHigh) {
|
||||
const afiComment =
|
||||
provider.afi.length === 2 ? "" : ` # ${provider.afi[0]} only`;
|
||||
lines.push(` Provider to add: AS${provider.asn}${afiComment}`);
|
||||
}
|
||||
|
||||
lines.push(`mnt-by: ${maintainer}`);
|
||||
lines.push(`source: RIPE`);
|
||||
lines.push(``);
|
||||
|
||||
// Add low-confidence providers as comments
|
||||
const lowConfidence = providers.filter((p) => p.confidence < 0.5);
|
||||
if (lowConfidence.length > 0) {
|
||||
lines.push(`% The following providers were detected with low confidence.`);
|
||||
lines.push(`% Uncomment and add them if they are legitimate providers:`);
|
||||
for (const provider of lowConfidence) {
|
||||
lines.push(`# The following providers were detected with low confidence.`);
|
||||
lines.push(`# Review before adding them:`);
|
||||
const sortedLow = [...lowConfidence].sort((a, b) => a.asn - b.asn);
|
||||
for (const provider of sortedLow) {
|
||||
lines.push(
|
||||
`% upstream: AS${provider.asn} # ${provider.name} (confidence: ${Math.round(provider.confidence * 100)}%)`
|
||||
`# AS${provider.asn} (${provider.name}, confidence: ${Math.round(provider.confidence * 100)}%)`
|
||||
);
|
||||
}
|
||||
lines.push(``);
|
||||
|
||||
@ -6,8 +6,8 @@
|
||||
* and detect route leaks in real time. Combines ASPA validation
|
||||
* with heuristic analysis for comprehensive leak detection.
|
||||
*
|
||||
* @see https://www.rfc-editor.org/rfc/rfc9582
|
||||
* @see https://www.rfc-editor.org/rfc/rfc7908 — Route Leak Problem Definition
|
||||
* @see https://datatracker.ietf.org/doc/draft-ietf-sidrops-aspa-verification/ (path validation, still an IETF draft, not yet an RFC)
|
||||
* @see https://www.rfc-editor.org/rfc/rfc7908 (Route Leak Problem Definition)
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
* Provides functions to retrieve ASPA objects from the RIPE Database
|
||||
* and maintain an in-memory cache with TTL-based expiration.
|
||||
*
|
||||
* @see https://www.ripe-editor.org/rfc/rfc9582
|
||||
* @see https://datatracker.ietf.org/doc/draft-ietf-sidrops-aspa-profile/ (object format, still an IETF draft, not yet an RFC)
|
||||
* @see https://apps.db.ripe.net/docs/DatabaseReference/RIPE-Database-Structure/
|
||||
*
|
||||
* @example
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
* if AS13335 had deployed it?" or "What is the aggregate prevention rate
|
||||
* across all incidents in the last 30 days?"
|
||||
*
|
||||
* @see https://www.rfc-editor.org/rfc/rfc9582
|
||||
* @see https://datatracker.ietf.org/doc/draft-ietf-sidrops-aspa-verification/ (path validation, still an IETF draft, not yet an RFC)
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
/**
|
||||
* @module aspa/validator
|
||||
* RFC 9582 Section 6 — ASPA-based AS path validation algorithm.
|
||||
* draft-ietf-sidrops-aspa-verification — ASPA-based AS path validation algorithm.
|
||||
*
|
||||
* Implements the Autonomous System Provider Authorization (ASPA) path
|
||||
* validation procedure as defined in RFC 9582. ASPA enables detection
|
||||
* validation procedure as defined in draft-ietf-sidrops-aspa-verification. ASPA enables detection
|
||||
* of route leaks and unauthorized path segments by verifying that each
|
||||
* AS in a BGP path has authorized its upstream provider relationship.
|
||||
*
|
||||
* @see https://www.rfc-editor.org/rfc/rfc9582#section-6
|
||||
* @see https://datatracker.ietf.org/doc/draft-ietf-sidrops-aspa-verification/ (still an IETF draft, not yet an RFC)
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
@ -34,7 +34,7 @@
|
||||
* Maps a customer AS to its authorized upstream providers,
|
||||
* optionally scoped to specific address families.
|
||||
*
|
||||
* @see RFC 9582 Section 3 — ASPA Profile
|
||||
* @see draft-ietf-sidrops-aspa-profile Section 3 (object format, still an IETF draft)
|
||||
*/
|
||||
export interface ASPAObject {
|
||||
/** The customer AS that created this authorization */
|
||||
@ -53,7 +53,7 @@ export interface ASPAObject {
|
||||
* found, and whether a route leak was detected.
|
||||
*/
|
||||
export interface ASPAValidationResult {
|
||||
/** Overall validation status per RFC 9582 Section 6 */
|
||||
/** Overall validation status per draft-ietf-sidrops-aspa-verification */
|
||||
readonly status: "valid" | "invalid" | "unknown" | "unverifiable";
|
||||
/** The AS path that was validated */
|
||||
readonly path: ReadonlyArray<number>;
|
||||
@ -98,7 +98,7 @@ export interface ASPAViolation {
|
||||
* @returns "provider" if authorized, "not-provider" if explicitly not listed,
|
||||
* or "no-attestation" if the customer has no ASPA object
|
||||
*
|
||||
* @see RFC 9582 Section 6 — Verification of Provider Authorization
|
||||
* @see draft-ietf-sidrops-aspa-verification — Verification of Provider Authorization
|
||||
*/
|
||||
function checkProviderAuthorization(
|
||||
customerAsn: number,
|
||||
@ -135,13 +135,13 @@ function deduplicatePath(path: ReadonlyArray<number>): ReadonlyArray<number> {
|
||||
// ── Core Validation Functions ───────────────────────────
|
||||
|
||||
/**
|
||||
* Validate an AS path in the upstream direction per RFC 9582 Section 6.
|
||||
* Validate an AS path in the upstream direction per draft-ietf-sidrops-aspa-verification.
|
||||
*
|
||||
* Walks the path from the origin AS (rightmost) toward the validating AS
|
||||
* (leftmost). For each pair (customer, provider), verifies that the
|
||||
* customer has authorized the provider via an ASPA object.
|
||||
*
|
||||
* The upstream validation procedure (RFC 9582 Section 6):
|
||||
* The upstream validation procedure (draft-ietf-sidrops-aspa-verification):
|
||||
* - If the path has 0 or 1 unique ASNs, the result is "valid".
|
||||
* - Walk from index N-1 (origin) toward index 0.
|
||||
* - At each hop, check if path[i] authorizes path[i-1] as its provider.
|
||||
@ -209,7 +209,7 @@ export function validateUpstream(
|
||||
|
||||
const confidence = totalHops > 0 ? coveredHops / totalHops : 1.0;
|
||||
|
||||
// Determine overall status per RFC 9582 Section 6
|
||||
// Determine overall status per draft-ietf-sidrops-aspa-verification
|
||||
if (violations.length > 0) {
|
||||
const leakingViolation = violations[0];
|
||||
return {
|
||||
@ -242,13 +242,13 @@ export function validateUpstream(
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate an AS path in the downstream direction per RFC 9582 Section 6.
|
||||
* Validate an AS path in the downstream direction per draft-ietf-sidrops-aspa-verification.
|
||||
*
|
||||
* Reverses the path and applies the upstream validation procedure.
|
||||
* Downstream validation is used when the validating AS is receiving
|
||||
* a route from a customer rather than a provider.
|
||||
*
|
||||
* Per RFC 9582, the downstream verification is the mirror image of upstream:
|
||||
* Per draft-ietf-sidrops-aspa-verification, the downstream verification is the mirror image of upstream:
|
||||
* - Reverse the path so the "origin" from the downstream perspective is leftmost.
|
||||
* - Apply the same provider-authorization checks.
|
||||
*
|
||||
@ -295,7 +295,7 @@ export function validateDownstream(
|
||||
* @returns Full validation result including status, violations, leak
|
||||
* detection, and confidence score
|
||||
*
|
||||
* @see RFC 9582 Section 6 — Procedure for Verifying the AS_PATH Attribute
|
||||
* @see draft-ietf-sidrops-aspa-verification — Procedure for Verifying the AS_PATH Attribute
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
|
||||
@ -5,7 +5,8 @@
|
||||
* Exposes ASPA validation, analysis, generation, simulation, coverage,
|
||||
* and leak detection capabilities through the Model Context Protocol.
|
||||
*
|
||||
* @see https://www.rfc-editor.org/rfc/rfc9582
|
||||
* @see https://datatracker.ietf.org/doc/draft-ietf-sidrops-aspa-profile/ (object format, still an IETF draft, not yet an RFC)
|
||||
* @see https://datatracker.ietf.org/doc/draft-ietf-sidrops-aspa-verification/ (path validation, still an IETF draft, not yet an RFC)
|
||||
*/
|
||||
|
||||
import { z } from "zod";
|
||||
@ -13,8 +14,8 @@ import type { ASPAValidationResult } from "../../aspa/validator.js";
|
||||
import { validatePath } from "../../aspa/validator.js";
|
||||
import type { ASPAObject } from "../../aspa/validator.js";
|
||||
import { fetchASPAObjects } from "../../aspa/objects.js";
|
||||
import { detectProviders, generateASPAObject, generateRipeDbTemplate } from "../../aspa/generator.js";
|
||||
import type { BGPPath } from "../../aspa/generator.js";
|
||||
import { detectProviders, summarizeASPAObject, generateASPASubmissionGuide } from "../../aspa/generator.js";
|
||||
import type { BGPPath, RIR } from "../../aspa/generator.js";
|
||||
import { simulateASPADeployment } from "../../aspa/simulator.js";
|
||||
import type { BGPIncident } from "../../aspa/simulator.js";
|
||||
import { getASPACoverage, getASPACoverageByRegion, compareASPAAdoption } from "../../aspa/coverage.js";
|
||||
@ -60,20 +61,22 @@ export const aspaAnalyzeSchema = z.object({
|
||||
*
|
||||
* @example
|
||||
* ```
|
||||
* > Generate an ASPA object for AS13335
|
||||
* > Generate an ASPA submission guide for AS13335, registered with RIPE
|
||||
*
|
||||
* Returns: RIPE DB-ready ASPA object template with detected
|
||||
* upstream providers (AS174, AS3356, etc.) and submission instructions.
|
||||
* Returns: detected upstream providers (AS174, AS3356, etc.) plus
|
||||
* RIR-specific instructions for creating the actual ASPA object.
|
||||
* ```
|
||||
*/
|
||||
export const aspaGenerateSchema = z.object({
|
||||
asn: z
|
||||
.union([z.string(), z.number()])
|
||||
.describe("ASN to generate ASPA object for"),
|
||||
maintainer: z
|
||||
.string()
|
||||
.describe("ASN to generate an ASPA submission guide for"),
|
||||
rir: z
|
||||
.enum(["ripe", "arin", "apnic", "lacnic", "afrinic"])
|
||||
.optional()
|
||||
.describe("RIPE DB maintainer handle (e.g., 'MNT-CLOUDFLARE')"),
|
||||
.describe(
|
||||
"The RIR this ASN is registered with. If omitted, guidance for all five RIRs is returned so the caller can pick the right one."
|
||||
),
|
||||
});
|
||||
|
||||
/**
|
||||
@ -143,7 +146,7 @@ export const aspaLeaksSchema = z.object({
|
||||
* Validate an AS path against ASPA objects.
|
||||
*
|
||||
* Fetches ASPA objects for all ASNs in the path, then runs the
|
||||
* RFC 9582 Section 6 validation algorithm.
|
||||
* validation algorithm from draft-ietf-sidrops-aspa-verification.
|
||||
*
|
||||
* @example
|
||||
* ```
|
||||
@ -205,7 +208,7 @@ export async function handleASPAValidate(
|
||||
* ],
|
||||
* "recommendations": [
|
||||
* "Register ASPA object listing AS174 and AS3356 as providers",
|
||||
* "Submit via RIPE DB at https://apps.db.ripe.net/db-web-ui/webupdates"
|
||||
* "ASPA objects are created through your RIR's hosted RPKI platform, not the RIPE Database"
|
||||
* ]
|
||||
* }
|
||||
* ```
|
||||
@ -263,10 +266,10 @@ export async function handleASPAAnalyze(
|
||||
`Register an ASPA object for AS${asn} with your RIR to enable route leak prevention.`
|
||||
);
|
||||
recommendations.push(
|
||||
`Submit via RIPE DB at https://apps.db.ripe.net/db-web-ui/webupdates`
|
||||
`ASPA objects are created through your RIR's hosted RPKI platform (e.g. the RIPE NCC RPKI Dashboard, ARIN Online's Routing Security section, or MyAPNIC), not through the RIPE Database (whois).`
|
||||
);
|
||||
recommendations.push(
|
||||
`Use the peercortex_aspa_generate tool to create a ready-to-submit template.`
|
||||
`Use the peercortex_aspa_generate tool with your RIR to get the detected provider list and the correct place to submit it.`
|
||||
);
|
||||
} else {
|
||||
recommendations.push(
|
||||
@ -294,22 +297,28 @@ export async function handleASPAAnalyze(
|
||||
};
|
||||
}
|
||||
|
||||
const RIR_LIST: ReadonlyArray<RIR> = ["ripe", "arin", "apnic", "lacnic", "afrinic"];
|
||||
|
||||
/**
|
||||
* Generate an ASPA object template for an ASN.
|
||||
* Generate an ASPA submission guide for an ASN.
|
||||
*
|
||||
* Detects upstream providers from BGP path data and generates
|
||||
* a RIPE DB-ready ASPA object template.
|
||||
* Detects upstream providers from BGP path data and produces a provider
|
||||
* list plus the correct place to actually create the ASPA object at the
|
||||
* given RIR. If no RIR is given, guidance for all five is returned.
|
||||
* There is no RPSL text format that can be pasted into a registry to
|
||||
* create an ASPA object; it is a signed RPKI object created through the
|
||||
* RIR's own hosted RPKI platform.
|
||||
*
|
||||
* @example
|
||||
* ```
|
||||
* > Generate an ASPA object for AS13335
|
||||
* > Generate an ASPA submission guide for AS13335, registered with RIPE
|
||||
*
|
||||
* Returns:
|
||||
* {
|
||||
* "asn": 13335,
|
||||
* "template": "aut-num: AS13335\nupstream: AS174\nupstream: AS3356\nmnt-by: MNT-CLOUDFLARE\nsource: RIPE",
|
||||
* "detectedProviders": [{ "asn": 174, ... }, { "asn": 3356, ... }],
|
||||
* "instructions": "Submit via RIPE DB..."
|
||||
* "summary": "Customer: AS13335\n Provider: AS174 ...",
|
||||
* "guides": { "ripe": "..." },
|
||||
* "detectedProviders": [{ "asn": 174, ... }, { "asn": 3356, ... }]
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
@ -317,17 +326,15 @@ export async function handleASPAGenerate(
|
||||
input: z.infer<typeof aspaGenerateSchema>
|
||||
): Promise<{
|
||||
asn: number;
|
||||
object: string;
|
||||
template: string;
|
||||
summary: string;
|
||||
guides: Partial<Record<RIR, string>>;
|
||||
detectedProviders: ReadonlyArray<{
|
||||
asn: number;
|
||||
name: string;
|
||||
confidence: number;
|
||||
}>;
|
||||
instructions: string;
|
||||
}> {
|
||||
const asn = parseASN(input.asn);
|
||||
const maintainer = input.maintainer ?? `MNT-AS${asn}`;
|
||||
|
||||
// Detect providers (in full implementation, fetch BGP paths from RIPE Stat)
|
||||
// For now, try to infer from any existing ASPA objects
|
||||
@ -354,25 +361,23 @@ export async function handleASPAGenerate(
|
||||
// Continue with empty provider list
|
||||
}
|
||||
|
||||
const object = generateASPAObject(asn, providers);
|
||||
const template = generateRipeDbTemplate(asn, providers, maintainer);
|
||||
const summary = summarizeASPAObject(asn, providers);
|
||||
|
||||
const rirsToGenerate = input.rir ? [input.rir] : RIR_LIST;
|
||||
const guides: Partial<Record<RIR, string>> = {};
|
||||
for (const rir of rirsToGenerate) {
|
||||
guides[rir] = generateASPASubmissionGuide(asn, providers, rir);
|
||||
}
|
||||
|
||||
return {
|
||||
asn,
|
||||
object,
|
||||
template,
|
||||
summary,
|
||||
guides,
|
||||
detectedProviders: providers.map((p) => ({
|
||||
asn: p.asn,
|
||||
name: p.name,
|
||||
confidence: p.confidence,
|
||||
})),
|
||||
instructions:
|
||||
`To register this ASPA object:\n` +
|
||||
`1. Review the detected providers and adjust as needed\n` +
|
||||
`2. Go to https://apps.db.ripe.net/db-web-ui/webupdates\n` +
|
||||
`3. Paste the template and submit\n` +
|
||||
`4. Alternatively, email the template to auto-dbm@ripe.net\n` +
|
||||
`\nNote: Your RIR must support ASPA objects. Check current support status.`,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user