diff --git a/server/api/ssh.go b/server/api/ssh.go index 48dd939..7169b3e 100644 --- a/server/api/ssh.go +++ b/server/api/ssh.go @@ -134,8 +134,18 @@ func SSHEndpoint(c echo.Context) (err error) { recording = path.Join(config.GlobalCfg.Guacd.Recording, sessionId, "recording.cast") } + attributes, err := assetRepository.FindAssetAttrMapByAssetId(s.AssetId) + if err != nil { + return WriteMessage(ws, NewMessage(Closed, "获取资产属性失败:"+err.Error())) + } + var xterm = "xterm-256color" - nextTerminal, err := term.NewNextTerminal(ip, port, username, password, privateKey, passphrase, rows, cols, recording, xterm, true) + var nextTerminal *term.NextTerminal + if "true" == attributes[constant.SocksProxyEnable] { + nextTerminal, err = term.NewNextTerminalUseSocks(ip, port, username, password, privateKey, passphrase, rows, cols, recording, xterm, true, attributes[constant.SocksProxyHost], attributes[constant.SocksProxyPort], attributes[constant.SocksProxyUsername], attributes[constant.SocksProxyPassword]) + } else { + nextTerminal, err = term.NewNextTerminal(ip, port, username, password, privateKey, passphrase, rows, cols, recording, xterm, true) + } if err != nil { return WriteMessage(ws, NewMessage(Closed, "创建SSH客户端失败:"+err.Error())) diff --git a/server/constant/const.go b/server/constant/const.go index 73f0439..3f11d7f 100644 --- a/server/constant/const.go +++ b/server/constant/const.go @@ -59,9 +59,15 @@ const ( StatusEnabled = "enabled" StatusDisabled = "disabled" + + SocksProxyEnable = "socks-proxy-enable" + SocksProxyHost = "socks-proxy-host" + SocksProxyPort = "socks-proxy-port" + SocksProxyUsername = "socks-proxy-username" + SocksProxyPassword = "socks-proxy-password" ) -var SSHParameterNames = []string{guacd.FontName, guacd.FontSize, guacd.ColorScheme, guacd.Backspace, guacd.TerminalType, SshMode} +var SSHParameterNames = []string{guacd.FontName, guacd.FontSize, guacd.ColorScheme, guacd.Backspace, guacd.TerminalType, SshMode, SocksProxyEnable, SocksProxyHost, SocksProxyPort, SocksProxyUsername, SocksProxyPassword} var RDPParameterNames = []string{guacd.Domain, guacd.RemoteApp, guacd.RemoteAppDir, guacd.RemoteAppArgs, guacd.EnableDrive, guacd.DrivePath} var VNCParameterNames = []string{guacd.ColorDepth, guacd.Cursor, guacd.SwapRedBlue, guacd.DestHost, guacd.DestPort} var TelnetParameterNames = []string{guacd.FontName, guacd.FontSize, guacd.ColorScheme, guacd.Backspace, guacd.TerminalType, guacd.UsernameRegex, guacd.PasswordRegex, guacd.LoginSuccessRegex, guacd.LoginFailureRegex} diff --git a/server/guacd/guacd.go b/server/guacd/guacd.go index 5b19f4b..ea725d9 100644 --- a/server/guacd/guacd.go +++ b/server/guacd/guacd.go @@ -229,7 +229,7 @@ func (opt *Tunnel) WriteInstructionAndFlush(instruction Instruction) error { } func (opt *Tunnel) WriteAndFlush(p []byte) (int, error) { - //fmt.Printf("-> %v\n", string(p)) + fmt.Printf("-> %v\n", string(p)) nn, err := opt.writer.Write(p) if err != nil { return nn, err @@ -257,7 +257,7 @@ func (opt *Tunnel) Read() (p []byte, err error) { } buffer = buffer[0:read] s := string(buffer) - //fmt.Printf("<- %v \n", s) + fmt.Printf("<- %v \n", s) if s == "rate=44100,channels=2;" { return make([]byte, 0), nil } diff --git a/server/term/next_terminal.go b/server/term/next_terminal.go index e1cb3a3..3be9d44 100644 --- a/server/term/next_terminal.go +++ b/server/term/next_terminal.go @@ -22,7 +22,18 @@ func NewNextTerminal(ip string, port int, username, password, privateKey, passph if err != nil { return nil, err } + return newNT(err, sshClient, pipe, recording, term, rows, cols) +} +func NewNextTerminalUseSocks(ip string, port int, username, password, privateKey, passphrase string, rows, cols int, recording, term string, pipe bool, socksProxyHost, socksProxyPort, socksProxyUsername, socksProxyPassword string) (*NextTerminal, error) { + sshClient, err := NewSshClientUseSocks(ip, port, username, password, privateKey, passphrase, socksProxyHost, socksProxyPort, socksProxyUsername, socksProxyPassword) + if err != nil { + return nil, err + } + return newNT(err, sshClient, pipe, recording, term, rows, cols) +} + +func newNT(err error, sshClient *ssh.Client, pipe bool, recording string, term string, rows int, cols int) (*NextTerminal, error) { sshSession, err := sshClient.NewSession() if err != nil { return nil, err diff --git a/server/term/ssh.go b/server/term/ssh.go index 9e0b032..70a0f3a 100644 --- a/server/term/ssh.go +++ b/server/term/ssh.go @@ -2,6 +2,8 @@ package term import ( "fmt" + "golang.org/x/net/proxy" + "net" "time" "golang.org/x/crypto/ssh" @@ -49,29 +51,73 @@ func NewSshClient(ip string, port int, username, password, privateKey, passphras } addr := fmt.Sprintf("%s:%d", ip, port) - // - //socks5, err := proxy.SOCKS5("tcp", "", - // &proxy.Auth{User: "username", Password: "password"}, - // &net.Dialer{ - // Timeout: 30 * time.Second, - // KeepAlive: 30 * time.Second, - // }, - //) - //if err != nil { - // return nil, err - //} - // - //conn, err := socks5.Dial("tcp", addr) - //if err != nil { - // return nil, err - //} - // - //clientConn, channels, requests, err := ssh.NewClientConn(conn, addr, config) - //if err != nil { - // return nil, err - //} - // - //ssh.NewClient(clientConn, channels, requests) - return ssh.Dial("tcp", addr, config) } + +func NewSshClientUseSocks(ip string, port int, username, password, privateKey, passphrase string, socksProxyHost, socksProxyPort, socksProxyUsername, socksProxyPassword string) (*ssh.Client, error) { + var authMethod ssh.AuthMethod + if username == "-" || username == "" { + username = "root" + } + if password == "-" { + password = "" + } + if privateKey == "-" { + privateKey = "" + } + if passphrase == "-" { + passphrase = "" + } + + var err error + if privateKey != "" { + var key ssh.Signer + if len(passphrase) > 0 { + key, err = ssh.ParsePrivateKeyWithPassphrase([]byte(privateKey), []byte(passphrase)) + if err != nil { + return nil, err + } + } else { + key, err = ssh.ParsePrivateKey([]byte(privateKey)) + if err != nil { + return nil, err + } + } + authMethod = ssh.PublicKeys(key) + } else { + authMethod = ssh.Password(password) + } + + config := &ssh.ClientConfig{ + Timeout: 3 * time.Second, + User: username, + Auth: []ssh.AuthMethod{authMethod}, + HostKeyCallback: ssh.InsecureIgnoreHostKey(), + } + + socksProxyAddr := fmt.Sprintf("%s:%s", socksProxyHost, socksProxyPort) + + socks5, err := proxy.SOCKS5("tcp", socksProxyAddr, + &proxy.Auth{User: socksProxyUsername, Password: socksProxyPassword}, + &net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + }, + ) + if err != nil { + return nil, err + } + + addr := fmt.Sprintf("%s:%d", ip, port) + conn, err := socks5.Dial("tcp", addr) + if err != nil { + return nil, err + } + + clientConn, channels, requests, err := ssh.NewClientConn(conn, addr, config) + if err != nil { + return nil, err + } + + return ssh.NewClient(clientConn, channels, requests), nil +} diff --git a/web/src/components/asset/Asset.js b/web/src/components/asset/Asset.js index 2979b79..017b5a2 100644 --- a/web/src/components/asset/Asset.js +++ b/web/src/components/asset/Asset.js @@ -283,6 +283,7 @@ class Asset extends Component { asset['use-ssl'] = asset['use-ssl'] === 'true'; asset['ignore-cert'] = asset['ignore-cert'] === 'true'; asset['enable-drive'] = asset['enable-drive'] === 'true'; + asset['socks-proxy-enable'] = asset['socks-proxy-enable'] === 'true'; this.setState({ modalTitle: title, diff --git a/web/src/components/asset/AssetModal.js b/web/src/components/asset/AssetModal.js index dd5ee9d..01f7460 100644 --- a/web/src/components/asset/AssetModal.js +++ b/web/src/components/asset/AssetModal.js @@ -126,6 +126,7 @@ const AssetModal = function ({title, visible, handleOk, handleCancel, confirmLoa } let [enableDrive, setEnableDrive] = useState(model['enable-drive']); + let [socksProxyEnable, setSocksProxyEnable] = useState(model['socks-proxy-enable']); let [storages, setStorages] = useState([]); useEffect(() => { @@ -138,7 +139,7 @@ const AssetModal = function ({title, visible, handleOk, handleCancel, confirmLoa getStorages(); }, []); - let [accessGateways,setAccessGateways] = useState([]); + let [accessGateways, setAccessGateways] = useState([]); useEffect(() => { const getAccessGateways = async () => { const result = await request.get('/access-gateways'); @@ -320,7 +321,7 @@ const AssetModal = function ({title, visible, handleOk, handleCancel, confirmLoa - { protocol === 'rdp' ? @@ -476,7 +477,46 @@ Windows需要对远程应用程序的名称使用特殊的符号。 - : undefined + : <> + Socks 代理} key="socks"> + + { + setSocksProxyEnable(checked); + }}/> + + + { + socksProxyEnable ? <> + + + + + + + + + + + + + + + + + : undefined + } + + } : undefined