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

libgit2: improve known_hosts error messages #783

Merged
merged 1 commit into from
Jun 22, 2022
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
17 changes: 2 additions & 15 deletions pkg/git/libgit2/managed/ssh.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,21 +191,8 @@ func (t *sshSmartSubtransport) Action(transportOptionsURL string, action git2go.
}

sshConfig.HostKeyCallback = func(hostname string, remote net.Addr, key ssh.PublicKey) error {
marshaledKey := key.Marshal()
cert := &git2go.Certificate{
Kind: git2go.CertificateHostkey,
Hostkey: git2go.HostkeyCertificate{
Kind: git2go.HostkeySHA256 | git2go.HostkeyRaw,
HashSHA256: sha256.Sum256(marshaledKey),
Hostkey: marshaledKey,
SSHPublicKey: key,
},
}

if len(opts.AuthOpts.KnownHosts) > 0 {
return KnownHostsCallback(hostname, opts.AuthOpts.KnownHosts)(cert, true, hostname)
}
return nil
keyHash := sha256.Sum256(key.Marshal())
return CheckKnownHost(hostname, opts.AuthOpts.KnownHosts, keyHash[:])
darkowlzz marked this conversation as resolved.
Show resolved Hide resolved
}

if t.connected {
Expand Down
13 changes: 11 additions & 2 deletions pkg/git/libgit2/managed/ssh_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ limitations under the License.
package managed

import (
"net/url"
"os"
"path/filepath"
"testing"
"time"

"github.com/fluxcd/pkg/ssh"
"github.com/fluxcd/source-controller/pkg/git"
Expand Down Expand Up @@ -97,13 +99,20 @@ func TestSSHManagedTransport_E2E(t *testing.T) {
err = server.InitRepo("../../testdata/git/repo", git.DefaultBranch, repoPath)
g.Expect(err).ToNot(HaveOccurred())

u, err := url.Parse(server.SSHAddress())
g.Expect(err).NotTo(HaveOccurred())
g.Expect(u.Host).ToNot(BeEmpty())
knownhosts, err := ssh.ScanHostKey(u.Host, 5*time.Second, git.HostKeyAlgos, false)
pjbgf marked this conversation as resolved.
Show resolved Hide resolved
g.Expect(err).NotTo(HaveOccurred())

transportOptsURL := "ssh://git@fake-url"
sshAddress := server.SSHAddress() + "/" + repoPath
AddTransportOptions(transportOptsURL, TransportOptions{
TargetURL: sshAddress,
AuthOpts: &git.AuthOptions{
Username: "user",
Identity: kp.PrivateKey,
Username: "user",
Identity: kp.PrivateKey,
KnownHosts: knownhosts,
},
})

Expand Down
44 changes: 29 additions & 15 deletions pkg/git/libgit2/managed/transport.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package managed

import (
"encoding/base64"
"fmt"
"net"

Expand All @@ -14,11 +15,6 @@ import (
// git.SSH Transports.
func KnownHostsCallback(host string, knownHosts []byte) git2go.CertificateCheckCallback {
return func(cert *git2go.Certificate, valid bool, hostname string) error {
kh, err := pkgkh.ParseKnownHosts(string(knownHosts))
if err != nil {
return fmt.Errorf("failed to parse known_hosts: %w", err)
}

// First, attempt to split the configured host and port to validate
// the port-less hostname given to the callback.
hostWithoutPort, _, err := net.SplitHostPort(host)
Expand Down Expand Up @@ -47,18 +43,36 @@ func KnownHostsCallback(host string, knownHosts []byte) git2go.CertificateCheckC
return fmt.Errorf("invalid host key kind, expected to be of kind SHA256")
}

// 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 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("hostkey could not be verified")
}
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.
Expand Down
45 changes: 44 additions & 1 deletion pkg/git/libgit2/managed/transport_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,17 @@ github.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAA
github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl
`

// To fetch latest knownhosts for source.developers.google.com run:
// ssh-keyscan -p 2022 source.developers.google.com
//
// Expected hash (used in the cases) can get found with:
// ssh-keyscan -p 2022 source.developers.google.com | ssh-keygen -l -f -
var knownHostsFixtureWithPort = `[source.developers.google.com]:2022 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBB5Iy4/cq/gt/fPqe3uyMy4jwv1Alc94yVPxmnwNhBzJqEV5gRPiRk5u4/JJMbbu9QUVAguBABxL7sBZa5PH/xY=`

// This is an incorrect known hosts entry, that does not aligned with
// the normalized format and therefore won't match.
var knownHostsFixtureUnormalized = `source.developers.google.com:2022 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBB5Iy4/cq/gt/fPqe3uyMy4jwv1Alc94yVPxmnwNhBzJqEV5gRPiRk5u4/JJMbbu9QUVAguBABxL7sBZa5PH/xY=`

func TestKnownHostsCallback(t *testing.T) {
tests := []struct {
name string
Expand All @@ -25,6 +36,38 @@ func TestKnownHostsCallback(t *testing.T) {
hostkey git2go.HostkeyCertificate
want error
}{
{
name: "Empty",
host: "source.developers.google.com",
knownHosts: []byte(""),
hostkey: git2go.HostkeyCertificate{Kind: git2go.HostkeySHA256, HashSHA256: sha256Fingerprint("AGvEpqYNMqsRNIviwyk4J4HM0lEylomDBKOWZsBn434")},
expectedHost: "source.developers.google.com:2022",
want: fmt.Errorf("hostkey verification aborted: no known_hosts found"),
},
{
name: "Mismatch incorrect known_hosts",
host: "source.developers.google.com",
knownHosts: []byte(knownHostsFixtureUnormalized),
hostkey: git2go.HostkeyCertificate{Kind: git2go.HostkeySHA256, HashSHA256: sha256Fingerprint("AGvEpqYNMqsRNIviwyk4J4HM0lEylomDBKOWZsBn434")},
expectedHost: "source.developers.google.com:2022",
want: fmt.Errorf("no entries in known_hosts match host '[source.developers.google.com]:2022' with fingerprint 'AGvEpqYNMqsRNIviwyk4J4HM0lEylomDBKOWZsBn434'"),
},
{
name: "Match when host has port",
host: "source.developers.google.com:2022",
knownHosts: []byte(knownHostsFixtureWithPort),
hostkey: git2go.HostkeyCertificate{Kind: git2go.HostkeySHA256, HashSHA256: sha256Fingerprint("AGvEpqYNMqsRNIviwyk4J4HM0lEylomDBKOWZsBn434")},
expectedHost: "source.developers.google.com:2022",
want: nil,
},
{
name: "Match even when host does not have port",
host: "source.developers.google.com",
knownHosts: []byte(knownHostsFixtureWithPort),
hostkey: git2go.HostkeyCertificate{Kind: git2go.HostkeySHA256, HashSHA256: sha256Fingerprint("AGvEpqYNMqsRNIviwyk4J4HM0lEylomDBKOWZsBn434")},
expectedHost: "source.developers.google.com:2022",
want: nil,
},
{
name: "Match",
host: "github.com",
Expand Down Expand Up @@ -66,7 +109,7 @@ func TestKnownHostsCallback(t *testing.T) {
knownHosts: []byte(knownHostsFixture),
hostkey: git2go.HostkeyCertificate{Kind: git2go.HostkeySHA256, HashSHA256: sha256Fingerprint("ROQFvPThGrW4RuWLoL9tq9I9zJ42fK4XywyRtbOz/EQ")},
expectedHost: "github.com",
want: fmt.Errorf("hostkey could not be verified"),
want: fmt.Errorf("no entries in known_hosts match host 'github.com' with fingerprint 'ROQFvPThGrW4RuWLoL9tq9I9zJ42fK4XywyRtbOz/EQ'"),
},
}
for _, tt := range tests {
Expand Down