Skip to content

feat(coderd): log tailnet tunnels to the connection log #27423

Draft
sreya wants to merge 5 commits into
mainfrom
jon/connection-log-tunnel
Draft

feat(coderd): log tailnet tunnels to the connection log #27423
sreya wants to merge 5 commits into
mainfrom
jon/connection-log-tunnel

Conversation

@sreya

@sreya sreya commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

No description provided.

cdigiamo-ant and others added 5 commits July 21, 2026 20:15
Agent-reported SSH/VSCode/JetBrains connection_log rows have no user_id
because the agent does not know which Coder user connected (see the
comment in coderd/agentapi/connectionlog.go). This makes it impossible
to attribute SSH/IDE sessions to a Coder user from the connection log.

Every such session is carried over a tailnet tunnel that the client
opens via /api/v2/workspaceagents/{id}/coordinate using the user's API
key, so coderd knows the user at that point.

Add a new connection_type 'tailnet' and write one connection_log row
from workspaceAgentClientCoordinate whenever an authenticated user
successfully upgrades the coordinate WebSocket. The row carries
user_id, ip, user_agent, workspace_id and agent_name, and is rendered
via WebInfo alongside workspace_app / port_forwarding.
Address review feedback on the original changeset:

- Rebase onto main and renumber the migration to 000551 to resolve the
  collision with 000539_ai_provider_icons.
- Rename the connection_type value tailnet to tunnel across the
  migration, codersdk, enterprise converter, and frontend, since the
  enum value is permanent API surface and should name the event rather
  than the transport.
- Implement a real down migration that recreates connection_type
  without the tunnel value, following the pattern in
  000533_nats_ca_crypto_key_feature.down.sql.
- Update the remaining connection_logs column comments (slug_or_port,
  disconnect_time, disconnect_reason) to the agent-reported vs
  coderd-reported taxonomy.
- Extract the connection log write into api.logTunnelConnection and
  bound it with a 3s timeout so connection log backpressure cannot
  stall tunnel establishment.
- Cover the status-filter exclusion with a tunnel fixture in
  TestConnectionLogsOffsetFilters and the WebInfo mapping with a
  WebInfoTunnel subtest.
- Harden the coordinate test: testutil.Context, AwaitReachable
  assertion, Code/ConnectionStatus expectations, and a second dial
  asserting each handshake produces its own row.
- Document tunnel connections and their semantics in
  docs/admin/monitoring/connection-logs.md.
- Clarify the connectionTypeIsWeb helper and replace the unreachable
  Unauthenticated user copy in the tunnel description branch.

Co-authored-by: Chris DiGiamo <cdigiamo@anthropic.com>
Reuse the workspace_app_audit_sessions mechanism to collapse tunnel
reconnections into one connection log row per active session. Clients
automatically re-dial the coordinate endpoint after network blips, load
balancer timeouts, and coderd restarts, so logging every handshake
would flood the connection log with reconnect noise that is
indistinguishable from genuine user activity.

Tunnel sessions are keyed on (agent, user, IP, user agent) with
app_id = uuid.Nil and an empty slug, matching how port forwarding uses
the table; status code 101 keeps them from colliding with app sessions.
A new row is logged only when no session exists for the key or the
session has been idle past the stale interval (1 hour by default,
sliding). On dedup failure the log write is skipped, matching the
workspace app precedent of not spamming the connection log during
database problems.
Removing an enum value requires recreating connection_type and
rewriting the connection_logs type column, which takes an exclusive
lock on the table and would have to delete all tunnel rows because
they cannot exist in the old type. Leaving the value in place is
harmless and matches the precedent of other enum-value additions.
Explain how the status code keeps tunnel audit sessions from
colliding with app and port forwarding sessions: the status code is
part of the session unique key, and app sessions record the token
authorization status (200, 4xx) while tunnel sessions always record
101, so the keys can never conflict.

Drop the redundant 'failed' suffix from the error log messages; the
Error level already implies failure.
@github-actions

Copy link
Copy Markdown

Docs preview

Check off each page once it's been reviewed. If a page changes in a later push, its checkbox clears automatically so it gets a fresh look. Pages not yet wired into the docs navigation aren't listed here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants