Skip to content

Commit

Permalink
Add support for HashKnownHosts SSH config option
Browse files Browse the repository at this point in the history
Excerpt from `man 5 ssh_config`:

> Indicates that ssh(1) should hash host names and addresses when they
> are added to ~/.ssh/known_hosts.  These hashed names may be used
> normally by  ssh(1)  and  sshd(8),  but they  do  not  visually reveal
> identifying information if the file's contents are disclosed.  The
> default is no.  Note that existing names and addresses in known hosts
> files will not be converted automatically, but may be manually hashed
> using ssh-keygen(1).

See: https://github.com/openssh/openssh-portable/blob/V_9_4_P1/ssh_config.5#L868-L884
See: https://pkg.go.dev/golang.org/x/[email protected]/ssh/knownhosts#HashHostname
  • Loading branch information
twz123 committed Aug 30, 2023
1 parent 96c2ea4 commit 080b876
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 14 deletions.
10 changes: 7 additions & 3 deletions pkg/ssh/hostkey/callbacks.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ var KnownHostsPathFromEnv = func() (string, bool) {
}

// KnownHostsFileCallback returns a HostKeyCallback that uses a known hosts file to verify host keys
func KnownHostsFileCallback(path string, permissive bool) (ssh.HostKeyCallback, error) {
func KnownHostsFileCallback(path string, permissive, hash bool) (ssh.HostKeyCallback, error) {
if path == "/dev/null" {
return InsecureIgnoreHostKeyCallback, nil
}
Expand All @@ -67,13 +67,13 @@ func KnownHostsFileCallback(path string, permissive bool) (ssh.HostKeyCallback,
return nil, fmt.Errorf("%w: knownhosts callback: %w", ErrCheckHostKey, err)
}

return wrapCallback(hkc, path, permissive), nil
return wrapCallback(hkc, path, permissive, hash), nil
}

// extends a knownhosts callback to not return an error when the key
// is not found in the known_hosts file but instead adds it to the file as new
// entry
func wrapCallback(hkc ssh.HostKeyCallback, path string, permissive bool) ssh.HostKeyCallback {
func wrapCallback(hkc ssh.HostKeyCallback, path string, permissive, hash bool) ssh.HostKeyCallback {
return ssh.HostKeyCallback(func(hostname string, remote net.Addr, key ssh.PublicKey) error {
mu.Lock()
defer mu.Unlock()
Expand All @@ -99,6 +99,10 @@ func wrapCallback(hkc ssh.HostKeyCallback, path string, permissive bool) ssh.Hos
}

knownHostsEntry := knownhosts.Normalize(remote.String())
if hash {
knownHostsEntry = knownhosts.HashHostname(knownHostsEntry)
}

row := knownhosts.Line([]string{knownHostsEntry}, key)
row = fmt.Sprintf("%s\n", strings.TrimSpace(row))

Expand Down
40 changes: 29 additions & 11 deletions ssh.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,14 +245,36 @@ func (c *SSH) IsWindows() bool {
return c.isWindows
}

func knownhostsCallback(path string, permissive bool) (ssh.HostKeyCallback, error) {
cb, err := hostkey.KnownHostsFileCallback(path, permissive)
func knownhostsCallback(path string, permissive, hash bool) (ssh.HostKeyCallback, error) {
cb, err := hostkey.KnownHostsFileCallback(path, permissive, hash)
if err != nil {
return nil, fmt.Errorf("%w: create host key validator: %w", ErrCantConnect, err)
}
return cb, nil
}

func isPermissive(c *SSH) bool {
if strict := c.getConfigAll("StrictHostkeyChecking"); len(strict) > 0 && strict[0] == "no" {
log.Debugf("%s: StrictHostkeyChecking is set to 'no'", c)
return true
}

return false
}

func shouldHash(c *SSH) bool {
var hash bool
if hashKnownHosts := c.getConfigAll("HashKnownHosts"); len(hashKnownHosts) == 1 {
hash := hashKnownHosts[0] == "yes"
if hash {
log.Debugf("%s: HashKnownHosts is set to %q, will hash newly added keys", c, hashKnownHosts[0])
} else {
log.Debugf("%s: HashKnownHosts is set to %q, won't hash newly added keys", c, hashKnownHosts[0])
}
}
return hash
}

func (c *SSH) hostkeyCallback() (ssh.HostKeyCallback, error) {
if c.HostKey != "" {
log.Debugf("%s: using host key from config", c)
Expand All @@ -262,19 +284,15 @@ func (c *SSH) hostkeyCallback() (ssh.HostKeyCallback, error) {
knownHostsMU.Lock()
defer knownHostsMU.Unlock()

var permissive bool
strict := c.getConfigAll("StrictHostkeyChecking")
if len(strict) > 0 && strict[0] == "no" {
log.Debugf("%s: StrictHostkeyChecking is set to 'no'", c)
permissive = true
}
permissive := isPermissive(c)
hash := shouldHash(c)

if path, ok := hostkey.KnownHostsPathFromEnv(); ok {
if path == "" {
return hostkey.InsecureIgnoreHostKeyCallback, nil
}
log.Tracef("%s: using known_hosts file from SSH_KNOWN_HOSTS: %s", c, path)
return knownhostsCallback(path, permissive)
return knownhostsCallback(path, permissive, hash)
}

var khPath string
Expand All @@ -295,7 +313,7 @@ func (c *SSH) hostkeyCallback() (ssh.HostKeyCallback, error) {

if khPath != "" {
log.Tracef("%s: using known_hosts file from ssh config %s", c, khPath)
return knownhostsCallback(khPath, permissive)
return knownhostsCallback(khPath, permissive, hash)
}

log.Tracef("%s: using default known_hosts file %s", c, hostkey.DefaultKnownHostsPath)
Expand All @@ -304,7 +322,7 @@ func (c *SSH) hostkeyCallback() (ssh.HostKeyCallback, error) {
return nil, err
}

return knownhostsCallback(defaultPath, permissive)
return knownhostsCallback(defaultPath, permissive, hash)
}

func (c *SSH) clientConfig() (*ssh.ClientConfig, error) {
Expand Down

0 comments on commit 080b876

Please sign in to comment.