Voltar para Documentação

Docs Técnicas

atlas-ingress

atlas-ingress is a small public REST ingress/proxy that sits in front of atlas-node's REST API. Its job is to expose a tightly allowlisted public surface for reads, transaction submission, and SSE events without exposing the full node API directly.

O conteúdo abaixo vem das fontes técnicas do repositório e é prerenderizado no site para leitura direta por pessoas, crawlers e agentes.

Summary

atlas-ingress is a small public REST ingress/proxy that sits in front of atlas-node's REST API. Its job is to expose a tightly allowlisted public surface for reads, transaction submission, and SSE events without exposing the full node API directly.

This crate does not embed the node runtime and does not depend on atlas-node as a Rust library. It talks to an upstream node over HTTP(S), which makes it a decoupled edge service rather than an internal runtime module.

Why This Crate Exists

  • expose a controlled public REST surface in front of atlas-node
  • block admin-only and mutation-heavy routes that should not be internet-facing
  • centralize CORS policy and request-body limits for the public edge
  • forward client IP context to the upstream node
  • keep the public ingress deployable as a separate lightweight process

Current Role In The Workspace

atlas-ingress currently owns four focused concerns:

  1. request filtering by path and method before any upstream call
  2. reverse-proxy forwarding to the configured atlas-node REST upstream
  3. CORS and body-size policy for the public edge
  4. a minimal standalone binary that can be started in dev, demo, or containerized environments

This is one of the clearest single-purpose crates in the workspace.

Public Surface

Top-Level Surface

The crate is effectively a single-module library plus a binary:

  • `lib.rs`
  • `main.rs`

Main Public API

  • IngressConfig
  • AppState
  • IngressError
  • build_router(...)
  • normalize_origins(...)
  • build_http_client(...)
  • default_state(...)

Package Targets

  • library target atlas_ingress
  • binary target atlas-ingress

Key Modules

  • `lib.rs`: router, request filtering, upstream URL construction, CORS setup, upstream validation, and tests
  • `main.rs`: CLI/env parsing, default config assembly, state creation, and Axum server startup

Inputs And Outputs

Inputs

  • bind address and port
  • upstream atlas-node REST base URL
  • body limit and request timeout values
  • configured CORS origins
  • incoming public HTTP requests

Outputs

  • a public Axum HTTP server
  • proxied requests to the upstream atlas-node
  • forwarded client IP headers: x-forwarded-for and x-real-ip
  • streamed upstream responses back to clients
  • structured error responses when a route, method, payload, or upstream is rejected

Internal Dependencies

Workspace Dependencies

  • none

That is a notable boundary: atlas-ingress is intentionally independent from the rest of the Rust workspace at the crate-dependency level.

External Dependencies That Shape The Design

  • axum for the public HTTP server and routing
  • reqwest for proxying requests to the upstream node
  • tower-http for CORS handling
  • clap for CLI/env configuration
  • tokio for async runtime
  • tracing and tracing-subscriber for operational logging

Used By

No Rust crate currently depends directly on atlas-ingress.

Operationally, the package is used as a standalone service in local and demo flows:

  • `start_ingress.sh`: convenience launcher for local ingress startup
  • `start_demo_dev.sh`: builds and runs the ingress alongside the demo stack
  • `start_demo_prod.sh`: production-style demo launcher also builds the ingress
  • `docker/supervisord.conf`: supervisord runs atlas-ingress as its own process in the container stack
  • `docker/Dockerfile`: builds and copies the standalone ingress binary

Request Model

Health Surface

The crate exposes a small local health endpoint:

  • GET /healthz

It reports:

  • static "ok" status
  • the configured upstream node URL

Allowlisted Public Surface

All other requests go through the fallback proxy path in `proxy_request(...)`.

The current public surface is explicitly allowlisted in code, not inferred from upstream behavior.

Allowed reads include:

  • /api/transactions
  • /api/events
  • /api/mempool
  • /api/balance
  • /api/global-balance
  • /api/accounts
  • /api/tokens
  • /api/audit/proposals
  • /api/institutions
  • /api/aec/*
  • /api/audit/proposal/*
  • /api/aliases/*
  • /api/kyc/*

Allowed writes are much narrower:

  • POST /api/transaction

Anything outside that surface, such as admin-style paths like /api/accounts/open or /api/kyc/issue, is rejected before proxying.

Method Policy

Method filtering is also explicit:

  • GET only for allowlisted public read paths
  • POST only for /api/transaction
  • OPTIONS only for known public paths

This means ingress policy is enforced locally even if the upstream node would accept more.

Proxy Behavior

For accepted requests, the crate:

  • builds the upstream URL by concatenating the configured base URL with the incoming path and query
  • reads the request body with a configured max size
  • forwards selected request headers
  • injects client IP information through x-forwarded-for and x-real-ip
  • issues the upstream request through a shared reqwest::Client
  • streams the upstream body back to the caller

The proxy currently preserves a small response-header subset:

  • content-type
  • cache-control
  • x-request-id
  • retry-after

That is enough for JSON APIs and SSE-style responses without trying to mirror every upstream header.

Security And Edge Policy

atlas-ingress is simple, but it still enforces a few meaningful edge constraints.

Upstream URL Validation

default_state(...) validates the configured upstream before the server starts:

  • upstream URL must be absolute
  • scheme must be http or https
  • host must be present
  • plain http is only allowed for loopback upstreams

So a remote upstream must use HTTPS, while local development can still target http://127.0.0.1:3001.

CORS Behavior

If no explicit origins are provided, normalize_origins(...) falls back to a dev-focused localhost allowlist rather than "*".

That default list includes local ports like:

  • localhost:5173-5177
  • 127.0.0.1:5173-5177

If "*" is configured explicitly, the ingress will allow any origin.

Body Limits And Timeouts

The main edge limits are configured through:

  • body_limit_bytes
  • request_timeout_ms

Request bodies that exceed the limit are rejected at the ingress before the upstream sees them.

Runtime And Deployment Model

The binary in `main.rs` is intentionally small:

  1. initialize tracing
  2. parse CLI args or env vars
  3. normalize CORS origins
  4. validate upstream config and build the shared HTTP client
  5. build the Axum router
  6. bind a TCP listener
  7. serve requests with ConnectInfo<SocketAddr> enabled

The main runtime env vars are:

  • ATLAS_INGRESS_BIND_ADDR
  • ATLAS_INGRESS_PORT
  • ATLAS_INGRESS_NODE_URL
  • ATLAS_INGRESS_BODY_LIMIT_BYTES
  • ATLAS_INGRESS_REQUEST_TIMEOUT_MS
  • ATLAS_INGRESS_CORS_ALLOWED_ORIGINS

Testing

The crate keeps its tests in `lib.rs`.

Coverage includes:

  • allowlisted route and method policy
  • upstream URL construction with preserved query strings
  • default CORS-origin normalization
  • upstream validation for loopback HTTP vs remote HTTPS
  • router rejection of blocked admin paths
  • router rejection of wrong methods before proxying
  • healthz behavior
  • CORS preflight handling

For a small edge service, that is a solid amount of behavioral coverage.

Risks Or Design Tension

Surface Allowlist Must Track `atlas-node`

The ingress hardcodes what public paths exist. That is good for safety, but it creates a maintenance requirement: every time atlas-node changes its intended public REST surface, this crate may need an explicit update.

Header Forwarding Is Intentionally Narrow

Only a selected set of request and response headers is forwarded. That keeps the proxy easier to reason about, but it may surprise future features that assume transparent passthrough.

REST-Only Boundary

This crate only fronts the REST surface. It does not proxy the node's public gRPC or secure proposal gRPC endpoints, so the public ingress story is intentionally partial rather than universal.

Policy Lives In Code, Not Shared Contracts

The list of public paths and methods is encoded directly in lib.rs, not generated from a shared API contract. That keeps it obvious, but also makes drift more likely over time.