Skip to content

Repository files navigation

rir-updater

CLI tool for syncing route objects to RIPE NCC, ARIN, and RADb, and RPKI ROAs to RIPE and ARIN.

Requirements

Installation

uv sync

Configuration

Copy config.example.yaml and fill in your values. All registry sections (ripe, arin, radb) are optional — include only the registries you use.

ripe:
  maintainer: "MAINT-AS12345"
  credentials:
    db_username: "op://vault/item/username"
    db_password: "op://vault/item/password"
    rpki_api_key: "op://vault/item/rpki-api-key"
    test_db_username: "op://vault/item/test-username"  # optional
    test_db_password: "op://vault/item/test-password"  # optional
  sso_emails:
    - "admin@example.com"
  routes:
    - prefix: "192.0.2.0/24"
      origin: "AS12345"
      description: "Example IPv4 prefix"
    - prefix: "2001:db8::/32"
      origin: "AS12345"
      description: "Example IPv6 prefix"
  roas:
    - prefix: "192.0.2.0/24"
      origin: "AS12345"
      max_length: 24
    - prefix: "2001:db8::/32"
      origin: "AS12345"
      max_length: 32

arin:
  org_handle: "EXAMPLEORG-1"
  credentials:
    api_key: "op://vault/item/arin-api-key"
  routes:
    - prefix: "192.0.2.0/24"
      origin: "AS12345"
      description: "Example IPv4 prefix"
    - prefix: "2001:db8::/32"
      origin: "AS12345"
      description: "Example IPv6 prefix"
  roas:
    - prefix: "192.0.2.0/24"
      origin: "AS12345"
      max_length: 24
    - prefix: "2001:db8::/32"
      origin: "AS12345"
      max_length: 32

radb:
  maintainer: "MAINT-AS12345"
  contact_email: "admin@example.com"
  credentials:
    portal_username: "op://vault/item/portal-username"
    portal_password: "op://vault/item/portal-password"
    mntner_password: "op://vault/item/mntner-password"
  routes:
    - prefix: "192.0.2.0/24"
      origin: "AS12345"
      description: "Example IPv4 prefix"
    - prefix: "2001:db8::/32"
      origin: "AS12345"
      description: "Example IPv6 prefix"

roas is optional. If omitted, only route objects are synced. ROA sync only manages prefixes explicitly listed — other ROAs in the account are left untouched.

Deleting route objects

Set delete: true on any route entry to remove it from the registry instead of syncing it:

routes:
  - prefix: "192.0.2.0/24"
    origin: "AS12345"
    delete: true

Automatic RADb mirroring

When a radb section is present in the config, every RIPE and ARIN route change (create, update, or delete) is automatically mirrored to RADb. You do not need to list the same prefix in the radb.routes section — duplicates are skipped automatically.

Routes listed exclusively under radb.routes (with no corresponding RIPE/ARIN entry) are still synced to RADb directly.

Attribute preservation

On update, only fields managed by this tool (route/route6, origin, mnt-by, source, changed, and optionally descr) are modified. All other attributes already present in the registry object (e.g. remarks, admin-c, tech-c) are preserved.

Credentials

Secrets are fetched from 1Password via the op CLI. The credentials block in the config file specifies the 1Password reference for each secret:

RIPE

Field Used for
db_username RIPE DB REST API username (production)
db_password RIPE DB REST API password (production)
rpki_api_key RIPE RPKI Management API key
test_db_username RIPE test DB username (optional, overrides db_username in test mode)
test_db_password RIPE test DB password (optional, overrides db_password in test mode)

ARIN

Field Used for
api_key ARIN API key for all IRR and RPKI requests (production)
test_api_key ARIN OTE API key (optional, overrides api_key in test mode)

The production API key must be linked to a POC with authority over your organization's resources. Create one at ARIN Online → Settings → Security Info → Manage API Keys. OTE keys are created the same way at account.ote.arin.net.

RADb

Field Used for
portal_username RADb portal email address (HTTP Basic auth)
portal_password RADb portal password (HTTP Basic auth)
mntner_password RADb mntner password for object-level authorization

References use the op://vault/item/field format. You must be signed in to the 1Password CLI (op signin) before running the tool.

Usage

# Dry-run all configured registries (test environments where applicable)
uv run rir-updater config.yaml

# Dry-run a specific registry only
uv run rir-updater config.yaml --registry arin
uv run rir-updater config.yaml --registry ripe --registry radb

# Apply changes to all registries in production
uv run rir-updater config.yaml --production --commit

# Apply changes to ARIN only (production)
uv run rir-updater config.yaml --registry arin --production --commit

# Set up the RIPE test database with objects replicated from production
uv run rir-updater config.yaml --setup-test

# Replicate ARIN production routes and ROAs into the OTE environment
uv run rir-updater config.yaml --setup-ote           # dry-run
uv run rir-updater config.yaml --setup-ote --commit  # apply

RADb always runs against production — --production only affects the RIPE and ARIN sections. ARIN uses its OT&E environment in test mode (reg.ote.arin.net) and production otherwise.

When no --registry flags are given, all registries present in the config are updated. Updates run in order: RIPE → ARIN → RADb. Within each registry, ROAs are published before route objects, because RPKI-aware consumers (such as RADb) reject a route until a covering ROA exists.

A dry-run reports the real ROA diff: it fetches the currently-published ROAs (read-only) and shows the actual number that would be added or deleted, rather than assuming everything is new.

RIPE test database bootstrap

The first time you use --setup-test, the mntner must be created manually via the RIPE web UI at apps-test.db.ripe.net — the API does not allow creating the first mntner programmatically due to a circular person↔mntner dependency. The tool will print instructions if the mntner is not found.

aut-num objects cannot be created in the test DB via the API — all aut-nums require authorization from TEST-DBM-MNT, which is restricted to RIPE staff. --setup-test will warn and continue if aut-num replication fails.

--setup-test only replicates prerequisite objects. Run without --setup-test afterwards to sync route objects and ROAs.

Error handling and exit codes

Each object (a route/route6, or the ROA set for a registry) is synced independently. If one object fails with an API error, the failure is reported and the run continues with the remaining objects — a single failure never aborts the whole batch or leaves you unsure which objects were processed.

Failures appear in the summary as ! lines, for example:

! radb route6 2001:db8::/32 FAILED: create radb route 2001:db8::/32 AS64496 failed (400): ...

The command exits non-zero if any object failed, even though the run completed; a fully successful run (or a clean dry-run) exits zero. Because syncs are idempotent, you can simply re-run the same config to retry only the objects that still need changes.

RADb API behavior

  • Output format — RADb's REST API defaults to text (RPSL) and ignores the Accept header, so the tool requests JSON explicitly with a ?format=json query parameter on every request.
  • Transient failures — RADb's API intermittently drops connections or returns 5xx. Idempotent requests are retried automatically with exponential backoff. A create whose response is lost is verified by re-checking existence (not blindly retried), so an object is never duplicated.

Development

uv run ruff check .   # lint
uv run ruff format .  # format
uv run pytest         # test

Contributing

This project uses a trunk-based workflow. main is the single long-lived branch; there is no develop branch.

  1. Branch off main for every change:
    git switch main && git pull
    git switch -c my-change
  2. Make your change and keep it green locally:
    uv run ruff check . && uv run ruff format --check . && uv run pytest
  3. Push the branch and open a pull request into main.
  4. CI runs automatically on the PR (.github/workflows/ci.yml runs the lint, format check, and tests above). It must pass.
  5. Get one approving review. main is protected: direct pushes are blocked, and a PR needs at least one approval before it can merge. You cannot approve your own PR.
  6. Merge with squash or rebasemain requires linear history, so merge commits are not allowed. Squash is preferred for a single, self-contained change.

Branch protection is enforced by a repository ruleset on main (required PR + review, required ci status check, linear history, no force-push, no deletion). Repository admins may bypass these rules when necessary.

About

Scripts to do updates to RIR databases

Resources

Code of conduct

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages