Skip to content

Commit c73552b

Browse files
committed
Restrict SSH auth to SSH/git URL schemes in setGitAuth
go-git's HTTP transport does not accept *ssh.PublicKeys, so applying SSH auth to an https:// URL causes the clone to fail with a confusing transport error. Guard the SSH auth path with an isSSH check. If ?sshkey= is explicitly provided for an HTTP(S) URL, return a clear error. If Auth.SSHPrivateKey is set alongside an HTTP(S) URL, silently ignore it and fall through to HTTP basic auth.
1 parent b08f651 commit c73552b

2 files changed

Lines changed: 85 additions & 0 deletions

File tree

internal/bundlereader/gitclone.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,12 +127,26 @@ func gitDownload(ctx context.Context, dst, rawURL string, auth Auth) error {
127127
// sshKeyPEM (from the URL ?sshkey= param) takes precedence over auth.SSHPrivateKey.
128128
// For HTTPS, credentials are taken from the URL userinfo first, then from auth.
129129
func setGitAuth(opts *gogit.CloneOptions, u *url.URL, sshKeyPEM []byte, auth Auth) error {
130+
isSSH := u.Scheme == "ssh" || u.Scheme == "git"
131+
130132
// Prefer the key from the URL query param over auth.SSHPrivateKey.
131133
keyPEM := sshKeyPEM
132134
if len(keyPEM) == 0 {
133135
keyPEM = auth.SSHPrivateKey
134136
}
135137

138+
if len(keyPEM) > 0 {
139+
if !isSSH {
140+
// ?sshkey= in an HTTP(S) URL is a misconfiguration.
141+
if len(sshKeyPEM) > 0 {
142+
return fmt.Errorf("?sshkey= is not supported for %s URLs", u.Scheme)
143+
}
144+
// Auth.SSHPrivateKey alongside an HTTP(S) URL: ignore and fall
145+
// through to HTTP basic auth below.
146+
keyPEM = nil
147+
}
148+
}
149+
136150
if len(keyPEM) > 0 {
137151
user := "git"
138152
if u.User != nil && u.User.Username() != "" {

internal/bundlereader/gitclone_test.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@ import (
1313
"net"
1414
"net/http"
1515
"net/http/httptest"
16+
"net/url"
1617
"testing"
1718
"time"
1819

20+
gogit "github.com/go-git/go-git/v5"
1921
"github.com/stretchr/testify/assert"
2022
"github.com/stretchr/testify/require"
2123
)
@@ -117,3 +119,72 @@ func TestGitDownloadCABundle(t *testing.T) {
117119
assert.Contains(t, err.Error(), "no valid PEM certificates")
118120
})
119121
}
122+
123+
// generateEd25519PEM returns a PEM-encoded Ed25519 private key for use in tests.
124+
func generateEd25519PEM(t *testing.T) []byte {
125+
t.Helper()
126+
127+
// Use ECDSA since crypto/ed25519 and go-git's SSH key parsing both work,
128+
// but PEM encoding of Ed25519 requires the x/crypto package. ECDSA P-256
129+
// is simpler to produce inline.
130+
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
131+
require.NoError(t, err)
132+
der, err := x509.MarshalECPrivateKey(key)
133+
require.NoError(t, err)
134+
return pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: der})
135+
}
136+
137+
// TestSetGitAuth verifies that setGitAuth respects URL scheme when choosing
138+
// which auth type to configure.
139+
func TestSetGitAuth(t *testing.T) {
140+
t.Parallel()
141+
142+
keyPEM := generateEd25519PEM(t)
143+
144+
t.Run("sshKey in URL for HTTPS URL returns error", func(t *testing.T) {
145+
t.Parallel()
146+
u, _ := url.Parse("https://example.com/repo.git")
147+
opts := &gogit.CloneOptions{}
148+
err := setGitAuth(opts, u, keyPEM, Auth{})
149+
require.Error(t, err)
150+
assert.Contains(t, err.Error(), "?sshkey= is not supported")
151+
assert.Nil(t, opts.Auth)
152+
})
153+
154+
t.Run("Auth.SSHPrivateKey alongside HTTPS URL is ignored", func(t *testing.T) {
155+
t.Parallel()
156+
u, _ := url.Parse("https://example.com/repo.git")
157+
opts := &gogit.CloneOptions{}
158+
err := setGitAuth(opts, u, nil, Auth{SSHPrivateKey: keyPEM})
159+
require.NoError(t, err)
160+
// No auth configured: callers that need basic auth supply URL credentials.
161+
assert.Nil(t, opts.Auth)
162+
})
163+
164+
t.Run("sshKey in URL for SSH URL configures SSH auth", func(t *testing.T) {
165+
t.Parallel()
166+
u, _ := url.Parse("ssh://git@example.com/repo.git")
167+
opts := &gogit.CloneOptions{}
168+
err := setGitAuth(opts, u, keyPEM, Auth{})
169+
require.NoError(t, err)
170+
assert.NotNil(t, opts.Auth)
171+
})
172+
173+
t.Run("Auth.SSHPrivateKey for SSH URL configures SSH auth", func(t *testing.T) {
174+
t.Parallel()
175+
u, _ := url.Parse("ssh://git@example.com/repo.git")
176+
opts := &gogit.CloneOptions{}
177+
err := setGitAuth(opts, u, nil, Auth{SSHPrivateKey: keyPEM})
178+
require.NoError(t, err)
179+
assert.NotNil(t, opts.Auth)
180+
})
181+
182+
t.Run("basic auth from URL userinfo for HTTPS URL", func(t *testing.T) {
183+
t.Parallel()
184+
u, _ := url.Parse("https://user:pass@example.com/repo.git")
185+
opts := &gogit.CloneOptions{}
186+
err := setGitAuth(opts, u, nil, Auth{})
187+
require.NoError(t, err)
188+
assert.NotNil(t, opts.Auth)
189+
})
190+
}

0 commit comments

Comments
 (0)