From 172f0d722dc50955dfd5a5452e0e4fb625d7ab68 Mon Sep 17 00:00:00 2001 From: sg Date: Fri, 30 Aug 2024 22:00:35 +0100 Subject: [PATCH 1/3] fix #327 by renaming the dependency track consumer debug flag --- components/consumers/dependency-track/main.go | 12 ++++++------ components/consumers/dependency-track/task.yaml | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/components/consumers/dependency-track/main.go b/components/consumers/dependency-track/main.go index 5446edc2c..952f4e528 100644 --- a/components/consumers/dependency-track/main.go +++ b/components/consumers/dependency-track/main.go @@ -28,7 +28,7 @@ var ( client *dtrack.Client ownerAnnotation string // used for debugging, turns off certificate and enables debug - debug bool + debugDT bool ) func main() { @@ -37,7 +37,7 @@ func main() { flag.StringVar(&projectName, "projectName", "", "dependency track project name") flag.StringVar(&projectUUID, "projectUUID", "", "dependency track project name") flag.StringVar(&projectVersion, "projectVersion", "", "dependency track project version") - flag.BoolVar(&debug, "debug", false, "setup client with no tls and enable debug") + flag.BoolVar(&debugDT, "debugDependencyTrackConnection", false, "setup client with no tls and enable debug") flag.StringVar( &ownerAnnotation, "ownerAnnotation", @@ -66,22 +66,22 @@ func main() { log.Fatal("project version is mandatory for dependency track") } - client, err := dtrack.NewClient( + c, err := dtrack.NewClient( authURL, dtrack.WithHttpClient( &http.Client{Transport: &http.Transport{ TLSClientConfig: &tls.Config{ - InsecureSkipVerify: debug, + InsecureSkipVerify: debugDT, }, }, }), - dtrack.WithDebug(debug), + dtrack.WithDebug(debugDT), dtrack.WithAPIKey(apiKey), ) if err != nil { log.Panicf("could not instantiate client err: %#v\n", err) } - + client = c abt, err := client.Metrics.LatestPortfolioMetrics(context.Background()) if err != nil { log.Fatalf("cannot connect to Dependency Track at %s, err:'%v'", authURL, err) diff --git a/components/consumers/dependency-track/task.yaml b/components/consumers/dependency-track/task.yaml index 7d2b7067e..98dbf534c 100644 --- a/components/consumers/dependency-track/task.yaml +++ b/components/consumers/dependency-track/task.yaml @@ -41,5 +41,5 @@ spec: "-projectVersion", "$(params.consumer-dependency-track-project-version)", "-projectUUID", "$(params.consumer-dependency-track-project-uuid)", "-ownerAnnotation","$(params.consumer-dependency-track-owner-annotation)", - "-debug", "$(params.consumer-dependency-track-debug)" + "-debugDependencyTrackConnection", "$(params.consumer-dependency-track-debug)" ] From 18a17e7164954255b4f025cf453d8b789640df7b Mon Sep 17 00:00:00 2001 From: sg Date: Fri, 30 Aug 2024 22:02:44 +0100 Subject: [PATCH 2/3] fix #329 by removing log.Fatal outside main method and adding structured logging and error returning to the Dependencty Track Consumer --- components/consumers/dependency-track/main.go | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/components/consumers/dependency-track/main.go b/components/consumers/dependency-track/main.go index 952f4e528..e0abcd940 100644 --- a/components/consumers/dependency-track/main.go +++ b/components/consumers/dependency-track/main.go @@ -12,6 +12,7 @@ import ( "strings" dtrack "github.com/DependencyTrack/client-go" + "github.com/go-errors/errors" "github.com/google/uuid" v1 "github.com/ocurity/dracon/api/proto/v1" @@ -79,14 +80,14 @@ func main() { dtrack.WithAPIKey(apiKey), ) if err != nil { - log.Panicf("could not instantiate client err: %#v\n", err) + log.Fatalf("could not instantiate client err: %#v\n", err) } client = c abt, err := client.Metrics.LatestPortfolioMetrics(context.Background()) if err != nil { log.Fatalf("cannot connect to Dependency Track at %s, err:'%v'", authURL, err) } - slog.Info("connection to DT successful listed projects in instance", "projects", abt.Projects) + slog.Info("Connection to DT successful, projects in instance:", "instance", abt.Projects) if consumers.Raw { responses, err := consumers.LoadToolResponse() if err != nil { @@ -115,8 +116,7 @@ func uploadBOMSFromEnriched(responses []*v1.EnrichedLaunchToolResponse) ([]strin if issue.GetRawIssue().GetCycloneDXSBOM() != "" && bomIssue == nil { bomIssue = issue } else if bomIssue != nil && bomIssue.GetRawIssue().GetCycloneDXSBOM() != "" { - log.Printf("Tool response for tool %s is malformed, we expected a single issue with an SBOM as part of the tool, got something else instead", - res.GetOriginalResults().GetToolName()) + slog.Error("tool:", res.GetOriginalResults().GetToolName(), "response is malformed, we expected a single issue with an SBOM as part of the tool, got something else instead") continue } } @@ -126,12 +126,11 @@ func uploadBOMSFromEnriched(responses []*v1.EnrichedLaunchToolResponse) ([]strin } token, err := uploadBOM(bomIssue.GetRawIssue().GetCycloneDXSBOM(), cdxbom.Metadata.Component.Version) if err != nil { - log.Fatal("could not upload bom to dependency track, err:", err) + return tokens, errors.Errorf("could not upload bom to dependency track, err:%w", err) } - log.Println("upload token is", token) + slog.Debug("upload", "token", token) tokens = append(tokens, token) if ownerAnnotation != "" { - log.Println("tagging owners") owners := []string{} for key, value := range bomIssue.Annotations { if strings.Contains(key, ownerAnnotation) { @@ -139,7 +138,7 @@ func uploadBOMSFromEnriched(responses []*v1.EnrichedLaunchToolResponse) ([]strin } } if err := addOwnersTags(owners); err != nil { - log.Println("could not tag owners, err:", err) + slog.Error("could not tag owners", "err", err) } } } @@ -154,8 +153,7 @@ func uploadBOMsFromRaw(responses []*v1.LaunchToolResponse) ([]string, error) { if *issue.CycloneDXSBOM != "" && bomIssue == nil { bomIssue = issue } else if bomIssue != nil && *bomIssue.CycloneDXSBOM != "" { - log.Printf("Tool response for tool %s is malformed, we expected a single issue with an SBOM as part of the tool, got multiple issues with sboms instead", - res.GetToolName()) + slog.Error("tool:", res.GetToolName(), "response is malformed, we expected a single issue with an SBOM as part of the tool, got something else instead") continue } } @@ -165,9 +163,9 @@ func uploadBOMsFromRaw(responses []*v1.LaunchToolResponse) ([]string, error) { } token, err := uploadBOM(*bomIssue.CycloneDXSBOM, cdxbom.Metadata.Component.Version) if err != nil { - log.Fatal("could not upload bod to dependency track, err:", err) + return tokens, errors.Errorf("could not upload bod to dependency track, err:%w", err) } - log.Println("upload token is", token) + slog.Info("upload", "token", token) tokens = append(tokens, token) } return tokens, nil @@ -179,7 +177,7 @@ func addOwnersTags(owners []string) error { uuid := uuid.MustParse(projectUUID) project, err := client.Project.Get(context.Background(), uuid) if err != nil { - log.Println("could not add project tags error getting project by uuid, err:", err) + slog.Error("could not add project tags error getting project by uuid", "err", err) return err } for _, owner := range owners { From 61729eb6edd9bb171cf0cba102f7e8b440c77184 Mon Sep 17 00:00:00 2001 From: sg Date: Fri, 30 Aug 2024 22:04:28 +0100 Subject: [PATCH 3/3] fix #328 by changing the BOM upload method from UploadBom to PostBOm, as PostBom does not have a size limitation --- components/consumers/dependency-track/main.go | 12 +-- .../consumers/dependency-track/main_test.go | 77 ++++++++++++------- 2 files changed, 57 insertions(+), 32 deletions(-) diff --git a/components/consumers/dependency-track/main.go b/components/consumers/dependency-track/main.go index e0abcd940..bd02d89f4 100644 --- a/components/consumers/dependency-track/main.go +++ b/components/consumers/dependency-track/main.go @@ -3,7 +3,6 @@ package main import ( "context" "crypto/tls" - "encoding/base64" "flag" "fmt" "log" @@ -201,13 +200,16 @@ func uploadBOM(bom string, projectVersion string) (string, error) { if projectVersion == "" { projectVersion = "Unknown" } - uuid := uuid.MustParse(projectUUID) - token, err := client.BOM.Upload(context.TODO(), dtrack.BOMUploadRequest{ + projUUID, err := uuid.Parse(projectUUID) + if err != nil { + return "", err + } + token, err := client.BOM.PostBom(context.TODO(), dtrack.BOMUploadRequest{ ProjectName: projectName, ProjectVersion: projectVersion, - ProjectUUID: &uuid, + ProjectUUID: &projUUID, AutoCreate: true, - BOM: base64.StdEncoding.EncodeToString([]byte(bom)), + BOM: bom, }) return string(token), err } diff --git a/components/consumers/dependency-track/main_test.go b/components/consumers/dependency-track/main_test.go index 6b46c205e..41ef3402d 100644 --- a/components/consumers/dependency-track/main_test.go +++ b/components/consumers/dependency-track/main_test.go @@ -8,6 +8,7 @@ import ( "os" "testing" + cdx "github.com/CycloneDX/cyclonedx-go" dtrack "github.com/DependencyTrack/client-go" "github.com/google/uuid" "github.com/stretchr/testify/assert" @@ -18,24 +19,34 @@ import ( ) func TestUploadBomsFromRaw(t *testing.T) { + rawSaaSBOM, err := os.ReadFile("./testdata/saasBOM.json") + require.NoError(t, err) + // we marshal and unmarshal to remove pretty formatting + bom := cdx.BOM{} + err = json.Unmarshal(rawSaaSBOM, &bom) + require.NoError(t, err) + marshalledBom, err := json.Marshal(bom) + require.NoError(t, err) + projUUID := uuid.MustParse("7c78f6c9-b4b0-493c-a912-0bb0a4f221f1") expectedRequest := dtrack.BOMUploadRequest{ ProjectName: "test", ProjectUUID: &projUUID, ProjectVersion: "2022-1", AutoCreate: true, - BOM: "eyJib21Gb3JtYXQiOiJDeWNsb25lRFgiLCJzcGVjVmVyc2lvbiI6IjEuNCIsInNlcmlhbE51bWJlciI6InVybjp1dWlkOjNlNjcxNjg3LTM5NWItNDFmNS1hMzBmLWE1ODkyMWE2OWI3OSIsInZlcnNpb24iOjEsIm1ldGFkYXRhIjp7InRpbWVzdGFtcCI6IjIwMjEtMDEtMTBUMTI6MDA6MDBaIiwiY29tcG9uZW50Ijp7ImJvbS1yZWYiOiJhY21lLWFwcGxpY2F0aW9uIiwidHlwZSI6ImFwcGxpY2F0aW9uIiwibmFtZSI6IkFjbWUgQ2xvdWQgRXhhbXBsZSIsInZlcnNpb24iOiIyMDIyLTEifX0sInNlcnZpY2VzIjpbeyJib20tcmVmIjoiYXBpLWdhdGV3YXkiLCJwcm92aWRlciI6eyJuYW1lIjoiQWNtZSBJbmMiLCJ1cmwiOlsiaHR0cHM6Ly9leGFtcGxlLmNvbSJdfSwiZ3JvdXAiOiJjb20uZXhhbXBsZSIsIm5hbWUiOiJBUEkgR2F0ZXdheSIsInZlcnNpb24iOiIyMDIyLTEiLCJkZXNjcmlwdGlvbiI6IkV4YW1wbGUgQVBJIEdhdGV3YXkiLCJlbmRwb2ludHMiOlsiaHR0cHM6Ly9leGFtcGxlLmNvbS8iLCJodHRwczovL2V4YW1wbGUuY29tL2FwcCJdLCJhdXRoZW50aWNhdGVkIjpmYWxzZSwieC10cnVzdC1ib3VuZGFyeSI6dHJ1ZSwiZGF0YSI6W3siZmxvdyI6ImJpLWRpcmVjdGlvbmFsIiwiY2xhc3NpZmljYXRpb24iOiJQSUkifSx7ImZsb3ciOiJiaS1kaXJlY3Rpb25hbCIsImNsYXNzaWZpY2F0aW9uIjoiUElGSSJ9LHsiZmxvdyI6ImJpLWRpcmVjdGlvbmFsIiwiY2xhc3NpZmljYXRpb24iOiJQdWJsaWMifV0sImV4dGVybmFsUmVmZXJlbmNlcyI6W3sidXJsIjoiaHR0cDovL2V4YW1wbGUuY29tL2FwcC9zd2FnZ2VyIiwidHlwZSI6ImRvY3VtZW50YXRpb24ifV0sInNlcnZpY2VzIjpbeyJib20tcmVmIjoibXMtMS5leGFtcGxlLmNvbSIsInByb3ZpZGVyIjp7Im5hbWUiOiJBY21lIEluYyIsInVybCI6WyJodHRwczovL2V4YW1wbGUuY29tIl19LCJncm91cCI6ImNvbS5leGFtcGxlIiwibmFtZSI6Ik1pY3Jvc2VydmljZSAxIiwidmVyc2lvbiI6IjIwMjItMSIsImRlc2NyaXB0aW9uIjoiRXhhbXBsZSBNaWNyb3NlcnZpY2UiLCJlbmRwb2ludHMiOlsiaHR0cHM6Ly9tcy0xLmV4YW1wbGUuY29tIl0sImF1dGhlbnRpY2F0ZWQiOnRydWUsIngtdHJ1c3QtYm91bmRhcnkiOmZhbHNlLCJkYXRhIjpbeyJmbG93IjoiYmktZGlyZWN0aW9uYWwiLCJjbGFzc2lmaWNhdGlvbiI6IlBJSSJ9XSwiZXh0ZXJuYWxSZWZlcmVuY2VzIjpbeyJ1cmwiOiJodHRwczovL21zLTEuZXhhbXBsZS5jb20vc3dhZ2dlciIsInR5cGUiOiJkb2N1bWVudGF0aW9uIn1dfSx7ImJvbS1yZWYiOiJtcy0yLmV4YW1wbGUuY29tIiwicHJvdmlkZXIiOnsibmFtZSI6IkFjbWUgSW5jIiwidXJsIjpbImh0dHBzOi8vZXhhbXBsZS5jb20iXX0sImdyb3VwIjoiY29tLmV4YW1wbGUiLCJuYW1lIjoiTWljcm9zZXJ2aWNlIDIiLCJ2ZXJzaW9uIjoiMjAyMi0xIiwiZGVzY3JpcHRpb24iOiJFeGFtcGxlIE1pY3Jvc2VydmljZSIsImVuZHBvaW50cyI6WyJodHRwczovL21zLTIuZXhhbXBsZS5jb20iXSwiYXV0aGVudGljYXRlZCI6dHJ1ZSwieC10cnVzdC1ib3VuZGFyeSI6ZmFsc2UsImRhdGEiOlt7ImZsb3ciOiJiaS1kaXJlY3Rpb25hbCIsImNsYXNzaWZpY2F0aW9uIjoiUElGSSJ9XSwiZXh0ZXJuYWxSZWZlcmVuY2VzIjpbeyJ1cmwiOiJodHRwczovL21zLTIuZXhhbXBsZS5jb20vc3dhZ2dlciIsInR5cGUiOiJkb2N1bWVudGF0aW9uIn1dfSx7ImJvbS1yZWYiOiJtcy0zLmV4YW1wbGUuY29tIiwicHJvdmlkZXIiOnsibmFtZSI6IkFjbWUgSW5jIiwidXJsIjpbImh0dHBzOi8vZXhhbXBsZS5jb20iXX0sImdyb3VwIjoiY29tLmV4YW1wbGUiLCJuYW1lIjoiTWljcm9zZXJ2aWNlIDMiLCJ2ZXJzaW9uIjoiMjAyMi0xIiwiZGVzY3JpcHRpb24iOiJFeGFtcGxlIE1pY3Jvc2VydmljZSIsImVuZHBvaW50cyI6WyJodHRwczovL21zLTMuZXhhbXBsZS5jb20iXSwiYXV0aGVudGljYXRlZCI6dHJ1ZSwieC10cnVzdC1ib3VuZGFyeSI6ZmFsc2UsImRhdGEiOlt7ImZsb3ciOiJiaS1kaXJlY3Rpb25hbCIsImNsYXNzaWZpY2F0aW9uIjoiUHVibGljIn1dLCJleHRlcm5hbFJlZmVyZW5jZXMiOlt7InVybCI6Imh0dHBzOi8vbXMtMy5leGFtcGxlLmNvbS9zd2FnZ2VyIiwidHlwZSI6ImRvY3VtZW50YXRpb24ifV19LHsiYm9tLXJlZiI6Im1zLTEtcGdzcWwuZXhhbXBsZS5jb20iLCJncm91cCI6Im9yZy5wb3N0Z3Jlc3FsIiwibmFtZSI6IlBvc3RncmVzIiwidmVyc2lvbiI6IjE0LjEiLCJkZXNjcmlwdGlvbiI6IlBvc3RncmVzIGRhdGFiYXNlIGZvciBNaWNyb3NlcnZpY2UgIzEiLCJlbmRwb2ludHMiOlsiaHR0cHM6Ly9tcy0xLXBnc3FsLmV4YW1wbGUuY29tOjU0MzIiXSwiYXV0aGVudGljYXRlZCI6dHJ1ZSwieC10cnVzdC1ib3VuZGFyeSI6ZmFsc2UsImRhdGEiOlt7ImZsb3ciOiJiaS1kaXJlY3Rpb25hbCIsImNsYXNzaWZpY2F0aW9uIjoiUElJIn1dfSx7ImJvbS1yZWYiOiJzMy1leGFtcGxlLmFtYXpvbi5jb20iLCJncm91cCI6ImNvbS5hbWF6b24iLCJuYW1lIjoiUzMiLCJkZXNjcmlwdGlvbiI6IlMzIGJ1Y2tldCIsImVuZHBvaW50cyI6WyJodHRwczovL3MzLWV4YW1wbGUuYW1hem9uLmNvbSJdLCJhdXRoZW50aWNhdGVkIjp0cnVlLCJ4LXRydXN0LWJvdW5kYXJ5Ijp0cnVlLCJkYXRhIjpbeyJmbG93IjoiYmktZGlyZWN0aW9uYWwiLCJjbGFzc2lmaWNhdGlvbiI6IlB1YmxpYyJ9XX1dfV0sImRlcGVuZGVuY2llcyI6W3sicmVmIjoiYWNtZS1hcHBsaWNhdGlvbiIsImRlcGVuZHNPbiI6WyJhcGktZ2F0ZXdheSJdfSx7InJlZiI6ImFwaS1nYXRld2F5IiwiZGVwZW5kc09uIjpbIm1zLTEuZXhhbXBsZS5jb20iLCJtcy0yLmV4YW1wbGUuY29tIiwibXMtMy5leGFtcGxlLmNvbSJdfSx7InJlZiI6Im1zLTEuZXhhbXBsZS5jb20iLCJkZXBlbmRzT24iOlsibXMtMS1wZ3NxbC5leGFtcGxlLmNvbSJdfSx7InJlZiI6Im1zLTIuZXhhbXBsZS5jb20iLCJkZXBlbmRzT24iOltdfSx7InJlZiI6Im1zLTMuZXhhbXBsZS5jb20iLCJkZXBlbmRzT24iOlsiczMtZXhhbXBsZS5hbWF6b24uY29tIl19XX0=", + BOM: string(marshalledBom), } //nolint:gosec expectedToken := "7c78f6c9-token" ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - body, err := io.ReadAll(r.Body) + err := r.ParseMultipartForm(500 << 20) require.NoError(t, err) - - var actualRequest dtrack.BOMUploadRequest - require.NoError(t, json.Unmarshal(body, &actualRequest)) - assert.Equal(t, expectedRequest, actualRequest) + require.Equal(t, "POST", r.Method) + require.Equal(t, []string{expectedRequest.ProjectName}, r.MultipartForm.Value["projectName"]) + require.Equal(t, []string{expectedRequest.ProjectUUID.String()}, r.MultipartForm.Value["project"]) + require.Equal(t, []string{expectedRequest.ProjectVersion}, r.MultipartForm.Value["projectVersion"]) + require.Equal(t, []string{expectedRequest.BOM}, r.MultipartForm.Value["bom"]) _, err = w.Write([]byte("{\"Token\":\"" + expectedToken + "\"}")) require.NoError(t, err) @@ -47,9 +58,6 @@ func TestUploadBomsFromRaw(t *testing.T) { c, err := dtrack.NewClient(ts.URL, dtrack.WithAPIKey(apiKey)) require.NoError(t, err) - rawSaaSBOM, err := os.ReadFile("./testdata/saasBOM.json") - require.NoError(t, err) - client = c issues, err := cyclonedx.ToDracon(rawSaaSBOM, "json") @@ -65,21 +73,32 @@ func TestUploadBomsFromRaw(t *testing.T) { func TestUploadBomsFromEnriched(t *testing.T) { projUUID := uuid.MustParse("7c78f6c9-b4b0-493c-a912-0bb0a4f221f1") + rawSaaSBOM, err := os.ReadFile("./testdata/saasBOM.json") + require.NoError(t, err) + + // we marshal and unmarshal to remove pretty formatting + bom := cdx.BOM{} + err = json.Unmarshal(rawSaaSBOM, &bom) + require.NoError(t, err) + marshalledBom, err := json.Marshal(bom) + require.NoError(t, err) + expectedRequest := dtrack.BOMUploadRequest{ ProjectName: "test", ProjectUUID: &projUUID, ProjectVersion: "2022-1", AutoCreate: true, - BOM: "eyJib21Gb3JtYXQiOiJDeWNsb25lRFgiLCJzcGVjVmVyc2lvbiI6IjEuNCIsInNlcmlhbE51bWJlciI6InVybjp1dWlkOjNlNjcxNjg3LTM5NWItNDFmNS1hMzBmLWE1ODkyMWE2OWI3OSIsInZlcnNpb24iOjEsIm1ldGFkYXRhIjp7InRpbWVzdGFtcCI6IjIwMjEtMDEtMTBUMTI6MDA6MDBaIiwiY29tcG9uZW50Ijp7ImJvbS1yZWYiOiJhY21lLWFwcGxpY2F0aW9uIiwidHlwZSI6ImFwcGxpY2F0aW9uIiwibmFtZSI6IkFjbWUgQ2xvdWQgRXhhbXBsZSIsInZlcnNpb24iOiIyMDIyLTEifX0sInNlcnZpY2VzIjpbeyJib20tcmVmIjoiYXBpLWdhdGV3YXkiLCJwcm92aWRlciI6eyJuYW1lIjoiQWNtZSBJbmMiLCJ1cmwiOlsiaHR0cHM6Ly9leGFtcGxlLmNvbSJdfSwiZ3JvdXAiOiJjb20uZXhhbXBsZSIsIm5hbWUiOiJBUEkgR2F0ZXdheSIsInZlcnNpb24iOiIyMDIyLTEiLCJkZXNjcmlwdGlvbiI6IkV4YW1wbGUgQVBJIEdhdGV3YXkiLCJlbmRwb2ludHMiOlsiaHR0cHM6Ly9leGFtcGxlLmNvbS8iLCJodHRwczovL2V4YW1wbGUuY29tL2FwcCJdLCJhdXRoZW50aWNhdGVkIjpmYWxzZSwieC10cnVzdC1ib3VuZGFyeSI6dHJ1ZSwiZGF0YSI6W3siZmxvdyI6ImJpLWRpcmVjdGlvbmFsIiwiY2xhc3NpZmljYXRpb24iOiJQSUkifSx7ImZsb3ciOiJiaS1kaXJlY3Rpb25hbCIsImNsYXNzaWZpY2F0aW9uIjoiUElGSSJ9LHsiZmxvdyI6ImJpLWRpcmVjdGlvbmFsIiwiY2xhc3NpZmljYXRpb24iOiJQdWJsaWMifV0sImV4dGVybmFsUmVmZXJlbmNlcyI6W3sidXJsIjoiaHR0cDovL2V4YW1wbGUuY29tL2FwcC9zd2FnZ2VyIiwidHlwZSI6ImRvY3VtZW50YXRpb24ifV0sInNlcnZpY2VzIjpbeyJib20tcmVmIjoibXMtMS5leGFtcGxlLmNvbSIsInByb3ZpZGVyIjp7Im5hbWUiOiJBY21lIEluYyIsInVybCI6WyJodHRwczovL2V4YW1wbGUuY29tIl19LCJncm91cCI6ImNvbS5leGFtcGxlIiwibmFtZSI6Ik1pY3Jvc2VydmljZSAxIiwidmVyc2lvbiI6IjIwMjItMSIsImRlc2NyaXB0aW9uIjoiRXhhbXBsZSBNaWNyb3NlcnZpY2UiLCJlbmRwb2ludHMiOlsiaHR0cHM6Ly9tcy0xLmV4YW1wbGUuY29tIl0sImF1dGhlbnRpY2F0ZWQiOnRydWUsIngtdHJ1c3QtYm91bmRhcnkiOmZhbHNlLCJkYXRhIjpbeyJmbG93IjoiYmktZGlyZWN0aW9uYWwiLCJjbGFzc2lmaWNhdGlvbiI6IlBJSSJ9XSwiZXh0ZXJuYWxSZWZlcmVuY2VzIjpbeyJ1cmwiOiJodHRwczovL21zLTEuZXhhbXBsZS5jb20vc3dhZ2dlciIsInR5cGUiOiJkb2N1bWVudGF0aW9uIn1dfSx7ImJvbS1yZWYiOiJtcy0yLmV4YW1wbGUuY29tIiwicHJvdmlkZXIiOnsibmFtZSI6IkFjbWUgSW5jIiwidXJsIjpbImh0dHBzOi8vZXhhbXBsZS5jb20iXX0sImdyb3VwIjoiY29tLmV4YW1wbGUiLCJuYW1lIjoiTWljcm9zZXJ2aWNlIDIiLCJ2ZXJzaW9uIjoiMjAyMi0xIiwiZGVzY3JpcHRpb24iOiJFeGFtcGxlIE1pY3Jvc2VydmljZSIsImVuZHBvaW50cyI6WyJodHRwczovL21zLTIuZXhhbXBsZS5jb20iXSwiYXV0aGVudGljYXRlZCI6dHJ1ZSwieC10cnVzdC1ib3VuZGFyeSI6ZmFsc2UsImRhdGEiOlt7ImZsb3ciOiJiaS1kaXJlY3Rpb25hbCIsImNsYXNzaWZpY2F0aW9uIjoiUElGSSJ9XSwiZXh0ZXJuYWxSZWZlcmVuY2VzIjpbeyJ1cmwiOiJodHRwczovL21zLTIuZXhhbXBsZS5jb20vc3dhZ2dlciIsInR5cGUiOiJkb2N1bWVudGF0aW9uIn1dfSx7ImJvbS1yZWYiOiJtcy0zLmV4YW1wbGUuY29tIiwicHJvdmlkZXIiOnsibmFtZSI6IkFjbWUgSW5jIiwidXJsIjpbImh0dHBzOi8vZXhhbXBsZS5jb20iXX0sImdyb3VwIjoiY29tLmV4YW1wbGUiLCJuYW1lIjoiTWljcm9zZXJ2aWNlIDMiLCJ2ZXJzaW9uIjoiMjAyMi0xIiwiZGVzY3JpcHRpb24iOiJFeGFtcGxlIE1pY3Jvc2VydmljZSIsImVuZHBvaW50cyI6WyJodHRwczovL21zLTMuZXhhbXBsZS5jb20iXSwiYXV0aGVudGljYXRlZCI6dHJ1ZSwieC10cnVzdC1ib3VuZGFyeSI6ZmFsc2UsImRhdGEiOlt7ImZsb3ciOiJiaS1kaXJlY3Rpb25hbCIsImNsYXNzaWZpY2F0aW9uIjoiUHVibGljIn1dLCJleHRlcm5hbFJlZmVyZW5jZXMiOlt7InVybCI6Imh0dHBzOi8vbXMtMy5leGFtcGxlLmNvbS9zd2FnZ2VyIiwidHlwZSI6ImRvY3VtZW50YXRpb24ifV19LHsiYm9tLXJlZiI6Im1zLTEtcGdzcWwuZXhhbXBsZS5jb20iLCJncm91cCI6Im9yZy5wb3N0Z3Jlc3FsIiwibmFtZSI6IlBvc3RncmVzIiwidmVyc2lvbiI6IjE0LjEiLCJkZXNjcmlwdGlvbiI6IlBvc3RncmVzIGRhdGFiYXNlIGZvciBNaWNyb3NlcnZpY2UgIzEiLCJlbmRwb2ludHMiOlsiaHR0cHM6Ly9tcy0xLXBnc3FsLmV4YW1wbGUuY29tOjU0MzIiXSwiYXV0aGVudGljYXRlZCI6dHJ1ZSwieC10cnVzdC1ib3VuZGFyeSI6ZmFsc2UsImRhdGEiOlt7ImZsb3ciOiJiaS1kaXJlY3Rpb25hbCIsImNsYXNzaWZpY2F0aW9uIjoiUElJIn1dfSx7ImJvbS1yZWYiOiJzMy1leGFtcGxlLmFtYXpvbi5jb20iLCJncm91cCI6ImNvbS5hbWF6b24iLCJuYW1lIjoiUzMiLCJkZXNjcmlwdGlvbiI6IlMzIGJ1Y2tldCIsImVuZHBvaW50cyI6WyJodHRwczovL3MzLWV4YW1wbGUuYW1hem9uLmNvbSJdLCJhdXRoZW50aWNhdGVkIjp0cnVlLCJ4LXRydXN0LWJvdW5kYXJ5Ijp0cnVlLCJkYXRhIjpbeyJmbG93IjoiYmktZGlyZWN0aW9uYWwiLCJjbGFzc2lmaWNhdGlvbiI6IlB1YmxpYyJ9XX1dfV0sImRlcGVuZGVuY2llcyI6W3sicmVmIjoiYWNtZS1hcHBsaWNhdGlvbiIsImRlcGVuZHNPbiI6WyJhcGktZ2F0ZXdheSJdfSx7InJlZiI6ImFwaS1nYXRld2F5IiwiZGVwZW5kc09uIjpbIm1zLTEuZXhhbXBsZS5jb20iLCJtcy0yLmV4YW1wbGUuY29tIiwibXMtMy5leGFtcGxlLmNvbSJdfSx7InJlZiI6Im1zLTEuZXhhbXBsZS5jb20iLCJkZXBlbmRzT24iOlsibXMtMS1wZ3NxbC5leGFtcGxlLmNvbSJdfSx7InJlZiI6Im1zLTIuZXhhbXBsZS5jb20iLCJkZXBlbmRzT24iOltdfSx7InJlZiI6Im1zLTMuZXhhbXBsZS5jb20iLCJkZXBlbmRzT24iOlsiczMtZXhhbXBsZS5hbWF6b24uY29tIl19XX0=", + BOM: string(marshalledBom), } expectedToken := "7c78f6c9-token" ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - body, err := io.ReadAll(r.Body) + err := r.ParseMultipartForm(500 << 20) require.NoError(t, err) - - var actualRequest dtrack.BOMUploadRequest - require.NoError(t, json.Unmarshal(body, &actualRequest)) - assert.Equal(t, expectedRequest, actualRequest) + require.Equal(t, "POST", r.Method) + require.Equal(t, []string{expectedRequest.ProjectName}, r.MultipartForm.Value["projectName"]) + require.Equal(t, []string{expectedRequest.ProjectUUID.String()}, r.MultipartForm.Value["project"]) + require.Equal(t, []string{expectedRequest.ProjectVersion}, r.MultipartForm.Value["projectVersion"]) + require.Equal(t, []string{expectedRequest.BOM}, r.MultipartForm.Value["bom"]) _, err = w.Write([]byte("{\"Token\":\"" + expectedToken + "\"}")) require.NoError(t, err) @@ -92,9 +111,6 @@ func TestUploadBomsFromEnriched(t *testing.T) { c, err := dtrack.NewClient(ts.URL, dtrack.WithAPIKey(apiKey)) require.NoError(t, err) - rawSaaSBOM, err := os.ReadFile("./testdata/saasBOM.json") - require.NoError(t, err) - client = c issues, err := cyclonedx.ToDracon(rawSaaSBOM, "json") @@ -116,12 +132,21 @@ func TestUploadBomsFromEnriched(t *testing.T) { func TestUploadBomsFromEnrichedWithOwners(t *testing.T) { projUUID := uuid.MustParse("7c78f6c9-b4b0-493c-a912-0bb0a4f221f1") + rawSaaSBOM, err := os.ReadFile("./testdata/saasBOM.json") + require.NoError(t, err) + // we marshal and unmarshal to remove pretty formatting + bom := cdx.BOM{} + err = json.Unmarshal(rawSaaSBOM, &bom) + require.NoError(t, err) + marshalledBom, err := json.Marshal(bom) + require.NoError(t, err) + expectedRequest := dtrack.BOMUploadRequest{ ProjectName: "test", ProjectUUID: &projUUID, ProjectVersion: "2022-1", AutoCreate: true, - BOM: "eyJib21Gb3JtYXQiOiJDeWNsb25lRFgiLCJzcGVjVmVyc2lvbiI6IjEuNCIsInNlcmlhbE51bWJlciI6InVybjp1dWlkOjNlNjcxNjg3LTM5NWItNDFmNS1hMzBmLWE1ODkyMWE2OWI3OSIsInZlcnNpb24iOjEsIm1ldGFkYXRhIjp7InRpbWVzdGFtcCI6IjIwMjEtMDEtMTBUMTI6MDA6MDBaIiwiY29tcG9uZW50Ijp7ImJvbS1yZWYiOiJhY21lLWFwcGxpY2F0aW9uIiwidHlwZSI6ImFwcGxpY2F0aW9uIiwibmFtZSI6IkFjbWUgQ2xvdWQgRXhhbXBsZSIsInZlcnNpb24iOiIyMDIyLTEifX0sInNlcnZpY2VzIjpbeyJib20tcmVmIjoiYXBpLWdhdGV3YXkiLCJwcm92aWRlciI6eyJuYW1lIjoiQWNtZSBJbmMiLCJ1cmwiOlsiaHR0cHM6Ly9leGFtcGxlLmNvbSJdfSwiZ3JvdXAiOiJjb20uZXhhbXBsZSIsIm5hbWUiOiJBUEkgR2F0ZXdheSIsInZlcnNpb24iOiIyMDIyLTEiLCJkZXNjcmlwdGlvbiI6IkV4YW1wbGUgQVBJIEdhdGV3YXkiLCJlbmRwb2ludHMiOlsiaHR0cHM6Ly9leGFtcGxlLmNvbS8iLCJodHRwczovL2V4YW1wbGUuY29tL2FwcCJdLCJhdXRoZW50aWNhdGVkIjpmYWxzZSwieC10cnVzdC1ib3VuZGFyeSI6dHJ1ZSwiZGF0YSI6W3siZmxvdyI6ImJpLWRpcmVjdGlvbmFsIiwiY2xhc3NpZmljYXRpb24iOiJQSUkifSx7ImZsb3ciOiJiaS1kaXJlY3Rpb25hbCIsImNsYXNzaWZpY2F0aW9uIjoiUElGSSJ9LHsiZmxvdyI6ImJpLWRpcmVjdGlvbmFsIiwiY2xhc3NpZmljYXRpb24iOiJQdWJsaWMifV0sImV4dGVybmFsUmVmZXJlbmNlcyI6W3sidXJsIjoiaHR0cDovL2V4YW1wbGUuY29tL2FwcC9zd2FnZ2VyIiwidHlwZSI6ImRvY3VtZW50YXRpb24ifV0sInNlcnZpY2VzIjpbeyJib20tcmVmIjoibXMtMS5leGFtcGxlLmNvbSIsInByb3ZpZGVyIjp7Im5hbWUiOiJBY21lIEluYyIsInVybCI6WyJodHRwczovL2V4YW1wbGUuY29tIl19LCJncm91cCI6ImNvbS5leGFtcGxlIiwibmFtZSI6Ik1pY3Jvc2VydmljZSAxIiwidmVyc2lvbiI6IjIwMjItMSIsImRlc2NyaXB0aW9uIjoiRXhhbXBsZSBNaWNyb3NlcnZpY2UiLCJlbmRwb2ludHMiOlsiaHR0cHM6Ly9tcy0xLmV4YW1wbGUuY29tIl0sImF1dGhlbnRpY2F0ZWQiOnRydWUsIngtdHJ1c3QtYm91bmRhcnkiOmZhbHNlLCJkYXRhIjpbeyJmbG93IjoiYmktZGlyZWN0aW9uYWwiLCJjbGFzc2lmaWNhdGlvbiI6IlBJSSJ9XSwiZXh0ZXJuYWxSZWZlcmVuY2VzIjpbeyJ1cmwiOiJodHRwczovL21zLTEuZXhhbXBsZS5jb20vc3dhZ2dlciIsInR5cGUiOiJkb2N1bWVudGF0aW9uIn1dfSx7ImJvbS1yZWYiOiJtcy0yLmV4YW1wbGUuY29tIiwicHJvdmlkZXIiOnsibmFtZSI6IkFjbWUgSW5jIiwidXJsIjpbImh0dHBzOi8vZXhhbXBsZS5jb20iXX0sImdyb3VwIjoiY29tLmV4YW1wbGUiLCJuYW1lIjoiTWljcm9zZXJ2aWNlIDIiLCJ2ZXJzaW9uIjoiMjAyMi0xIiwiZGVzY3JpcHRpb24iOiJFeGFtcGxlIE1pY3Jvc2VydmljZSIsImVuZHBvaW50cyI6WyJodHRwczovL21zLTIuZXhhbXBsZS5jb20iXSwiYXV0aGVudGljYXRlZCI6dHJ1ZSwieC10cnVzdC1ib3VuZGFyeSI6ZmFsc2UsImRhdGEiOlt7ImZsb3ciOiJiaS1kaXJlY3Rpb25hbCIsImNsYXNzaWZpY2F0aW9uIjoiUElGSSJ9XSwiZXh0ZXJuYWxSZWZlcmVuY2VzIjpbeyJ1cmwiOiJodHRwczovL21zLTIuZXhhbXBsZS5jb20vc3dhZ2dlciIsInR5cGUiOiJkb2N1bWVudGF0aW9uIn1dfSx7ImJvbS1yZWYiOiJtcy0zLmV4YW1wbGUuY29tIiwicHJvdmlkZXIiOnsibmFtZSI6IkFjbWUgSW5jIiwidXJsIjpbImh0dHBzOi8vZXhhbXBsZS5jb20iXX0sImdyb3VwIjoiY29tLmV4YW1wbGUiLCJuYW1lIjoiTWljcm9zZXJ2aWNlIDMiLCJ2ZXJzaW9uIjoiMjAyMi0xIiwiZGVzY3JpcHRpb24iOiJFeGFtcGxlIE1pY3Jvc2VydmljZSIsImVuZHBvaW50cyI6WyJodHRwczovL21zLTMuZXhhbXBsZS5jb20iXSwiYXV0aGVudGljYXRlZCI6dHJ1ZSwieC10cnVzdC1ib3VuZGFyeSI6ZmFsc2UsImRhdGEiOlt7ImZsb3ciOiJiaS1kaXJlY3Rpb25hbCIsImNsYXNzaWZpY2F0aW9uIjoiUHVibGljIn1dLCJleHRlcm5hbFJlZmVyZW5jZXMiOlt7InVybCI6Imh0dHBzOi8vbXMtMy5leGFtcGxlLmNvbS9zd2FnZ2VyIiwidHlwZSI6ImRvY3VtZW50YXRpb24ifV19LHsiYm9tLXJlZiI6Im1zLTEtcGdzcWwuZXhhbXBsZS5jb20iLCJncm91cCI6Im9yZy5wb3N0Z3Jlc3FsIiwibmFtZSI6IlBvc3RncmVzIiwidmVyc2lvbiI6IjE0LjEiLCJkZXNjcmlwdGlvbiI6IlBvc3RncmVzIGRhdGFiYXNlIGZvciBNaWNyb3NlcnZpY2UgIzEiLCJlbmRwb2ludHMiOlsiaHR0cHM6Ly9tcy0xLXBnc3FsLmV4YW1wbGUuY29tOjU0MzIiXSwiYXV0aGVudGljYXRlZCI6dHJ1ZSwieC10cnVzdC1ib3VuZGFyeSI6ZmFsc2UsImRhdGEiOlt7ImZsb3ciOiJiaS1kaXJlY3Rpb25hbCIsImNsYXNzaWZpY2F0aW9uIjoiUElJIn1dfSx7ImJvbS1yZWYiOiJzMy1leGFtcGxlLmFtYXpvbi5jb20iLCJncm91cCI6ImNvbS5hbWF6b24iLCJuYW1lIjoiUzMiLCJkZXNjcmlwdGlvbiI6IlMzIGJ1Y2tldCIsImVuZHBvaW50cyI6WyJodHRwczovL3MzLWV4YW1wbGUuYW1hem9uLmNvbSJdLCJhdXRoZW50aWNhdGVkIjp0cnVlLCJ4LXRydXN0LWJvdW5kYXJ5Ijp0cnVlLCJkYXRhIjpbeyJmbG93IjoiYmktZGlyZWN0aW9uYWwiLCJjbGFzc2lmaWNhdGlvbiI6IlB1YmxpYyJ9XX1dfV0sImRlcGVuZGVuY2llcyI6W3sicmVmIjoiYWNtZS1hcHBsaWNhdGlvbiIsImRlcGVuZHNPbiI6WyJhcGktZ2F0ZXdheSJdfSx7InJlZiI6ImFwaS1nYXRld2F5IiwiZGVwZW5kc09uIjpbIm1zLTEuZXhhbXBsZS5jb20iLCJtcy0yLmV4YW1wbGUuY29tIiwibXMtMy5leGFtcGxlLmNvbSJdfSx7InJlZiI6Im1zLTEuZXhhbXBsZS5jb20iLCJkZXBlbmRzT24iOlsibXMtMS1wZ3NxbC5leGFtcGxlLmNvbSJdfSx7InJlZiI6Im1zLTIuZXhhbXBsZS5jb20iLCJkZXBlbmRzT24iOltdfSx7InJlZiI6Im1zLTMuZXhhbXBsZS5jb20iLCJkZXBlbmRzT24iOlsiczMtZXhhbXBsZS5hbWF6b24uY29tIl19XX0=", + BOM: string(marshalledBom), } expectedProjectUpdate := dtrack.Project{ UUID: projUUID, @@ -138,12 +163,13 @@ func TestUploadBomsFromEnrichedWithOwners(t *testing.T) { expectedToken := "7c78f6c9-token" ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.String() == "/api/v1/bom" { - body, err := io.ReadAll(r.Body) + err := r.ParseMultipartForm(500 << 20) require.NoError(t, err) - - var actualRequest dtrack.BOMUploadRequest - require.NoError(t, json.Unmarshal(body, &actualRequest)) - assert.Equal(t, expectedRequest, actualRequest) + require.Equal(t, "POST", r.Method) + require.Equal(t, []string{expectedRequest.ProjectName}, r.MultipartForm.Value["projectName"]) + require.Equal(t, []string{expectedRequest.ProjectUUID.String()}, r.MultipartForm.Value["project"]) + require.Equal(t, []string{expectedRequest.ProjectVersion}, r.MultipartForm.Value["projectVersion"]) + require.Equal(t, []string{expectedRequest.BOM}, r.MultipartForm.Value["bom"]) _, err = w.Write([]byte("{\"Token\":\"" + expectedToken + "\"}")) require.NoError(t, err) @@ -179,9 +205,6 @@ func TestUploadBomsFromEnrichedWithOwners(t *testing.T) { c, err := dtrack.NewClient(ts.URL, dtrack.WithAPIKey(apiKey)) require.NoError(t, err) - rawSaaSBOM, err := os.ReadFile("./testdata/saasBOM.json") - require.NoError(t, err) - client = c issues, err := cyclonedx.ToDracon(rawSaaSBOM, "json") require.NoError(t, err)