Security
Authentication
The API Gateway is the entry point for all external client access. It supports Dapr's service-to-service mTLS for backend communication with WorkManager and WebSocketManager.
gRPC and REST Endpoints
Both the gRPC and HTTP surfaces are protected by API-key authentication: ApiKeyInterceptor enforces it for gRPC calls, ApiKeyHttpMiddleware for the HTTP surface (/v1/grpc/call, OpenAPI/Scalar docs, JSON-transcoded gRPC). Callers must send the key as the x-api-key gRPC metadata entry or HTTP header; the value is checked with a constant-time comparison against the configured keys. Health checks (grpc.health.v1.Health/*, /health, /healthz) are exempt so orchestrators can probe the pod without a key.
Keys are configured under ApiAuth:Keys:<name> (a name → secret map), typically set via the environment variable form ApiAuth__Keys__<name>=<secret> — see Configuration: API Key Authentication. If no keys are configured, authentication is disabled (the default for local dev). To generate and wire in a key for a new user, run:
python scripts/provision_api_key.py <username>
State Store Security
The state store supports optimistic concurrency via ETags:
- No ETag: Last-write-wins semantics
- Matching ETag: Write succeeds
- Mismatched ETag: Write fails with
StatusCode.Aborted, protecting against lost updates
ETag conflict detection is implemented for both SaveState and DeleteState operations. The Dapr state store's native concurrency mode (ConcurrencyMode.FirstWrite) provides an additional layer of atomicity.
Key Tracking
State keys are registered automatically on first use via SaveState or GetState. Only registered keys are returned by GetAllState. Services should call RegisterKeys on startup to re-register their known keys after a restart.
Dapr Component Security
The API Gateway relies on Dapr for state storage and pub/sub messaging. Ensure Dapr components are configured with:
- State store: Authentication enabled (Redis password for Valkey)
- Pub/sub: Authentication enabled on the message broker
- mTLS: Enabled in Dapr configuration (default since Dapr 1.0)
Configuration & Secrets
The API Gateway reads its configuration from the standard .NET configuration sources (in order: appsettings.json, environment variables, command-line). The following keys control runtime behavior and are the only ones that carry security-sensitive values:
| Key | Default | Security role |
|---|---|---|
Cors:AllowedOrigins |
[] |
Restricts which browser origins can call the REST endpoints. Always set explicitly in production. |
ApiAuth:Keys:<name> |
unset | Name → secret map checked against the x-api-key header/metadata on every call. No keys set = authentication disabled. See Configuration: API Key Authentication. |
ServicesConfigPath |
config/services.json |
Path to the backend services configuration. Treat as security-sensitive — only point at files you control. |
Logging:LogLevel:Default |
Information |
Set to Warning or higher in production to avoid leaking sensitive request data into logs. |
Backend Dapr component values (Redis password, broker credentials) are mounted at runtime via Dapr's secret management component (secretstores/kubernetes) — never commit them to source control. Dapr resolves them by key (e.g., stateStore.password) and the application reads them via DaprClient.GetSecretAsync.
Resiliency
Every Dapr SDK call made by the API — including the Gateway.Invoke
and Gateway.InvokeJson gRPC calls that proxy to downstream services
(WebSocketManager, WorkManager) — is wrapped in a Polly pipeline:
retry with exponential backoff, then circuit breaker.
The pipeline prevents a failing downstream from blocking the gateway:
a 50% failure rate trips the circuit (configurable via
DAPR_CIRCUIT_BREAKER_THRESHOLD), the circuit stays open for
DAPR_CIRCUIT_BREAKER_DURATION_SECONDS, then transitions to
half-open to test recovery. The circuit state is exposed at
/health (tag dapr); when the circuit is open, the health check
reports Unhealthy and the Kubernetes orchestrator will not route
traffic to this pod.
Both sides (API + WorkManager) ship matching env vars so the policy behaves symmetrically across the stack — see Configuration: Dapr Resiliency.
Audit & Logging
The API Gateway emits structured logs for every state-mutating operation (Invoke, ListServices, ListMethods, Subscribe, SaveState, DeleteState, PublishEvent, RegisterKeys) and every Dapr bridge subscription lifecycle event. Each log record includes the request ID, caller identity (if available from the gRPC metadata), and a correlation ID derived from the incoming gRPC request.
Fire-and-forget logging calls (heartbeat writes, sweep events) are wrapped in try/catch and downgrade to LogLevel.Warning if the underlying stream write fails — this prevents observability failures from cascading into service failures.
Reporting Issues
Report security concerns to the Virtufin security team.