Skip to content

feat: ASPA path validation in rpki stage + HTTP auth, websocket and stage fixes#34

Merged
pforemski merged 1 commit intomainfrom
feat-aspa
Apr 10, 2026
Merged

feat: ASPA path validation in rpki stage + HTTP auth, websocket and stage fixes#34
pforemski merged 1 commit intomainfrom
feat-aspa

Conversation

@pforemski
Copy link
Copy Markdown
Contributor

Extends the rpki stage with ASPA path validation on top of the existing ROV, and picks up several orthogonal fixes to the HTTP API, websocket stage and a few others.

rpki stage — ASPA

  • Implements draft-ietf-sidrops-aspa-verification (-24) valley-free AS_PATH validation with VALID / UNKNOWN / INVALID states.
  • VRP and ASPA records are pulled over RTR v2 (draft-ietf-sidrops-8210bis) against Cloudflare's public cache by default (rtr.rpki.cloudflare.com:8282), or loaded from a local file.
  • Local file parser accepts Routinator-style JSON (VRPs and ASPAs) or a simple VRP-only CSV. Files are hashed and auto-reloaded.
  • Peer BGP role resolved per-direction from the RFC 9234 BGP Role capability (--aspa-role auto, default) or forced via --aspa-role provider|customer|peer|rs|rs-client. First-hop ASN check
    is skipped for RS peers per RFC 7947.
  • New flags: --aspa, --aspa-invalid (withdraw|drop|keep), --aspa-tag, --aspa-event, --aspa-role. Existing ROV flags unchanged. Tags: aspa/status alongside rpki/status.
  • Prometheus: bgpipe_rpki_aspa_{valid,unknown,invalid}_total counters and bgpipe_rpki_aspa_entries gauge (registered only when --aspa is set).

HTTP API

  • --http-auth now fails-closed: the HTTP API refuses to start without credentials unless --http-open is passed explicitly.
  • Credential sources: literal user:pass, $ENV variable, or /path to a file.
  • Constant-time Basic comparison via crypto/subtle.
  • --pprof: http mounts under the authed main mux; any other value starts a separate (unauthed) pprof server on that address — useful for keeping diagnostics off the public listener.
  • ReadHeaderTimeout set on both servers (slowloris).

Websocket stage

  • CheckOrigin enforces same-origin by default.
  • SetReadLimit(65535) on both client and server connections.
  • Constant-time credential comparison; credential file handle is now closed.

Other stage fixes

  • limit: checkUnreach counted u.Reach instead of u.Unreach after filtering withdrawals — the bucket underflowed on pure withdrawal messages. Fixed.
  • speaker stage: expose --remote-asn and --remote-hold flags, wiring the bgpfix speaker validation from the other PR.
  • rpki file loader: validate maxLength ranges, handle JSON ASN as either string or number, drop the dead int type branch.

Docs

docs/stages/rpki.md updated for all of the above: new flags, ASPA action table, RS first-hop note, JSON-vs-CSV capability matrix, new metrics.

Compatibility

ROV behaviour is unchanged by default. ASPA is opt-in (--aspa). --http-token was dropped in favour of --http-auth; callers must migrate (this is the only breaking change).

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Extends the rpki stage with optional ASPA (AS_PATH) validation alongside existing ROV, and hardens/adjusts the HTTP API and websocket stage behavior (auth, origin checks, limits, timeouts), plus a few correctness fixes and dependency bumps.

Changes:

  • Add ASPA path validation (VALID/UNKNOWN/INVALID) and migrate RPKI data handling from ROAs to VRPs + ASPAs (RTR + file loader + metrics + docs).
  • Make HTTP API auth fail-closed by default, add flexible credential sources, and adjust pprof mounting/serving behavior.
  • Harden websocket connections (same-origin default, read limits, constant-time auth compare) and fix a withdrawal counting bug in limit.

Reviewed changes

Copilot reviewed 21 out of 22 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
stages/websocket.go Adds constant-time auth compare, same-origin checks, read limits, and server ReadHeaderTimeout.
stages/speaker.go Exposes remote ASN/hold validation flags and wires them into speaker options.
stages/rpki/validate.go Refactors ROV validation against VRP caches and integrates optional ASPA validation.
stages/rpki/validate_test.go Updates unit tests to VRP terminology/types and new ROV status constants.
stages/rpki/validate_msg_test.go Adds message-level tests covering ROV withdrawal behavior and ASPA edge cases.
stages/rpki/rtr.go Replaces stayrtr client with bgpfix/rtr client, adding ASPA callbacks and new session handling.
stages/rpki/rpki.go Adds ASPA config/state/metrics, renames ROA→VRP structures, updates flags and HTTP status output.
stages/rpki/next.go Implements VRP/ASPA incremental “next” cache management and validation of maxLength.
stages/rpki/next_test.go Updates “next” cache tests for VRP naming/types and adds invalid maxLength coverage.
stages/rpki/file.go Enhances local file loader to support VRPs+ASPAs (JSON) and improves input validation/trim.
stages/rpki/file_test.go Updates file parser tests for VRP naming and expected counts.
stages/rpki/aspa.go Implements ASPA authorization + valley-free AS_PATH verification and enforcement actions.
stages/rpki/aspa_test.go Adds unit tests for ASPA authorization/verification and role parsing helpers.
stages/limit.go Fixes withdrawal-counting bug in checkUnreach.
pkg/util/net.go Adds documentation to ConnPublish.
core/http.go Adds HTTP Basic auth middleware, credential sourcing, pprof split-server option, and hardening timeouts.
core/config.go Introduces --http-auth, --http-open, and new string-based --pprof flag.
core/bgpipe.go Tracks whether pprof is mounted on the main HTTP server for dashboard links.
core/attach.go Updates HTTP stage attachment call after attachHTTPStages signature change.
docs/stages/rpki.md Documents ASPA behavior, new flags, file formats, tags, and metrics.
go.mod Bumps Go version/toolchain and updates dependencies (adds testify direct dep).
go.sum Updates module checksums to match dependency changes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

core/http.go Outdated
Comment on lines +114 to +124
fh, err := os.Open(v)
if err != nil {
return nil, err
}
buf := make([]byte, 128)
n, err := fh.Read(buf)
fh.Close()
if err != nil {
return nil, fmt.Errorf("file %s: %w", v, err)
}
line, _, _ := bytes.Cut(buf[:n], []byte{'\n'})
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Credential file reads will commonly return err==io.EOF after a successful short read, but this code treats any non-nil error as fatal. As a result, using --http-auth /path (and similar) can fail even when the file is valid. Consider using os.ReadFile / io.ReadAll, or accept io.EOF when n>0 (and ensure the file is closed via defer, handling close errors if relevant).

Suggested change
fh, err := os.Open(v)
if err != nil {
return nil, err
}
buf := make([]byte, 128)
n, err := fh.Read(buf)
fh.Close()
if err != nil {
return nil, fmt.Errorf("file %s: %w", v, err)
}
line, _, _ := bytes.Cut(buf[:n], []byte{'\n'})
buf, err := os.ReadFile(v)
if err != nil {
return nil, fmt.Errorf("file %s: %w", v, err)
}
line, _, _ := bytes.Cut(buf, []byte{'\n'})

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — replaced manual open/read/close with os.ReadFile, dropping the 128-byte buffer.

Comment on lines 144 to 148
cred = make([]byte, 128)
n, err := fh.Read(cred)
fh.Close()
if err != nil {
return fmt.Errorf("--auth: file %s: %w", v, err)
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading the credential file via fh.Read() will typically return err==io.EOF for small files; treating any non-nil error as fatal can make --auth /path fail even with valid credentials. Consider using os.ReadFile / io.ReadAll, or accept io.EOF when n>0 (and close the file via defer).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — same as the http.go fix: replaced manual open/read/close with os.ReadFile.

// NB: role resolved once per direction on first UPDATE. BGP guarantees OPEN
// is exchanged before any UPDATE. If --aspa-role auto and peer didn't
// send BGP Role capability, ASPA is permanently skipped for this direction.
di := m.Dir & 1 // direction index: 0=R, 1=L
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using di := m.Dir & 1 to index per-direction state is brittle and the comment is inconsistent with the project’s dir constants (e.g. core/http.go:274-275 indicates DIR_R=1 and DIR_L=2, so DIR_R maps to di=1 and DIR_L to di=0). Consider mapping explicitly (e.g. switch on m.Dir for DIR_R/DIR_L) to avoid confusing inversions and any future changes to dir bit values.

Suggested change
di := m.Dir & 1 // direction index: 0=R, 1=L
var di int
switch m.Dir {
case dir.DIR_L:
di = 0
case dir.DIR_R:
di = 1
default:
return true
}

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not applying. The reviewer has the constants backwards: DIR_L = 0b01 = 1, DIR_R = 0b10 = 2. So DIR_R & 1 = 0 and DIR_L & 1 = 1, which matches the comment // 0=R, 1=L exactly. The code is correct.

@pforemski pforemski merged commit b30dfdf into main Apr 10, 2026
1 check passed
@pforemski pforemski deleted the feat-aspa branch April 10, 2026 12:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants