1
0
mirror of https://github.com/rancher/cli.git synced 2026-02-06 21:48:05 +01:00
Files
cli/cmd/ssh.go
Dan Ramich 6e2f325052 Update ssh to accept users and pass through all args
Problem:
SSH command does not accept a user or allow passing through of commands

Solution:
Add support for -l flag as well as <user>@<node> syntax
Pass through all following args to SSH
2018-04-20 10:46:43 -07:00

193 lines
3.9 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 name but specify the username to use
$ rancher ssh -l user1 nodeFoo
# SSH into a node by specifying user and node using the @ syntax while adding a command to run
$ rancher ssh user1@nodeFoo env
`
func SSHCommand() cli.Command {
return cli.Command{
Name: "ssh",
Usage: "SSH into a node",
Description: sshDescription,
ArgsUsage: "[NODE_ID/NODE_NAME]",
Action: nodeSSH,
UsageText: "potato",
SkipFlagParsing: true,
}
}
func nodeSSH(ctx *cli.Context) error {
if ctx.NArg() == 0 {
return cli.ShowCommandHelp(ctx, "ssh")
}
args := ctx.Args()
if len(args) > 0 && (args[0] == "-h" || args[0] == "--help") {
return cli.ShowCommandHelp(ctx, "ssh")
}
// ssh nodeName or ssh -l user nodeName or ssh user@nodeName
var user string
var nodeName string
if strings.Contains(args[0], "@") {
pieces := strings.Split(args[0], "@")
user = pieces[0]
nodeName = pieces[1]
args = args[1:]
} else if args[0] == "-l" {
user = args[1]
nodeName = args[2]
args = args[3:]
} else {
nodeName = args[0]
args = args[1:]
}
c, err := GetClient(ctx)
if err != nil {
return err
}
resource, err := Lookup(c, nodeName, "node")
if nil != err {
return err
}
sshNode, err := getNodeByID(ctx, c, resource.ID)
if nil != err {
return err
}
if user == "" {
user = sshNode.SshUser
}
key, err := getSSHKey(c, sshNode)
if err != nil {
return err
}
return processExitCode(callSSH(key, sshNode.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")
}