Active · v0.7.0 · RDP + SSH + temporal correlation
PythonFastAPISQLAlchemyAlembicMariaDBUvicornsystemdNginxHTTPXRESTJSON

From an RDP API to a common remote-session domain

The first version of this project solved a focused problem: receive RDP session telemetry from multiple Windows Servers without giving dashboards or integrations administrative access to the monitored hosts.

As the system evolved, the domain stopped being RDP-only. The same infrastructure needed to represent SSH sessions, preserve connection-origin evidence, and support historical investigation without breaking Windows consumers that were already in production.

The repository kept its historical name, RDP-Session-API, but the application evolved into a Remote Session API capable of representing both RDP and SSH in a common domain.

The current 0.7.0 release preserves /api/v1 for Windows/RDP and adds a generic /api/v2 contract for remote sessions.

Problem

Remote-session monitoring looks simple while the question is only “who is connected right now?” The real problem appears when the system must answer, consistently over time:

  • who opened a session;
  • on which server;
  • through which protocol;
  • when it connected and ended;
  • whether it disconnected and reconnected;
  • what connection origin was observed;
  • which device was associated with that address at that point in time;
  • and how state can recover after network failures, reboots, or missed lifecycle records.

Querying every server directly also creates operational coupling. Every consumer would need to understand Windows Event Log, WTS, OpenSSH, logind, administrative credentials, and the underlying topology.

The architecture therefore centralizes persistence, normalization, and querying without centralizing collection.

Current architecture

Current remote-session monitoring architecture
01 Windows and Linux Agents RDP Session Agent publishes events and snapshots through /api/v1; SSH Session Agent uses /api/v2, always over outbound HTTPS.
02 HTTPS reverse proxy Terminates TLS and forwards authenticated calls to the FastAPI service bound only to loopback.
03 Remote Session API Authenticates, normalizes v1/v2, persists events, applies the state machine, reconciles snapshots, and exposes global history.
04A MariaDB / MySQL Raw events, consolidated sessions, per-server credentials, correlation jobs, and frozen temporal evidence.
04B Correlation Worker Processes enrichment asynchronously without blocking ingestion and queries the temporal resolver using source_ip + timestamp.
04C Temporal Network Resolver Returns MATCHED, AMBIGUOUS, or UNRESOLVED with network evidence valid for the observed point in time.
05 Portal, Grafana, and read-only consumers Query history, active sessions, timeline, alerts, and metrics using a separate read credential.

The critical ingestion path is deliberately independent from network enrichment. An event can be authenticated, persisted, and consolidated even when the resolver is unavailable.

Origin correlation happens later, asynchronously.

Compatibility between /api/v1 and /api/v2

A key design decision was not to force-migrate the Windows Agent just to introduce SSH.

/api/v1 remains the stable RDP surface:

POST /api/v1/agent/events
POST /api/v1/agent/snapshot
GET  /api/v1/servers
GET  /api/v1/servers/{id}/summary
GET  /api/v1/servers/{id}/sessions/active
GET  /api/v1/servers/{id}/sessions/history

/api/v2 represents sessions independently of provider specifics:

POST /api/v2/agent/events
POST /api/v2/agent/snapshot
GET  /api/v2/servers
GET  /api/v2/sessions/active
GET  /api/v2/sessions/history
GET  /api/v2/sessions/{id}
GET  /api/v2/sessions/{id}/timeline
GET  /api/v2/correlation/metrics

The generic identity includes fields such as:

  • platform;
  • protocol;
  • boot_id;
  • provider_session_id;
  • provider_event_id.

A Windows Session ID and a Linux logind:42 can therefore coexist without forcing one operating system’s semantics onto the other.

Internal normalization

RDP v1 payloads continue to be accepted normally, but they are normalized internally into the same common session domain used by v2.

This allowed the database, history, and query model to evolve without breaking the existing Windows Agent.

It is a pattern I consider important in operational systems: compatibility at the edge, a common model at the core.

Session state machine

The API maintains three consolidated states:

ACTIVE
DISCONNECTED
CLOSED

For RDP, the normal lifecycle is:

LOGON        -> ACTIVE
DISCONNECT   -> DISCONNECTED
RECONNECT    -> ACTIVE
LOGOFF       -> CLOSED

SSH has a smaller lifecycle, typically LOGON -> LOGOFF, while still using the same consolidated session entity.

The API can also close sessions using derived reasons such as:

  • RECONCILIATION: central state still considered a session open, but a fresh snapshot no longer observed it;
  • REBOOT: the boot identity changed and the session belonged to a previous boot.

Events and snapshots solve different problems

The architecture does not treat snapshots as a replacement for lifecycle events.

Events    -> chronology: what happened and when
Snapshots -> observed truth: what exists now

Combining both allows the system to recover consistency after temporary Agent, API, or network outages.

A session can be created or updated from events and later corrected by a snapshot if consolidated state drifts from the host.

Idempotent ingestion

Agents use local spooling precisely because the network can fail at any point.

The API therefore needs replay to be safe.

Every event receives deterministic identity/fingerprinting. A batch can be transmitted again without recreating the session or replaying a transition that has already been persisted.

For v1, the historical fingerprint behavior was preserved to remain compatible with existing Windows Agent spools. In v2, identity includes the generic provider, boot, and session context.

Idempotency is not merely an optimization here; it is part of the recovery protocol.

Capability-separated authentication

Write and read credentials are intentionally separate.

Each monitored server receives:

X-Server-ID: <server-id>
Authorization: Bearer <agent-secret>

The Agent secret is unique per server, and only its hash is stored by the API.

Read access uses a different credential:

X-API-Key: <query-api-key>

Compromising an Agent credential therefore does not automatically grant global session-history access.

The correlation worker uses a third dedicated service-to-service credential for the temporal resolver.

Connection-origin evidence

Agents can send source_ip and, when the local evidence supports it, source_port.

Those values are treated as evidence, not permanent identity.

A missing IP does not invalidate a session. Likewise, the API does not turn a device’s current IP ownership into a historical conclusion simply because that address belongs to the device now.

That distinction became essential once session telemetry started being correlated with inventory data.

Asynchronous temporal correlation

The correct question is not:

who owns this IP now?

It is:

what network evidence associated this IP with a device at the time the session occurred?

Correlation uses source_ip + timestamp and runs in a worker separate from the HTTP API.

The result is deterministic:

MATCHED
AMBIGUOUS
UNRESOLVED

AMBIGUOUS and UNRESOLVED are valid outcomes. The system deliberately prefers no association over a false MATCHED result.

Transient transport failures can be retried, but missing or conflicting evidence is not “fixed” through guesswork.

Frozen correlation evidence

When correlation completes, the API preserves the resolver evidence that supported the decision.

This avoids a subtle historical problem: DHCP state, inventory, and addresses continue to change after the session occurred. If old sessions were interpreted only using current network state, their meaning could silently change over time.

The session therefore retains the temporal evidence used at the time of correlation.

Global history and timeline

The v2 surface adds cross-server and cross-protocol history.

Queries can filter by attributes such as:

  • server;
  • protocol;
  • state;
  • username;
  • time range;
  • source IP;
  • correlation status.

The API also exposes a per-session timeline reconstructed from the underlying events.

This matters because provider identifiers can be reused. An RDP Session ID or a logind ID alone is not a permanent global identity.

Linux operations

The application runs on Linux with Uvicorn bound to loopback behind an HTTPS reverse proxy.

Runtime is split into two independent systemd processes:

HTTP API
Correlation Worker

This separation allows enrichment to be stopped or degraded without stopping telemetry ingestion.

Alembic manages migrations, and schema evolution has been kept additive to support gradual rollout between API and Agents.

Observing the monitoring system itself

The system monitors the health of its own telemetry pipeline, not only user sessions.

Operational metrics include:

  • accepted and duplicate events;
  • source_ip coverage;
  • correlation rate;
  • MATCHED, UNRESOLVED, and AMBIGUOUS counts;
  • enrichment queue depth and age;
  • per-server last_seen;
  • Agent version distribution.

This avoids the anti-pattern of building an observability system that cannot explain its own degradation.

Evolution without a big-bang migration

The expansion was intentionally additive:

  1. keep the existing RDP contract stable;
  2. add optional origin fields;
  3. introduce the generic v2 domain;
  4. add global history;
  5. introduce asynchronous temporal correlation;
  6. integrate the SSH Agent;
  7. expand rollout without requiring all collectors to upgrade simultaneously.

This made it possible to evolve the architecture without a destructive migration of already monitored servers.

Current state

Version 0.7.0 now represents a remote-session system rather than an RDP-only API.

The project currently provides:

  • backward-compatible /api/v1 for RDP;
  • generic /api/v2 for RDP and SSH;
  • idempotent ingestion;
  • snapshots and reconciliation;
  • global history and timeline;
  • optional IPv4/IPv6 origin evidence;
  • asynchronous temporal correlation;
  • frozen historical evidence;
  • per-server Agent authentication;
  • a separate query API key;
  • managed migrations and Linux deployment.

What the project demonstrates

The evolution of Remote Session API reflects a common infrastructure problem: the first implementation solves a concrete use case, but the long-term value comes from generalizing the domain without breaking what already works.

Technically, the project combines contract versioning, idempotent ingestion, state machines, reconciliation, asynchronous processing, temporal evidence modeling, capability-separated authentication, and secure operation of Python infrastructure services.

Most importantly, it treats telemetry as evidence that can be incomplete or ambiguous rather than assuming an impossible perfect stream of facts.