libgit2: fix gitlab redirection for HTTP

Gitlab only supports HTTP redirection for GET operations,
and fails POST operations targeting a repository without
the .git suffix.

Fixes: https://github.com/fluxcd/image-automation-controller/issues/379
Signed-off-by: Paulo Gomes <paulo.gomes@weave.works>
This commit is contained in:
Paulo Gomes 2022-06-07 15:11:16 +01:00
parent 2a52056594
commit b764bdbeaa
No known key found for this signature in database
GPG Key ID: 9995233870E99BEE
2 changed files with 106 additions and 15 deletions

View File

@ -52,6 +52,7 @@ import (
"io" "io"
"net/http" "net/http"
"net/url" "net/url"
"strings"
"sync" "sync"
pool "github.com/fluxcd/source-controller/internal/transport" pool "github.com/fluxcd/source-controller/internal/transport"
@ -59,6 +60,13 @@ import (
git2go "github.com/libgit2/git2go/v33" git2go "github.com/libgit2/git2go/v33"
) )
var actionSuffixes = []string{
"/info/refs?service=git-upload-pack",
"/git-upload-pack",
"/info/refs?service=git-receive-pack",
"/git-receive-pack",
}
// registerManagedHTTP registers a Go-native implementation of an // registerManagedHTTP registers a Go-native implementation of an
// HTTP(S) transport that doesn't rely on any lower-level libraries // HTTP(S) transport that doesn't rely on any lower-level libraries
// such as OpenSSL. // such as OpenSSL.
@ -152,12 +160,40 @@ func (t *httpSmartSubtransport) Action(transportOptionsURL string, action git2go
return http.ErrUseLastResponse return http.ErrUseLastResponse
} }
// Some Git servers (i.e. Gitlab) only support redirection on the GET operations.
// Therefore, on the initial GET operation we update the target URL to include the
// new target, so the subsequent actions include the correct target URL.
// Example of this is trying to access a Git repository without the .git suffix.
if req.Response != nil && req.Response.StatusCode == http.StatusMovedPermanently {
if newURL, err := req.Response.Location(); err == nil && newURL != nil {
if strings.EqualFold(newURL.Host, req.URL.Host) && strings.EqualFold(newURL.Port(), req.URL.Port()) {
opts, _ := getTransportOptions(transportOptionsURL)
if opts == nil {
opts = &TransportOptions{}
}
opts.TargetURL = trimActionSuffix(newURL.String())
AddTransportOptions(transportOptionsURL, *opts)
}
}
}
return nil return nil
} }
return stream, nil return stream, nil
} }
func trimActionSuffix(url string) string {
newUrl := url
for _, s := range actionSuffixes {
newUrl = strings.TrimSuffix(newUrl, s)
}
return newUrl
}
func createClientRequest(targetURL string, action git2go.SmartServiceAction, func createClientRequest(targetURL string, action git2go.SmartServiceAction,
t *http.Transport, authOpts *git.AuthOptions) (*http.Client, *http.Request, error) { t *http.Transport, authOpts *git.AuthOptions) (*http.Client, *http.Request, error) {
var req *http.Request var req *http.Request

View File

@ -200,26 +200,81 @@ func TestHTTPManagedTransport_E2E(t *testing.T) {
repo.Free() repo.Free()
} }
func TestHTTPManagedTransport_HandleRedirect(t *testing.T) { func TestTrimActionSuffix(t *testing.T) {
g := NewWithT(t) tests := []struct {
name string
inURL string
wantURL string
}{
{
name: "ignore other suffixes",
inURL: "https://gitlab/repo/podinfo.git/somethingelse",
wantURL: "https://gitlab/repo/podinfo.git/somethingelse",
},
{
name: "trim /info/refs?service=git-upload-pack",
inURL: "https://gitlab/repo/podinfo.git/info/refs?service=git-upload-pack",
wantURL: "https://gitlab/repo/podinfo.git",
},
{
name: "trim /git-upload-pack",
inURL: "https://gitlab/repo/podinfo.git/git-upload-pack",
wantURL: "https://gitlab/repo/podinfo.git",
},
{
name: "trim /info/refs?service=git-receive-pack",
inURL: "https://gitlab/repo/podinfo.git/info/refs?service=git-receive-pack",
wantURL: "https://gitlab/repo/podinfo.git",
},
{
name: "trim /git-receive-pack",
inURL: "https://gitlab/repo/podinfo.git/git-receive-pack",
wantURL: "https://gitlab/repo/podinfo.git",
},
}
tmpDir := t.TempDir() for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t)
gotURL := trimActionSuffix(tt.inURL)
g.Expect(gotURL).To(Equal(tt.wantURL))
})
}
}
func TestHTTPManagedTransport_HandleRedirect(t *testing.T) {
tests := []struct {
name string
repoURL string
}{
{name: "http to https", repoURL: "http://github.com/stefanprodan/podinfo"},
{name: "handle gitlab redirect", repoURL: "https://gitlab.com/stefanprodan/podinfo"},
}
// Force managed transport to be enabled // Force managed transport to be enabled
InitManagedTransport(logr.Discard()) InitManagedTransport(logr.Discard())
id := "http://obj-id" for _, tt := range tests {
AddTransportOptions(id, TransportOptions{ t.Run(tt.name, func(t *testing.T) {
TargetURL: "http://github.com/stefanprodan/podinfo", g := NewWithT(t)
})
// GitHub will cause a 301 and redirect to https tmpDir := t.TempDir()
repo, err := git2go.Clone(id, tmpDir, &git2go.CloneOptions{
CheckoutOptions: git2go.CheckoutOptions{
Strategy: git2go.CheckoutForce,
},
})
g.Expect(err).ToNot(HaveOccurred()) id := "http://obj-id"
repo.Free() AddTransportOptions(id, TransportOptions{
TargetURL: tt.repoURL,
})
// GitHub will cause a 301 and redirect to https
repo, err := git2go.Clone(id, tmpDir, &git2go.CloneOptions{
CheckoutOptions: git2go.CheckoutOptions{
Strategy: git2go.CheckoutForce,
},
})
g.Expect(err).ToNot(HaveOccurred())
repo.Free()
})
}
} }