🌐 US-Proxy
class="logged-out env-production page-responsive" style="word-wrap: break-word;" >
Skip to content

feat: log tailnet tunnels to the connection log - #27423

Merged
sreya merged 11 commits into
mainfrom
jon/connection-log-tunnel
Jul 28, 2026
Merged

feat: log tailnet tunnels to the connection log #27423
sreya merged 11 commits into
mainfrom
jon/connection-log-tunnel

Conversation

@sreya

@sreya sreya commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Agent-reported SSH/IDE connection log rows can't identify the connecting user, but the tailnet tunnel carrying those sessions is opened via the coordinate endpoint with the user's API key. This PR adds a tunnel connection type and logs a row there with the user's identity, IP, and user agent. Events are deduplicated per (user, agent, IP, client) through the existing workspace_app_audit_sessions mechanism, so client reconnect churn doesn't flood the log.

Closes #27006
Supersedes #27005

@github-actions

github-actions Bot commented Jul 22, 2026

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.

@sreya
sreya force-pushed the jon/connection-log-tunnel branch from 565ac81 to 761271b Compare July 28, 2026 15:41
@sreya
sreya marked this pull request as ready for review July 28, 2026 15:45
@sreya
sreya requested a review from a team as a code owner July 28, 2026 15:45
@sreya sreya changed the title feat(coderd): log tailnet tunnels to the connection log feat: log tailnet tunnels to the connection log Jul 28, 2026
@coder-tasks

coder-tasks Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Documentation Check

Updates Needed

  • docs/admin/monitoring/connection-logs.md - New Tunnel Connections section documents the new tunnel event type, its dedup semantics, the Coder Connect gap, and its point-in-time nature. The username/user_email filter notes were also clarified to say "agent-reported".
  • docs/reference/api/schemas.md - Regenerated for the new tunnel enum value and the updated web_info description.
  • docs/admin/monitoring/connection-logs.md - ## How to Filter Connection Logs is now incomplete for the status filter: it still says "Some events are neither ongoing nor completed, such as the opening of a workspace app", while GetConnectionLogsOffset now also excludes tunnel from status: results (coderd/database/queries/connectionlogs.sql). Mention tunnel events there so the filter reference stands alone.

No new pages are needed: the tunnel type is an addition to an existing documented surface, and docs/manifest.json already covers the connection logs page with premium state. Migration, dump.sql comment, generated docs/reference/api/*, test, and frontend changes need no separate docs.


Automated review via Coder Agents

@sreya

sreya commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 761271b49c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread coderd/workspaceagents.go Outdated
@sreya
sreya requested a review from Emyrk July 28, 2026 17:02
cdigiamo-ant and others added 10 commits July 28, 2026 17:26
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.
…dline

Renumber the tunnel enum migration from 000551 to 000556 after rebasing
onto main, which gained migrations 000551 through 000555.

Give the connection log enqueue its own 3s deadline detached from the
audit session upsert's budget. Sharing one deadline meant a slow
session upsert could exhaust it before the enqueue ran, committing the
session row with no log row written; the active session would then
suppress retries for the entire stale interval, dropping the tunnel
from the audit log for up to an hour after the database recovered.
…tion

Keep the migration to the enum addition only. The prior comment
rewording was cosmetic and added churn to both the up and down
migrations.
Use one named writeCtx for both the audit session upsert and the
connection log enqueue instead of a shadowed ctx plus a separate
enqueue deadline, inline the system-restricted context, and rename
newOrStale to newSession. The shared-budget tradeoff (a slow session
upsert can drop the log row until the session goes stale) is
documented at the timeout.
Main gained 000556_user_secrets_enabled.
@sreya
sreya force-pushed the jon/connection-log-tunnel branch from 79d9c3e to 14bf16c Compare July 28, 2026 17:28

@Emyrk Emyrk left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comments inline. The mechanism is sound — dedupe key verified collision-free against every existing session writer, status-filter exclusion is the only consistent choice, and the docs' interpretation caveats are unusually good. Main open question is the attribution gaps (workspace proxies, Coder Desktop).

Coder Agents on behalf of @Emyrk.

Comment thread coderd/workspaceagents.go
// workspace-proxy-authenticated requests carry no API key and are
// skipped.
func (api *API) logTunnelConnection(ctx context.Context, r *http.Request, waws database.GetWorkspaceAgentAndWorkspaceByIDRow) {
apiKey, ok := httpmw.APIKeyOptional(r)

@Emyrk Emyrk Jul 28, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checked the proxy path: the skip here is correct by design. Proxy-authenticated coordinates (RequireAPIKeyOrWorkspaceProxyAuth) are shared proxy↔agent tunnels carrying many users' traffic, and per-user attribution for proxied activity already happens at the app layer (the wsproxy's workspaceapps provider writes workspace_app/port_forwarding/terminal rows with user identity). Logging them as tunnel rows would just be unattributed noise.

That leaves Coder Desktop / the user-scoped tailnet API as the one real attribution gap — is that tracked as a follow-up?

Coder Agents on behalf of @Emyrk.

Comment thread coderd/workspaceagents.go

@Emyrk Emyrk left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The empty-slug reservation is now programmatically enforced in workspaceapps/db.go (8c22808) — exactly what I asked for. Verified the dedupe key against every session writer, and the workspace-proxy skip is correct by design (proxied activity is attributed at the app layer). The Coder Desktop / user-scoped tailnet API question stands as a non-blocking follow-up.

Coder Agents on behalf of @Emyrk.

@sreya
sreya merged commit 1a6a8be into main Jul 28, 2026
31 of 32 checks passed
@sreya
sreya deleted the jon/connection-log-tunnel branch July 28, 2026 20:30
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 28, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Connection log: attribute SSH/IDE sessions to the Coder user

3 participants