From 492e99edffec895c3b41e9453fc210dc065677f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20K=C3=B6berl?= Date: Mon, 31 Jan 2022 14:34:55 +0100 Subject: [PATCH] feat: add version, use project auto-creation With this change the SBOM is now just uploaded to Dependency Track with the "auto-creat" option set to true. So, each project/version combinations gets an entry in Dependency Track. The Docker repository (registry + image) is used as the project name, the Docker tag is used for the version. --- go.mod | 2 +- internal/daemon/daemon.go | 2 +- internal/kubernetes/kubernetes.go | 3 +- internal/target/dtrack_target.go | 88 ++++++++----------------------- internal/target/git_target.go | 4 +- internal/target/target.go | 6 ++- 6 files changed, 33 insertions(+), 72 deletions(-) diff --git a/go.mod b/go.mod index d3af6d13..272d8e37 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,6 @@ go 1.17 require ( github.com/anchore/syft v0.36.0 github.com/docker/cli v20.10.12+incompatible - github.com/google/uuid v1.3.0 github.com/novln/docker-parser v1.0.0 github.com/nscuro/dtrack-client v0.3.0 github.com/onsi/ginkgo/v2 v2.1.1 @@ -46,6 +45,7 @@ require ( github.com/go-git/go-billy/v5 v5.3.1 // indirect github.com/go-restruct/restruct v1.2.0-alpha // indirect github.com/golang/snappy v0.0.3 // indirect + github.com/google/uuid v1.3.0 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/go-multierror v1.1.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect diff --git a/internal/daemon/daemon.go b/internal/daemon/daemon.go index 82299561..f17a2534 100644 --- a/internal/daemon/daemon.go +++ b/internal/daemon/daemon.go @@ -68,7 +68,7 @@ func (c *CronService) runBackgroundService() { } for _, t := range c.targets { - t.ProcessSbom(image.ImageID, sbom) + t.ProcessSbom(image, sbom) } } diff --git a/internal/kubernetes/kubernetes.go b/internal/kubernetes/kubernetes.go index a67ce252..4e84babf 100644 --- a/internal/kubernetes/kubernetes.go +++ b/internal/kubernetes/kubernetes.go @@ -16,6 +16,7 @@ import ( ) type ContainerImage struct { + Image string ImageID string Auth []byte Pods []corev1.Pod @@ -106,7 +107,7 @@ func (client *KubeClient) LoadImageInfos(namespaces []corev1.Namespace, podLabel if !client.hasAnnotation(annotations, c) { img, ok := images[c.ImageID] if !ok { - img = ContainerImage{ImageID: c.ImageID, Auth: pullSecrets, Pods: []corev1.Pod{}} + img = ContainerImage{Image: c.Image, ImageID: c.ImageID, Auth: pullSecrets, Pods: []corev1.Pod{}} } img.Pods = append(img.Pods, pod) diff --git a/internal/target/dtrack_target.go b/internal/target/dtrack_target.go index bcb06add..79007e92 100644 --- a/internal/target/dtrack_target.go +++ b/internal/target/dtrack_target.go @@ -4,20 +4,19 @@ import ( "context" "encoding/base64" "fmt" - "strings" - "github.com/google/uuid" + parser "github.com/novln/docker-parser" dtrack "github.com/nscuro/dtrack-client" "github.com/sirupsen/logrus" "github.com/spf13/viper" "github.com/ckotzbauer/sbom-operator/internal" + "github.com/ckotzbauer/sbom-operator/internal/kubernetes" ) type DependencyTrackTarget struct { - baseUrl string - apiKey string - imageToProject map[string]uuid.UUID + baseUrl string + apiKey string } func NewDependencyTrackTarget() *DependencyTrackTarget { @@ -40,79 +39,35 @@ func (g *DependencyTrackTarget) ValidateConfig() error { } func (g *DependencyTrackTarget) Initialize() { - client, _ := dtrack.NewClient(g.baseUrl, dtrack.WithAPIKey(g.apiKey)) - g.imageToProject = make(map[string]uuid.UUID) - - const pageSize = 10 - pageNumber := 1 - for { - projectsPage, err := client.Project.GetAll(context.TODO(), dtrack.PageOptions{ - PageNumber: pageNumber, - PageSize: pageSize, - }) - if err != nil { - logrus.Errorf("Could not fetch projects: %v", err) - return - } - - for _, project := range projectsPage.Projects { - for _, property := range project.Properties { - if property.Name == "image-name" { - g.imageToProject[property.Value] = project.UUID - } - } - } - - if pageNumber*pageSize >= projectsPage.TotalCount { - break - } - - pageNumber++ - } } -func (g *DependencyTrackTarget) ProcessSbom(imageID, sbom string) { - if sbom == "" { - logrus.Infof("Empty SBOM - skip image (image=%s)", imageID) +func (g *DependencyTrackTarget) ProcessSbom(image kubernetes.ContainerImage, sbom string) { + fullRef, err := parser.Parse(image.Image) + if err != nil { + logrus.WithError(err).Errorf("Could not parse imageID %s", image.ImageID) return } - client, _ := dtrack.NewClient(g.baseUrl, dtrack.WithAPIKey(g.apiKey)) - logrus.Infof("Sending SBOM to Dependency Track (image=%s)", imageID) + imageName := fullRef.Repository() + tagName := fullRef.Tag() + if tagName == "" { + tagName = "latest" + } - if !strings.ContainsRune(imageID, '@') { - logrus.Warnf("Image id %s does not contain an @sha256", imageID) + if sbom == "" { + logrus.Infof("Empty SBOM - skip image (image=%s)", image.ImageID) return } - imageSplit := strings.Split(imageID, "@") - imageName := imageSplit[0] - projectId := g.imageToProject[imageName] - if projectId == uuid.Nil { - project, err := client.Project.Create(context.TODO(), - dtrack.Project{ - Active: true, - Classifier: "APPLICATION", - Name: imageName, - Properties: []dtrack.ProjectProperty{ - {Name: "image-name", Group: "container", Value: imageName, Type: "STRING"}, - }, - // TODO check if to add PURL: "pkg:docker/" + imageID, - }) - if err != nil { - logrus.Errorf("Could not create project (%s): %v", imageName, err) - } - projectId = project.UUID - g.imageToProject[imageName] = projectId - } + client, _ := dtrack.NewClient(g.baseUrl, dtrack.WithAPIKey(g.apiKey)) - if projectId == uuid.Nil { - logrus.Warnf("No project id for image %s", imageName) - return - } + logrus.Infof("Sending SBOM to Dependency Track (project=%s, version=%s)", imageName, tagName) sbomBase64 := base64.StdEncoding.EncodeToString([]byte(sbom)) - uploadToken, err := client.BOM.Upload(context.TODO(), dtrack.BOMUploadRequest{ProjectUUID: &projectId, BOM: sbomBase64, AutoCreate: false}) + uploadToken, err := client.BOM.Upload( + context.TODO(), + dtrack.BOMUploadRequest{ProjectName: imageName, ProjectVersion: tagName, AutoCreate: true, BOM: sbomBase64}, + ) if err != nil { logrus.Errorf("Could not upload BOM: %v", err) } @@ -120,5 +75,4 @@ func (g *DependencyTrackTarget) ProcessSbom(imageID, sbom string) { } func (g *DependencyTrackTarget) Cleanup(allImages []string) { - g.imageToProject = nil } diff --git a/internal/target/git_target.go b/internal/target/git_target.go index a8f869bc..edd15b92 100644 --- a/internal/target/git_target.go +++ b/internal/target/git_target.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/ckotzbauer/sbom-operator/internal" + "github.com/ckotzbauer/sbom-operator/internal/kubernetes" "github.com/ckotzbauer/sbom-operator/internal/syft" "github.com/ckotzbauer/sbom-operator/internal/target/git" "github.com/sirupsen/logrus" @@ -79,7 +80,8 @@ func (g *GitTarget) Initialize() { viper.GetString(internal.ConfigKeyGitBranch)) } -func (g *GitTarget) ProcessSbom(imageID, sbom string) { +func (g *GitTarget) ProcessSbom(image kubernetes.ContainerImage, sbom string) { + imageID := image.ImageID filePath := g.imageIDToFilePath(imageID) dir := filepath.Dir(filePath) diff --git a/internal/target/target.go b/internal/target/target.go index 8e0407bc..6438d1b2 100644 --- a/internal/target/target.go +++ b/internal/target/target.go @@ -1,8 +1,12 @@ package target +import ( + "github.com/ckotzbauer/sbom-operator/internal/kubernetes" +) + type Target interface { Initialize() ValidateConfig() error - ProcessSbom(imageID, sbom string) + ProcessSbom(imageID kubernetes.ContainerImage, sbom string) Cleanup(allImages []string) }