diff --git a/commands/curation/curationaudit_test.go b/commands/curation/curationaudit_test.go index 064c69f32..8809b99db 100644 --- a/commands/curation/curationaudit_test.go +++ b/commands/curation/curationaudit_test.go @@ -4976,6 +4976,67 @@ func TestSendBoundedRequestRejectsRedirectOutsideRepository(t *testing.T) { } } +// TestSendBoundedRequestFollowsCloudStorageRedirect covers XRAY-167698: a redirect to cloud +// storage (e.g. S3) must be followed, without leaking credentials or custom headers to it. +func TestSendBoundedRequestFollowsCloudStorageRedirect(t *testing.T) { + for _, tech := range []techutils.Technology{techutils.Pip, techutils.Poetry, techutils.Pipenv, techutils.Uv} { + t.Run(tech.String(), func(t *testing.T) { + var storageAuth, storageWaiver string + var storageRequests atomic.Int32 + storage := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + storageRequests.Add(1) + storageAuth = r.Header.Get("Authorization") + storageWaiver = r.Header.Get("X-Artifactory-Curation-Request-Waiver") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("binary-content")) + })) + defer storage.Close() + + var artifactoryWaiver string + var requests atomic.Int32 + artifactory := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + requests.Add(1) + artifactoryWaiver = r.Header.Get("X-Artifactory-Curation-Request-Waiver") + http.Redirect(w, r, storage.URL+"/presigned?sig=abc", http.StatusFound) + })) + defer artifactory.Close() + + serverDetails := &config.ServerDetails{ + Url: artifactory.URL + "/", + ArtifactoryUrl: artifactory.URL + "/", + AccessToken: "super-secret-token", + InsecureTls: true, + } + rtManager, err := rtUtils.CreateServiceManager(serverDetails, 0, 0, false) + require.NoError(t, err) + rtAuth := rtManager.GetConfig().GetServiceDetails() + analyzer := treeAnalyzer{ + rtManager: rtManager, + httpClientDetails: rtAuth.CreateHttpClientDetails(), + url: rtAuth.GetUrl(), + repo: "repo", + tech: tech, + } + requestDetails := analyzer.httpClientDetails.Clone() + requestDetails.Headers["X-Artifactory-Curation-Request-Waiver"] = "syn" + + resp, body, err := analyzer.sendBoundedRequest(http.MethodGet, + strings.TrimSuffix(analyzer.url, "/")+"/api/pypi/repo/packages/pkg.whl", requestDetails) + + require.NoError(t, err, "a redirect to Artifactory's own cloud storage backend must be followed, not rejected") + require.NotNil(t, resp) + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.Equal(t, "binary-content", string(body)) + assert.Equal(t, int32(1), requests.Load()) + assert.Equal(t, int32(1), storageRequests.Load()) + + assert.Equal(t, "syn", artifactoryWaiver, "the configured Artifactory server must still receive the waiver header") + assert.Empty(t, storageAuth, "credentials must never be forwarded to the cloud storage host") + assert.Empty(t, storageWaiver, "custom curation headers must never be forwarded to the cloud storage host") + }) + } +} + func TestCvsMetadataRejectsRedirectOutsideRepository(t *testing.T) { tests := []struct { name string diff --git a/go.mod b/go.mod index 7c04f76f8..12328af71 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( github.com/jfrog/jfrog-apps-config v1.0.1 github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20261101122816-c0098229a376 github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20261027135447-d76f4db4eedc - github.com/jfrog/jfrog-client-go v1.55.1-0.20261029150622-a2075f5e551a + github.com/jfrog/jfrog-client-go v1.55.1-0.20261102081622-45c51cfca4f7 github.com/magiconair/properties v1.18.11 github.com/owenrumney/go-sarif/v3 v3.2.3 github.com/package-url/packageurl-go v0.1.3 @@ -158,8 +158,6 @@ require ( gopkg.in/warnings.v0 v0.1.2 // indirect ) -// replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go master - // replace github.com/jfrog/jfrog-cli-core/v2 => github.com/jfrog/jfrog-cli-core/v2 master //replace github.com/jfrog/jfrog-cli-artifactory => github.com/jfrog/jfrog-cli-artifactory main diff --git a/go.sum b/go.sum index c25b3051f..787773260 100644 --- a/go.sum +++ b/go.sum @@ -173,8 +173,8 @@ github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20261101122816-c0098229a376 h1:K github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20261101122816-c0098229a376/go.mod h1:94/aLloMSK6avu4umcJS3DJ6IuGM5K10PwZ6kDqgzSo= github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20261027135447-d76f4db4eedc h1:xrIYOKdHgIcYZEP9+bw14Ts6ZQyeSs2UGD6cQxhXV40= github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20261027135447-d76f4db4eedc/go.mod h1:dU4UKp2AB2MPsVd+v+o9a2/O3vVO2/Yt2dZ5ajqQUnI= -github.com/jfrog/jfrog-client-go v1.55.1-0.20261029150622-a2075f5e551a h1:YcDqBspQfxmIgjKY1S6XWzHiAoYL2e6pP4Yy0s09Ky0= -github.com/jfrog/jfrog-client-go v1.55.1-0.20261029150622-a2075f5e551a/go.mod h1:+WGudVz1kZofy4hfEidGdTXqF/E51IC65z4yTtt0TwE= +github.com/jfrog/jfrog-client-go v1.55.1-0.20261102081622-45c51cfca4f7 h1:gd1jzPoUxEsfL0N2qebHyemEHKqvob6d9Q8yOl0D5zE= +github.com/jfrog/jfrog-client-go v1.55.1-0.20261102081622-45c51cfca4f7/go.mod h1:+WGudVz1kZofy4hfEidGdTXqF/E51IC65z4yTtt0TwE= github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= github.com/kevinburke/ssh_config v1.6.0 h1:J1FBfmuVosPHf5GRdltRLhPJtJpTlMdKTBjRgTaQBFY=