github.com/linapex/ethereum-dpos-chinese@v0.0.0-20190316121959-b78b3a4a1ece/cmd/puppeth/ssh.go (about)

     1  
     2  //<developer>
     3  //    <name>linapex 曹一峰</name>
     4  //    <email>linapex@163.com</email>
     5  //    <wx>superexc</wx>
     6  //    <qqgroup>128148617</qqgroup>
     7  //    <url>https://jsq.ink</url>
     8  //    <role>pku engineer</role>
     9  //    <date>2019-03-16 12:09:31</date>
    10  //</624342604028317696>
    11  
    12  
    13  package main
    14  
    15  import (
    16  	"bufio"
    17  	"bytes"
    18  	"errors"
    19  	"fmt"
    20  	"io/ioutil"
    21  	"net"
    22  	"os"
    23  	"os/user"
    24  	"path/filepath"
    25  	"strings"
    26  
    27  	"github.com/ethereum/go-ethereum/log"
    28  	"golang.org/x/crypto/ssh"
    29  	"golang.org/x/crypto/ssh/terminal"
    30  )
    31  
    32  //ssh client是Go的ssh客户机的一个小包装,有几个实用方法
    33  //在顶部实施。
    34  type sshClient struct {
    35  server  string //没有端口号的服务器名或IP
    36  address string //远程服务器的IP地址
    37  pubkey  []byte //用于对服务器进行身份验证的RSA公钥
    38  	client  *ssh.Client
    39  	logger  log.Logger
    40  }
    41  
    42  //拨号使用当前用户和
    43  //
    44  //
    45  func dial(server string, pubkey []byte) (*sshClient, error) {
    46  //
    47  	hostname := ""
    48  	hostport := server
    49  	username := ""
    50  identity := "id_rsa" //违约
    51  
    52  	if strings.Contains(server, "@") {
    53  		prefix := server[:strings.Index(server, "@")]
    54  		if strings.Contains(prefix, ":") {
    55  			username = prefix[:strings.Index(prefix, ":")]
    56  			identity = prefix[strings.Index(prefix, ":")+1:]
    57  		} else {
    58  			username = prefix
    59  		}
    60  		hostport = server[strings.Index(server, "@")+1:]
    61  	}
    62  	if strings.Contains(hostport, ":") {
    63  		hostname = hostport[:strings.Index(hostport, ":")]
    64  	} else {
    65  		hostname = hostport
    66  		hostport += ":22"
    67  	}
    68  	logger := log.New("server", server)
    69  	logger.Debug("Attempting to establish SSH connection")
    70  
    71  	user, err := user.Current()
    72  	if err != nil {
    73  		return nil, err
    74  	}
    75  	if username == "" {
    76  		username = user.Username
    77  	}
    78  //
    79  	var auths []ssh.AuthMethod
    80  
    81  	path := filepath.Join(user.HomeDir, ".ssh", identity)
    82  	if buf, err := ioutil.ReadFile(path); err != nil {
    83  		log.Warn("No SSH key, falling back to passwords", "path", path, "err", err)
    84  	} else {
    85  		key, err := ssh.ParsePrivateKey(buf)
    86  		if err != nil {
    87  			fmt.Printf("What's the decryption password for %s? (won't be echoed)\n>", path)
    88  			blob, err := terminal.ReadPassword(int(os.Stdin.Fd()))
    89  			fmt.Println()
    90  			if err != nil {
    91  				log.Warn("Couldn't read password", "err", err)
    92  			}
    93  			key, err := ssh.ParsePrivateKeyWithPassphrase(buf, blob)
    94  			if err != nil {
    95  				log.Warn("Failed to decrypt SSH key, falling back to passwords", "path", path, "err", err)
    96  			} else {
    97  				auths = append(auths, ssh.PublicKeys(key))
    98  			}
    99  		} else {
   100  			auths = append(auths, ssh.PublicKeys(key))
   101  		}
   102  	}
   103  	auths = append(auths, ssh.PasswordCallback(func() (string, error) {
   104  		fmt.Printf("What's the login password for %s at %s? (won't be echoed)\n> ", username, server)
   105  		blob, err := terminal.ReadPassword(int(os.Stdin.Fd()))
   106  
   107  		fmt.Println()
   108  		return string(blob), err
   109  	}))
   110  //
   111  	addr, err := net.LookupHost(hostname)
   112  	if err != nil {
   113  		return nil, err
   114  	}
   115  	if len(addr) == 0 {
   116  		return nil, errors.New("no IPs associated with domain")
   117  	}
   118  //
   119  	logger.Trace("Dialing remote SSH server", "user", username)
   120  	keycheck := func(hostname string, remote net.Addr, key ssh.PublicKey) error {
   121  //
   122  		if pubkey == nil {
   123  			fmt.Println()
   124  			fmt.Printf("The authenticity of host '%s (%s)' can't be established.\n", hostname, remote)
   125  			fmt.Printf("SSH key fingerprint is %s [MD5]\n", ssh.FingerprintLegacyMD5(key))
   126  			fmt.Printf("Are you sure you want to continue connecting (yes/no)? ")
   127  
   128  			text, err := bufio.NewReader(os.Stdin).ReadString('\n')
   129  			switch {
   130  			case err != nil:
   131  				return err
   132  			case strings.TrimSpace(text) == "yes":
   133  				pubkey = key.Marshal()
   134  				return nil
   135  			default:
   136  				return fmt.Errorf("unknown auth choice: %v", text)
   137  			}
   138  		}
   139  //
   140  		if bytes.Equal(pubkey, key.Marshal()) {
   141  			return nil
   142  		}
   143  //
   144  		return errors.New("ssh key mismatch, readd the machine to update")
   145  	}
   146  	client, err := ssh.Dial("tcp", hostport, &ssh.ClientConfig{User: username, Auth: auths, HostKeyCallback: keycheck})
   147  	if err != nil {
   148  		return nil, err
   149  	}
   150  //
   151  	c := &sshClient{
   152  		server:  hostname,
   153  		address: addr[0],
   154  		pubkey:  pubkey,
   155  		client:  client,
   156  		logger:  logger,
   157  	}
   158  	if err := c.init(); err != nil {
   159  		client.Close()
   160  		return nil, err
   161  	}
   162  	return c, nil
   163  }
   164  
   165  //
   166  //
   167  func (client *sshClient) init() error {
   168  	client.logger.Debug("Verifying if docker is available")
   169  	if out, err := client.Run("docker version"); err != nil {
   170  		if len(out) == 0 {
   171  			return err
   172  		}
   173  		return fmt.Errorf("docker configured incorrectly: %s", out)
   174  	}
   175  	client.logger.Debug("Verifying if docker-compose is available")
   176  	if out, err := client.Run("docker-compose version"); err != nil {
   177  		if len(out) == 0 {
   178  			return err
   179  		}
   180  		return fmt.Errorf("docker-compose configured incorrectly: %s", out)
   181  	}
   182  	return nil
   183  }
   184  
   185  //
   186  func (client *sshClient) Close() error {
   187  	return client.client.Close()
   188  }
   189  
   190  //
   191  //
   192  func (client *sshClient) Run(cmd string) ([]byte, error) {
   193  //
   194  	session, err := client.client.NewSession()
   195  	if err != nil {
   196  		return nil, err
   197  	}
   198  	defer session.Close()
   199  
   200  //
   201  	client.logger.Trace("Running command on remote server", "cmd", cmd)
   202  	return session.CombinedOutput(cmd)
   203  }
   204  
   205  //
   206  //
   207  func (client *sshClient) Stream(cmd string) error {
   208  //
   209  	session, err := client.client.NewSession()
   210  	if err != nil {
   211  		return err
   212  	}
   213  	defer session.Close()
   214  
   215  	session.Stdout = os.Stdout
   216  	session.Stderr = os.Stderr
   217  
   218  //
   219  	client.logger.Trace("Streaming command on remote server", "cmd", cmd)
   220  	return session.Run(cmd)
   221  }
   222  
   223  //
   224  //同时存在文件夹。
   225  func (client *sshClient) Upload(files map[string][]byte) ([]byte, error) {
   226  //建立一个单一的命令会话
   227  	session, err := client.client.NewSession()
   228  	if err != nil {
   229  		return nil, err
   230  	}
   231  	defer session.Close()
   232  
   233  //创建流式处理SCP内容的goroutine
   234  	go func() {
   235  		out, _ := session.StdinPipe()
   236  		defer out.Close()
   237  
   238  		for file, content := range files {
   239  			client.logger.Trace("Uploading file to server", "file", file, "bytes", len(content))
   240  
   241  fmt.Fprintln(out, "D0755", 0, filepath.Dir(file))             //Ensure the folder exists
   242  fmt.Fprintln(out, "C0644", len(content), filepath.Base(file)) //创建实际文件
   243  out.Write(content)                                            //
   244  fmt.Fprint(out, "\x00")                                       //
   245  fmt.Fprintln(out, "E")                                        //离开目录(更简单)
   246  		}
   247  	}()
   248  	return session.CombinedOutput("/usr/bin/scp -v -tr ./")
   249  }
   250