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};