Skip to content

Commit b31c98f

Browse files
author
Paulo Gomes
authored
Merge pull request #720 from pjbgf/git-tests
libgit2: Add support for hashed known_hosts
2 parents e180b3c + 8b50367 commit b31c98f

File tree

9 files changed

+207
-84
lines changed

9 files changed

+207
-84
lines changed

controllers/gitrepository_controller_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -478,7 +478,7 @@ func TestGitRepositoryReconciler_reconcileSource_authStrategy(t *testing.T) {
478478
u, err := url.Parse(obj.Spec.URL)
479479
g.Expect(err).NotTo(HaveOccurred())
480480
g.Expect(u.Host).ToNot(BeEmpty())
481-
knownHosts, err := ssh.ScanHostKey(u.Host, timeout, git.HostKeyAlgos)
481+
knownHosts, err := ssh.ScanHostKey(u.Host, timeout, git.HostKeyAlgos, false)
482482
g.Expect(err).NotTo(HaveOccurred())
483483
secret.Data["known_hosts"] = knownHosts
484484
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ require (
2626
github.com/fluxcd/pkg/helmtestserver v0.7.2
2727
github.com/fluxcd/pkg/lockedfile v0.1.0
2828
github.com/fluxcd/pkg/runtime v0.15.1
29-
github.com/fluxcd/pkg/ssh v0.3.3
29+
github.com/fluxcd/pkg/ssh v0.3.4
3030
github.com/fluxcd/pkg/testserver v0.2.0
3131
github.com/fluxcd/pkg/untar v0.1.0
3232
github.com/fluxcd/pkg/version v0.1.0

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -366,8 +366,8 @@ github.com/fluxcd/pkg/lockedfile v0.1.0/go.mod h1:EJLan8t9MiOcgTs8+puDjbE6I/KAfH
366366
github.com/fluxcd/pkg/runtime v0.13.0-rc.6/go.mod h1:4oKUO19TeudXrnCRnxCfMSS7EQTYpYlgfXwlQuDJ/Eg=
367367
github.com/fluxcd/pkg/runtime v0.15.1 h1:PKooYqlZM+KLhnNz10sQnBH0AHllS40PIDHtiRH/BGU=
368368
github.com/fluxcd/pkg/runtime v0.15.1/go.mod h1:TPAoOEgUFG60FXBA4ID41uaPldxuXCEI4jt3qfd5i5Q=
369-
github.com/fluxcd/pkg/ssh v0.3.3 h1:/tc7W7LO1VoVUI5jB+p9ZHCA+iQaXTkaSCDZJsxcZ9k=
370-
github.com/fluxcd/pkg/ssh v0.3.3/go.mod h1:+bKhuv0/pJy3HZwkK54Shz68sNv1uf5aI6wtPaEHaYk=
369+
github.com/fluxcd/pkg/ssh v0.3.4 h1:Ko+MUNiiQG3evyoMO19iRk7d4X0VJ6w6+GEeVQ1jLC0=
370+
github.com/fluxcd/pkg/ssh v0.3.4/go.mod h1:KGgOUOy1uI6RC6+qxIBLvP1AeOOs/nLB25Ca6TZMIXE=
371371
github.com/fluxcd/pkg/testserver v0.2.0 h1:Mj0TapmKaywI6Fi5wvt1LAZpakUHmtzWQpJNKQ0Krt4=
372372
github.com/fluxcd/pkg/testserver v0.2.0/go.mod h1:bgjjydkXsZTeFzjz9Cr4heGANr41uTB1Aj1Q5qzuYVk=
373373
github.com/fluxcd/pkg/untar v0.1.0 h1:k97V/xV5hFrAkIkVPuv5AVhyxh1ZzzAKba/lbDfGo6o=

pkg/git/gogit/checkout_test.go

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,7 @@ func Test_KeyTypes(t *testing.T) {
461461
g.Expect(err).NotTo(HaveOccurred())
462462
g.Expect(u.Host).ToNot(BeEmpty())
463463

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

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

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

606606
// No authentication is required for this test, but it is
@@ -644,6 +644,7 @@ func TestHostKeyAlgos(t *testing.T) {
644644
name string
645645
keyType ssh.KeyPairType
646646
ClientHostKeyAlgos []string
647+
hashHostNames bool
647648
}{
648649
{
649650
name: "support for hostkey: ssh-rsa",
@@ -680,6 +681,48 @@ func TestHostKeyAlgos(t *testing.T) {
680681
keyType: ssh.ED25519,
681682
ClientHostKeyAlgos: []string{"ssh-ed25519"},
682683
},
684+
{
685+
name: "support for hostkey: ssh-rsa with hashed host names",
686+
keyType: ssh.RSA_4096,
687+
ClientHostKeyAlgos: []string{"ssh-rsa"},
688+
hashHostNames: true,
689+
},
690+
{
691+
name: "support for hostkey: rsa-sha2-256 with hashed host names",
692+
keyType: ssh.RSA_4096,
693+
ClientHostKeyAlgos: []string{"rsa-sha2-256"},
694+
hashHostNames: true,
695+
},
696+
{
697+
name: "support for hostkey: rsa-sha2-512 with hashed host names",
698+
keyType: ssh.RSA_4096,
699+
ClientHostKeyAlgos: []string{"rsa-sha2-512"},
700+
hashHostNames: true,
701+
},
702+
{
703+
name: "support for hostkey: ecdsa-sha2-nistp256 with hashed host names",
704+
keyType: ssh.ECDSA_P256,
705+
ClientHostKeyAlgos: []string{"ecdsa-sha2-nistp256"},
706+
hashHostNames: true,
707+
},
708+
{
709+
name: "support for hostkey: ecdsa-sha2-nistp384 with hashed host names",
710+
keyType: ssh.ECDSA_P384,
711+
ClientHostKeyAlgos: []string{"ecdsa-sha2-nistp384"},
712+
hashHostNames: true,
713+
},
714+
{
715+
name: "support for hostkey: ecdsa-sha2-nistp521 with hashed host names",
716+
keyType: ssh.ECDSA_P521,
717+
ClientHostKeyAlgos: []string{"ecdsa-sha2-nistp521"},
718+
hashHostNames: true,
719+
},
720+
{
721+
name: "support for hostkey: ssh-ed25519 with hashed host names",
722+
keyType: ssh.ED25519,
723+
ClientHostKeyAlgos: []string{"ssh-ed25519"},
724+
hashHostNames: true,
725+
},
683726
}
684727

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

730-
knownHosts, err := ssh.ScanHostKey(u.Host, timeout, git.HostKeyAlgos)
773+
knownHosts, err := ssh.ScanHostKey(u.Host, timeout, git.HostKeyAlgos, tt.hashHostNames)
731774
g.Expect(err).ToNot(HaveOccurred())
732775

733776
// No authentication is required for this test, but it is

pkg/git/libgit2/checkout_test.go

Lines changed: 0 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,13 @@ import (
2020
"context"
2121
"errors"
2222
"fmt"
23-
"net/url"
2423
"os"
2524
"path/filepath"
2625
"testing"
2726
"time"
2827

2928
git2go "github.com/libgit2/git2go/v33"
3029
. "github.com/onsi/gomega"
31-
corev1 "k8s.io/api/core/v1"
32-
33-
"github.com/fluxcd/pkg/gittestserver"
34-
"github.com/fluxcd/pkg/ssh"
35-
36-
"github.com/fluxcd/source-controller/pkg/git"
3730
)
3831

3932
func TestCheckoutBranch_Checkout(t *testing.T) {
@@ -517,67 +510,3 @@ func mockSignature(time time.Time) *git2go.Signature {
517510
When: time,
518511
}
519512
}
520-
521-
// This test is specifically to detect regression in libgit2's ED25519 key
522-
// support for client authentication.
523-
// Refer: https://github.com/fluxcd/source-controller/issues/399
524-
func TestCheckout_ED25519(t *testing.T) {
525-
g := NewWithT(t)
526-
timeout := 5 * time.Second
527-
528-
// Create a git test server.
529-
server, err := gittestserver.NewTempGitServer()
530-
g.Expect(err).ToNot(HaveOccurred())
531-
defer os.RemoveAll(server.Root())
532-
server.Auth("test-user", "test-pswd")
533-
server.AutoCreate()
534-
535-
server.KeyDir(filepath.Join(server.Root(), "keys"))
536-
g.Expect(server.ListenSSH()).To(Succeed())
537-
538-
go func() {
539-
server.StartSSH()
540-
}()
541-
defer server.StopSSH()
542-
543-
repoPath := "test.git"
544-
545-
err = server.InitRepo(testRepositoryPath, git.DefaultBranch, repoPath)
546-
g.Expect(err).NotTo(HaveOccurred())
547-
548-
sshURL := server.SSHAddress()
549-
repoURL := sshURL + "/" + repoPath
550-
551-
// Fetch host key.
552-
u, err := url.Parse(sshURL)
553-
g.Expect(err).NotTo(HaveOccurred())
554-
g.Expect(u.Host).ToNot(BeEmpty())
555-
knownHosts, err := ssh.ScanHostKey(u.Host, timeout, git.HostKeyAlgos)
556-
g.Expect(err).ToNot(HaveOccurred())
557-
558-
kp, err := ssh.NewEd25519Generator().Generate()
559-
g.Expect(err).ToNot(HaveOccurred())
560-
561-
secret := corev1.Secret{
562-
Data: map[string][]byte{
563-
"identity": kp.PrivateKey,
564-
"known_hosts": knownHosts,
565-
},
566-
}
567-
568-
authOpts, err := git.AuthOptionsFromSecret(repoURL, &secret)
569-
g.Expect(err).ToNot(HaveOccurred())
570-
571-
// Prepare for checkout.
572-
branchCheckoutStrat := &CheckoutBranch{Branch: git.DefaultBranch}
573-
tmpDir := t.TempDir()
574-
575-
ctx, cancel := context.WithTimeout(context.TODO(), timeout)
576-
defer cancel()
577-
578-
// Checkout the repo.
579-
// This should always fail because the generated key above isn't present in
580-
// the git server.
581-
_, err = branchCheckoutStrat.Checkout(ctx, tmpDir, repoURL, authOpts)
582-
g.Expect(err).ToNot(HaveOccurred())
583-
}

pkg/git/libgit2/managed_test.go

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ func Test_ManagedSSH_KeyTypes(t *testing.T) {
9696
g.Expect(err).NotTo(HaveOccurred())
9797
g.Expect(u.Host).ToNot(BeEmpty())
9898

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

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

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

244244
// No authentication is required for this test, but it is
@@ -282,6 +282,7 @@ func Test_ManagedSSH_HostKeyAlgos(t *testing.T) {
282282
name string
283283
keyType ssh.KeyPairType
284284
ClientHostKeyAlgos []string
285+
hashHostNames bool
285286
}{
286287
{
287288
name: "support for hostkey: ssh-rsa",
@@ -318,6 +319,48 @@ func Test_ManagedSSH_HostKeyAlgos(t *testing.T) {
318319
keyType: ssh.ED25519,
319320
ClientHostKeyAlgos: []string{"ssh-ed25519"},
320321
},
322+
{
323+
name: "support for hostkey: ssh-rsa with hashed host names",
324+
keyType: ssh.RSA_4096,
325+
ClientHostKeyAlgos: []string{"ssh-rsa"},
326+
hashHostNames: true,
327+
},
328+
{
329+
name: "support for hostkey: rsa-sha2-256 with hashed host names",
330+
keyType: ssh.RSA_4096,
331+
ClientHostKeyAlgos: []string{"rsa-sha2-256"},
332+
hashHostNames: true,
333+
},
334+
{
335+
name: "support for hostkey: rsa-sha2-512 with hashed host names",
336+
keyType: ssh.RSA_4096,
337+
ClientHostKeyAlgos: []string{"rsa-sha2-512"},
338+
hashHostNames: true,
339+
},
340+
{
341+
name: "support for hostkey: ecdsa-sha2-nistp256 with hashed host names",
342+
keyType: ssh.ECDSA_P256,
343+
ClientHostKeyAlgos: []string{"ecdsa-sha2-nistp256"},
344+
hashHostNames: true,
345+
},
346+
{
347+
name: "support for hostkey: ecdsa-sha2-nistp384 with hashed host names",
348+
keyType: ssh.ECDSA_P384,
349+
ClientHostKeyAlgos: []string{"ecdsa-sha2-nistp384"},
350+
hashHostNames: true,
351+
},
352+
{
353+
name: "support for hostkey: ecdsa-sha2-nistp521 with hashed host names",
354+
keyType: ssh.ECDSA_P521,
355+
ClientHostKeyAlgos: []string{"ecdsa-sha2-nistp521"},
356+
hashHostNames: true,
357+
},
358+
{
359+
name: "support for hostkey: ssh-ed25519 with hashed host names",
360+
keyType: ssh.ED25519,
361+
ClientHostKeyAlgos: []string{"ssh-ed25519"},
362+
hashHostNames: true,
363+
},
321364
}
322365

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

371-
knownHosts, err := ssh.ScanHostKey(u.Host, timeout, tt.ClientHostKeyAlgos)
414+
knownHosts, err := ssh.ScanHostKey(u.Host, timeout, tt.ClientHostKeyAlgos, tt.hashHostNames)
372415
g.Expect(err).ToNot(HaveOccurred())
373416

374417
// No authentication is required for this test, but it is

pkg/git/libgit2/transport.go

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,12 @@ import (
2020
"bufio"
2121
"bytes"
2222
"context"
23+
"crypto/hmac"
2324
"crypto/md5"
2425
"crypto/sha1"
2526
"crypto/sha256"
2627
"crypto/x509"
28+
"encoding/base64"
2729
"fmt"
2830
"hash"
2931
"io"
@@ -288,10 +290,54 @@ func (k knownKey) matches(host string, hostkey git2go.HostkeyCertificate) bool {
288290
}
289291

290292
func containsHost(hosts []string, host string) bool {
291-
for _, h := range hosts {
292-
if h == host {
293+
for _, kh := range hosts {
294+
// hashed host must start with a pipe
295+
if kh[0] == '|' {
296+
match, _ := MatchHashedHost(kh, host)
297+
if match {
298+
return true
299+
}
300+
301+
} else if kh == host { // unhashed host check
293302
return true
294303
}
295304
}
296305
return false
297306
}
307+
308+
// MatchHashedHost tries to match a hashed known host (kh) to
309+
// host.
310+
//
311+
// Note that host is not hashed, but it is rather hashed during
312+
// the matching process using the same salt used when hashing
313+
// the known host.
314+
func MatchHashedHost(kh, host string) (bool, error) {
315+
if kh == "" || kh[0] != '|' {
316+
return false, fmt.Errorf("hashed known host must begin with '|': '%s'", kh)
317+
}
318+
319+
components := strings.Split(kh, "|")
320+
if len(components) != 4 {
321+
return false, fmt.Errorf("invalid format for hashed known host: '%s'", kh)
322+
}
323+
324+
if components[1] != "1" {
325+
return false, fmt.Errorf("unsupported hash type '%s'", components[1])
326+
}
327+
328+
hkSalt, err := base64.StdEncoding.DecodeString(components[2])
329+
if err != nil {
330+
return false, fmt.Errorf("cannot decode hashed known host: '%w'", err)
331+
}
332+
333+
hkHash, err := base64.StdEncoding.DecodeString(components[3])
334+
if err != nil {
335+
return false, fmt.Errorf("cannot decode hashed known host: '%w'", err)
336+
}
337+
338+
mac := hmac.New(sha1.New, hkSalt)
339+
mac.Write([]byte(host))
340+
hostHash := mac.Sum(nil)
341+
342+
return bytes.Equal(hostHash, hkHash), nil
343+
}

0 commit comments

Comments
 (0)