sftp_test_go 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. // Copyright © 2015 Jerry Jacobs <jerry.jacobs@xor-gate.org>.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. // http://www.apache.org/licenses/LICENSE-2.0
  7. //
  8. // Unless required by applicable law or agreed to in writing, software
  9. // distributed under the License is distributed on an "AS IS" BASIS,
  10. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11. // See the License for the specific language governing permissions and
  12. // limitations under the License.
  13. package afero
  14. import (
  15. "testing"
  16. "os"
  17. "log"
  18. "fmt"
  19. "net"
  20. "flag"
  21. "time"
  22. "io/ioutil"
  23. "crypto/rsa"
  24. _rand "crypto/rand"
  25. "encoding/pem"
  26. "crypto/x509"
  27. "golang.org/x/crypto/ssh"
  28. "github.com/pkg/sftp"
  29. )
  30. type SftpFsContext struct {
  31. sshc *ssh.Client
  32. sshcfg *ssh.ClientConfig
  33. sftpc *sftp.Client
  34. }
  35. // TODO we only connect with hardcoded user+pass for now
  36. // it should be possible to use $HOME/.ssh/id_rsa to login into the stub sftp server
  37. func SftpConnect(user, password, host string) (*SftpFsContext, error) {
  38. /*
  39. pemBytes, err := ioutil.ReadFile(os.Getenv("HOME") + "/.ssh/id_rsa")
  40. if err != nil {
  41. return nil,err
  42. }
  43. signer, err := ssh.ParsePrivateKey(pemBytes)
  44. if err != nil {
  45. return nil,err
  46. }
  47. sshcfg := &ssh.ClientConfig{
  48. User: user,
  49. Auth: []ssh.AuthMethod{
  50. ssh.Password(password),
  51. ssh.PublicKeys(signer),
  52. },
  53. }
  54. */
  55. sshcfg := &ssh.ClientConfig{
  56. User: user,
  57. Auth: []ssh.AuthMethod{
  58. ssh.Password(password),
  59. },
  60. }
  61. sshc, err := ssh.Dial("tcp", host, sshcfg)
  62. if err != nil {
  63. return nil,err
  64. }
  65. sftpc, err := sftp.NewClient(sshc)
  66. if err != nil {
  67. return nil,err
  68. }
  69. ctx := &SftpFsContext{
  70. sshc: sshc,
  71. sshcfg: sshcfg,
  72. sftpc: sftpc,
  73. }
  74. return ctx,nil
  75. }
  76. func (ctx *SftpFsContext) Disconnect() error {
  77. ctx.sftpc.Close()
  78. ctx.sshc.Close()
  79. return nil
  80. }
  81. // TODO for such a weird reason rootpath is "." when writing "file1" with afero sftp backend
  82. func RunSftpServer(rootpath string) {
  83. var (
  84. readOnly bool
  85. debugLevelStr string
  86. debugLevel int
  87. debugStderr bool
  88. rootDir string
  89. )
  90. flag.BoolVar(&readOnly, "R", false, "read-only server")
  91. flag.BoolVar(&debugStderr, "e", true, "debug to stderr")
  92. flag.StringVar(&debugLevelStr, "l", "none", "debug level")
  93. flag.StringVar(&rootDir, "root", rootpath, "root directory")
  94. flag.Parse()
  95. debugStream := ioutil.Discard
  96. if debugStderr {
  97. debugStream = os.Stderr
  98. debugLevel = 1
  99. }
  100. // An SSH server is represented by a ServerConfig, which holds
  101. // certificate details and handles authentication of ServerConns.
  102. config := &ssh.ServerConfig{
  103. PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
  104. // Should use constant-time compare (or better, salt+hash) in
  105. // a production setting.
  106. fmt.Fprintf(debugStream, "Login: %s\n", c.User())
  107. if c.User() == "test" && string(pass) == "test" {
  108. return nil, nil
  109. }
  110. return nil, fmt.Errorf("password rejected for %q", c.User())
  111. },
  112. }
  113. privateBytes, err := ioutil.ReadFile("./test/id_rsa")
  114. if err != nil {
  115. log.Fatal("Failed to load private key", err)
  116. }
  117. private, err := ssh.ParsePrivateKey(privateBytes)
  118. if err != nil {
  119. log.Fatal("Failed to parse private key", err)
  120. }
  121. config.AddHostKey(private)
  122. // Once a ServerConfig has been configured, connections can be
  123. // accepted.
  124. listener, err := net.Listen("tcp", "0.0.0.0:2022")
  125. if err != nil {
  126. log.Fatal("failed to listen for connection", err)
  127. }
  128. fmt.Printf("Listening on %v\n", listener.Addr())
  129. nConn, err := listener.Accept()
  130. if err != nil {
  131. log.Fatal("failed to accept incoming connection", err)
  132. }
  133. // Before use, a handshake must be performed on the incoming
  134. // net.Conn.
  135. _, chans, reqs, err := ssh.NewServerConn(nConn, config)
  136. if err != nil {
  137. log.Fatal("failed to handshake", err)
  138. }
  139. fmt.Fprintf(debugStream, "SSH server established\n")
  140. // The incoming Request channel must be serviced.
  141. go ssh.DiscardRequests(reqs)
  142. // Service the incoming Channel channel.
  143. for newChannel := range chans {
  144. // Channels have a type, depending on the application level
  145. // protocol intended. In the case of an SFTP session, this is "subsystem"
  146. // with a payload string of "<length=4>sftp"
  147. fmt.Fprintf(debugStream, "Incoming channel: %s\n", newChannel.ChannelType())
  148. if newChannel.ChannelType() != "session" {
  149. newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
  150. fmt.Fprintf(debugStream, "Unknown channel type: %s\n", newChannel.ChannelType())
  151. continue
  152. }
  153. channel, requests, err := newChannel.Accept()
  154. if err != nil {
  155. log.Fatal("could not accept channel.", err)
  156. }
  157. fmt.Fprintf(debugStream, "Channel accepted\n")
  158. // Sessions have out-of-band requests such as "shell",
  159. // "pty-req" and "env". Here we handle only the
  160. // "subsystem" request.
  161. go func(in <-chan *ssh.Request) {
  162. for req := range in {
  163. fmt.Fprintf(debugStream, "Request: %v\n", req.Type)
  164. ok := false
  165. switch req.Type {
  166. case "subsystem":
  167. fmt.Fprintf(debugStream, "Subsystem: %s\n", req.Payload[4:])
  168. if string(req.Payload[4:]) == "sftp" {
  169. ok = true
  170. }
  171. }
  172. fmt.Fprintf(debugStream, " - accepted: %v\n", ok)
  173. req.Reply(ok, nil)
  174. }
  175. }(requests)
  176. server, err := sftp.NewServer(channel, channel, debugStream, debugLevel, readOnly, rootpath)
  177. if err != nil {
  178. log.Fatal(err)
  179. }
  180. if err := server.Serve(); err != nil {
  181. log.Fatal("sftp server completed with error:", err)
  182. }
  183. }
  184. }
  185. // MakeSSHKeyPair make a pair of public and private keys for SSH access.
  186. // Public key is encoded in the format for inclusion in an OpenSSH authorized_keys file.
  187. // Private Key generated is PEM encoded
  188. func MakeSSHKeyPair(bits int, pubKeyPath, privateKeyPath string) error {
  189. privateKey, err := rsa.GenerateKey(_rand.Reader, bits)
  190. if err != nil {
  191. return err
  192. }
  193. // generate and write private key as PEM
  194. privateKeyFile, err := os.Create(privateKeyPath)
  195. defer privateKeyFile.Close()
  196. if err != nil {
  197. return err
  198. }
  199. privateKeyPEM := &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey)}
  200. if err := pem.Encode(privateKeyFile, privateKeyPEM); err != nil {
  201. return err
  202. }
  203. // generate and write public key
  204. pub, err := ssh.NewPublicKey(&privateKey.PublicKey)
  205. if err != nil {
  206. return err
  207. }
  208. return ioutil.WriteFile(pubKeyPath, ssh.MarshalAuthorizedKey(pub), 0655)
  209. }
  210. func TestSftpCreate(t *testing.T) {
  211. os.Mkdir("./test", 0777)
  212. MakeSSHKeyPair(1024, "./test/id_rsa.pub", "./test/id_rsa")
  213. go RunSftpServer("./test/")
  214. time.Sleep(5 * time.Second)
  215. ctx, err := SftpConnect("test", "test", "localhost:2022")
  216. if err != nil {
  217. t.Fatal(err)
  218. }
  219. defer ctx.Disconnect()
  220. var AppFs Fs = SftpFs{
  221. SftpClient: ctx.sftpc,
  222. }
  223. AppFs.MkdirAll("test/dir1/dir2/dir3", os.FileMode(0777))
  224. AppFs.Mkdir("test/foo", os.FileMode(0000))
  225. AppFs.Chmod("test/foo", os.FileMode(0700))
  226. AppFs.Mkdir("test/bar", os.FileMode(0777))
  227. file, err := AppFs.Create("file1")
  228. if err != nil {
  229. t.Error(err)
  230. }
  231. defer file.Close()
  232. file.Write([]byte("hello\t"))
  233. file.WriteString("world!\n")
  234. f1, err := AppFs.Open("file1")
  235. if err != nil {
  236. log.Fatalf("open: %v", err)
  237. }
  238. defer f1.Close()
  239. b := make([]byte, 100)
  240. _, err = f1.Read(b)
  241. fmt.Println(string(b))
  242. // TODO check here if "hello\tworld\n" is in buffer b
  243. }