diff --git a/pkg/artifacts/artifacts_v4.go b/pkg/artifacts/artifacts_v4.go index 9cbe0b3c863..ea8075a0917 100644 --- a/pkg/artifacts/artifacts_v4.go +++ b/pkg/artifacts/artifacts_v4.go @@ -210,16 +210,28 @@ func (r artifactV4Routes) buildSignature(endp, expires, artifactName string, tas func (r artifactV4Routes) buildArtifactURL(endp, artifactName string, taskID int64) string { expires := time.Now().Add(60 * time.Minute).Format("2006-01-02 15:04:05.999999999 -0700 MST") uploadURL := "http://" + strings.TrimSuffix(r.AppURL, "/") + strings.TrimSuffix(r.prefix, "/") + - "/" + endp + "?sig=" + base64.URLEncoding.EncodeToString(r.buildSignature(endp, expires, artifactName, taskID)) + "&expires=" + url.QueryEscape(expires) + "&artifactName=" + url.QueryEscape(artifactName) + "&taskID=" + fmt.Sprint(taskID) + "/" + endp + "?sig=" + base64.RawURLEncoding.EncodeToString(r.buildSignature(endp, expires, artifactName, taskID)) + "&expires=" + url.QueryEscape(expires) + "&artifactName=" + url.QueryEscape(artifactName) + "&taskID=" + fmt.Sprint(taskID) return uploadURL } +func decodeBase64URLSignature(sig string) ([]byte, error) { + if decoded, err := base64.RawURLEncoding.DecodeString(sig); err == nil { + return decoded, nil + } + return base64.URLEncoding.DecodeString(sig) +} + func (r artifactV4Routes) verifySignature(ctx *ArtifactContext, endp string) (int64, string, bool) { rawTaskID := ctx.Req.URL.Query().Get("taskID") sig := ctx.Req.URL.Query().Get("sig") expires := ctx.Req.URL.Query().Get("expires") artifactName := ctx.Req.URL.Query().Get("artifactName") - dsig, _ := base64.URLEncoding.DecodeString(sig) + dsig, err := decodeBase64URLSignature(sig) + if err != nil { + log.Error("Error unauthorized") + ctx.Error(http.StatusUnauthorized, "Error unauthorized") + return -1, "", false + } taskID, _ := strconv.ParseInt(rawTaskID, 10, 64) expecedsig := r.buildSignature(endp, expires, artifactName, taskID) diff --git a/pkg/artifacts/server_test.go b/pkg/artifacts/server_test.go index 0591fbb9987..943da53c879 100644 --- a/pkg/artifacts/server_test.go +++ b/pkg/artifacts/server_test.go @@ -6,6 +6,7 @@ import ( "fmt" "net/http" "net/http/httptest" + "net/url" "os" "path" "path/filepath" @@ -393,3 +394,44 @@ func TestArtifactUploadBlobUnsafePath(t *testing.T) { assert.Equal("success", response.Message) assert.Equal("content", string(memfs["artifact/server/path/1/some/file"].Data)) } + +func TestArtifactV4BuildArtifactURLUsesRawBase64Signature(t *testing.T) { + assert := assert.New(t) + + route := artifactV4Routes{ + prefix: ArtifactV4RouteBase, + AppURL: "localhost", + } + + uploadURL, err := url.Parse(route.buildArtifactURL("UploadArtifact", "test-artifact", 1)) + assert.NoError(err) + assert.NotContains(uploadURL.Query().Get("sig"), "=") +} + +func TestArtifactV4UploadAcceptsUnpaddedBase64Signature(t *testing.T) { + assert := assert.New(t) + + var memfs = fstest.MapFS(map[string]*fstest.MapFile{}) + route := artifactV4Routes{ + fs: writeMapFS{memfs}, + rfs: memfs, + baseDir: "artifact/server/path", + prefix: ArtifactV4RouteBase, + AppURL: "localhost", + } + + uploadURL, err := url.Parse(route.buildArtifactURL("UploadArtifact", "test-artifact", 1)) + assert.NoError(err) + query := uploadURL.Query() + query.Set("sig", strings.TrimRight(query.Get("sig"), "=")) + query.Set("comp", "block") + uploadURL.RawQuery = query.Encode() + + req, _ := http.NewRequest("PUT", uploadURL.String(), strings.NewReader("content")) + rr := httptest.NewRecorder() + + route.uploadArtifact(&ArtifactContext{Req: req, Resp: rr}) + + assert.Equal(http.StatusCreated, rr.Code) + assert.Equal("content", string(memfs["artifact/server/path/1/test-artifact/test-artifact.zip"].Data)) +}