feat(ip-enrich): consume dunite-ipenrich for advisory ASN/VPN lookup (BUNYIP-475) #493

Merged
longjacksonle merged 1 commit from feat/BUNYIP-475-consume-ipenrich into main 2026-08-04 21:34:59 +02:00

Closes BUNYIP-475 (BUNYIP-437 follow-up): mokosh becomes the second product consuming the shared dunite-ipenrich crate, so the same offline IP2Proxy ASN/VPN signal is available here without a second implementation that would drift. This satisfies the BUNYIP-437 "consumable by at least two products without duplicating ingestion" criterion in full (bunyip was the first consumer).

Manifest

Adds dunite-ipenrich pinned at ae62c27 (the dunite rev that introduced it). It is a leaf crate (owns its error, depends only on ip2location), so it is pinned independently of the older dunite-stripe-core rev; the Cargo.lock change is just the new crate (9 lines, no unrelated churn).

Wiring (mirrors the geoip service)

  • Config: new IP2PROXY_DB_PATH -> ip2proxy_db_path, an offline IP2Proxy PX .BIN.
  • Service: main.rs builds an optional IpEnrichService; unset or a failed load leaves it None and the feature reports nothing (never fatal). Threaded positionally through create_api_router exactly like geoip.
  • Endpoint: a new ip_enrich module exposes an admin-gated GET /api/v1/ip-enrichment?ip=<addr> (RequireAdmin) that returns ASN, owning organization, ISP, classified network category and VPN/proxy likelihood labels, a one-bit is_anonymizing, and an always-true advisory marker. None-tolerant: no dataset / private / unknown address all return null; only a malformed IP is a 400.

Advisory, never a verdict

The signal describes an address for an admin reviewing an IP (e.g. an actor IP in the audit log), never an automatic abuse verdict. mokosh has no automated IP abuse decision to hook, so the lookup is the honest surface; a VPN must not auto-classify a request, per BUNYIP-437.

Tests / verification

  • Response-mapping unit tests (labels, is_anonymizing, always-advisory; a data-centre address is not flagged anonymizing).
  • In the pinned rust-builder-glibc:v1.0.1-rust1.94-trixie image: cargo fmt --check, cargo clippy --all-targets -- -D warnings (compiles the integration harness, which receives the new None arg), cargo test --lib, cargo test --doc all pass. No .sqlx regen (mokosh uses runtime queries only).

🤖 Generated with Claude Code

https://claude.ai/code/session_018TXaT3P192nDsZzbzHETb9

Closes [BUNYIP-475](https://niceguyit.myjetbrains.com/youtrack/issue/BUNYIP-475) (BUNYIP-437 follow-up): mokosh becomes the second product consuming the shared `dunite-ipenrich` crate, so the same offline IP2Proxy ASN/VPN signal is available here without a second implementation that would drift. This satisfies the BUNYIP-437 "consumable by at least two products without duplicating ingestion" criterion in full (bunyip was the first consumer). ## Manifest Adds `dunite-ipenrich` pinned at `ae62c27` (the dunite rev that introduced it). It is a leaf crate (owns its error, depends only on `ip2location`), so it is pinned independently of the older `dunite-stripe-core` rev; the `Cargo.lock` change is just the new crate (9 lines, no unrelated churn). ## Wiring (mirrors the geoip service) - **Config:** new `IP2PROXY_DB_PATH` -> `ip2proxy_db_path`, an offline IP2Proxy PX `.BIN`. - **Service:** `main.rs` builds an optional `IpEnrichService`; unset or a failed load leaves it `None` and the feature reports nothing (never fatal). Threaded positionally through `create_api_router` exactly like `geoip`. - **Endpoint:** a new `ip_enrich` module exposes an admin-gated `GET /api/v1/ip-enrichment?ip=<addr>` (`RequireAdmin`) that returns ASN, owning organization, ISP, classified network category and VPN/proxy likelihood labels, a one-bit `is_anonymizing`, and an always-true `advisory` marker. `None`-tolerant: no dataset / private / unknown address all return `null`; only a malformed IP is a 400. ## Advisory, never a verdict The signal describes an address for an admin reviewing an IP (e.g. an actor IP in the audit log), never an automatic abuse verdict. mokosh has no automated IP abuse decision to hook, so the lookup is the honest surface; a VPN must not auto-classify a request, per BUNYIP-437. ## Tests / verification - Response-mapping unit tests (labels, `is_anonymizing`, always-advisory; a data-centre address is not flagged anonymizing). - In the pinned `rust-builder-glibc:v1.0.1-rust1.94-trixie` image: `cargo fmt --check`, `cargo clippy --all-targets -- -D warnings` (compiles the integration harness, which receives the new `None` arg), `cargo test --lib`, `cargo test --doc` all pass. No `.sqlx` regen (mokosh uses runtime queries only). 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_018TXaT3P192nDsZzbzHETb9
feat(ip-enrich): consume dunite-ipenrich for advisory ASN/VPN lookup (BUNYIP-475)
Some checks failed
E2E / Playwright against staging (pull_request) Successful in 3m15s
Check / fmt + clippy + build + tests (pull_request) Successful in 3m41s
Create release / Gate (release-branch merges only) (pull_request) Successful in 9s
Create release / Create release from merged PR (pull_request) Has been skipped
Integration / integration tests (pull_request) Failing after 3m54s
90aada9f81
Makes mokosh the second product consuming the shared dunite-ipenrich crate (BUNYIP-437), so the same offline IP2Proxy ASN/VPN signal is available here without a second implementation that would drift. The crate is a leaf (owns its error, depends only on ip2location), so it is pinned to the dunite rev that introduced it (ae62c27) independently of the older dunite-stripe-core pin; the lockfile change is just the new crate.

Wiring mirrors the existing geoip service exactly. A new IP2PROXY_DB_PATH config builds an optional IpEnrichService in main.rs (unset or a failed .BIN load leaves it None and the feature simply reports nothing, never fatal), threaded positionally through create_api_router like geoip. A new ip_enrich module exposes a narrow admin-gated endpoint GET /api/v1/ip-enrichment?ip=<addr> that maps an address to its ASN, owning organization, ISP, classified network category and VPN/proxy likelihood, a one-bit is_anonymizing summary, and an always-true advisory marker. It is None-tolerant: no dataset, a private/reserved address, or an unknown address all return null; only a malformed IP is a 400.

The signal is advisory by construction: it describes an address for an admin reviewing an IP (e.g. an actor IP in the audit log), never an automatic abuse verdict. mokosh has no automated IP abuse decision to hook, so the lookup is the honest surface; a VPN must not auto-classify a request, per BUNYIP-437.

Tests: the response mapping (labels, is_anonymizing, always-advisory; data-centre is not anonymizing). Verified in the pinned rust-builder image: cargo fmt --check, cargo clippy --all-targets -D warnings (compiles the integration harness, which gets the new None arg), cargo test --lib, cargo test --doc all pass. No .sqlx regen (mokosh uses runtime queries only).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018TXaT3P192nDsZzbzHETb9
longjacksonle scheduled this pull request to auto merge when all checks succeed 2026-08-04 21:32:17 +02:00
longjacksonle deleted branch feat/BUNYIP-475-consume-ipenrich 2026-08-04 21:35:00 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/mokosh-server!493
No description provided.