Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ssh-host command for getting the ssh host keys #9630

Merged
merged 2 commits into from
Dec 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmd/minikube/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ func init() {
Message: translate.T("Troubleshooting Commands:"),
Commands: []*cobra.Command{
sshKeyCmd,
sshHostCmd,
ipCmd,
logsCmd,
updateCheckCmd,
Expand Down
116 changes: 116 additions & 0 deletions cmd/minikube/cmd/ssh-host.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package cmd

import (
"fmt"
"os"
"path/filepath"

"github.com/spf13/cobra"
"k8s.io/client-go/util/homedir"

"k8s.io/minikube/pkg/minikube/config"
"k8s.io/minikube/pkg/minikube/driver"
"k8s.io/minikube/pkg/minikube/exit"
"k8s.io/minikube/pkg/minikube/machine"
"k8s.io/minikube/pkg/minikube/mustload"
"k8s.io/minikube/pkg/minikube/node"
"k8s.io/minikube/pkg/minikube/out"
"k8s.io/minikube/pkg/minikube/reason"
"k8s.io/minikube/pkg/minikube/sshutil"
)

var (
appendKnown bool
)

// sshHostCmd represents the sshHostCmd command
var sshHostCmd = &cobra.Command{
Use: "ssh-host",
Short: "Retrieve the ssh host key of the specified node",
Long: "Retrieve the ssh host key of the specified node.",
Run: func(cmd *cobra.Command, args []string) {
cname := ClusterFlagValue()
co := mustload.Running(cname)
if co.CP.Host.DriverName == driver.None {
exit.Message(reason.Usage, "'none' driver does not support 'minikube ssh-host' command")
}

var err error
var n *config.Node
if nodeName == "" {
n = co.CP.Node
} else {
n, _, err = node.Retrieve(*co.Config, nodeName)
if err != nil {
exit.Message(reason.GuestNodeRetrieve, "Node {{.nodeName}} does not exist.", out.V{"nodeName": nodeName})
}
}

scanArgs := []string{"-t", "rsa"}

keys, err := machine.RunSSHHostCommand(co.API, *co.Config, *n, "ssh-keyscan", scanArgs)
if err != nil {
// This is typically due to a non-zero exit code, so no need for flourish.
out.ErrLn("ssh-keyscan: %v", err)
// It'd be nice if we could pass up the correct error code here :(
os.Exit(1)
}

if appendKnown {
addr, port, err := machine.GetSSHHostAddrPort(co.API, *co.Config, *n)
if err != nil {
out.ErrLn("GetSSHHostAddrPort: %v", err)
os.Exit(1)
}

host := addr
if port != 22 {
host = fmt.Sprintf("[%s]:%d", addr, port)
}
knownHosts := filepath.Join(homedir.HomeDir(), ".ssh", "known_hosts")

fmt.Fprintf(os.Stderr, "Host added: %s (%s)\n", knownHosts, host)
if sshutil.KnownHost(host, knownHosts) {
return
}

f, err := os.OpenFile(knownHosts, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600)
if err != nil {
out.ErrLn("OpenFile: %v", err)
os.Exit(1)
}
defer f.Close()

_, err = f.WriteString(keys)
if err != nil {
out.ErrLn("WriteString: %v", err)
os.Exit(1)
}

return
}

fmt.Printf("%s", keys)
},
}

func init() {
sshHostCmd.Flags().StringVarP(&nodeName, "node", "n", "", "The node to ssh into. Defaults to the primary control plane.")
sshHostCmd.Flags().BoolVar(&appendKnown, "append-known", false, "Add host key to SSH known_hosts file")
}
63 changes: 58 additions & 5 deletions pkg/minikube/machine/ssh.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,29 +17,42 @@ limitations under the License.
package machine

import (
"fmt"
"os/exec"

"github.com/docker/machine/libmachine"
"github.com/docker/machine/libmachine/host"
"github.com/docker/machine/libmachine/ssh"
"github.com/docker/machine/libmachine/state"
"github.com/pkg/errors"
"k8s.io/minikube/pkg/minikube/config"
"k8s.io/minikube/pkg/minikube/driver"
)

// CreateSSHShell creates a new SSH shell / client
func CreateSSHShell(api libmachine.API, cc config.ClusterConfig, n config.Node, args []string, native bool) error {
func getHost(api libmachine.API, cc config.ClusterConfig, n config.Node) (*host.Host, error) {
machineName := driver.MachineName(cc, n)
host, err := LoadHost(api, machineName)
if err != nil {
return errors.Wrap(err, "host exists and load")
return nil, errors.Wrap(err, "host exists and load")
}

currentState, err := host.Driver.GetState()
if err != nil {
return errors.Wrap(err, "state")
return nil, errors.Wrap(err, "state")
}

if currentState != state.Running {
return errors.Errorf("%q is not running", machineName)
return nil, errors.Errorf("%q is not running", machineName)
}

return host, nil
}

// CreateSSHShell creates a new SSH shell / client
func CreateSSHShell(api libmachine.API, cc config.ClusterConfig, n config.Node, args []string, native bool) error {
host, err := getHost(api, cc, n)
if err != nil {
return err
}

if native {
Expand All @@ -55,3 +68,43 @@ func CreateSSHShell(api libmachine.API, cc config.ClusterConfig, n config.Node,
}
return client.Shell(args...)
}

func GetSSHHostAddrPort(api libmachine.API, cc config.ClusterConfig, n config.Node) (string, int, error) {
host, err := getHost(api, cc, n)
if err != nil {
return "", 0, err
}

addr, err := host.Driver.GetSSHHostname()
if err != nil {
return "", 0, err
}
port, err := host.Driver.GetSSHPort()
if err != nil {
return "", 0, err
}

return addr, port, nil
}

// RunSSHHostCommand runs a command to the SSH host
func RunSSHHostCommand(api libmachine.API, cc config.ClusterConfig, n config.Node, command string, args []string) (string, error) {
addr, port, err := GetSSHHostAddrPort(api, cc, n)
if err != nil {
return "", err
}

cmdPath, err := exec.LookPath(command)
if err != nil {
return "", err
}

args = append(args, "-p")
args = append(args, fmt.Sprintf("%d", port))

args = append(args, addr)

cmd := exec.Command(cmdPath, args...)
output, err := cmd.Output()
return string(output), err
}
28 changes: 28 additions & 0 deletions pkg/minikube/sshutil/sshutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,17 @@ limitations under the License.
package sshutil

import (
"bufio"
"net"
"os"
"strconv"
"time"

"github.com/docker/machine/libmachine/drivers"
machinessh "github.com/docker/machine/libmachine/ssh"
"github.com/pkg/errors"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/knownhosts"
"k8s.io/klog/v2"

"k8s.io/minikube/pkg/util/retry"
Expand Down Expand Up @@ -89,3 +92,28 @@ func newSSHHost(d drivers.Driver) (*sshHost, error) {
Username: d.GetSSHUsername(),
}, nil
}

func KnownHost(host string, knownHosts string) bool {
fd, err := os.Open(knownHosts)
if err != nil {
return false
}
defer fd.Close()

hashhost := knownhosts.HashHostname(host)
scanner := bufio.NewScanner(fd)
for scanner.Scan() {
_, hosts, _, _, _, err := ssh.ParseKnownHosts(scanner.Bytes())
if err != nil {
continue
}

for _, h := range hosts {
if h == host || h == hashhost {
return true
}
}
}

return false
}
47 changes: 47 additions & 0 deletions site/content/en/docs/commands/ssh-host.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
---
title: "ssh-host"
description: >
Retrieve the ssh host key of the specified node
---


## minikube ssh-host

Retrieve the ssh host key of the specified node

### Synopsis

Retrieve the ssh host key of the specified node.

```shell
minikube ssh-host [flags]
```

### Options

```
--append-known Add host key to SSH known_hosts file
-n, --node string The node to ssh into. Defaults to the primary control plane.
```

### Options inherited from parent commands

```
--add_dir_header If true, adds the file directory to the header of the log messages
--alsologtostderr log to standard error as well as files
-b, --bootstrapper string The name of the cluster bootstrapper that will set up the Kubernetes cluster. (default "kubeadm")
-h, --help
--log_backtrace_at traceLocation when logging hits line file:N, emit a stack trace (default :0)
--log_dir string If non-empty, write log files in this directory
--log_file string If non-empty, use this log file
--log_file_max_size uint Defines the maximum size a log file can grow to. Unit is megabytes. If the value is 0, the maximum file size is unlimited. (default 1800)
--logtostderr log to standard error instead of files
--one_output If true, only write logs to their native severity level (vs also writing to each lower severity level
-p, --profile string The name of the minikube VM being used. This can be set to allow having multiple instances of minikube independently. (default "minikube")
--skip_headers If true, avoid header prefixes in the log messages
--skip_log_headers If true, avoid headers when opening log files
--stderrthreshold severity logs at or above this threshold go to stderr (default 2)
-v, --v Level number for the log level verbosity
--vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging
```