Skip to content

Connect to databases through an SSH jump host - #7

Open
igor-alexandrov wants to merge 2 commits into
pgrundev:mainfrom
igor-alexandrov:claude/database-ssh-connection-889dd0
Open

igor-alexandrov wants to merge 2 commits into
pgrundev:mainfrom
igor-alexandrov:claude/database-ssh-connection-889dd0

Conversation

@igor-alexandrov

Copy link
Copy Markdown

Closes #6.

Profiles get an ssh field — [user@]host[:port] or a bare ~/.ssh/config alias:

pgterm add production --env PROD_DATABASE_URL --ssh deploy@bastion.example.com

How it works

Two paths, because pgterm has two ways of touching a database:

  • Health checks hand the spec to pgbot via PGBOT_SSH_TUNNEL — it tunnels natively. An ambient tunnel var is stripped when the profile has none, so an exported PGBOT_SSH_TUNNEL can't silently reroute every database (same rule the runner already applies to PGBOT_DATABASE_URL).
  • The SQL and Data tabs ride an ssh -W child process: its stdio is the connection (connect_raw), so no local port opens and the DSN keeps naming the real host — sslmode=verify-full and .pgpass keep working, same properties as pgbot's dialer.

For pgterm's own leg I went with the system ssh rather than the russh port sketched in the issue. It keeps the dependency tree unchanged and gets ssh_config, the agent, ProxyJump, hardware keys, ControlMaster and known_hosts behaving exactly like the user's own ssh bastion — the two pieces that would have needed hand-porting from pgbot (accept-new known_hosts, IdentitiesOnly agent filtering) come for free. BatchMode means a refused login fails with its reason (folded into the tab's error) instead of prompting into a screen the TUI owns. The trade-off is an OpenSSH client requirement, which feels fair for a terminal tool; PGTERM_SSH_BIN overrides the binary.

The spec is parsed and validated before it ever reaches argv — anything option-shaped (-oProxyCommand=…) is refused at add time.

Testing

  • Unit tests: spec parsing incl. IPv6 and hostile inputs, config round-trip, CLI flag, tunnel error paths (missing binary, refused login carrying ssh's stderr, unix-socket DSN refusal).
  • Integration tests: the spec reaches the pgbot child as env (never argv); an ambient var is stripped.
  • End-to-end: SELECT 1 over the tunneled stream against a real Postgres 17 (stdio bridge standing in for ssh -W); also kept as an ignored live test (PGTERM_TEST_SSH_TUNNEL).
  • cargo test (176 passed), clippy clean, fmt applied.

The TUI add-popup doesn't collect the spec yet — config file and CLI only. Happy to add the field in a follow-up if wanted.

🤖 Generated with Claude Code

Profiles gain an `ssh` field ([user@]host[:port] or a ~/.ssh/config
alias), set with `pgterm add --ssh`. Health checks hand the spec to
pgbot via PGBOT_SSH_TUNNEL — it tunnels natively — and an ambient
tunnel var is stripped when the profile has none, same rule as
PGBOT_DATABASE_URL.

The SQL and Data tabs ride an `ssh -W` child: its stdio is the
connection (connect_raw), so no local port opens and the DSN keeps
naming the real host — sslmode=verify-full and .pgpass keep working.
Delegating to the user's own ssh keeps ssh_config, the agent,
ProxyJump and known_hosts behaving exactly as `ssh bastion` does;
BatchMode makes a refused login fail with its reason (folded into
the error) instead of prompting into the TUI.

Closes pgrundev#6

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@alexshapalov

Copy link
Copy Markdown
Contributor

@igor-alexandrov thanks for the PR! WIP

tokio-postgres only has Host::Unix on unix targets; on Windows a
socket path parses as a Tcp "host", so the tunnel refusal never
fired and ssh was spawned toward a path — the Windows CI test died
on the resulting DNS error instead of the intended usage error.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@igor-alexandrov

Copy link
Copy Markdown
Author

@alexshapalov fixed. Thanks for reviewing this!

This branch has not been deployed

No deployments
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.

Support connecting to the database over an SSH tunnel

2 participants