Skip to content

Commit

Permalink
Merge pull request #720 from pjbgf/git-tests
Browse files Browse the repository at this point in the history
libgit2: Add support for hashed known_hosts
  • Loading branch information
Paulo Gomes authored May 16, 2022
2 parents e180b3c + 8b50367 commit b31c98f
Show file tree
Hide file tree
Showing 9 changed files with 207 additions and 84 deletions.
2 changes: 1 addition & 1 deletion controllers/gitrepository_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -478,7 +478,7 @@ func TestGitRepositoryReconciler_reconcileSource_authStrategy(t *testing.T) {
u, err := url.Parse(obj.Spec.URL)
g.Expect(err).NotTo(HaveOccurred())
g.Expect(u.Host).ToNot(BeEmpty())
knownHosts, err := ssh.ScanHostKey(u.Host, timeout, git.HostKeyAlgos)
knownHosts, err := ssh.ScanHostKey(u.Host, timeout, git.HostKeyAlgos, false)
g.Expect(err).NotTo(HaveOccurred())
secret.Data["known_hosts"] = knownHosts
}
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ require (
github.com/fluxcd/pkg/helmtestserver v0.7.2
github.com/fluxcd/pkg/lockedfile v0.1.0
github.com/fluxcd/pkg/runtime v0.15.1
github.com/fluxcd/pkg/ssh v0.3.3
github.com/fluxcd/pkg/ssh v0.3.4
github.com/fluxcd/pkg/testserver v0.2.0
github.com/fluxcd/pkg/untar v0.1.0
github.com/fluxcd/pkg/version v0.1.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -366,8 +366,8 @@ github.com/fluxcd/pkg/lockedfile v0.1.0/go.mod h1:EJLan8t9MiOcgTs8+puDjbE6I/KAfH
github.com/fluxcd/pkg/runtime v0.13.0-rc.6/go.mod h1:4oKUO19TeudXrnCRnxCfMSS7EQTYpYlgfXwlQuDJ/Eg=
github.com/fluxcd/pkg/runtime v0.15.1 h1:PKooYqlZM+KLhnNz10sQnBH0AHllS40PIDHtiRH/BGU=
github.com/fluxcd/pkg/runtime v0.15.1/go.mod h1:TPAoOEgUFG60FXBA4ID41uaPldxuXCEI4jt3qfd5i5Q=
github.com/fluxcd/pkg/ssh v0.3.3 h1:/tc7W7LO1VoVUI5jB+p9ZHCA+iQaXTkaSCDZJsxcZ9k=
github.com/fluxcd/pkg/ssh v0.3.3/go.mod h1:+bKhuv0/pJy3HZwkK54Shz68sNv1uf5aI6wtPaEHaYk=
github.com/fluxcd/pkg/ssh v0.3.4 h1:Ko+MUNiiQG3evyoMO19iRk7d4X0VJ6w6+GEeVQ1jLC0=
github.com/fluxcd/pkg/ssh v0.3.4/go.mod h1:KGgOUOy1uI6RC6+qxIBLvP1AeOOs/nLB25Ca6TZMIXE=
github.com/fluxcd/pkg/testserver v0.2.0 h1:Mj0TapmKaywI6Fi5wvt1LAZpakUHmtzWQpJNKQ0Krt4=
github.com/fluxcd/pkg/testserver v0.2.0/go.mod h1:bgjjydkXsZTeFzjz9Cr4heGANr41uTB1Aj1Q5qzuYVk=
github.com/fluxcd/pkg/untar v0.1.0 h1:k97V/xV5hFrAkIkVPuv5AVhyxh1ZzzAKba/lbDfGo6o=
Expand Down
49 changes: 46 additions & 3 deletions pkg/git/gogit/checkout_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,7 @@ func Test_KeyTypes(t *testing.T) {
g.Expect(err).NotTo(HaveOccurred())
g.Expect(u.Host).ToNot(BeEmpty())

knownHosts, err := ssh.ScanHostKey(u.Host, timeout, git.HostKeyAlgos)
knownHosts, err := ssh.ScanHostKey(u.Host, timeout, git.HostKeyAlgos, false)
g.Expect(err).ToNot(HaveOccurred())

for _, tt := range tests {
Expand Down Expand Up @@ -600,7 +600,7 @@ func Test_KeyExchangeAlgos(t *testing.T) {
g.Expect(err).NotTo(HaveOccurred())
g.Expect(u.Host).ToNot(BeEmpty())

knownHosts, err := ssh.ScanHostKey(u.Host, timeout, git.HostKeyAlgos)
knownHosts, err := ssh.ScanHostKey(u.Host, timeout, git.HostKeyAlgos, false)
g.Expect(err).ToNot(HaveOccurred())

// No authentication is required for this test, but it is
Expand Down Expand Up @@ -644,6 +644,7 @@ func TestHostKeyAlgos(t *testing.T) {
name string
keyType ssh.KeyPairType
ClientHostKeyAlgos []string
hashHostNames bool
}{
{
name: "support for hostkey: ssh-rsa",
Expand Down Expand Up @@ -680,6 +681,48 @@ func TestHostKeyAlgos(t *testing.T) {
keyType: ssh.ED25519,
ClientHostKeyAlgos: []string{"ssh-ed25519"},
},
{
name: "support for hostkey: ssh-rsa with hashed host names",
keyType: ssh.RSA_4096,
ClientHostKeyAlgos: []string{"ssh-rsa"},
hashHostNames: true,
},
{
name: "support for hostkey: rsa-sha2-256 with hashed host names",
keyType: ssh.RSA_4096,
ClientHostKeyAlgos: []string{"rsa-sha2-256"},
hashHostNames: true,
},
{
name: "support for hostkey: rsa-sha2-512 with hashed host names",
keyType: ssh.RSA_4096,
ClientHostKeyAlgos: []string{"rsa-sha2-512"},
hashHostNames: true,
},
{
name: "support for hostkey: ecdsa-sha2-nistp256 with hashed host names",
keyType: ssh.ECDSA_P256,
ClientHostKeyAlgos: []string{"ecdsa-sha2-nistp256"},
hashHostNames: true,
},
{
name: "support for hostkey: ecdsa-sha2-nistp384 with hashed host names",
keyType: ssh.ECDSA_P384,
ClientHostKeyAlgos: []string{"ecdsa-sha2-nistp384"},
hashHostNames: true,
},
{
name: "support for hostkey: ecdsa-sha2-nistp521 with hashed host names",
keyType: ssh.ECDSA_P521,
ClientHostKeyAlgos: []string{"ecdsa-sha2-nistp521"},
hashHostNames: true,
},
{
name: "support for hostkey: ssh-ed25519 with hashed host names",
keyType: ssh.ED25519,
ClientHostKeyAlgos: []string{"ssh-ed25519"},
hashHostNames: true,
},
}

for _, tt := range tests {
Expand Down Expand Up @@ -727,7 +770,7 @@ func TestHostKeyAlgos(t *testing.T) {
g.Expect(err).NotTo(HaveOccurred())
g.Expect(u.Host).ToNot(BeEmpty())

knownHosts, err := ssh.ScanHostKey(u.Host, timeout, git.HostKeyAlgos)
knownHosts, err := ssh.ScanHostKey(u.Host, timeout, git.HostKeyAlgos, tt.hashHostNames)
g.Expect(err).ToNot(HaveOccurred())

// No authentication is required for this test, but it is
Expand Down
71 changes: 0 additions & 71 deletions pkg/git/libgit2/checkout_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,13 @@ import (
"context"
"errors"
"fmt"
"net/url"
"os"
"path/filepath"
"testing"
"time"

git2go "github.com/libgit2/git2go/v33"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"

"github.com/fluxcd/pkg/gittestserver"
"github.com/fluxcd/pkg/ssh"

"github.com/fluxcd/source-controller/pkg/git"
)

func TestCheckoutBranch_Checkout(t *testing.T) {
Expand Down Expand Up @@ -517,67 +510,3 @@ func mockSignature(time time.Time) *git2go.Signature {
When: time,
}
}

// This test is specifically to detect regression in libgit2's ED25519 key
// support for client authentication.
// Refer: https://github.com/fluxcd/source-controller/issues/399
func TestCheckout_ED25519(t *testing.T) {
g := NewWithT(t)
timeout := 5 * time.Second

// Create a git test server.
server, err := gittestserver.NewTempGitServer()
g.Expect(err).ToNot(HaveOccurred())
defer os.RemoveAll(server.Root())
server.Auth("test-user", "test-pswd")
server.AutoCreate()

server.KeyDir(filepath.Join(server.Root(), "keys"))
g.Expect(server.ListenSSH()).To(Succeed())

go func() {
server.StartSSH()
}()
defer server.StopSSH()

repoPath := "test.git"

err = server.InitRepo(testRepositoryPath, git.DefaultBranch, repoPath)
g.Expect(err).NotTo(HaveOccurred())

sshURL := server.SSHAddress()
repoURL := sshURL + "/" + repoPath

// Fetch host key.
u, err := url.Parse(sshURL)
g.Expect(err).NotTo(HaveOccurred())
g.Expect(u.Host).ToNot(BeEmpty())
knownHosts, err := ssh.ScanHostKey(u.Host, timeout, git.HostKeyAlgos)
g.Expect(err).ToNot(HaveOccurred())

kp, err := ssh.NewEd25519Generator().Generate()
g.Expect(err).ToNot(HaveOccurred())

secret := corev1.Secret{
Data: map[string][]byte{
"identity": kp.PrivateKey,
"known_hosts": knownHosts,
},
}

authOpts, err := git.AuthOptionsFromSecret(repoURL, &secret)
g.Expect(err).ToNot(HaveOccurred())

// Prepare for checkout.
branchCheckoutStrat := &CheckoutBranch{Branch: git.DefaultBranch}
tmpDir := t.TempDir()

ctx, cancel := context.WithTimeout(context.TODO(), timeout)
defer cancel()

// Checkout the repo.
// This should always fail because the generated key above isn't present in
// the git server.
_, err = branchCheckoutStrat.Checkout(ctx, tmpDir, repoURL, authOpts)
g.Expect(err).ToNot(HaveOccurred())
}
49 changes: 46 additions & 3 deletions pkg/git/libgit2/managed_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ func Test_ManagedSSH_KeyTypes(t *testing.T) {
g.Expect(err).NotTo(HaveOccurred())
g.Expect(u.Host).ToNot(BeEmpty())

knownHosts, err := ssh.ScanHostKey(u.Host, timeout, git.HostKeyAlgos)
knownHosts, err := ssh.ScanHostKey(u.Host, timeout, git.HostKeyAlgos, false)
g.Expect(err).ToNot(HaveOccurred())

for _, tt := range tests {
Expand Down Expand Up @@ -238,7 +238,7 @@ func Test_ManagedSSH_KeyExchangeAlgos(t *testing.T) {
g.Expect(err).NotTo(HaveOccurred())
g.Expect(u.Host).ToNot(BeEmpty())

knownHosts, err := ssh.ScanHostKey(u.Host, timeout, git.HostKeyAlgos)
knownHosts, err := ssh.ScanHostKey(u.Host, timeout, git.HostKeyAlgos, false)
g.Expect(err).ToNot(HaveOccurred())

// No authentication is required for this test, but it is
Expand Down Expand Up @@ -282,6 +282,7 @@ func Test_ManagedSSH_HostKeyAlgos(t *testing.T) {
name string
keyType ssh.KeyPairType
ClientHostKeyAlgos []string
hashHostNames bool
}{
{
name: "support for hostkey: ssh-rsa",
Expand Down Expand Up @@ -318,6 +319,48 @@ func Test_ManagedSSH_HostKeyAlgos(t *testing.T) {
keyType: ssh.ED25519,
ClientHostKeyAlgos: []string{"ssh-ed25519"},
},
{
name: "support for hostkey: ssh-rsa with hashed host names",
keyType: ssh.RSA_4096,
ClientHostKeyAlgos: []string{"ssh-rsa"},
hashHostNames: true,
},
{
name: "support for hostkey: rsa-sha2-256 with hashed host names",
keyType: ssh.RSA_4096,
ClientHostKeyAlgos: []string{"rsa-sha2-256"},
hashHostNames: true,
},
{
name: "support for hostkey: rsa-sha2-512 with hashed host names",
keyType: ssh.RSA_4096,
ClientHostKeyAlgos: []string{"rsa-sha2-512"},
hashHostNames: true,
},
{
name: "support for hostkey: ecdsa-sha2-nistp256 with hashed host names",
keyType: ssh.ECDSA_P256,
ClientHostKeyAlgos: []string{"ecdsa-sha2-nistp256"},
hashHostNames: true,
},
{
name: "support for hostkey: ecdsa-sha2-nistp384 with hashed host names",
keyType: ssh.ECDSA_P384,
ClientHostKeyAlgos: []string{"ecdsa-sha2-nistp384"},
hashHostNames: true,
},
{
name: "support for hostkey: ecdsa-sha2-nistp521 with hashed host names",
keyType: ssh.ECDSA_P521,
ClientHostKeyAlgos: []string{"ecdsa-sha2-nistp521"},
hashHostNames: true,
},
{
name: "support for hostkey: ssh-ed25519 with hashed host names",
keyType: ssh.ED25519,
ClientHostKeyAlgos: []string{"ssh-ed25519"},
hashHostNames: true,
},
}

for _, tt := range tests {
Expand Down Expand Up @@ -368,7 +411,7 @@ func Test_ManagedSSH_HostKeyAlgos(t *testing.T) {
g.Expect(err).NotTo(HaveOccurred())
g.Expect(u.Host).ToNot(BeEmpty())

knownHosts, err := ssh.ScanHostKey(u.Host, timeout, tt.ClientHostKeyAlgos)
knownHosts, err := ssh.ScanHostKey(u.Host, timeout, tt.ClientHostKeyAlgos, tt.hashHostNames)
g.Expect(err).ToNot(HaveOccurred())

// No authentication is required for this test, but it is
Expand Down
50 changes: 48 additions & 2 deletions pkg/git/libgit2/transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ import (
"bufio"
"bytes"
"context"
"crypto/hmac"
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"fmt"
"hash"
"io"
Expand Down Expand Up @@ -288,10 +290,54 @@ func (k knownKey) matches(host string, hostkey git2go.HostkeyCertificate) bool {
}

func containsHost(hosts []string, host string) bool {
for _, h := range hosts {
if h == host {
for _, kh := range hosts {
// hashed host must start with a pipe
if kh[0] == '|' {
match, _ := MatchHashedHost(kh, host)
if match {
return true
}

} else if kh == host { // unhashed host check
return true
}
}
return false
}

// MatchHashedHost tries to match a hashed known host (kh) to
// host.
//
// Note that host is not hashed, but it is rather hashed during
// the matching process using the same salt used when hashing
// the known host.
func MatchHashedHost(kh, host string) (bool, error) {
if kh == "" || kh[0] != '|' {
return false, fmt.Errorf("hashed known host must begin with '|': '%s'", kh)
}

components := strings.Split(kh, "|")
if len(components) != 4 {
return false, fmt.Errorf("invalid format for hashed known host: '%s'", kh)
}

if components[1] != "1" {
return false, fmt.Errorf("unsupported hash type '%s'", components[1])
}

hkSalt, err := base64.StdEncoding.DecodeString(components[2])
if err != nil {
return false, fmt.Errorf("cannot decode hashed known host: '%w'", err)
}

hkHash, err := base64.StdEncoding.DecodeString(components[3])
if err != nil {
return false, fmt.Errorf("cannot decode hashed known host: '%w'", err)
}

mac := hmac.New(sha1.New, hkSalt)
mac.Write([]byte(host))
hostHash := mac.Sum(nil)

return bytes.Equal(hostHash, hkHash), nil
}
Loading

0 comments on commit b31c98f

Please sign in to comment.