前言
最近在使用 sftp
服務時,被告知發起了海量的連接,直接把服務器搞崩,ip
被封了。
這是啥情況?
golang
寫的代碼,我就正常的訪問 sftp
服務,連接使用過后也都關閉了,咋會出現連接一直連著沒關的情況呢?
原因分析
先上代碼,主要使用了開源庫 github.com/pkg/sftp
package mainimport ("fmt""github.com/pkg/sftp""golang.org/x/crypto/ssh""time"
)func main() {ticker := time.NewTicker(time.Second)for range ticker.C {client, err := createDefaultSftpClient()if err != nil {panic(err)}// 演示用wd, err := client.Getwd()if err != nil {panic(err)}fmt.Println(wd)client.Close()}
}func createSFTPClient(user, pwd, host, port string, pemBytes []byte) (*ssh.Client, *sftp.Client, error) {var authMethods []ssh.AuthMethodif pwd != "" {authMethods = append(authMethods, ssh.Password(pwd))}if len(pemBytes) > 0 {signer, err := ssh.ParsePrivateKey(pemBytes)if err != nil {return nil, nil, err}authMethods = append(authMethods, ssh.PublicKeys(signer))}config := &ssh.ClientConfig{User: user,Auth: authMethods,HostKeyCallback: ssh.InsecureIgnoreHostKey(),}conn, err := ssh.Dial("tcp", host+":"+port, config)if err != nil {return nil, nil, err}client, err := sftp.NewClient(conn)if err != nil {return nil, nil, err}return conn, client, nil
}var defaultSftpPemBytes = []byte(``)func createDefaultSftpClient() (*sftp.Client, error) {_, client, err := createSFTPClient("foo", "test", "127.0.0.1", "2222", defaultSftpPemBytes)return client, err
}
本篇的測試環境是 windows
,當上述程序跑起來后,查看本機 2222
端口的使用情況(netstat -ano | findstr 2222
),這一看,果然是好多連接啊。為什么我的 client
已經 Close
了還是會有這么多連接未關閉呢?
想必細心的朋友們已經發現了問題,我故意給 createSFTPClient
返回了兩個連接,一個是 ssh.Client
還有一個是 sftp.Client
,但是 createDefaultSftpClient
只返回了 sftp.Client
。
查看 github.com/pkg/sftp
源碼發現:
sftp.NewClient
會調用SSH
連接的NewSession
方法創建一個新會話,但不會持有SSH
連接的所有權。sftpClient.Close()
僅關閉SFTP
會話的Channel
,而SSH
連接的生命周期由調用方(即sshClient
)控制。
原因就是 ssh.Client
創建了沒有關閉,必須要顯示調用 sshClient.Close()
!!!
搭建測試 sftp 服務
使用 docker
鏡像 atmoz/sftp
搭建 sftp
服務。
docker-compose.yaml
version: '3.8'services:sftp:image: atmoz/sftpvolumes:- ./atmoz_data:/home/foo/uploads# - ./atmoz_ssh_keys:/home/foo/.ssh/keys # 支持密鑰登錄command: foo:test:1001 # 用戶名:空密碼:UID:GIDports:- "2222:22"
這里我踩了兩個坑
linux
環境下,服務啟動后,嘗試創建目錄時報錯mkdir /test: permission denied
。 是因為宿主機目錄的權限或所有權未與容器內用戶的UID/GID
匹配。例如,容器內用戶UID
為 1001,但宿主機目錄所有者是root
,導致權限沖突。可以調整宿主機目錄的權限chown -R 1001:1001 /宿主機目錄/atmoz_data
。- 若需支持密鑰登錄,需將上述的
docker-compose.yaml
中volumes
的注釋去掉。注意:宿主機的atmoz_ssh_keys
目錄下一定要把自己生成的ssh key
公鑰放進去,不然會報錯。
[/usr/local/bin/create-sftp-user] Parsing user data: "foo:test:1001"
cat: '/home/foo/.ssh/keys/*': No such file or directory
因為使用 atmoz/sftp
鏡像時,若在 command
中定義了用戶 foo:test:1001
,鏡像會自動執行以下操作:
- 創建用戶
foo(UID 1001)
。 - 嘗試從
/home/foo/.ssh/keys/
目錄加載公鑰文件(*.pub
或authorized_keys
)。 - 將公鑰寫入
/home/foo/.ssh/authorized_keys
。
由于將容器內的 /home/foo/.ssh/keys/
目錄映射出去了,但是有沒放密鑰文件進去,找不到文件,所以就直接報錯了。
這里還有一點,atmoz/sftp
搭建的服務應該是不支持 Ed25519
類型的密鑰的,可以使用 rsa
。
ssh-keygen -t rsa -b 4096
總結
本文主要分析了使用 go
三方庫 github.com/pkg/sftp
訪問 sftp
服務出現大量的連接未關閉的異常情況,需要顯式調用 sshClient.Close()
。
接著介紹了如何使用 docker
搭建 sftp
服務,一個權限,一個密鑰,需要注意。