Skip to content

Commit c4bbb11

Browse files
committed
Extract SSH public key auth into internal/ssh
Centralise SSH private-key loading and public-key auth creation into internal/ssh so both bundlereader and gitcloner share a single implementation. pkg/git/netutils.go gains SSHPublicKeysFromFile for callers in pkg/git that already use a file path directly. Prefer known_hosts from the secret; fall back to the cluster-wide value. Add unit tests for NewSSHPublicKeys.
1 parent 8e0355a commit c4bbb11

3 files changed

Lines changed: 129 additions & 18 deletions

File tree

internal/ssh/auth.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package ssh
2+
3+
import (
4+
gossh "github.com/go-git/go-git/v5/plumbing/transport/ssh"
5+
golangssh "golang.org/x/crypto/ssh"
6+
)
7+
8+
// NewSSHPublicKeys creates a go-git SSH auth method from the given PEM key and
9+
// optional known_hosts bytes. If knownHosts is empty, InsecureIgnoreHostKey is
10+
// used as the host key callback, matching Fleet's default behaviour across all
11+
// git cloning code paths.
12+
func NewSSHPublicKeys(user string, keyPEM []byte, knownHosts []byte) (*gossh.PublicKeys, error) {
13+
pubKeys, err := gossh.NewPublicKeys(user, keyPEM, "")
14+
if err != nil {
15+
return nil, err
16+
}
17+
if len(knownHosts) > 0 {
18+
pubKeys.HostKeyCallback, err = CreateKnownHostsCallBack(knownHosts)
19+
if err != nil {
20+
return nil, err
21+
}
22+
} else {
23+
//nolint:gosec // G106: InsecureIgnoreHostKey is the intentional fallback
24+
// when no known_hosts are provided; matches behaviour across gitcloner,
25+
// bundlereader, and pkg/git.
26+
pubKeys.HostKeyCallback = golangssh.InsecureIgnoreHostKey()
27+
}
28+
return pubKeys, nil
29+
}

internal/ssh/auth_test.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package ssh_test
2+
3+
import (
4+
"crypto/ecdsa"
5+
"crypto/elliptic"
6+
"crypto/rand"
7+
"crypto/x509"
8+
"encoding/pem"
9+
"fmt"
10+
"net"
11+
"testing"
12+
13+
golangssh "golang.org/x/crypto/ssh"
14+
15+
"github.com/rancher/fleet/internal/ssh"
16+
)
17+
18+
func generateECPrivateKeyPEM(t *testing.T) []byte {
19+
t.Helper()
20+
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
21+
if err != nil {
22+
t.Fatalf("generating key: %v", err)
23+
}
24+
der, err := x509.MarshalECPrivateKey(key)
25+
if err != nil {
26+
t.Fatalf("marshalling key: %v", err)
27+
}
28+
return pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: der})
29+
}
30+
31+
func generateKnownHostsEntry(t *testing.T, hostname string) (golangssh.PublicKey, string) {
32+
t.Helper()
33+
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
34+
if err != nil {
35+
t.Fatalf("generating host key: %v", err)
36+
}
37+
sshPubKey, err := golangssh.NewPublicKey(&key.PublicKey)
38+
if err != nil {
39+
t.Fatalf("creating SSH public key: %v", err)
40+
}
41+
entry := fmt.Sprintf("%s %s", hostname, string(golangssh.MarshalAuthorizedKey(sshPubKey)))
42+
return sshPubKey, entry
43+
}
44+
45+
// fakeAddr implements net.Addr for use in HostKeyCallback tests.
46+
type fakeAddr struct{ s string }
47+
48+
func (a fakeAddr) Network() string { return "tcp" }
49+
func (a fakeAddr) String() string { return a.s }
50+
51+
func TestNewSSHPublicKeys_NoKnownHosts(t *testing.T) {
52+
keyPEM := generateECPrivateKeyPEM(t)
53+
pubKeys, err := ssh.NewSSHPublicKeys("git", keyPEM, nil)
54+
if err != nil {
55+
t.Fatalf("unexpected error: %v", err)
56+
}
57+
if pubKeys == nil {
58+
t.Fatal("expected non-nil PublicKeys")
59+
}
60+
// With no known hosts, InsecureIgnoreHostKey must accept any host.
61+
if err := pubKeys.HostKeyCallback("any-host:22", fakeAddr{"any-host:22"}, nil); err != nil {
62+
t.Fatalf("InsecureIgnoreHostKey should accept any host, got: %v", err)
63+
}
64+
}
65+
66+
func TestNewSSHPublicKeys_WithKnownHosts(t *testing.T) {
67+
keyPEM := generateECPrivateKeyPEM(t)
68+
hostPubKey, knownHostsEntry := generateKnownHostsEntry(t, "myhost.example.com")
69+
70+
pubKeys, err := ssh.NewSSHPublicKeys("git", keyPEM, []byte(knownHostsEntry))
71+
if err != nil {
72+
t.Fatalf("unexpected error: %v", err)
73+
}
74+
75+
// Matching key for the known host must be accepted.
76+
addr := &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 22}
77+
if err := pubKeys.HostKeyCallback("myhost.example.com:22", addr, hostPubKey); err != nil {
78+
t.Fatalf("expected callback to accept matching key, got: %v", err)
79+
}
80+
81+
// An unknown host must be rejected.
82+
if err := pubKeys.HostKeyCallback("unknown-host:22", addr, hostPubKey); err == nil {
83+
t.Fatal("expected error for unknown host, got nil")
84+
}
85+
}
86+
87+
func TestNewSSHPublicKeys_InvalidKnownHosts(t *testing.T) {
88+
keyPEM := generateECPrivateKeyPEM(t)
89+
// Malformed known_hosts must be rejected at construction time.
90+
_, err := ssh.NewSSHPublicKeys("git", keyPEM, []byte("not-valid-known-hosts @@@@"))
91+
if err == nil {
92+
t.Fatal("expected error for invalid known_hosts, got nil")
93+
}
94+
}

pkg/git/netutils.go

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,7 @@ import (
99

1010
"github.com/go-git/go-git/v5/plumbing/transport"
1111
httpgit "github.com/go-git/go-git/v5/plumbing/transport/http"
12-
gossh "github.com/go-git/go-git/v5/plumbing/transport/ssh"
1312
giturls "github.com/rancher/fleet/pkg/git-urls"
14-
"golang.org/x/crypto/ssh"
1513
corev1 "k8s.io/api/core/v1"
1614

1715
fleetgithub "github.com/rancher/fleet/internal/github"
@@ -51,25 +49,15 @@ func GetAuthFromSecret(url string, creds *corev1.Secret, knownHosts string) (tra
5149
if err != nil {
5250
return nil, err
5351
}
54-
auth, err := gossh.NewPublicKeys(gitURL.User.Username(), creds.Data[corev1.SSHAuthPrivateKey], "")
52+
// Prefer known_hosts from the secret; fall back to the cluster-wide value.
53+
knownHostsData := creds.Data["known_hosts"]
54+
if len(knownHostsData) == 0 {
55+
knownHostsData = []byte(knownHosts)
56+
}
57+
auth, err := fleetssh.NewSSHPublicKeys(gitURL.User.Username(), creds.Data[corev1.SSHAuthPrivateKey], knownHostsData)
5558
if err != nil {
5659
return nil, err
5760
}
58-
switch {
59-
case creds.Data["known_hosts"] != nil:
60-
auth.HostKeyCallback, err = fleetssh.CreateKnownHostsCallBack(creds.Data["known_hosts"])
61-
if err != nil {
62-
return nil, err
63-
}
64-
case len(knownHosts) > 0:
65-
auth.HostKeyCallback, err = fleetssh.CreateKnownHostsCallBack([]byte(knownHosts))
66-
if err != nil {
67-
return nil, err
68-
}
69-
default:
70-
//nolint:gosec // G106: Use of ssh InsecureIgnoreHostKey should be audited
71-
auth.HostKeyCallback = ssh.InsecureIgnoreHostKey()
72-
}
7361
return auth, nil
7462
default:
7563
auth, err := fleetgithub.GetGithubAppAuthFromSecret(url, creds, GitHubAppGetter)

0 commit comments

Comments
 (0)