Internal RDAP data API + privileged-grant store (backend)#2937
Open
OlegPhenomenon wants to merge 6 commits into
Open
Internal RDAP data API + privileged-grant store (backend)#2937OlegPhenomenon wants to merge 6 commits into
OlegPhenomenon wants to merge 6 commits into
Conversation
The .ee RDAP service must stop reading the registry DB directly and instead consume a registry-side API (lead decision 2026-06-25). This adds the contract + implementation guide for that internal API: - GET /api/v1/internal/rdap/domains/:name (full privileged domain) - GET /api/v1/internal/rdap/registrars/:code (code,name,phone,website) - GET /api/v1/internal/rdap/nameservers/:host (hostname,hostname_puny) - GET /api/v1/internal/rdap/grants/active?subject= (net-new grant store) Authoritative spec lives in the RDAP repo (specs/specs/pending/08-registry-side-rdap-api). No code yet; doc only. Branch off master for review; do not merge to master.
Auth = pre-shared key + IP-allowlist; backend-only (no admin UI this spec); categories/states confirmed; defaults for namespace/touch/delete-date/disclosure.
New machine-to-machine JSON API consumed by the public RDAP service, so RDAP
no longer reads the registry's normalized DB directly. Namespace
/api/v1/internal/rdap/*, auth = pre-shared key + IP-allowlist.
Endpoints:
- GET domains/:name full privileged domain (PII + glue + DNSSEC)
- GET registrars/:code narrow entity {code,name,phone,website}
- GET nameservers/:host thin {hostname,hostname_puny}, DISTINCT-collapsed
- GET grants/active the single authoritative-active privilege grant
- POST grants/:id/touch best-effort last_used_at (204)
Net-new rdap_privilege_grants table + model (active_for_subject resolution,
fail-closed). Secrets-free Serializers::Rdap::Domain (never emits transfer_code/
auth_info/registrant_verification_token). minitest integration + model tests.
Backend only: no admin UI (grants seeded via fixtures/console per the spec).
Without this, a blank/unset shared key makes expected = 'Basic ' and any caller sending an empty Basic credential would authenticate. Reject when the key is not configured.
…rar test structure.sql: only the new rdap_privilege_grants table (CREATE TABLE + sequence + pkey + 3 indexes) and its schema_migrations version — no unrelated churn (the noisy full pg_dump regen was discarded). Validated: db:test:prepare reloads it from scratch and the suite is green (36 runs, 101 assertions, 0F/0E). registrar happy-path test now asserts full-hash equality (proves the exact narrow 4-key shape, drops the assert_nil deprecation).
Companion to the RDAP spec 10-rdap-issued-api-token. RDAP owns no database,
so the opaque API tokens it mints persist here and are reached over the
internal RDAP data API — the same seam that already serves domains,
registrars, nameservers and privilege grants.
Implements the six pinned token operations RDAP's registry client calls,
all keyed by the token_hash (RDAP's keyed HMAC-SHA-256 digest of the raw
token; the raw token and the HMAC secret never reach the registry):
POST /api/v1/internal/rdap/tokens create (store hash + metadata)
GET /api/v1/internal/rdap/tokens/active find active by hash (404 if
revoked/expired/unknown — no
caller-visible distinction)
GET /api/v1/internal/rdap/tokens?subject= list a subject's tokens
POST /api/v1/internal/rdap/tokens/revoke revoke one (idempotent, 204)
POST /api/v1/internal/rdap/tokens/revoke_all kill-all-by-subject (-> count)
POST /api/v1/internal/rdap/tokens/touch best-effort last_used_at only
- rdap_api_tokens table (token_hash unique, subject+revoked_at index); no
privilege field — authorization stays 100% grant-driven. Migration +
hand-spliced structure.sql (schema_format :sql).
- Model RdapApiToken: active_by_hash / for_subject scopes, idempotent revoke!,
touch_last_used! that never moves expires_at (no sliding renewal).
- Fix (existing): add subject + token_hash to filter_parameters — the grants
controller already said 'do not log the subject' (a national id) but nothing
enforced it; now both are filtered out of logs.
- Tests: model + integration (24 new; full internal/rdap suite 60 runs / 158
assertions / 0 failures via docker-images-registry-1).
Contract mirrors the RDAP-side interface (Rdap::Registry::Client TOKEN_OPERATIONS
+ Token value object) so the RDAP RealClient token methods drop straight onto it.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Internal RDAP data API + privileged-grant store (backend only)
The public .ee RDAP service must stop reading the registry DB directly and instead consume a registry-side API (lead decision 2026-06-25). RDAP already shipped the consumer side (a client interface + an in-memory mock). This PR is the real API behind that mock.
Spec (authoritative): lives in the RDAP repo —
specs/specs/completed/08-registry-side-rdap-api/(proposal, requirements,api-contract.md,registry-grounding.md). In-repo copy:doc/api/v1/rdap.md.Endpoints (new namespace
/api/v1/internal/rdap/*)GET …/domains/:nameGET …/registrars/:code{code,name,phone,website}onlyGET …/nameservers/:host{hostname,hostname_puny}(DISTINCT-collapsed)GET …/grants/active?subject=POST …/grants/:id/touchlast_used_at(204)What's in it
Api::V1::Internal::BaseController+Api::V1::Internal::Rdap::{Domains,Registrars,Nameservers,Grants}Controller.authenticate_shared_keypattern;secure_compare; fail-closed when the key is unset). mTLS is a later prod-hardening step. ENV:rdap_internal_api_shared_key,rdap_internal_api_allowed_ips.RdapPrivilegeGrantmodel + migration (categoriespolice/cert/ria/eis_internal, statusesactive/revoked/suspended, server-authoritativeactive_for_subject). The registry had no authority-access concept before.Serializers::Rdap::Domain— never emitstransfer_code/auth_info/registrant_verification_token(does NOT reuseSerializers::Repp::Domain(sponsored: true), which leakstransfer_code). Returns raw disclosure flags (union) — RDAP applies disclosure policy, not the registry.Scope
Backend only — no admin UI this PR (grants seeded via fixtures/console; admin CRUD is a later spec).
Tests
minitest integration + model tests. Verified locally inside the
docker-imagesstack (docker exec docker-images-registry-1): 36 runs, 101 assertions, 0 failures, 0 errors.structure.sqlupdated with only the new table and validated by reloading from scratch (db:test:prepare).doc/api/v1/rdap.mdwere already resolved with the RDAP lead; the namespace/URL is the one thing easiest to change if you prefer a different home.