Add SOCKS5/HTTP proxy support for SSH connections

Allow SSH connections to be routed through SOCKS5 or HTTP CONNECT proxies.

Backend:
- Add ProxyType, ProxyHost, ProxyPort, ProxyUsername, ProxyPassword to SSHConfig
- dialTarget() dispatches to direct TCP, SOCKS5, or HTTP CONNECT based on config
- SOCKS5: uses golang.org/x/net/proxy with optional username/password auth
- HTTP CONNECT: sends CONNECT request with optional Basic auth, validates 200 OK
- Proxy dialling replaces ssh.Dial for non-jump-host connections; the resulting
  net.Conn is passed to ssh.NewClientConn for the SSH handshake

Frontend:
- Add Proxy section to SSH profile editor (collapsed by default)
- Proxy type selector: None / SOCKS5 / HTTP CONNECT
- Host, port, username, password fields
- Collect proxy values on save
This commit is contained in:
2026-04-17 00:00:00 +01:00
parent 2446e037ce
commit 9d3b3ff507
5 changed files with 148 additions and 6 deletions
+28
View File
@@ -408,6 +408,27 @@ class ProfileEditor {
// TOTP / 2FA
sections.push(this._renderTOTPSection(profile));
// Proxy
sections.push(this._renderSection('Proxy', [
this._field('select', 'ssh.proxyType', 'Proxy Type', ssh.proxyType || 'none', {
options: [
{ value: 'none', label: 'None (direct connection)' },
{ value: 'socks5', label: 'SOCKS5' },
{ value: 'http', label: 'HTTP CONNECT' },
],
}),
this._field('text', 'ssh.proxyHost', 'Proxy Host', ssh.proxyHost || '', {
placeholder: 'proxy.example.com',
}),
this._field('number', 'ssh.proxyPort', 'Proxy Port', ssh.proxyPort || 1080, { min: 1, max: 65535 }),
this._field('text', 'ssh.proxyUsername', 'Proxy Username', ssh.proxyUsername || '', {
placeholder: 'Optional',
}),
this._field('text', 'ssh.proxyPassword', 'Proxy Password', ssh.proxyPassword || '', {
placeholder: 'Optional',
}),
], true));
// Advanced
sections.push(this._renderSection('Advanced', [
this._field('text', 'ssh.terminalType', 'Terminal Type', ssh.terminalType, { placeholder: 'xterm-256color' }),
@@ -1264,6 +1285,13 @@ class ProfileEditor {
p.ssh.maxReconnectTries = val('ssh.maxReconnectTries') ?? 5;
p.ssh.startupCommand = val('ssh.startupCommand') || '';
// Proxy settings
p.ssh.proxyType = val('ssh.proxyType') || 'none';
p.ssh.proxyHost = val('ssh.proxyHost') || '';
p.ssh.proxyPort = val('ssh.proxyPort') || 1080;
p.ssh.proxyUsername = val('ssh.proxyUsername') || '';
p.ssh.proxyPassword = val('ssh.proxyPassword') || '';
const authStr = val('ssh.authMethods');
if (authStr !== undefined) {
p.ssh.authMethods = authStr.split(',').map((s) => s.trim()).filter(Boolean);