1
0
mirror of https://github.com/rancher/cli.git synced 2026-02-05 18:48:50 +01:00
Files
cli/cmd/ssh.go
2020-02-27 14:44:29 -07:00

198 lines
4.1 KiB
Go

package cmd
import (
"archive/zip"
"bytes"
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"net/http"
"os"
"os/exec"
"path"
"strings"
"github.com/pkg/errors"
"github.com/rancher/cli/cliclient"
managementClient "github.com/rancher/types/client/management/v3"
"github.com/urfave/cli"
)
const sshDescription = `
For any nodes created through Rancher using docker-machine,
you can SSH into the node. This is not supported for any custom nodes.
Examples:
# SSH into a node by ID/name
$ rancher ssh nodeFoo
# SSH into a node by ID/name using the external IP address
$ rancher ssh -e nodeFoo
# SSH into a node by name but specify the login name to use
$ rancher ssh -l login1 nodeFoo
# SSH into a node by specifying login name and node using the @ syntax while adding a command to run
$ rancher ssh login1@nodeFoo -- netstat -p tcp
`
func SSHCommand() cli.Command {
return cli.Command{
Name: "ssh",
Usage: "SSH into a node",
Description: sshDescription,
Action: nodeSSH,
ArgsUsage: "[NODE_ID/NODE_NAME]",
Flags: []cli.Flag{
cli.BoolFlag{
Name: "external,e",
Usage: "Use the external ip address of the node",
},
cli.StringFlag{
Name: "login,l",
Usage: "The login name",
},
},
}
}
func nodeSSH(ctx *cli.Context) error {
args := ctx.Args()
if len(args) > 0 && (args[0] == "-h" || args[0] == "--help") {
return cli.ShowCommandHelp(ctx, "ssh")
}
if ctx.NArg() == 0 {
return cli.ShowCommandHelp(ctx, "ssh")
}
user := ctx.String("login")
nodeName := ctx.Args().First()
if strings.Contains(nodeName, "@") {
user = strings.Split(nodeName, "@")[0]
nodeName = strings.Split(nodeName, "@")[1]
}
args = args[1:]
c, err := GetClient(ctx)
if err != nil {
return err
}
resource, err := Lookup(c, nodeName, "node")
if err != nil {
return err
}
sshNode, err := getNodeByID(ctx, c, resource.ID)
if err != nil {
return err
}
if user == "" {
user = sshNode.SshUser
}
key, err := getSSHKey(c, sshNode)
if err != nil {
return err
}
ipAddress := sshNode.IPAddress
if ctx.Bool("external") {
ipAddress = sshNode.ExternalIPAddress
}
return processExitCode(callSSH(key, ipAddress, user, args))
}
func callSSH(content []byte, ip string, user string, args []string) error {
dest := fmt.Sprintf("%s@%s", user, ip)
tmpfile, err := ioutil.TempFile("", "ssh")
if err != nil {
return err
}
defer os.Remove(tmpfile.Name())
if err := os.Chmod(tmpfile.Name(), 0600); err != nil {
return err
}
_, err = tmpfile.Write(content)
if err != nil {
return err
}
if err := tmpfile.Close(); err != nil {
return err
}
cmd := exec.Command("ssh", append([]string{"-i", tmpfile.Name(), dest}, args...)...)
cmd.Stdout = os.Stdout
cmd.Stdin = os.Stdin
cmd.Stderr = os.Stderr
return cmd.Run()
}
func getSSHKey(c *cliclient.MasterClient, node managementClient.Node) ([]byte, error) {
link, ok := node.Links["nodeConfig"]
if !ok {
return nil, fmt.Errorf("failed to find SSH key for %s", getNodeName(node))
}
req, err := http.NewRequest("GET", link, nil)
if err != nil {
return nil, err
}
req.SetBasicAuth(c.UserConfig.AccessKey, c.UserConfig.SecretKey)
req.Header.Add("Accept-Encoding", "zip")
client := &http.Client{}
if c.UserConfig.CACerts != "" {
roots := x509.NewCertPool()
ok := roots.AppendCertsFromPEM([]byte(c.UserConfig.CACerts))
if !ok {
return []byte{}, err
}
tr := &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: roots,
},
}
client.Transport = tr
}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
zipFiles, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if resp.StatusCode != 200 {
return nil, fmt.Errorf("%s", zipFiles)
}
zipReader, err := zip.NewReader(bytes.NewReader(zipFiles), resp.ContentLength)
if err != nil {
return nil, err
}
for _, file := range zipReader.File {
if path.Base(file.Name) == "id_rsa" {
r, err := file.Open()
if err != nil {
return nil, err
}
defer r.Close()
return ioutil.ReadAll(r)
}
}
return nil, errors.New("can't find private key file")
}