原生 ssh 增加 socks 代理功能
This commit is contained in:
@ -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()))
|
||||
|
@ -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}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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(() => {
|
||||
@ -320,7 +321,7 @@ const AssetModal = function ({title, visible, handleOk, handleCancel, confirmLoa
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={11}>
|
||||
<Collapse defaultActiveKey={['remote-app', '认证', 'VNC中继', 'storage', '模式设置', '显示设置', '控制终端行为']}
|
||||
<Collapse defaultActiveKey={['remote-app', '认证', 'VNC中继', 'storage', '模式设置', '显示设置', '控制终端行为', 'socks']}
|
||||
ghost>
|
||||
{
|
||||
protocol === 'rdp' ?
|
||||
@ -476,8 +477,47 @@ Windows需要对远程应用程序的名称使用特殊的符号。
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Panel>
|
||||
</> : <>
|
||||
<Panel header={<Text strong>Socks 代理</Text>} key="socks">
|
||||
<Form.Item
|
||||
name="socks-proxy-enable"
|
||||
label="使用Socks代理"
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch checkedChildren="是" unCheckedChildren="否"
|
||||
onChange={(checked, event) => {
|
||||
setSocksProxyEnable(checked);
|
||||
}}/>
|
||||
</Form.Item>
|
||||
|
||||
{
|
||||
socksProxyEnable ? <>
|
||||
<Form.Item label="代理地址" name='socks-proxy-host'
|
||||
rules={[{required: true}]}>
|
||||
<Input placeholder="Socks 代理的主机地址"/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="代理端口" name='socks-proxy-port'
|
||||
rules={[{required: true}]}>
|
||||
<InputNumber min={1} max={65535}
|
||||
placeholder='Socks 代理的主机端口'/>
|
||||
</Form.Item>
|
||||
|
||||
<input type='password' hidden={true}
|
||||
autoComplete='new-password'/>
|
||||
<Form.Item label="代理账号" name='socks-proxy-username'>
|
||||
<Input autoComplete="off" placeholder="代理账号,没有可以不填"/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="代理密码" name='socks-proxy-password'>
|
||||
<Input.Password autoComplete="off"
|
||||
placeholder="代理密码,没有可以不填"/>
|
||||
</Form.Item>
|
||||
</> : undefined
|
||||
}
|
||||
</Panel>
|
||||
</>
|
||||
}
|
||||
|
||||
</> : undefined
|
||||
}
|
||||
|
Reference in New Issue
Block a user