104 lines
3.4 KiB
Go
104 lines
3.4 KiB
Go
package managed
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"fmt"
|
|
"net"
|
|
|
|
pkgkh "github.com/fluxcd/pkg/ssh/knownhosts"
|
|
git2go "github.com/libgit2/git2go/v33"
|
|
"golang.org/x/crypto/ssh/knownhosts"
|
|
)
|
|
|
|
// knownHostCallback returns a CertificateCheckCallback that verifies
|
|
// the key of Git server against the given host and known_hosts for
|
|
// git.SSH Transports.
|
|
func KnownHostsCallback(host string, knownHosts []byte) git2go.CertificateCheckCallback {
|
|
return func(cert *git2go.Certificate, valid bool, hostname string) error {
|
|
// First, attempt to split the configured host and port to validate
|
|
// the port-less hostname given to the callback.
|
|
hostWithoutPort, _, err := net.SplitHostPort(host)
|
|
if err != nil {
|
|
// SplitHostPort returns an error if the host is missing
|
|
// a port, assume the host has no port.
|
|
hostWithoutPort = host
|
|
}
|
|
|
|
// Different versions of libgit handle this differently.
|
|
// This fixes the case in which ports may be sent back.
|
|
hostnameWithoutPort, _, err := net.SplitHostPort(hostname)
|
|
if err != nil {
|
|
hostnameWithoutPort = hostname
|
|
}
|
|
|
|
if hostnameWithoutPort != hostWithoutPort {
|
|
return fmt.Errorf("host mismatch: %q %q", hostWithoutPort, hostnameWithoutPort)
|
|
}
|
|
|
|
var fingerprint []byte
|
|
switch {
|
|
case cert.Hostkey.Kind&git2go.HostkeySHA256 > 0:
|
|
fingerprint = cert.Hostkey.HashSHA256[:]
|
|
default:
|
|
return fmt.Errorf("invalid host key kind, expected to be of kind SHA256")
|
|
}
|
|
|
|
return CheckKnownHost(host, knownHosts, fingerprint)
|
|
}
|
|
}
|
|
|
|
// CheckKnownHost checks whether the host being connected to is
|
|
// part of the known_hosts, and if so, it ensures the host
|
|
// fingerprint matches the fingerprint of the known host with
|
|
// the same name.
|
|
func CheckKnownHost(host string, knownHosts []byte, fingerprint []byte) error {
|
|
kh, err := pkgkh.ParseKnownHosts(string(knownHosts))
|
|
if err != nil {
|
|
return fmt.Errorf("failed to parse known_hosts: %w", err)
|
|
}
|
|
|
|
if len(kh) == 0 {
|
|
return fmt.Errorf("hostkey verification aborted: no known_hosts found")
|
|
}
|
|
|
|
// We are now certain that the configured host and the hostname
|
|
// given to the callback match. Use the configured host (that
|
|
// includes the port), and normalize it, so we can check if there
|
|
// is an entry for the hostname _and_ port.
|
|
h := knownhosts.Normalize(host)
|
|
for _, k := range kh {
|
|
if k.Matches(h, fingerprint) {
|
|
return nil
|
|
}
|
|
}
|
|
return fmt.Errorf("no entries in known_hosts match host '%s' with fingerprint '%s'",
|
|
h, base64.RawStdEncoding.EncodeToString(fingerprint))
|
|
}
|
|
|
|
// RemoteCallbacks constructs git2go.RemoteCallbacks with dummy callbacks.
|
|
func RemoteCallbacks() git2go.RemoteCallbacks {
|
|
// This may not be fully removed as without some of the callbacks git2go
|
|
// gets anxious and panics.
|
|
return git2go.RemoteCallbacks{
|
|
CredentialsCallback: credentialsCallback(),
|
|
CertificateCheckCallback: certificateCallback(),
|
|
}
|
|
}
|
|
|
|
// credentialsCallback constructs a dummy CredentialsCallback.
|
|
func credentialsCallback() git2go.CredentialsCallback {
|
|
return func(url string, username string, allowedTypes git2go.CredentialType) (*git2go.Credential, error) {
|
|
// If credential is nil, panic will ensue. We fake it as managed transport does not
|
|
// require it.
|
|
return git2go.NewCredentialUserpassPlaintext("", "")
|
|
}
|
|
}
|
|
|
|
// certificateCallback constructs a dummy CertificateCallback.
|
|
func certificateCallback() git2go.CertificateCheckCallback {
|
|
// returning a nil func can cause git2go to panic.
|
|
return func(cert *git2go.Certificate, valid bool, hostname string) error {
|
|
return nil
|
|
}
|
|
}
|