Skip to main content

tensor_wasm_api/
lib.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 Craton Software Company
3
4//! HTTP serverless API gateway built on axum 0.7.
5//!
6//! Exposes deploy/invoke/metrics/healthz endpoints with structured JSON
7//! errors. Application state is a `DashMap<Uuid, FunctionRecord>` shared
8//! via `Arc<AppState>`. The synchronous and async invoke paths both drive
9//! `tensor_wasm_exec::executor::TensorWasmExecutor`.
10//!
11//! ## Security surface
12//!
13//! * **Body limit.** Every request is capped at 64 MiB by
14//!   [`axum::extract::DefaultBodyLimit::max`]; oversized bodies are
15//!   rejected with `413 Payload Too Large` before any handler runs.
16//! * **CORS.** The gateway installs an explicit-allowlist
17//!   [`tower_http::cors::CorsLayer`]; the allowlist is empty by default
18//!   (every cross-origin request rejected). Widen by setting
19//!   `TENSOR_WASM_API_CORS_ALLOWED_ORIGINS` to a comma-separated list of
20//!   origins. See [`middleware::CorsConfig`].
21//! * **Bearer auth.** Reads `TENSOR_WASM_API_TOKENS` (comma-separated allowlist)
22//!   at startup. Empty/unset means dev mode (pass-through with warning);
23//!   otherwise requests must carry `Authorization: Bearer <token>`.
24//! * **Scoped tokens.** Each entry in `TENSOR_WASM_API_TOKENS` may carry a
25//!   `:tenant=` scope clause (`token:tenant=1,2,3` or `token:tenant=*`).
26//!   Routes that bind to a tenant return `403 tenant_scope_denied` when the
27//!   caller's bearer token is not scoped to that tenant. Bare entries are
28//!   treated as wildcard with a one-shot deprecation warning at startup
29//!   (removal targeted for v1.0). See [`token_scope`].
30//! * **Tenant scoping.** The `X-TensorWasm-Tenant: <u64>` header is parsed and
31//!   threaded through to the executor. Set `TENSOR_WASM_API_REQUIRE_TENANT=1`
32//!   to make the header mandatory.
33//! * **Per-token rate limiting.** Configurable QPS + burst per bearer token,
34//!   enforced behind bearer auth. Reads
35//!   `TENSOR_WASM_API_RATE_LIMIT_QPS` and `TENSOR_WASM_API_RATE_LIMIT_BURST`;
36//!   either zero or unset disables the limiter. Rejections return
37//!   `429 Too Many Requests` with a `Retry-After` header. See
38//!   [`rate_limit`].
39//! * **Audit log.** Every state-mutating call (`POST /functions`,
40//!   `DELETE /functions/{id}`, `POST /functions/{id}/invoke[-async]`)
41//!   emits a structured JSON record to the sink selected by
42//!   `TENSOR_WASM_API_AUDIT_LOG` (default: stdout). Read-only routes
43//!   emit nothing. See [`audit`] and `docs/AUDIT-LOG.md`.
44//! * **XFCC spoofing gate via `TENSOR_WASM_API_TRUSTED_XFCC_PROXIES`.**
45//!   Comma-separated allowlist of IPv4/IPv6 addresses or CIDR ranges
46//!   whose `X-Forwarded-Client-Cert` headers the audit middleware will
47//!   honour. Empty / unset = trust nobody (safe default — every inbound
48//!   XFCC is dropped). Deployments behind an mTLS-terminating proxy
49//!   should set this to the proxy's IP(s); operators not running an
50//!   XFCC-stripping proxy should leave it unset. See
51//!   [`audit::TrustedProxies`].
52//! * **Snapshot HMAC key.** When
53//!   `TENSOR_WASM_API_SNAPSHOT_HMAC_KEY` is set (64-char hex, 32 bytes)
54//!   the `/snapshot/save` route HMAC-SHA256-signs the snapshot blob it
55//!   returns and `/snapshot/restore` verifies the signature (always
56//!   requiring a signature on restore). Set
57//!   `TENSOR_WASM_API_SNAPSHOT_REQUIRE_SIGNATURE=true` for the matching
58//!   strict-restore posture. The routes are wired into the protected
59//!   stack (bearer auth + tenant scope + per-tenant ownership) by
60//!   [`build_router`], which reads the key via
61//!   [`AppConfig::from_env`](config::AppConfig::from_env); when the key is
62//!   unset both routes return `503 snapshot_signing_not_configured`. See
63//!   [`routes::snapshot_save`] / [`routes::snapshot_restore`] and
64//!   [`config`] for the schema.
65//! * **HTTP request metrics.** A tower middleware emits
66//!   `tensor_wasm_http_requests_total`,
67//!   `tensor_wasm_http_request_duration_seconds`, and
68//!   `tensor_wasm_http_requests_in_flight` per `(route, method, status)`,
69//!   labelled with the axum route template (never the substituted id).
70//!   The layer sits OUTSIDE bearer auth so `401`/`429` responses are
71//!   counted too — required by the `availability_http` SLI in
72//!   `docs/SLO.md`. See [`http_metrics`].
73//!
74//! See [`API.md`](../API.md) for the wire-format contract.
75#![deny(missing_docs)]
76
77pub mod audit;
78pub mod config;
79pub mod http_metrics;
80#[cfg(feature = "kernel-registry-api")]
81pub mod kernels;
82pub mod middleware;
83pub mod openai;
84pub mod openai_translator;
85pub mod rate_limit;
86pub mod routes;
87pub mod server;
88pub mod token_scope;
89pub mod trace_propagation;
90
91pub use audit::{
92    audit_log_middleware, AuditAction, AuditActor, AuditActorKind, AuditConfig, AuditOutcome,
93    AuditRecord, AuditResource, AuditSink, FileJsonSink, NoopSink, StdoutJsonSink, TokenScopeView,
94    TrustedProxies, ENV_AUDIT_LOG, ENV_TRUSTED_XFCC_PROXIES, HEADER_XFCC,
95};
96pub use config::{
97    AppConfig, ConfigError, HexParseReason, ENV_SNAPSHOT_HMAC_KEY, ENV_SNAPSHOT_REQUIRE_SIGNATURE,
98    SNAPSHOT_HMAC_KEY_LEN,
99};
100pub use http_metrics::{
101    http_metrics_middleware, HttpMetricsLayerConfig, RouteAllowList, DEFAULT_ROUTE_ALLOWLIST,
102    UNKNOWN_ROUTE,
103};
104pub use middleware::{
105    normalize_method, sanitize_path, AuthConfig, CorsConfig, KernelPublishTokens, TenantConfig,
106    TrustedHosts, ENV_API_TOKENS, ENV_CORS_ALLOWED_ORIGINS, ENV_KERNEL_PUBLISH_TOKENS,
107    ENV_REQUIRE_TENANT, ENV_TRUSTED_HOSTS, HEADER_TENANT, MAX_AUTH_HEADER_BYTES, MAX_PATH_LEN,
108    MAX_REQUEST_BODY_BYTES,
109};
110pub use openai::{
111    ChatCompletionsRequest, ChatMessage, CompletionsRequest, OpenAiError, OpenAiErrorBody,
112};
113pub use openai_translator::{
114    assemble_chat_prompt, model_map_from_env, parse_model_map_env,
115    translate_chat_completions_request, translate_completions_request, ModelMap, TranslatedRequest,
116    ENV_OPENAI_MODEL_MAP,
117};
118pub use rate_limit::{
119    AuthContext, PerTenantRateLimitConfig, RateLimitConfig, RateLimiter, TokenId,
120};
121pub use routes::{
122    snapshot_restore, snapshot_save, ApiError, AppState, FunctionRecord, JobRecord, JobStatus,
123    SnapshotRestoreRequest, SnapshotRestoreResponse, SnapshotSaveRequest, SnapshotSaveResponse,
124};
125pub use server::{
126    build_router, build_router_with_audit, build_router_with_config, build_router_with_full_config,
127    build_router_with_kernel_publish_tokens, build_router_with_trusted_proxies, serve,
128};
129pub use token_scope::{
130    parse_token_entry, parse_tokens_env, ParsedTokens, ScopeParseError, TenantScope, TokenScope,
131};
132pub use trace_propagation::{
133    current_trace_id, extract_parent_context, inject_trace_id_header, install_w3c_propagator,
134    HeaderMapExtractor, HEADER_TRACE_ID,
135};