Fazlul Shahriar | df1b4d2 | 2012-03-27 14:40:42 -0400 | [diff] [blame] | 1 | // Copyright 2011 The Go Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style |
| 3 | // license that can be found in the LICENSE file. |
| 4 | |
Dave Cheney | 7db4366 | 2015-02-01 17:57:17 +1100 | [diff] [blame] | 5 | package ssh_test |
Fazlul Shahriar | df1b4d2 | 2012-03-27 14:40:42 -0400 | [diff] [blame] | 6 | |
| 7 | import ( |
| 8 | "bytes" |
Fazlul Shahriar | df1b4d2 | 2012-03-27 14:40:42 -0400 | [diff] [blame] | 9 | "fmt" |
| 10 | "io/ioutil" |
Dave Cheney | b333fd1 | 2012-04-26 20:37:06 +1000 | [diff] [blame] | 11 | "log" |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 12 | "net" |
Dave Cheney | b333fd1 | 2012-04-26 20:37:06 +1000 | [diff] [blame] | 13 | "net/http" |
David Symonds | 521edf8 | 2012-03-30 15:27:01 +1100 | [diff] [blame] | 14 | |
Dave Cheney | 7db4366 | 2015-02-01 17:57:17 +1100 | [diff] [blame] | 15 | "golang.org/x/crypto/ssh" |
Andrew Gerrand | a73c6bb | 2014-11-10 08:50:25 +1100 | [diff] [blame] | 16 | "golang.org/x/crypto/ssh/terminal" |
Fazlul Shahriar | df1b4d2 | 2012-03-27 14:40:42 -0400 | [diff] [blame] | 17 | ) |
| 18 | |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 19 | func ExampleNewServerConn() { |
Emmanuel Odeke | bde08f2 | 2016-09-18 01:01:53 -0700 | [diff] [blame] | 20 | // Public key authentication is done by comparing |
| 21 | // the public key of a received connection |
| 22 | // with the entries in the authorized_keys file. |
| 23 | authorizedKeysBytes, err := ioutil.ReadFile("authorized_keys") |
| 24 | if err != nil { |
| 25 | log.Fatalf("Failed to load authorized_keys, err: %v", err) |
| 26 | } |
| 27 | |
| 28 | authorizedKeysMap := map[string]bool{} |
| 29 | for len(authorizedKeysBytes) > 0 { |
| 30 | pubKey, _, _, rest, err := ssh.ParseAuthorizedKey(authorizedKeysBytes) |
| 31 | if err != nil { |
| 32 | log.Fatal(err) |
| 33 | } |
| 34 | |
| 35 | authorizedKeysMap[string(pubKey.Marshal())] = true |
| 36 | authorizedKeysBytes = rest |
| 37 | } |
| 38 | |
Fazlul Shahriar | df1b4d2 | 2012-03-27 14:40:42 -0400 | [diff] [blame] | 39 | // An SSH server is represented by a ServerConfig, which holds |
| 40 | // certificate details and handles authentication of ServerConns. |
Dave Cheney | 7db4366 | 2015-02-01 17:57:17 +1100 | [diff] [blame] | 41 | config := &ssh.ServerConfig{ |
Emmanuel Odeke | bde08f2 | 2016-09-18 01:01:53 -0700 | [diff] [blame] | 42 | // Remove to disable password auth. |
Dave Cheney | 7db4366 | 2015-02-01 17:57:17 +1100 | [diff] [blame] | 43 | PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) { |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 44 | // Should use constant-time compare (or better, salt+hash) in |
| 45 | // a production setting. |
| 46 | if c.User() == "testuser" && string(pass) == "tiger" { |
| 47 | return nil, nil |
| 48 | } |
| 49 | return nil, fmt.Errorf("password rejected for %q", c.User()) |
Fazlul Shahriar | df1b4d2 | 2012-03-27 14:40:42 -0400 | [diff] [blame] | 50 | }, |
Emmanuel Odeke | bde08f2 | 2016-09-18 01:01:53 -0700 | [diff] [blame] | 51 | |
| 52 | // Remove to disable public key auth. |
| 53 | PublicKeyCallback: func(c ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) { |
| 54 | if authorizedKeysMap[string(pubKey.Marshal())] { |
| 55 | return nil, nil |
| 56 | } |
| 57 | return nil, fmt.Errorf("unknown public key for %q", c.User()) |
| 58 | }, |
Fazlul Shahriar | df1b4d2 | 2012-03-27 14:40:42 -0400 | [diff] [blame] | 59 | } |
| 60 | |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 61 | privateBytes, err := ioutil.ReadFile("id_rsa") |
Fazlul Shahriar | df1b4d2 | 2012-03-27 14:40:42 -0400 | [diff] [blame] | 62 | if err != nil { |
Mike Houston | 484eb34 | 2015-11-08 16:19:38 +0000 | [diff] [blame] | 63 | log.Fatal("Failed to load private key: ", err) |
Fazlul Shahriar | df1b4d2 | 2012-03-27 14:40:42 -0400 | [diff] [blame] | 64 | } |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 65 | |
Dave Cheney | 7db4366 | 2015-02-01 17:57:17 +1100 | [diff] [blame] | 66 | private, err := ssh.ParsePrivateKey(privateBytes) |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 67 | if err != nil { |
Mike Houston | 484eb34 | 2015-11-08 16:19:38 +0000 | [diff] [blame] | 68 | log.Fatal("Failed to parse private key: ", err) |
Fazlul Shahriar | df1b4d2 | 2012-03-27 14:40:42 -0400 | [diff] [blame] | 69 | } |
| 70 | |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 71 | config.AddHostKey(private) |
| 72 | |
Fazlul Shahriar | df1b4d2 | 2012-03-27 14:40:42 -0400 | [diff] [blame] | 73 | // Once a ServerConfig has been configured, connections can be |
| 74 | // accepted. |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 75 | listener, err := net.Listen("tcp", "0.0.0.0:2022") |
Fazlul Shahriar | df1b4d2 | 2012-03-27 14:40:42 -0400 | [diff] [blame] | 76 | if err != nil { |
Mike Houston | 484eb34 | 2015-11-08 16:19:38 +0000 | [diff] [blame] | 77 | log.Fatal("failed to listen for connection: ", err) |
Fazlul Shahriar | df1b4d2 | 2012-03-27 14:40:42 -0400 | [diff] [blame] | 78 | } |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 79 | nConn, err := listener.Accept() |
Fazlul Shahriar | df1b4d2 | 2012-03-27 14:40:42 -0400 | [diff] [blame] | 80 | if err != nil { |
Mike Houston | 484eb34 | 2015-11-08 16:19:38 +0000 | [diff] [blame] | 81 | log.Fatal("failed to accept incoming connection: ", err) |
Fazlul Shahriar | df1b4d2 | 2012-03-27 14:40:42 -0400 | [diff] [blame] | 82 | } |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 83 | |
| 84 | // Before use, a handshake must be performed on the incoming |
| 85 | // net.Conn. |
Dave Cheney | 7db4366 | 2015-02-01 17:57:17 +1100 | [diff] [blame] | 86 | _, chans, reqs, err := ssh.NewServerConn(nConn, config) |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 87 | if err != nil { |
Mike Houston | 484eb34 | 2015-11-08 16:19:38 +0000 | [diff] [blame] | 88 | log.Fatal("failed to handshake: ", err) |
Fazlul Shahriar | df1b4d2 | 2012-03-27 14:40:42 -0400 | [diff] [blame] | 89 | } |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 90 | // The incoming Request channel must be serviced. |
Dave Cheney | 7db4366 | 2015-02-01 17:57:17 +1100 | [diff] [blame] | 91 | go ssh.DiscardRequests(reqs) |
Fazlul Shahriar | df1b4d2 | 2012-03-27 14:40:42 -0400 | [diff] [blame] | 92 | |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 93 | // Service the incoming Channel channel. |
Emmanuel Odeke | bde08f2 | 2016-09-18 01:01:53 -0700 | [diff] [blame] | 94 | |
| 95 | // Service the incoming Channel channel. |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 96 | for newChannel := range chans { |
Fazlul Shahriar | df1b4d2 | 2012-03-27 14:40:42 -0400 | [diff] [blame] | 97 | // Channels have a type, depending on the application level |
| 98 | // protocol intended. In the case of a shell, the type is |
| 99 | // "session" and ServerShell may be used to present a simple |
| 100 | // terminal interface. |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 101 | if newChannel.ChannelType() != "session" { |
Dave Cheney | 7db4366 | 2015-02-01 17:57:17 +1100 | [diff] [blame] | 102 | newChannel.Reject(ssh.UnknownChannelType, "unknown channel type") |
Fazlul Shahriar | df1b4d2 | 2012-03-27 14:40:42 -0400 | [diff] [blame] | 103 | continue |
| 104 | } |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 105 | channel, requests, err := newChannel.Accept() |
| 106 | if err != nil { |
Emmanuel Odeke | bde08f2 | 2016-09-18 01:01:53 -0700 | [diff] [blame] | 107 | log.Fatalf("Could not accept channel: %v", err) |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 108 | } |
| 109 | |
| 110 | // Sessions have out-of-band requests such as "shell", |
| 111 | // "pty-req" and "env". Here we handle only the |
| 112 | // "shell" request. |
Dave Cheney | 7db4366 | 2015-02-01 17:57:17 +1100 | [diff] [blame] | 113 | go func(in <-chan *ssh.Request) { |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 114 | for req := range in { |
Emmanuel Odeke | bde08f2 | 2016-09-18 01:01:53 -0700 | [diff] [blame] | 115 | req.Reply(req.Type == "shell", nil) |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 116 | } |
| 117 | }(requests) |
Fazlul Shahriar | df1b4d2 | 2012-03-27 14:40:42 -0400 | [diff] [blame] | 118 | |
| 119 | term := terminal.NewTerminal(channel, "> ") |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 120 | |
Fazlul Shahriar | df1b4d2 | 2012-03-27 14:40:42 -0400 | [diff] [blame] | 121 | go func() { |
| 122 | defer channel.Close() |
| 123 | for { |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 124 | line, err := term.ReadLine() |
Fazlul Shahriar | df1b4d2 | 2012-03-27 14:40:42 -0400 | [diff] [blame] | 125 | if err != nil { |
| 126 | break |
| 127 | } |
| 128 | fmt.Println(line) |
| 129 | } |
| 130 | }() |
| 131 | } |
| 132 | } |
| 133 | |
| 134 | func ExampleDial() { |
Scott Bell | 7b42871 | 2016-04-19 13:20:54 -0700 | [diff] [blame] | 135 | // An SSH client is represented with a ClientConn. |
Fazlul Shahriar | df1b4d2 | 2012-03-27 14:40:42 -0400 | [diff] [blame] | 136 | // |
| 137 | // To authenticate with the remote server you must pass at least one |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 138 | // implementation of AuthMethod via the Auth field in ClientConfig. |
Dave Cheney | 7db4366 | 2015-02-01 17:57:17 +1100 | [diff] [blame] | 139 | config := &ssh.ClientConfig{ |
Fazlul Shahriar | df1b4d2 | 2012-03-27 14:40:42 -0400 | [diff] [blame] | 140 | User: "username", |
Dave Cheney | 7db4366 | 2015-02-01 17:57:17 +1100 | [diff] [blame] | 141 | Auth: []ssh.AuthMethod{ |
| 142 | ssh.Password("yourpassword"), |
Fazlul Shahriar | df1b4d2 | 2012-03-27 14:40:42 -0400 | [diff] [blame] | 143 | }, |
| 144 | } |
Dave Cheney | 7db4366 | 2015-02-01 17:57:17 +1100 | [diff] [blame] | 145 | client, err := ssh.Dial("tcp", "yourserver.com:22", config) |
Fazlul Shahriar | df1b4d2 | 2012-03-27 14:40:42 -0400 | [diff] [blame] | 146 | if err != nil { |
Mike Houston | 484eb34 | 2015-11-08 16:19:38 +0000 | [diff] [blame] | 147 | log.Fatal("Failed to dial: ", err) |
Fazlul Shahriar | df1b4d2 | 2012-03-27 14:40:42 -0400 | [diff] [blame] | 148 | } |
| 149 | |
| 150 | // Each ClientConn can support multiple interactive sessions, |
| 151 | // represented by a Session. |
| 152 | session, err := client.NewSession() |
| 153 | if err != nil { |
Mike Houston | 484eb34 | 2015-11-08 16:19:38 +0000 | [diff] [blame] | 154 | log.Fatal("Failed to create session: ", err) |
Fazlul Shahriar | df1b4d2 | 2012-03-27 14:40:42 -0400 | [diff] [blame] | 155 | } |
| 156 | defer session.Close() |
| 157 | |
| 158 | // Once a Session is created, you can execute a single command on |
| 159 | // the remote side using the Run method. |
| 160 | var b bytes.Buffer |
| 161 | session.Stdout = &b |
| 162 | if err := session.Run("/usr/bin/whoami"); err != nil { |
Mike Houston | 484eb34 | 2015-11-08 16:19:38 +0000 | [diff] [blame] | 163 | log.Fatal("Failed to run: " + err.Error()) |
Fazlul Shahriar | df1b4d2 | 2012-03-27 14:40:42 -0400 | [diff] [blame] | 164 | } |
| 165 | fmt.Println(b.String()) |
| 166 | } |
Dave Cheney | b333fd1 | 2012-04-26 20:37:06 +1000 | [diff] [blame] | 167 | |
Scott Bell | 7b42871 | 2016-04-19 13:20:54 -0700 | [diff] [blame] | 168 | func ExamplePublicKeys() { |
| 169 | // A public key may be used to authenticate against the remote |
| 170 | // server by using an unencrypted PEM-encoded private key file. |
| 171 | // |
| 172 | // If you have an encrypted private key, the crypto/x509 package |
| 173 | // can be used to decrypt it. |
| 174 | key, err := ioutil.ReadFile("/home/user/.ssh/id_rsa") |
| 175 | if err != nil { |
| 176 | log.Fatalf("unable to read private key: %v", err) |
| 177 | } |
| 178 | |
| 179 | // Create the Signer for this private key. |
| 180 | signer, err := ssh.ParsePrivateKey(key) |
| 181 | if err != nil { |
| 182 | log.Fatalf("unable to parse private key: %v", err) |
| 183 | } |
| 184 | |
| 185 | config := &ssh.ClientConfig{ |
| 186 | User: "user", |
| 187 | Auth: []ssh.AuthMethod{ |
| 188 | // Use the PublicKeys method for remote authentication. |
| 189 | ssh.PublicKeys(signer), |
| 190 | }, |
| 191 | } |
| 192 | |
| 193 | // Connect to the remote server and perform the SSH handshake. |
| 194 | client, err := ssh.Dial("tcp", "host.com:22", config) |
| 195 | if err != nil { |
| 196 | log.Fatalf("unable to connect: %v", err) |
| 197 | } |
| 198 | defer client.Close() |
| 199 | } |
| 200 | |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 201 | func ExampleClient_Listen() { |
Dave Cheney | 7db4366 | 2015-02-01 17:57:17 +1100 | [diff] [blame] | 202 | config := &ssh.ClientConfig{ |
Dave Cheney | b333fd1 | 2012-04-26 20:37:06 +1000 | [diff] [blame] | 203 | User: "username", |
Dave Cheney | 7db4366 | 2015-02-01 17:57:17 +1100 | [diff] [blame] | 204 | Auth: []ssh.AuthMethod{ |
| 205 | ssh.Password("password"), |
Dave Cheney | b333fd1 | 2012-04-26 20:37:06 +1000 | [diff] [blame] | 206 | }, |
| 207 | } |
| 208 | // Dial your ssh server. |
Dave Cheney | 7db4366 | 2015-02-01 17:57:17 +1100 | [diff] [blame] | 209 | conn, err := ssh.Dial("tcp", "localhost:22", config) |
Dave Cheney | b333fd1 | 2012-04-26 20:37:06 +1000 | [diff] [blame] | 210 | if err != nil { |
Mike Houston | 484eb34 | 2015-11-08 16:19:38 +0000 | [diff] [blame] | 211 | log.Fatal("unable to connect: ", err) |
Dave Cheney | b333fd1 | 2012-04-26 20:37:06 +1000 | [diff] [blame] | 212 | } |
| 213 | defer conn.Close() |
| 214 | |
| 215 | // Request the remote side to open port 8080 on all interfaces. |
| 216 | l, err := conn.Listen("tcp", "0.0.0.0:8080") |
| 217 | if err != nil { |
Mike Houston | 484eb34 | 2015-11-08 16:19:38 +0000 | [diff] [blame] | 218 | log.Fatal("unable to register tcp forward: ", err) |
Dave Cheney | b333fd1 | 2012-04-26 20:37:06 +1000 | [diff] [blame] | 219 | } |
| 220 | defer l.Close() |
| 221 | |
| 222 | // Serve HTTP with your SSH server acting as a reverse proxy. |
| 223 | http.Serve(l, http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { |
| 224 | fmt.Fprintf(resp, "Hello world!\n") |
| 225 | })) |
| 226 | } |
Willem van der Schyff | 2fccde5 | 2012-10-21 16:16:55 +1100 | [diff] [blame] | 227 | |
| 228 | func ExampleSession_RequestPty() { |
| 229 | // Create client config |
Dave Cheney | 7db4366 | 2015-02-01 17:57:17 +1100 | [diff] [blame] | 230 | config := &ssh.ClientConfig{ |
Willem van der Schyff | 2fccde5 | 2012-10-21 16:16:55 +1100 | [diff] [blame] | 231 | User: "username", |
Dave Cheney | 7db4366 | 2015-02-01 17:57:17 +1100 | [diff] [blame] | 232 | Auth: []ssh.AuthMethod{ |
| 233 | ssh.Password("password"), |
Willem van der Schyff | 2fccde5 | 2012-10-21 16:16:55 +1100 | [diff] [blame] | 234 | }, |
| 235 | } |
| 236 | // Connect to ssh server |
Dave Cheney | 7db4366 | 2015-02-01 17:57:17 +1100 | [diff] [blame] | 237 | conn, err := ssh.Dial("tcp", "localhost:22", config) |
Willem van der Schyff | 2fccde5 | 2012-10-21 16:16:55 +1100 | [diff] [blame] | 238 | if err != nil { |
Mike Houston | 484eb34 | 2015-11-08 16:19:38 +0000 | [diff] [blame] | 239 | log.Fatal("unable to connect: ", err) |
Willem van der Schyff | 2fccde5 | 2012-10-21 16:16:55 +1100 | [diff] [blame] | 240 | } |
| 241 | defer conn.Close() |
| 242 | // Create a session |
| 243 | session, err := conn.NewSession() |
| 244 | if err != nil { |
Mike Houston | 484eb34 | 2015-11-08 16:19:38 +0000 | [diff] [blame] | 245 | log.Fatal("unable to create session: ", err) |
Willem van der Schyff | 2fccde5 | 2012-10-21 16:16:55 +1100 | [diff] [blame] | 246 | } |
| 247 | defer session.Close() |
| 248 | // Set up terminal modes |
Dave Cheney | 7db4366 | 2015-02-01 17:57:17 +1100 | [diff] [blame] | 249 | modes := ssh.TerminalModes{ |
| 250 | ssh.ECHO: 0, // disable echoing |
| 251 | ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud |
| 252 | ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud |
Willem van der Schyff | 2fccde5 | 2012-10-21 16:16:55 +1100 | [diff] [blame] | 253 | } |
| 254 | // Request pseudo terminal |
Henrik Hodne | 2cbd8ea | 2016-01-05 20:55:22 +0000 | [diff] [blame] | 255 | if err := session.RequestPty("xterm", 40, 80, modes); err != nil { |
Mike Houston | 484eb34 | 2015-11-08 16:19:38 +0000 | [diff] [blame] | 256 | log.Fatal("request for pseudo terminal failed: ", err) |
Willem van der Schyff | 2fccde5 | 2012-10-21 16:16:55 +1100 | [diff] [blame] | 257 | } |
| 258 | // Start remote shell |
| 259 | if err := session.Shell(); err != nil { |
Mike Houston | 484eb34 | 2015-11-08 16:19:38 +0000 | [diff] [blame] | 260 | log.Fatal("failed to start shell: ", err) |
Willem van der Schyff | 2fccde5 | 2012-10-21 16:16:55 +1100 | [diff] [blame] | 261 | } |
| 262 | } |