Skip to content

feat: add connections, a saved SSH connection manager#6483

Open
gustavosbarreto wants to merge 1 commit into
masterfrom
feature/direct-hosts
Open

feat: add connections, a saved SSH connection manager#6483
gustavosbarreto wants to merge 1 commit into
masterfrom
feature/direct-hosts

Conversation

@gustavosbarreto

@gustavosbarreto gustavosbarreto commented Jun 13, 2026

Copy link
Copy Markdown
Member

Connections are saved SSH targets, kept separate from devices. A connection targets either a direct host (dial host:port directly, with no agent) or a device (an agent-registered machine reached through the gateway). It adds a connection-manager entry point alongside the fleet.

Direct connections are dialed by the ssh service over a lightweight /ws/connect bridge, independent of the device session machinery. Device connections reuse the existing terminal flow.

Reachability is probed server-side over TCP: shown per row in the list and checked at save time, where an unreachable direct host surfaces a hint to install the agent (for hosts behind NAT or a firewall) with a save-anyway override.

What changed

  • APIconnection routes/service/store across pg, with migrations 007_connections and 008_connection_device_target, new permissions, request/response models.
  • SSH/ws/connect bridge in ssh/web/connect.go that dials direct hosts independently of the device session machinery.
  • UI (console) — connections page, device picker, sidebar entry, terminal integration, hooks/stores/types.

Screenshots

Connections list — direct hosts and devices side by side, each with a live status dot.

Add a connection — direct host — dial a host by address, no agent required.

Unreachable host → install-the-agent funnel — save-time TCP probe; if the host can't be reached, it nudges toward installing the agent (NAT/firewall), with a save-anyway override.

Add a connection — device — pick an agent-registered machine via a server-side searched picker.

Connect — device connections reuse the rich terminal/auth flow.

Screenshots are hotlinked from branch docs/connections-screenshots (images only, deletable once this PR closes).

@gustavosbarreto gustavosbarreto force-pushed the feature/direct-hosts branch 2 times, most recently from 811282a to 642aaf5 Compare June 18, 2026 20:32
@gustavosbarreto gustavosbarreto force-pushed the feature/direct-hosts branch 4 times, most recently from e3240d0 to 3c57555 Compare June 23, 2026 22:10
@gustavosbarreto gustavosbarreto marked this pull request as ready for review June 23, 2026 22:10
@gustavosbarreto gustavosbarreto requested review from a team as code owners June 23, 2026 22:10
@gustavosbarreto

Copy link
Copy Markdown
Member Author

/review

@github-actions

github-actions Bot commented Jun 23, 2026

Copy link
Copy Markdown

Claude finished @gustavosbarreto's task in 21m 18s —— View job


Code Review Complete

Reviewed 94 files across code quality, security, testing, Go/TypeScript patterns, and architecture (including cross-repo impact on cloud/). 1 inline issue posted (medium severity).

Summary

This is a well-structured MVP. The SSRF egress guardian (pkg/egress) is solid — socket-level validation via code.dny.dev/ssrf, dev-only private-range allowlist, single shared policy. Owner-scoped connection resolution prevents cross-user leaks. Host key verification (TOFU with mismatch detection) and single-use tokens (LoadAndDelete) are correctly implemented. The store layer uses parameterized queries throughout, and the new permission iota values are appended safely. Cross-repo impact is clean: cloud inherits the new store interfaces via embedding, migration tables are separate, and route paths don't collide.

The one finding is that scanHostKey, ConnectionStatus, and ProbeReachability use Dial instead of DialContext, making outbound probes uncancellable when the request context is done.

Additional notes

  • The encryptPassword/decryptPassword //nolint:errcheck pattern in connect.go:77,125 mirrors the existing web.go:59,121 convention — not flagged as it's pre-existing, but worth considering a follow-up to handle failures gracefully in both paths.
  • KnownHostGet.Host and KnownHostDelete.Host are missing the hostname_rfc1123|ip validation that KnownHostScan and KnownHostAccept have — low impact (parameterized queries prevent injection) but worth aligning for consistency.
  • Test coverage for the new service and store methods is sparse (only GetConnection and UpdateConnectionRejectsKindChange are tested). The security-critical validateDeviceTarget (prevents cross-namespace device references) and AcceptKnownHost role guard (namespace-scope writes require operator+) would benefit from dedicated tests.

To request another review round, comment /review.

Comment thread api/services/known_host.go Outdated
@gustavosbarreto

Copy link
Copy Markdown
Member Author

/review

@github-actions

github-actions Bot commented Jun 23, 2026

Copy link
Copy Markdown

Claude finished @gustavosbarreto's task in 7m 51s —— View job


Code Review Complete

Reviewed 94 files across code quality, security, testing, Go/TypeScript patterns, and architecture (including cross-repo impact on cloud/). 1 inline issue posted (high severity).

Summary

Well-structured MVP. The SSRF egress guardian (pkg/egress) is solid — socket-level validation via code.dny.dev/ssrf, dev-only private-range allowlist, single shared policy. Owner-scoped connection resolution prevents cross-user leaks. Host key verification (TOFU with mismatch detection) and single-use tokens (LoadAndDelete) are correctly implemented. The store layer uses parameterized queries throughout, and the new permission iota values are appended safely. Tests are correct in what they cover.

The one finding is that KnownHostUpsert uses .Returning().Exec() instead of .Returning().Scan(), so the returned id/created_at are silently wrong on an upsert conflict.

Additional notes

  • Cross-repo breakage in cloud/: cloud/internal/cloud/services/team-connection.go:156 calls egress.Reachable(connection.Host, connection.Port) with 2 args, but after the DialContext fix in b27ccd3 the signature is now Reachable(ctx, host, port) (3 args). This will fail to compile. The fix is egress.Reachable(ctx, connection.Host, connection.Port).
  • The encryptPassword/decryptPassword //nolint:errcheck pattern in connect.go:77,125 mirrors the existing web.go:59,121 convention — same as noted in the previous review round.
  • KnownHostGet.Host and KnownHostDelete.Host are missing the hostname_rfc1123|ip validation that KnownHostScan and KnownHostAccept have — low impact (parameterized queries prevent injection) but worth aligning for consistency.

To request another review round, comment /review.

Comment thread api/store/pg/known_host.go Outdated
@gustavosbarreto gustavosbarreto force-pushed the feature/direct-hosts branch 2 times, most recently from 75d8f91 to 57571b8 Compare June 24, 2026 13:55
@gustavosbarreto

Copy link
Copy Markdown
Member Author

/review

@github-actions

github-actions Bot commented Jun 24, 2026

Copy link
Copy Markdown

Claude finished @gustavosbarreto's task in 19m 19s —— View job


Code Review Complete

Reviewed 96 files across code quality, security, testing, Go/TypeScript patterns, and architecture (including cross-repo impact on cloud/). 0 inline issues posted.

Summary

Third review round — both previously flagged issues (DialContext propagation and KnownHostUpsert .Exec.Scan) are confirmed fixed. No new issues found.

The codebase demonstrates solid practices throughout: SSRF egress protection via socket-level validation (code.dny.dev/ssrf) with dev-only private-range allowlist; owner-scoped connection resolution preventing cross-tenant leaks; parameterized queries in all store methods; host key TOFU with mismatch detection; single-use tokens via LoadAndDelete; proper context propagation through all dial paths; and correct permission iota ordering. The cloud/ repo is fully compatible — egress.Reachable now receives ctx at all call sites, the embedded store inherits the new ConnectionStore/KnownHostStore interfaces, and mock methods are regenerated.

Additional notes

The observations from prior rounds still apply as follow-up considerations (not blockers):

  • The encryptPassword/decryptPassword //nolint:errcheck pattern in connect.go:77,125 mirrors the existing web.go:59,121 convention. A follow-up to handle failures gracefully in both paths would be a worthwhile hardening pass.
  • KnownHostGet.Host and KnownHostDelete.Host are missing the hostname_rfc1123|ip validation that KnownHostScan and KnownHostAccept have — low impact (parameterized queries prevent injection) but worth aligning for consistency.

To request another review round, comment /review.

A personal address book for reaching SSH targets from the browser: saved
external hosts (dialed straight through an SSRF guardian, no agent) and
ShellHub devices, each with its own auth. Keys come from the vault or a
one-off paste, and host keys are pinned on a trust-on-first-use basis.

The target kind is fixed at creation. Team connections are a Cloud/Enterprise
capability, surfaced as an upsell on the Community edition.
@gustavosbarreto gustavosbarreto force-pushed the feature/direct-hosts branch 2 times, most recently from 5a03aa8 to 0a5dff8 Compare June 24, 2026 14:29
@gustavosbarreto

Copy link
Copy Markdown
Member Author

/review

@github-actions

github-actions Bot commented Jun 24, 2026

Copy link
Copy Markdown

Claude finished @gustavosbarreto's task in 2m 8s —— View job


Code Review

  • Gathered PR context
  • Reviewing with 5 specialized agents (all running in parallel)
    • Code Quality (sonnet)
    • Security (opus)
    • Testing (opus)
    • Go/TypeScript Patterns (sonnet)
    • Architecture & Cross-repo (opus)
  • Aggregating and validating findings
  • Posting feedback

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant