# SSH tunnels and proxies All network indirection uses the same model: the backend opens a local listener on `127.0.0.1:` per connection id, the frontend stores that port as memory-only `tunnelPort`, and `effectiveConnectionString()` rewrites the database URL to it. A failing hop, agent or proxy aborts activation and the connection test with a German error that names the failing hop; there is never a direct-connect fallback. ## SSH authentication - **Password / key**: unchanged. Key paths may start with `~/`. - **Agent**: uses `SSH_AUTH_SOCK` or a custom socket path. Every identity (keys and certificates) is tried in order until the server accepts one. RSA keys use the best `rsa-sha2-*` variant the server offers. - 1Password: macOS `~/Library/Group Containers/2BUA8C4S2C.com.1password/t/agent.sock`, Linux `~/.1password/agent.sock` (preset button in the editor). - Windows: default is the OpenSSH named pipe `\\.\pipe\openssh-ssh-agent` (also used by 1Password); a socket value of `pageant` uses Pageant. ## Jump hosts (ProxyJump) `ssh.jumpHosts` is an ordered list. The first hop is reached via TCP (or the proxy), every further hop and finally the SSH server via `direct-tcpip` channels of the previous hop. Host keys are checked against known_hosts for every hop using the host name and port as seen from the previous hop; "Neue SSH-Host-Keys akzeptieren" (TOFU) applies to all hops. ## Proxies `proxy` (`socks5` or `http`, optional user) is used to reach the first SSH hop. Without SSH, the backend forwards the local port through the proxy to the database host and port taken from the connection string (`open_proxy_tunnel`); opening the forwarder probes the proxy once so bad credentials or unreachable targets fail immediately. SOCKS5 sends host names to the proxy (remote DNS). ## Command tunnels "Tunnel-Befehl" runs a user-supplied command instead of SSH or a proxy (not combinable with either), e.g. `kubectl port-forward svc/postgres {localPort}:5432`, `aws ssm start-session … localPortNumber={localPort}` or `cloudflared access tcp --hostname db.example.com --url localhost:{localPort}` (presets in the editor). The backend (`db/ssh/command.rs`, `open_command_tunnel`) picks a free local port (or the fixed port from the editor; `{localPort}` may then be omitted), substitutes `{localPort}` and starts the command via the login shell (`$SHELL -lc`, PATH extended with the same tool directories as `pg_dump` lookup) in its own process group. It waits until `127.0.0.1:` accepts TCP (timeout per connection, default 20 s). An early exit or the timeout fails with the command's stderr; a later exit marks the tunnel broken and emits `ssh-tunnel-failed`. The process group is killed when the tunnel is closed or replaced and on app exit. The connection then uses `tunnelPort` like any other tunnel. The command is stored in the connection (not the keychain), so do not put secrets into it. ## Secrets Secrets never enter localStorage. Keychain accounts per connection: `` (database), `:ssh` (password/passphrase), `:ssh-jumps` (JSON array, one entry per jump host), `:proxy` (proxy password). Exports contain hosts, users, key paths and agent sockets but no secrets. ## ~/.ssh/config import "Aus ~/.ssh/config übernehmen" reads `Host` blocks (`HostName`, `User`, `Port`, `IdentityFile`, `IdentityAgent`, `ProxyJump`, `Host *` defaults). Aliases in `ProxyJump` are resolved against other blocks. `Include`, `Match` and wildcard host patterns are ignored. ## TLS `sslmode` follows libpq for every SQL family that supports TLS: | Mode | Encrypted | Certificate chain | Hostname | |---|---|---|---| | `disable` | no | – | – | | `prefer` | if the server offers it | not checked | not checked | | `require` | yes | only checked when `sslrootcert` is set | not checked | | `verify-ca` | yes | checked | not checked | | `verify-full` | yes | checked | checked | Without `sslrootcert` the OS trust store is used. With `sslrootcert` only that CA (PEM) is trusted. - **PostgreSQL**: `sslrootcert`, `sslcert` + `sslkey` (PKCS#8 PEM) or `sslcert=.p12` + `sslpassword`. `schema=`/`currentSchema=` become `search_path`; client-only parameters (e.g. Prisma's `pgbouncer`, `connection_limit`) are dropped. `pg_dump`/`pg_restore` get `PGSSLROOTCERT`, `PGSSLCERT`, `PGSSLKEY`. - **MySQL/MariaDB**: `sslrootcert` (alias `ssl-ca`), client certificates as PKCS#12 (`sslcert=.p12`, `sslpassword`). `prefer` falls back to plaintext only when the server has no TLS; `require` then fails. JDBC parameters (`useSSL`, `requireSSL`, `verifyServerCertificate`, `serverTimezone`, …) are accepted, unknown ones are ignored. `enable_cleartext_plugin=true` enables the cleartext auth plugin (PAM/LDAP, AWS IAM tokens over TLS). - **SQL Server**: `sslmode` as above, CA via `sslrootcert` or `TrustServerCertificateCA`. ADO-style `encrypt=true` keeps verifying the certificate unless `TrustServerCertificate=true`. - **Cassandra/ScyllaDB**: TLS is opt-in via `sslmode=require|verify-ca|verify-full` or `ssl=true`; `prefer` stays plaintext because CQL has no TLS negotiation. Hostname checks use the host from the URL, not the node IP. Client certificates via `sslcert` + `sslkey` (PEM). - **SQLite/DuckDB**: a missing file is an error; append `?mode=rwc` to create a new database file. ## Limitations - SSH host certificates are rejected (host keys only), as before. - MCP server connections do not support SSH, proxies or command tunnels. - The proxy forwarder needs a plain host and port in the connection string; URLs whose endpoint is resolved by the driver (e.g. DNS SRV records) cannot be forwarded. - Windows agent support (named pipe, Pageant) uses russh's Windows agent API but is not covered by the Docker lab.