Skip to content

Security

Authentication

The WebSocketManager gRPC and HTTP endpoints accept connections authenticated via Dapr's service-to-service mTLS. All inter-service calls (API Gateway → WebSocketManager) are secured by Dapr's built-in mutual TLS.

For external WebSocket servers, authentication is handled at the connection level:

  • Managed connections: Credentials can be embedded in the WebSocket URL or exchanged via the connection's message protocol
  • Binary pipeline: Messages are sent as raw bytes over WebSocket Binary or Text frames, determined by the content_type field. No correlation envelope wrapping occurs on external servers — correlation is handled via a 16-byte UUID prefix.

For external client access to the WebSocketManager itself, authentication should be handled at the API Gateway layer. See API Gateway Security for JWT bearer token and role-based authorization configuration.

Connection Ownership

Each connection is owned by the service instance (Kubernetes pod) that created it. Only the owning instance can modify or disconnect a connection. The instance ID is derived from the HOSTNAME environment variable (Kubernetes pod name). If HOSTNAME is unset (e.g. local dotnet run), the instance ID falls back to a fresh GUID per process start.

Per-connection routing uses heartbeat-based peer discovery and direct gRPC forwarding. Each instance periodically publishes its identity to the Dapr state store. When a connection-specific RPC (Disconnect, StartPublish, StopPublish, Send, SendRaw) lands on the wrong instance, the handler reads the connection's owner from the state store and forwards the call via a direct gRPC channel. On forward failure (peer dead), the forwarder reclaims the resource by re-stamping the state-store record with its own instance ID and retries locally.

Pod replacement

Kubernetes pod replacement (rolling update, eviction, node drain, HPA scale-down) changes the underlying compute. When a pod dies:

  1. The heartbeat service on surviving instances detects the missing peer (heartbeat TTL expires) and removes it from the peer index.
  2. The next RPC targeting the dead pod's resources triggers a forward attempt → Unavailable → reclamation (state-store record re-stamped, resource re-created locally).
  3. For connections, the surviving instance creates a fresh ClientWebSocket if auto_reconnect=true.

Straggler connection records can be cleared on startup with CLEAR_ALL_ON_START=TRUE.

Configuration & Secrets

The WebSocketManager 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.
Logging:LogLevel:Default Information Set to Warning or higher in production to avoid leaking sensitive request data into logs.

Connection 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.

Dapr Component Security

The WebSocketManager 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)

Audit & Logging

The WebSocketManager emits structured logs for every state-mutating operation (connect, disconnect, send, start_publish, stop_publish) and every reclaim sweep. Each log record includes the instance ID, connection ID, and a correlation ID derived from the incoming gRPC request metadata.

Fire-and-forget logging calls (subscription health sweep, reclaim sweep) 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.