knownhosts

package module
v1.2.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 11, 2024 License: Apache-2.0 Imports: 12 Imported by: 0

README

knownhosts: enhanced Golang SSH known_hosts management

build status code coverage godoc

This repo is brought to you by Skeema, a declarative pure-SQL schema management system for MySQL and MariaDB. Our premium products include extensive SSH tunnel functionality, which internally makes use of this package.

Go provides excellent functionality for OpenSSH known_hosts files in its external package golang.org/x/crypto/ssh/knownhosts. However, that package is somewhat low-level, making it difficult to implement full known_hosts management similar to command-line ssh's behavior for StrictHostKeyChecking=no configuration.

This repo (github.com/skeema/knownhosts) is a thin wrapper package around golang.org/x/crypto/ssh/knownhosts, adding the following functionality:

  • Look up known_hosts public keys for any given host
  • Auto-populate ssh.ClientConfig.HostKeyAlgorithms easily based on known_hosts, providing a solution for golang/go#29286
  • Write new known_hosts entries to an io.Writer
  • Properly format/normalize new known_hosts entries containing ipv6 addresses, providing a solution for golang/go#53463
  • Determine if an ssh.HostKeyCallback's error corresponds to a host whose key has changed (indicating potential MitM attack) vs a host that just isn't known yet

How host key lookup works

Although golang.org/x/crypto/ssh/knownhosts doesn't directly expose a way to query its known_host map, we use a subtle trick to do so: invoke the HostKeyCallback with a valid host but a bogus key. The resulting KeyError allows us to determine which public keys are actually present for that host.

By using this technique, github.com/skeema/knownhosts doesn't need to duplicate any of the core known_hosts host-lookup logic from golang.org/x/crypto/ssh/knownhosts.

Populating ssh.ClientConfig.HostKeyAlgorithms based on known_hosts

Hosts often have multiple public keys, each of a different type (algorithm). This can be problematic in golang.org/x/crypto/ssh/knownhosts: if a host's first public key is not in known_hosts, but a key of a different type is, the HostKeyCallback returns an error. The solution is to populate ssh.ClientConfig.HostKeyAlgorithms based on the algorithms of the known_hosts entries for that host, but golang.org/x/crypto/ssh/knownhosts does not provide an obvious way to do so.

This package uses its host key lookup trick in order to make ssh.ClientConfig.HostKeyAlgorithms easy to populate:

import (
	"golang.org/x/crypto/ssh"
	"github.com/skeema/knownhosts"
)

func sshConfigForHost(hostWithPort string) (*ssh.ClientConfig, error) {
	kh, err := knownhosts.NewDB("/home/myuser/.ssh/known_hosts")
	if err != nil {
		return nil, err
	}
	config := &ssh.ClientConfig{
		User:              "myuser",
		Auth:              []ssh.AuthMethod{ /* ... */ },
		HostKeyCallback:   kh.HostKeyCallback(),
		HostKeyAlgorithms: kh.HostKeyAlgorithms(hostWithPort),
	}
	return config, nil
}

Writing new known_hosts entries

If you wish to mimic the behavior of OpenSSH's StrictHostKeyChecking=no or StrictHostKeyChecking=ask, this package provides a few functions to simplify this task. For example:

sshHost := "yourserver.com:22"
khPath := "/home/myuser/.ssh/known_hosts"
kh, err := knownhosts.NewDB(khPath)
if err != nil {
	log.Fatal("Failed to read known_hosts: ", err)
}

// Create a custom permissive hostkey callback which still errors on hosts
// with changed keys, but allows unknown hosts and adds them to known_hosts
cb := ssh.HostKeyCallback(func(hostname string, remote net.Addr, key ssh.PublicKey) error {
	innerCallback := kh.HostKeyCallback()
	err := innerCallback(hostname, remote, key)
	if knownhosts.IsHostKeyChanged(err) {
		return fmt.Errorf("REMOTE HOST IDENTIFICATION HAS CHANGED for host %s! This may indicate a MitM attack.", hostname)
	} else if knownhosts.IsHostUnknown(err) {
		f, ferr := os.OpenFile(khPath, os.O_APPEND|os.O_WRONLY, 0600)
		if ferr == nil {
			defer f.Close()
			ferr = knownhosts.WriteKnownHost(f, hostname, remote, key)
		}
		if ferr == nil {
			log.Printf("Added host %s to known_hosts\n", hostname)
		} else {
			log.Printf("Failed to add host %s to known_hosts: %v\n", hostname, ferr)
		}
		return nil // permit previously-unknown hosts (warning: may be insecure)
	}
	return err
})

config := &ssh.ClientConfig{
	User:              "myuser",
	Auth:              []ssh.AuthMethod{ /* ... */ },
	HostKeyCallback:   cb,
	HostKeyAlgorithms: kh.HostKeyAlgorithms(sshHost),
}

License

Source code copyright 2024 Skeema LLC and the Skeema Knownhosts authors

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.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func HostKeyAlgorithms

func HostKeyAlgorithms(cb ssh.HostKeyCallback, hostWithPort string) []string

HostKeyAlgorithms is a convenience function for performing host key algorithm lookups on an ssh.HostKeyCallback directly. It is intended for use in code paths that stay with the New method of golang.org/x/crypto/ssh/knownhosts rather than this package's New or NewDB methods. The returned values will not include ssh.CertAlgo* values. If any known_hosts lines had @cert-authority prefixes, their original key algo will be returned instead. For proper CA support, use HostKeyDB.HostKeyAlgorithms.

func IsHostKeyChanged

func IsHostKeyChanged(err error) bool

IsHostKeyChanged returns a boolean indicating whether the error indicates the host key has changed. It is intended to be called on the error returned from invoking a host key callback, to check whether an SSH host is known.

func IsHostUnknown

func IsHostUnknown(err error) bool

IsHostUnknown returns a boolean indicating whether the error represents an unknown host. It is intended to be called on the error returned from invoking a host key callback to check whether an SSH host is known.

func Line

func Line(addresses []string, key ssh.PublicKey) string

Line returns a line to append to the known_hosts files. This implementation uses the local patched implementation of Normalize in order to solve https://github.com/golang/go/issues/53463.

func Normalize

func Normalize(address string) string

Normalize normalizes an address into the form used in known_hosts. This implementation includes a fix for https://github.com/golang/go/issues/53463 and will omit brackets around ipv6 addresses on standard port 22.

func WriteKnownHost

func WriteKnownHost(w io.Writer, hostname string, remote net.Addr, key ssh.PublicKey) error

WriteKnownHost writes a known_hosts line to writer for the supplied hostname, remote, and key. This is useful when writing a custom hostkey callback which wraps a callback obtained from this package to provide additional known_hosts management functionality. The hostname, remote, and key typically correspond to the callback's args. This function does not support writing @cert-authority lines.

Types

type HostKeyCallback

type HostKeyCallback ssh.HostKeyCallback

HostKeyCallback wraps ssh.HostKeyCallback with an additional method to perform host key algorithm lookups from the known_hosts entries. It is otherwise identical to ssh.HostKeyCallback, and does not introduce any file- parsing behavior beyond what is in golang.org/x/crypto/ssh/knownhosts.

Note that its HostKeys and HostKeyAlgorithms methods do not provide any special treatment for @cert-authority lines, which will look like normal non-CA host keys. For proper CA support, e.g. when building a general-purpose SSH client, use HostKeyDB instead.

HostKeyCallback should generally only be used in situations in which @cert-authority lines are unlikely (for example, Git-related use-cases, since Git forges generally don't use them), or in situations where the extra file- parsing is undesirable, for reasons of code trust / security or perhaps performance impact.

func New

func New(files ...string) (HostKeyCallback, error)

New creates a HostKeyCallback from the given OpenSSH known_hosts file(s). The returned value may be used in ssh.ClientConfig.HostKeyCallback by casting it to ssh.HostKeyCallback, or using its HostKeyCallback method. Otherwise, it operates the same as the New function in golang.org/x/crypto/ssh/knownhosts. When supplying multiple files, their order does not matter.

func (HostKeyCallback) HostKeyAlgorithms

func (hkcb HostKeyCallback) HostKeyAlgorithms(hostWithPort string) (algos []string)

HostKeyAlgorithms returns a slice of host key algorithms for the supplied host:port found in the known_hosts file(s), or an empty slice if the host is not already known. The result may be used in ssh.ClientConfig's HostKeyAlgorithms field, either as-is or after filtering (if you wish to ignore or prefer particular algorithms). For hosts that have multiple known_hosts entries (for different key types), the result will be sorted by known_hosts filename and line number. The returned values will not include ssh.CertAlgo* values. If any known_hosts lines had @cert-authority prefixes, their original key algo will be returned instead. For proper CA support, use HostKeyDB.HostKeyAlgorithms.

func (HostKeyCallback) HostKeyCallback

func (hkcb HostKeyCallback) HostKeyCallback() ssh.HostKeyCallback

HostKeyCallback simply casts the receiver back to ssh.HostKeyCallback, for use in ssh.ClientConfig.HostKeyCallback.

func (HostKeyCallback) HostKeys

func (hkcb HostKeyCallback) HostKeys(hostWithPort string) []ssh.PublicKey

HostKeys returns a slice of known host public keys for the supplied host:port found in the known_hosts file(s), or an empty slice if the host is not already known. For hosts that have multiple known_hosts entries (for different key types), the result will be sorted by known_hosts filename and line number. In the returned values, there is no way to distinguish between CA keys (known_hosts lines beginning with @cert-authority) and regular keys. To do so, use HostKeyDB.HostKeys instead.

type HostKeyDB

type HostKeyDB struct {
	// contains filtered or unexported fields
}

HostKeyDB wraps logic in golang.org/x/crypto/ssh/knownhosts with additional behaviors, such as the ability to perform host key/algorithm lookups from the known_hosts entries. It fully supports @cert-authority lines as well, and can return ssh.CertAlgo* values when looking up algorithms. To create a HostKeyDB, use NewDB.

func NewDB

func NewDB(files ...string) (*HostKeyDB, error)

NewDB creates a HostKeyDB from the given OpenSSH known_hosts file(s). It reads and parses the provided files one additional time (beyond logic in golang.org/x/crypto/ssh/knownhosts) in order to handle CA lines properly. When supplying multiple files, their order does not matter.

func (*HostKeyDB) HostKeyAlgorithms

func (hkdb *HostKeyDB) HostKeyAlgorithms(hostWithPort string) (algos []string)

HostKeyAlgorithms returns a slice of host key algorithms for the supplied host:port found in the known_hosts file(s), or an empty slice if the host is not already known. The result may be used in ssh.ClientConfig's HostKeyAlgorithms field, either as-is or after filtering (if you wish to ignore or prefer particular algorithms). For hosts that have multiple known_hosts entries (of different key types), the result will be sorted by known_hosts filename and line number. For @cert-authority lines, the returned algorithm will be the correct ssh.CertAlgo* value.

func (*HostKeyDB) HostKeyCallback

func (hkdb *HostKeyDB) HostKeyCallback() ssh.HostKeyCallback

HostKeyCallback returns an ssh.HostKeyCallback for use in ssh.ClientConfig.HostKeyCallback.

func (*HostKeyDB) HostKeys

func (hkdb *HostKeyDB) HostKeys(hostWithPort string) (keys []PublicKey)

HostKeys returns a slice of known host public keys for the supplied host:port found in the known_hosts file(s), or an empty slice if the host is not already known. For hosts that have multiple known_hosts entries (for different key types), the result will be sorted by known_hosts filename and line number.

type PublicKey

type PublicKey struct {
	ssh.PublicKey
	Cert bool
}

PublicKey wraps ssh.PublicKey with an additional field, to identify whether they key corresponds to a certificate authority.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL