diff --git a/docs/drivers/generic.md b/docs/drivers/generic.md index 39ea86e3de..1c5487e13e 100644 --- a/docs/drivers/generic.md +++ b/docs/drivers/generic.md @@ -33,9 +33,20 @@ Options: Environment variables and default values: -| CLI option | Environment variable | Default | -| -------------------------- | -------------------- | ------------------- | -| **`--generic-ip-address`** | `GENERIC_IP_ADDRESS` | - | -| `--generic-ssh-key` | `GENERIC_SSH_KEY` | `$HOME/.ssh/id_rsa` | -| `--generic-ssh-user` | `GENERIC_SSH_USER` | `root` | -| `--generic-ssh-port` | `GENERIC_SSH_PORT` | `22` | +| CLI option | Environment variable | Default | +|----------------------------|----------------------|---------------------------| +| **`--generic-ip-address`** | `GENERIC_IP_ADDRESS` | - | +| `--generic-ssh-key` | `GENERIC_SSH_KEY` | _(defers to `ssh-agent`)_ | +| `--generic-ssh-user` | `GENERIC_SSH_USER` | `root` | +| `--generic-ssh-port` | `GENERIC_SSH_PORT` | `22` | + +##### Interaction with SSH Agents + +When an SSH identity is not provided (with the `--generic-ssh-key` flag), +the SSH agent (if running) will be consulted. This makes it possible to +easily use password-protected SSH keys. + +Note that this usage is _only_ supported if you're using the external SSH client, +which is the default behaviour when the `ssh` binary is available. If you're +using the native client (with `--native-ssh`), using the SSH agent is not yet +supported. diff --git a/drivers/generic/generic.go b/drivers/generic/generic.go index febc447a1f..d97f153c97 100644 --- a/drivers/generic/generic.go +++ b/drivers/generic/generic.go @@ -5,7 +5,6 @@ import ( "fmt" "net" "os" - "path/filepath" "strconv" "time" @@ -25,10 +24,6 @@ const ( defaultTimeout = 1 * time.Second ) -var ( - defaultSourceSSHKey = filepath.Join(mcnutils.GetHomeDir(), ".ssh", "id_rsa") -) - // GetCreateFlags registers the flags this driver adds to // "docker hosts create" func (d *Driver) GetCreateFlags() []mcnflag.Flag { @@ -46,8 +41,8 @@ func (d *Driver) GetCreateFlags() []mcnflag.Flag { }, mcnflag.StringFlag{ Name: "generic-ssh-key", - Usage: "SSH private key path", - Value: defaultSourceSSHKey, + Usage: "SSH private key path (if not provided, identities in ssh-agent will be used)", + Value: "", EnvVar: "GENERIC_SSH_KEY", }, mcnflag.IntFlag{ @@ -66,7 +61,6 @@ func NewDriver(hostName, storePath string) drivers.Driver { MachineName: hostName, StorePath: storePath, }, - SSHKey: defaultSourceSSHKey, } } @@ -83,6 +77,17 @@ func (d *Driver) GetSSHUsername() string { return d.SSHUser } +func (d *Driver) GetSSHKeyPath() string { + if d.SSHKey == "" { + return "" + } + + if d.SSHKeyPath == "" { + d.SSHKeyPath = d.ResolveStorePath("id_rsa") + } + return d.SSHKeyPath +} + func (d *Driver) SetConfigFromFlags(flags drivers.DriverOptions) error { d.IPAddress = flags.String("generic-ip-address") d.SSHUser = flags.String("generic-ssh-user") @@ -93,31 +98,33 @@ func (d *Driver) SetConfigFromFlags(flags drivers.DriverOptions) error { return errors.New("generic driver requires the --generic-ip-address option") } - if d.SSHKey == "" { - return errors.New("generic driver requires the --generic-ssh-key option") - } - return nil } func (d *Driver) PreCreateCheck() error { - if _, err := os.Stat(d.SSHKey); os.IsNotExist(err) { - return fmt.Errorf("Ssh key does not exist: %q", d.SSHKey) + if d.SSHKey != "" { + if _, err := os.Stat(d.SSHKey); os.IsNotExist(err) { + return fmt.Errorf("Ssh key does not exist: %q", d.SSHKey) + } } return nil } func (d *Driver) Create() error { - log.Info("Importing SSH key...") + if d.SSHKey == "" { + log.Info("No SSH key specified. Connecting to this machine now and in the" + + " future will require the ssh agent to contain the appropriate key.") + } else { + log.Info("Importing SSH key...") + // TODO: validate the key is a valid key + if err := mcnutils.CopyFile(d.SSHKey, d.GetSSHKeyPath()); err != nil { + return fmt.Errorf("unable to copy ssh key: %s", err) + } - // TODO: validate the key is a valid key - if err := mcnutils.CopyFile(d.SSHKey, d.GetSSHKeyPath()); err != nil { - return fmt.Errorf("unable to copy ssh key: %s", err) - } - - if err := os.Chmod(d.GetSSHKeyPath(), 0600); err != nil { - return fmt.Errorf("unable to set permissions on the ssh key: %s", err) + if err := os.Chmod(d.GetSSHKeyPath(), 0600); err != nil { + return fmt.Errorf("unable to set permissions on the ssh key: %s", err) + } } log.Debugf("IP: %s", d.IPAddress) diff --git a/libmachine/drivers/utils.go b/libmachine/drivers/utils.go index 0a31443a28..f5f38249c6 100644 --- a/libmachine/drivers/utils.go +++ b/libmachine/drivers/utils.go @@ -19,8 +19,13 @@ func GetSSHClientFromDriver(d Driver) (ssh.Client, error) { return nil, err } - auth := &ssh.Auth{ - Keys: []string{d.GetSSHKeyPath()}, + var auth *ssh.Auth + if d.GetSSHKeyPath() == "" { + auth = &ssh.Auth{} + } else { + auth = &ssh.Auth{ + Keys: []string{d.GetSSHKeyPath()}, + } } client, err := ssh.NewClient(d.GetSSHUsername(), address, port, auth) diff --git a/libmachine/host/host.go b/libmachine/host/host.go index 1c7d04a8c8..74ac2191a2 100644 --- a/libmachine/host/host.go +++ b/libmachine/host/host.go @@ -69,8 +69,13 @@ func (h *Host) CreateSSHClient() (ssh.Client, error) { return ssh.ExternalClient{}, err } - auth := &ssh.Auth{ - Keys: []string{h.Driver.GetSSHKeyPath()}, + var auth *ssh.Auth + if h.Driver.GetSSHKeyPath() == "" { + auth = &ssh.Auth{} + } else { + auth = &ssh.Auth{ + Keys: []string{h.Driver.GetSSHKeyPath()}, + } } return ssh.NewClient(h.Driver.GetSSHUsername(), addr, port, auth) diff --git a/libmachine/ssh/client.go b/libmachine/ssh/client.go index 26c9c1a2fc..2b83f71e75 100644 --- a/libmachine/ssh/client.go +++ b/libmachine/ssh/client.go @@ -49,7 +49,6 @@ const ( var ( baseSSHArgs = []string{ "-o", "PasswordAuthentication=no", - "-o", "IdentitiesOnly=yes", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", "-o", "LogLevel=quiet", // suppress "Warning: Permanently added '[localhost]:2022' (ECDSA) to the list of known hosts." @@ -262,9 +261,17 @@ func NewExternalClient(sshBinaryPath, user, host string, port int, auth *Auth) ( args := append(baseSSHArgs, fmt.Sprintf("%s@%s", user, host)) + // If no identities are explicitly provided, also look at the identities + // offered by ssh-agent + if len(auth.Keys) > 0 { + args = append(args, "-o", "IdentitiesOnly=yes") + } + // Specify which private keys to use to authorize the SSH request. for _, privateKeyPath := range auth.Keys { - args = append(args, "-i", privateKeyPath) + if privateKeyPath != "" { + args = append(args, "-i", privateKeyPath) + } } // Set which port to use for SSH.