feat: Support enabling proxy when adding nodes (#11525)

This commit is contained in:
ssongliu 2025-12-31 22:31:43 +08:00 committed by GitHub
parent d9a5418029
commit 6205eac51c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 155 additions and 114 deletions

View file

@ -709,7 +709,7 @@ func checkProxy(req dto.ProxyUpdate) error {
if len(req.ProxyUser) != 0 {
proxyURL.User = url.UserPassword(req.ProxyUser, req.ProxyPasswd)
}
transport = http.Transport{Proxy: http.ProxyURL(proxyURL)}
transport = http.Transport{Proxy: http.ProxyURL(proxyURL), TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}
case "socks5":
var auth *proxy.Auth
if len(req.ProxyUser) == 0 {

View file

@ -1,103 +0,0 @@
package cloud_storage
import (
"io"
"net"
"os"
"path"
"time"
"github.com/1Panel-dev/1Panel/core/global"
"github.com/pkg/sftp"
"golang.org/x/crypto/ssh"
)
type SftpClient struct {
connInfo string
config *ssh.ClientConfig
}
func NewSftpClient(vars map[string]interface{}) (*SftpClient, error) {
address := loadParamFromVars("address", vars)
port := loadParamFromVars("port", vars)
if len(port) == 0 {
global.LOG.Errorf("load param port from vars failed, err: not exist!")
}
authMode := loadParamFromVars("authMode", vars)
passPhrase := loadParamFromVars("passPhrase", vars)
username := loadParamFromVars("username", vars)
password := loadParamFromVars("password", vars)
var auth []ssh.AuthMethod
if authMode == "key" {
var signer ssh.Signer
var err error
if len(passPhrase) != 0 {
signer, err = ssh.ParsePrivateKeyWithPassphrase([]byte(password), []byte(passPhrase))
} else {
signer, err = ssh.ParsePrivateKey([]byte(password))
}
if err != nil {
return nil, err
}
auth = []ssh.AuthMethod{ssh.PublicKeys(signer)}
} else {
auth = []ssh.AuthMethod{ssh.Password(password)}
}
clientConfig := &ssh.ClientConfig{
User: username,
Auth: auth,
Timeout: 30 * time.Second,
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
return nil
},
}
addr := net.JoinHostPort(address, port)
if _, err := ssh.Dial("tcp", addr, clientConfig); err != nil {
return nil, err
}
return &SftpClient{connInfo: addr, config: clientConfig}, nil
}
func (s SftpClient) Upload(src, target string) (bool, error) {
sshClient, err := ssh.Dial("tcp", s.connInfo, s.config)
if err != nil {
return false, err
}
defer sshClient.Close()
client, err := sftp.NewClient(sshClient)
if err != nil {
return false, err
}
defer client.Close()
srcFile, err := os.Open(src)
if err != nil {
return false, err
}
defer srcFile.Close()
targetDir, _ := path.Split(target)
if len(targetDir) != 0 {
if _, err = client.Stat(targetDir); err != nil {
if os.IsNotExist(err) {
if err = client.MkdirAll(targetDir); err != nil {
return false, err
}
} else {
return false, err
}
}
}
dstFile, err := client.Create(target)
if err != nil {
return false, err
}
defer dstFile.Close()
if _, err := io.Copy(dstFile, srcFile); err != nil {
return false, err
}
return true, nil
}

81
core/utils/ssh/http.go Normal file
View file

@ -0,0 +1,81 @@
package ssh
import (
"bufio"
"crypto/tls"
"encoding/base64"
"fmt"
"net"
"strings"
"time"
"github.com/1Panel-dev/1Panel/core/global"
)
type HTTPProxyDialer struct {
Type string
URL string
User string
Password string
}
func HTTPDial(dialer HTTPProxyDialer, network, addr string) (net.Conn, error) {
var conn net.Conn
var err error
global.LOG.Debugf("Dialing HTTP proxy %s for %s", dialer.URL, addr)
dialer.URL = strings.TrimPrefix(dialer.URL, dialer.Type+"://")
if dialer.Type == "https" {
conn, err = tls.DialWithDialer(
&net.Dialer{Timeout: 30 * time.Second},
network,
dialer.URL,
&tls.Config{InsecureSkipVerify: true},
)
} else {
conn, err = net.DialTimeout(network, dialer.URL, 30*time.Second)
}
if err != nil {
return nil, err
}
conn.SetDeadline(time.Now().Add(30 * time.Second))
connectReq := fmt.Sprintf("CONNECT %s HTTP/1.1\r\n", addr)
connectReq += fmt.Sprintf("Host: %s\r\n", addr)
connectReq += "User-Agent: Go-ssh-client/1.0\r\n"
if dialer.User != "" {
auth := base64.StdEncoding.EncodeToString(
[]byte(dialer.User + ":" + dialer.Password),
)
connectReq += fmt.Sprintf("Proxy-Authorization: Basic %s\r\n", auth)
}
connectReq += "Connection: keep-alive\r\n\r\n"
if _, err := conn.Write([]byte(connectReq)); err != nil {
conn.Close()
return nil, err
}
reader := bufio.NewReader(conn)
response, err := reader.ReadString('\n')
if err != nil {
conn.Close()
return nil, err
}
if !strings.HasPrefix(response, "HTTP/1.1 200") &&
!strings.HasPrefix(response, "HTTP/1.0 200") {
conn.Close()
return nil, fmt.Errorf("proxy connection failed: %s", strings.TrimSpace(response))
}
for {
line, err := reader.ReadString('\n')
if err != nil {
conn.Close()
return nil, err
}
if line == "\r\n" || line == "\n" {
break
}
}
conn.SetDeadline(time.Time{})
return conn, nil
}

View file

@ -8,18 +8,23 @@ import (
"strings"
"time"
"github.com/1Panel-dev/1Panel/core/app/repo"
"github.com/1Panel-dev/1Panel/core/global"
"github.com/1Panel-dev/1Panel/core/utils/encrypt"
gossh "golang.org/x/crypto/ssh"
"golang.org/x/net/proxy"
)
type ConnInfo struct {
User string `json:"user"`
Addr string `json:"addr"`
Port int `json:"port"`
AuthMode string `json:"authMode"`
Password string `json:"password"`
PrivateKey []byte `json:"privateKey"`
PassPhrase []byte `json:"passPhrase"`
User string `json:"user"`
Addr string `json:"addr"`
Port int `json:"port"`
AuthMode string `json:"authMode"`
Password string `json:"password"`
PrivateKey []byte `json:"privateKey"`
PassPhrase []byte `json:"passPhrase"`
UseProxy bool `json:"useProxy"`
DialTimeOut time.Duration `json:"dialTimeOut"`
}
@ -52,7 +57,7 @@ func NewClient(c ConnInfo) (*SSHClient, error) {
if strings.Contains(c.Addr, ":") {
proto = "tcp6"
}
client, err := DialWithTimeout(proto, addr, config)
client, err := DialWithTimeout(proto, addr, c.UseProxy, config)
if nil != err {
return nil, err
}
@ -240,8 +245,14 @@ func (c *SSHClient) RunWithStreamOutput(command string, outputCallback func(stri
return err
}
func DialWithTimeout(network, addr string, config *gossh.ClientConfig) (*gossh.Client, error) {
conn, err := net.DialTimeout(network, addr, config.Timeout)
func DialWithTimeout(network, addr string, useProxy bool, config *gossh.ClientConfig) (*gossh.Client, error) {
var conn net.Conn
var err error
if useProxy {
conn, err = loadSSHConnByProxy(network, addr, config.Timeout)
} else {
conn, err = net.DialTimeout(network, addr, config.Timeout)
}
if err != nil {
return nil, err
}
@ -256,3 +267,55 @@ func DialWithTimeout(network, addr string, config *gossh.ClientConfig) (*gossh.C
}
return gossh.NewClient(c, chans, reqs), nil
}
func loadSSHConnByProxy(network, addr string, timeout time.Duration) (net.Conn, error) {
settingRepo := repo.NewISettingRepo()
proxyType, err := settingRepo.Get(repo.WithByKey("ProxyType"))
if err != nil {
return nil, fmt.Errorf("get proxy type from db failed, err: %v", err)
}
if len(proxyType.Value) == 0 {
return nil, fmt.Errorf("get proxy type from db failed, err: %v", err)
}
proxyUrl, _ := settingRepo.Get(repo.WithByKey("ProxyUrl"))
port, _ := settingRepo.Get(repo.WithByKey("ProxyPort"))
user, _ := settingRepo.Get(repo.WithByKey("ProxyUser"))
passwd, _ := settingRepo.Get(repo.WithByKey("ProxyPasswd"))
pass, _ := encrypt.StringDecrypt(passwd.Value)
proxyItem := fmt.Sprintf("%s:%s", proxyUrl.Value, port.Value)
switch proxyType.Value {
case "http", "https":
item := HTTPProxyDialer{
Type: proxyType.Value,
URL: proxyItem,
User: user.Value,
Password: pass,
}
return HTTPDial(item, network, addr)
case "socks5":
var auth *proxy.Auth
if len(user.Value) == 0 {
auth = nil
} else {
auth = &proxy.Auth{
User: user.Value,
Password: pass,
}
}
dialer, err := proxy.SOCKS5("tcp", proxyItem, auth, &net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
})
if err != nil {
return nil, fmt.Errorf("new socks5 proxy failed, err: %v", err)
}
return dialer.Dial(network, addr)
default:
conn, err := net.DialTimeout(network, addr, timeout)
if err != nil {
return nil, err
}
return conn, nil
}
}