~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/juju/utils/ssh/ssh_gocrypto.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// Copyright 2013 Canonical Ltd.
 
2
// Licensed under the AGPLv3, see LICENCE file for details.
 
3
 
 
4
package ssh
 
5
 
 
6
import (
 
7
        "io"
 
8
        "io/ioutil"
 
9
        "net"
 
10
        "os"
 
11
        "os/exec"
 
12
        "os/user"
 
13
        "strconv"
 
14
        "strings"
 
15
 
 
16
        "github.com/juju/errors"
 
17
        "github.com/juju/utils"
 
18
        "golang.org/x/crypto/ssh"
 
19
)
 
20
 
 
21
const sshDefaultPort = 22
 
22
 
 
23
// GoCryptoClient is an implementation of Client that
 
24
// uses the embedded go.crypto/ssh SSH client.
 
25
//
 
26
// GoCryptoClient is intentionally limited in the
 
27
// functionality that it enables, as it is currently
 
28
// intended to be used only for non-interactive command
 
29
// execution.
 
30
type GoCryptoClient struct {
 
31
        signers []ssh.Signer
 
32
}
 
33
 
 
34
// NewGoCryptoClient creates a new GoCryptoClient.
 
35
//
 
36
// If no signers are specified, NewGoCryptoClient will
 
37
// use the private key generated by LoadClientKeys.
 
38
func NewGoCryptoClient(signers ...ssh.Signer) (*GoCryptoClient, error) {
 
39
        return &GoCryptoClient{signers: signers}, nil
 
40
}
 
41
 
 
42
// Command implements Client.Command.
 
43
func (c *GoCryptoClient) Command(host string, command []string, options *Options) *Cmd {
 
44
        shellCommand := utils.CommandString(command...)
 
45
        signers := c.signers
 
46
        if len(signers) == 0 {
 
47
                signers = privateKeys()
 
48
        }
 
49
        user, host := splitUserHost(host)
 
50
        port := sshDefaultPort
 
51
        var proxyCommand []string
 
52
        if options != nil {
 
53
                if options.port != 0 {
 
54
                        port = options.port
 
55
                }
 
56
                proxyCommand = options.proxyCommand
 
57
        }
 
58
        logger.Tracef(`running (equivalent of): ssh "%s@%s" -p %d '%s'`, user, host, port, shellCommand)
 
59
        return &Cmd{impl: &goCryptoCommand{
 
60
                signers:      signers,
 
61
                user:         user,
 
62
                addr:         net.JoinHostPort(host, strconv.Itoa(port)),
 
63
                command:      shellCommand,
 
64
                proxyCommand: proxyCommand,
 
65
        }}
 
66
}
 
67
 
 
68
// Copy implements Client.Copy.
 
69
//
 
70
// Copy is currently unimplemented, and will always return an error.
 
71
func (c *GoCryptoClient) Copy(args []string, options *Options) error {
 
72
        return errors.Errorf("scp command is not implemented (OpenSSH scp not available in PATH)")
 
73
}
 
74
 
 
75
type goCryptoCommand struct {
 
76
        signers      []ssh.Signer
 
77
        user         string
 
78
        addr         string
 
79
        command      string
 
80
        proxyCommand []string
 
81
        stdin        io.Reader
 
82
        stdout       io.Writer
 
83
        stderr       io.Writer
 
84
        client       *ssh.Client
 
85
        sess         *ssh.Session
 
86
}
 
87
 
 
88
var sshDial = ssh.Dial
 
89
 
 
90
var sshDialWithProxy = func(addr string, proxyCommand []string, config *ssh.ClientConfig) (*ssh.Client, error) {
 
91
        if len(proxyCommand) == 0 {
 
92
                return sshDial("tcp", addr, config)
 
93
        }
 
94
        // User has specified a proxy. Create a pipe and
 
95
        // redirect the proxy command's stdin/stdout to it.
 
96
        host, port, err := net.SplitHostPort(addr)
 
97
        if err != nil {
 
98
                host = addr
 
99
        }
 
100
        for i, arg := range proxyCommand {
 
101
                arg = strings.Replace(arg, "%h", host, -1)
 
102
                if port != "" {
 
103
                        arg = strings.Replace(arg, "%p", port, -1)
 
104
                }
 
105
                arg = strings.Replace(arg, "%r", config.User, -1)
 
106
                proxyCommand[i] = arg
 
107
        }
 
108
        client, server := net.Pipe()
 
109
        logger.Tracef(`executing proxy command %q`, proxyCommand)
 
110
        cmd := exec.Command(proxyCommand[0], proxyCommand[1:]...)
 
111
        cmd.Stdin = server
 
112
        cmd.Stdout = server
 
113
        cmd.Stderr = os.Stderr
 
114
        if err := cmd.Start(); err != nil {
 
115
                return nil, err
 
116
        }
 
117
        conn, chans, reqs, err := ssh.NewClientConn(client, addr, config)
 
118
        if err != nil {
 
119
                return nil, err
 
120
        }
 
121
        return ssh.NewClient(conn, chans, reqs), nil
 
122
}
 
123
 
 
124
func (c *goCryptoCommand) ensureSession() (*ssh.Session, error) {
 
125
        if c.sess != nil {
 
126
                return c.sess, nil
 
127
        }
 
128
        if len(c.signers) == 0 {
 
129
                return nil, errors.Errorf("no private keys available")
 
130
        }
 
131
        if c.user == "" {
 
132
                currentUser, err := user.Current()
 
133
                if err != nil {
 
134
                        return nil, errors.Errorf("getting current user: %v", err)
 
135
                }
 
136
                c.user = currentUser.Username
 
137
        }
 
138
        config := &ssh.ClientConfig{
 
139
                User: c.user,
 
140
                Auth: []ssh.AuthMethod{
 
141
                        ssh.PublicKeysCallback(func() ([]ssh.Signer, error) {
 
142
                                return c.signers, nil
 
143
                        }),
 
144
                },
 
145
        }
 
146
        client, err := sshDialWithProxy(c.addr, c.proxyCommand, config)
 
147
        if err != nil {
 
148
                return nil, err
 
149
        }
 
150
        sess, err := client.NewSession()
 
151
        if err != nil {
 
152
                client.Close()
 
153
                return nil, err
 
154
        }
 
155
        c.client = client
 
156
        c.sess = sess
 
157
        c.sess.Stdin = c.stdin
 
158
        c.sess.Stdout = c.stdout
 
159
        c.sess.Stderr = c.stderr
 
160
        return sess, nil
 
161
}
 
162
 
 
163
func (c *goCryptoCommand) Start() error {
 
164
        sess, err := c.ensureSession()
 
165
        if err != nil {
 
166
                return err
 
167
        }
 
168
        if c.command == "" {
 
169
                return sess.Shell()
 
170
        }
 
171
        return sess.Start(c.command)
 
172
}
 
173
 
 
174
func (c *goCryptoCommand) Close() error {
 
175
        if c.sess == nil {
 
176
                return nil
 
177
        }
 
178
        err0 := c.sess.Close()
 
179
        err1 := c.client.Close()
 
180
        if err0 == nil {
 
181
                err0 = err1
 
182
        }
 
183
        c.sess = nil
 
184
        c.client = nil
 
185
        return err0
 
186
}
 
187
 
 
188
func (c *goCryptoCommand) Wait() error {
 
189
        if c.sess == nil {
 
190
                return errors.Errorf("command has not been started")
 
191
        }
 
192
        err := c.sess.Wait()
 
193
        c.Close()
 
194
        return err
 
195
}
 
196
 
 
197
func (c *goCryptoCommand) Kill() error {
 
198
        if c.sess == nil {
 
199
                return errors.Errorf("command has not been started")
 
200
        }
 
201
        return c.sess.Signal(ssh.SIGKILL)
 
202
}
 
203
 
 
204
func (c *goCryptoCommand) SetStdio(stdin io.Reader, stdout, stderr io.Writer) {
 
205
        c.stdin = stdin
 
206
        c.stdout = stdout
 
207
        c.stderr = stderr
 
208
}
 
209
 
 
210
func (c *goCryptoCommand) StdinPipe() (io.WriteCloser, io.Reader, error) {
 
211
        sess, err := c.ensureSession()
 
212
        if err != nil {
 
213
                return nil, nil, err
 
214
        }
 
215
        wc, err := sess.StdinPipe()
 
216
        return wc, sess.Stdin, err
 
217
}
 
218
 
 
219
func (c *goCryptoCommand) StdoutPipe() (io.ReadCloser, io.Writer, error) {
 
220
        sess, err := c.ensureSession()
 
221
        if err != nil {
 
222
                return nil, nil, err
 
223
        }
 
224
        wc, err := sess.StdoutPipe()
 
225
        return ioutil.NopCloser(wc), sess.Stdout, err
 
226
}
 
227
 
 
228
func (c *goCryptoCommand) StderrPipe() (io.ReadCloser, io.Writer, error) {
 
229
        sess, err := c.ensureSession()
 
230
        if err != nil {
 
231
                return nil, nil, err
 
232
        }
 
233
        wc, err := sess.StderrPipe()
 
234
        return ioutil.NopCloser(wc), sess.Stderr, err
 
235
}
 
236
 
 
237
func splitUserHost(s string) (user, host string) {
 
238
        userHost := strings.SplitN(s, "@", 2)
 
239
        if len(userHost) == 2 {
 
240
                return userHost[0], userHost[1]
 
241
        }
 
242
        return "", userHost[0]
 
243
}