diff --git a/.gitignore b/.gitignore index 016fd6d..71e8020 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +local_imports/ ENV defs/ */.venv/ diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..efadaf0 --- /dev/null +++ b/go.mod @@ -0,0 +1,12 @@ +module dtrack-defectdojo-automatio-go/sbom-dd-dt + +go 1.24.4 + +replace local_imports/defectdojo-client-go => ./local_imports/defectdojo-client-go + +replace local_imports/dependencytrack-client-go => ./local_imports/dependencytrack-client-go + +require ( + local_imports/defectdojo-client-go v0.0.0-00010101000000-000000000000 + local_imports/dependencytrack-client-go v0.0.0-00010101000000-000000000000 +) diff --git a/src/sbom-dd-dt/main.go b/src/sbom-dd-dt/main.go new file mode 100644 index 0000000..125563f --- /dev/null +++ b/src/sbom-dd-dt/main.go @@ -0,0 +1,178 @@ +package main + +import ( + "bytes" + "context" + "errors" + "fmt" + "io/ioutil" + "log" + "os" + "os/exec" + "strconv" + "time" + + defectdojo_api "local_imports/defectdojo-client-go" + dependencytrack_api "local_imports/dependencytrack-client-go" +) + +var verbose bool + +func mustEnv(key string) string { + val, ok := os.LookupEnv(key) + if !ok { + log.Fatalf("Missing required env variable: %s", key) + } + return val +} + +func generateSBOM(target, name, version string) ([]byte, error) { + cmd := exec.Command("syft", "scan", target, "-o", "cyclonedx-json", "--source-name", name, "--source-version", version) + var out, stderr bytes.Buffer + cmd.Stdout = &out + cmd.Stderr = &stderr + + err := cmd.Run() + if err != nil { + log.Println("SBOM generation failed:", stderr.String()) + return nil, err + } + + return out.Bytes(), nil +} + +func main() { + // Load env + dtrackURL := mustEnv("DTRACK_API_URL") + dtrackToken := mustEnv("DTRACK_TOKEN") + defectdojoURL := mustEnv("DEFECTDOJO_URL") + defectdojoToken := mustEnv("DEFECTDOJO_TOKEN") + + // Flags + projectName := os.Args[1] // Simplified args + projectVersion := os.Args[2] + projectDescription := os.Args[3] + productType, _ := strconv.Atoi(os.Args[4]) + projectClassifier := os.Args[5] + sbomFile := os.Args[6] // optional + + // Get SBOM + var sbom []byte + var err error + if sbomFile != "" { + sbom, err = os.ReadFile(sbomFile) + if err != nil { + log.Fatalf("Could not read SBOM file: %v", err) + } + } else { + sbom, err = generateSBOM(".", projectName, projectVersion) + if err != nil { + log.Fatalf("Failed to generate SBOM: %v", err) + } + } + + // DefectDojo client + ddConfig := defectdojo_api.NewConfiguration() + ddConfig.Servers = defectdojo_api.ServerConfigurations{ + {URL: defectdojoURL}, + } + ddConfig.AddDefaultHeader("Authorization", "Token "+defectdojoToken) + + ddClient := defectdojo_api.NewAPIClient(ddConfig) + ctx := context.Background() + + // Create product + prodReq := defectdojo_api.ProductRequest{ + Name: projectName + ":" + projectVersion, + Description: projectDescription, + ProdType: int32(productType), + } + prodResp, _, err := ddClient.ProductsAPI.ProductsCreate(ctx).ProductRequest(prodReq).Execute() + if err != nil { + log.Fatalf("Failed to create product: %v", err) + } + log.Println("Created product:", prodResp.Id) + + // Create engagement + now := time.Now() + end := now.AddDate(10, 0, 0) + engagementReq := defectdojo_api.EngagementRequest{ + Name: projectName + " DTrack Link", + TargetStart: now.Format("2006-01-02"), + TargetEnd: end.Format("2006-01-02"), + Status: "In Progress", + Product: prodResp.Id, + } + engagementResp, _, err := ddClient.EngagementsAPI.EngagementsCreate(ctx).EngagementRequest(engagementReq).Execute() + if err != nil { + log.Fatalf("Failed to create engagement: %v", err) + } + log.Println("Created engagement:", engagementResp.Id) + + // DependencyTrack client + dtConfig := dependencytrack_api.NewConfiguration() + dtConfig.Servers = dependencytrack_api.ServerConfigurations{ + {URL: dtrackURL + "/api"}, + } + dtConfig.AddDefaultHeader("X-Api-Key", dtrackToken) + + dtClient := dependencytrack_api.NewAPIClient(dtConfig) + + // Create project + projectReq := dependencytrack_api.Project{ + Name: &projectName, + Version: &projectVersion, + Classifier: &projectClassifier, + } + projectResp, _, err := dtClient.ProjectApi.CreateProject(ctx).Project(projectReq).Execute() + if err != nil { + log.Fatalf("Failed to create DTrack project: %v", err) + } + log.Println("Created DTrack project UUID:", *projectResp.Uuid) + + // Set properties + properties := []dependencytrack_api.ProjectProperty{ + { + GroupName: ptr("integrations"), + PropertyName: ptr("defectdojo.engagementId"), + PropertyValue: ptr(fmt.Sprintf("%d", *engagementResp.Id)), + PropertyType: ptr("STRING"), + }, + { + GroupName: ptr("integrations"), + PropertyName: ptr("defectdojo.doNotReactivate"), + PropertyValue: ptr("true"), + PropertyType: ptr("BOOLEAN"), + }, + { + GroupName: ptr("integrations"), + PropertyName: ptr("defectdojo.reimport"), + PropertyValue: ptr("true"), + PropertyType: ptr("BOOLEAN"), + }, + } + + for _, p := range properties { + _, err := dtClient.ProjectPropertyApi.CreateProperty1(ctx, *projectResp.Uuid).ProjectProperty(p).Execute() + if err != nil { + log.Fatalf("Failed to create property: %v", err) + } + } + + // Upload SBOM + _, err = dtClient.BomApi.UploadBom(ctx). + ProjectName(projectName). + ProjectVersion(projectVersion). + AutoCreate(true). + Bom(string(sbom)). + Execute() + if err != nil { + log.Fatalf("Failed to upload SBOM: %v", err) + } + + log.Println("SBOM uploaded successfully") +} + +func ptr[T any](v T) *T { + return &v +}