diff --git a/.github/workflows/devguard.yml b/.github/workflows/devguard.yml index 6412aa74..076f3ba8 100644 --- a/.github/workflows/devguard.yml +++ b/.github/workflows/devguard.yml @@ -5,9 +5,11 @@ on: push: jobs: - devguard-scanner: - uses: l3montree-dev/devguard-action/.github/workflows/full.yml@main - with: - asset-name: "l3montree-cybersecurity/projects/devguard/assets/devguard-operator" - secrets: - devguard-token: ${{ secrets.DEVGUARD_TOKEN }} \ No newline at end of file + call-devsecops: + uses: l3montree-dev/devguard-action/.github/workflows/full.yml@main + with: + asset-name: "l3montree-cybersecurity/projects/devguard/assets/devguard-operator" + api-url: "https://api.main.devguard.org" + web-ui: "https://main.devguard.org" + secrets: + devguard-token: "${{ secrets.DEVGUARD_TOKEN }}" diff --git a/.gitignore b/.gitignore index 632f0f6c..926f3f22 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,4 @@ dist work /auth +devguard-operator.yaml diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..60c830bd --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + + { + "name": "Launch Package", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "." + } + ] +} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 2d2f5536..792a025e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,11 @@ -FROM golang:1.23.4-alpine AS golang-builder +FROM golang:1.26.4-alpine3.24@sha256:f1ddd9fe14fffc091dd98cb4bfa999f32c5fc77d2f2305ea9f0e2595c5437c14 AS golang-builder # set the working directory WORKDIR /app RUN apk add --no-cache curl git -RUN curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin v0.54.1 +RUN curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin v0.71.1 COPY . . diff --git a/Makefile b/Makefile index 560daeca..2a654cf5 100644 --- a/Makefile +++ b/Makefile @@ -1,2 +1,2 @@ run: - go run ./main.go + go run . diff --git a/config.go b/config.go index 1120e48e..e3e9f841 100644 --- a/config.go +++ b/config.go @@ -12,9 +12,8 @@ type Config struct { RegistryProxies []string `yaml:"registryProxy" env:"SBOM_REGISTRY_PROXY" flag:"registryProxy"` Verbosity string `env:"SBOM_VERBOSITY" flag:"verbosity"` - DevGuardToken string `yaml:"devGuardToken" env:"DEVGUARD_TOKEN" flag:"token"` - DevGuardApiURL string `yaml:"devGuardApiURL" env:"DEVGUARD_API_URL" flag:"apiUrl"` - DevGuardProjectName string `yaml:"devGuardProjectID" env:"DEVGUARD_PROJECT_NAME" flag:"projectName"` + DevGuardToken string `yaml:"devGuardToken" env:"DEVGUARD_TOKEN" flag:"token"` + DevGuardProjectURL string `yaml:"devGuardProjectURL" env:"DEVGUARD_PROJECT_URL" flag:"projectUrl"` } var ( @@ -28,9 +27,8 @@ var ( ConfigKeyFallbackPullSecret = "fallbackPullSecret" ConfigKeyRegistryProxy = "registryProxy" - ConfigDevGuardToken = "token" - ConfigDevGuardApiURL = "apiUrl" - ConfigDevGuardProjectName = "projectName" + ConfigDevGuardToken = "token" + ConfigDevGuardProjectURL = "projectUrl" OperatorConfig *Config ) diff --git a/daemon.go b/daemon.go index 85b8bcfc..dbdbf16b 100644 --- a/daemon.go +++ b/daemon.go @@ -60,12 +60,6 @@ func (c *CronService) runBackgroundService() { slog.Info("Execute background-service") for _, t := range c.processor.Targets { - err := t.Initialize() - if err != nil { - slog.Error("Target could not be initialized", "err", err) - continue - } - t.LoadImages() } diff --git a/devguard_target.go b/devguard_target.go index d480c1da..21f01b09 100644 --- a/devguard_target.go +++ b/devguard_target.go @@ -1,15 +1,13 @@ package main import ( - "bytes" "encoding/json" - "fmt" + "errors" "log/slog" "net/http" "strings" "sync" - "github.com/gosimple/slug" "github.com/l3montree-dev/devguard/pkg/devguard" parser "github.com/novln/docker-parser" @@ -18,399 +16,113 @@ import ( ) type DevGuardTarget struct { - apiUrl string - token string - tags []string - assetNameAnnotationKey string - - rootProjectID string - rootProjectSlug string - organizationSlug string - - client devguard.HTTPClient -} - -func NewDevGuardTarget(token, apiUrl, rootProjectName string, tags []string) *DevGuardTarget { - // fetch the root project id - client := devguard.NewHTTPClient(token, apiUrl) - - arr := strings.Split(rootProjectName, "/") - if len(arr) != 3 { - slog.Error(fmt.Sprintf("invalid root project name: %s. Needs to be /projects/", rootProjectName)) - panic(fmt.Sprintf("invalid root project name: %s. Needs to be /projects/", rootProjectName)) - } - - return &DevGuardTarget{ - apiUrl: apiUrl, - token: token, - tags: tags, - - rootProjectSlug: arr[2], - organizationSlug: arr[0], - client: client, - } -} - -func (g *DevGuardTarget) ValidateConfig() error { - if g.token == "" { - return fmt.Errorf("%s is empty", ConfigDevGuardToken) - } - - if g.apiUrl == "" { - return fmt.Errorf("%s is empty", ConfigDevGuardApiURL) - } - - if len(g.tags) == 0 { - g.tags = []string{"kubernetes-cluster"} - } - - return nil -} - -func (g *DevGuardTarget) Initialize() error { - // set the root project id - rootProject, err := g.getProjectBySlug(g.rootProjectSlug) - if err != nil { - return err - } - - g.rootProjectID = rootProject["id"].(string) - - // check if already marked as kubernetes cluster - if rootProject["type"].(string) != "kubernetesCluster" { - // update the project type - body := map[string]interface{}{ - "type": "kubernetesCluster", - } - - // to json - jsonBody, err := json.Marshal(body) - if err != nil { - return err - } - - req, err := http.NewRequest("PATCH", fmt.Sprintf("/api/v1/organizations/%s/projects/%s/", g.organizationSlug, g.rootProjectSlug), bytes.NewBuffer(jsonBody)) - if err != nil { - return err - } - - req.Header.Set("Content-Type", "application/json") - - _, err = g.client.Do(req) - if err != nil { - return err - } - - slog.Info("Updated root project to kubernetesCluster", "rootProjectSlug", g.rootProjectSlug) - } - - return nil -} - -func (g *DevGuardTarget) getProjectBySlug(slug string) (map[string]interface{}, error) { - req, err := http.NewRequest("GET", fmt.Sprintf("/api/v1/organizations/%s/projects/%s/", g.organizationSlug, slug), nil) - if err != nil { - return nil, err - } - - req.Header.Set("Content-Type", "application/json") - resp, err := g.client.Do(req) - - if err != nil { - return nil, err - } - - if resp.StatusCode == http.StatusNotFound { - return nil, fmt.Errorf("project not found") - } - - var project map[string]interface{} - // parse the response body - err = json.NewDecoder(resp.Body).Decode(&project) - if err != nil { - return nil, err - } - - return project, nil + projectURL string + token string + tags []string + client devguard.HTTPClient } -func (g *DevGuardTarget) getAssetBySlug(projectSlug, slug string) (map[string]interface{}, error) { - req, err := http.NewRequest("GET", fmt.Sprintf("/api/v1/organizations/%s/projects/%s/assets/%s/", g.organizationSlug, projectSlug, slug), nil) - if err != nil { - return nil, err - } - - req.Header.Set("Content-Type", "application/json") - resp, err := g.client.Do(req) - - if err != nil { - return nil, err - } - - if resp.StatusCode == http.StatusNotFound { - return nil, fmt.Errorf("asset not found") - } - - var asset map[string]interface{} - // parse the response body - err = json.NewDecoder(resp.Body).Decode(&asset) - if err != nil { - return nil, err - } - - return asset, nil +type DevGuardRequest struct { + Verb string `json:"verb"` + ProjectExternalEntityID string `json:"projectExternalEntityId"` + AssetExternalEntityID string `json:"assetExternalEntityId"` + AssetVersion string `json:"assetVersion"` + Sbom json.RawMessage `json:"sbom,omitempty"` } -func (g *DevGuardTarget) createAssetInsideProject(projectSlug string, assetName string) (map[string]interface{}, error) { - // the asset does not exist, create it - createRequestBody := map[string]interface{}{ - "name": assetName, - "description": fmt.Sprintf("Controlled by an Kubernetes Operator. Asset %s", assetName), - - "confidentialityRequirement": "medium", - "integrityRequirement": "medium", - "availabilityRequirement": "medium", - } - - // to json - jsonBody, err := json.Marshal(createRequestBody) - if err != nil { - return nil, err - } - - req, err := http.NewRequest("POST", fmt.Sprintf("/api/v1/organizations/%s/projects/%s/assets/", g.organizationSlug, projectSlug), bytes.NewReader(jsonBody)) - - if err != nil { - return nil, err - } - - req.Header.Set("Content-Type", "application/json") - resp, err := g.client.Do(req) - if err != nil { - return nil, err - } - - // parse the response body - var asset map[string]interface{} - err = json.NewDecoder(resp.Body).Decode(&asset) - if err != nil { - return nil, err - } - - return asset, nil +type projectAssetsResponse struct { + ProjectExternalEntityID string `json:"projectExternalEntityId"` + Assets []struct { + AssetExternalEntityID string `json:"assetExternalEntityId"` + Versions []string `json:"versions"` + } `json:"assets"` } -func (g *DevGuardTarget) createChildNamespaceProject(namespace string) (map[string]interface{}, error) { - // the project does not exist, create it - createRequestBody := map[string]interface{}{ - "name": namespace, - "description": fmt.Sprintf("Controlled by an Kubernetes Operator. Namespace %s", namespace), - "parentId": g.rootProjectID, - "type": "kubernetesNamespace", - } - - // to json - jsonBody, err := json.Marshal(createRequestBody) - if err != nil { - return nil, err - } - - req, err := http.NewRequest("POST", fmt.Sprintf("/api/v1/organizations/%s/projects/", g.organizationSlug), bytes.NewReader(jsonBody)) - - if err != nil { - return nil, err - } - - req.Header.Set("Content-Type", "application/json") - resp, err := g.client.Do(req) - if err != nil { - return nil, err - } - - // parse the response body - var project map[string]interface{} - err = json.NewDecoder(resp.Body).Decode(&project) - if err != nil { - return nil, err +func NewDevGuardTarget(token, projectURL string, tags []string) *DevGuardTarget { + client := devguard.NewHTTPClient(token, projectURL) + projectURL = projectURL + "/dn/devguard-operator" + return &DevGuardTarget{ + projectURL: projectURL, + token: token, + tags: tags, + client: client, } - - return project, nil } func (g *DevGuardTarget) LoadImages() ([]kubernetes.ImageInNamespace, error) { - // fetch all projects inside the root project - req, err := http.NewRequest("GET", fmt.Sprintf("/api/v1/organizations/%s/projects?parentId=%s", g.organizationSlug, g.rootProjectID), nil) + req, err := http.NewRequest("GET", g.projectURL, nil) if err != nil { - slog.Error("Could not fetch projects", "err", err) return nil, err } - // check all subprojects - // for each subproject, iterate over all assets. - var res []map[string]interface{} resp, err := g.client.Do(req) if err != nil { - slog.Error("Could not fetch projects", "err", err) return nil, err } + defer resp.Body.Close() - err = json.NewDecoder(resp.Body).Decode(&res) - if err != nil { - slog.Error("Could not fetch projects", "err", err) - return nil, err + if resp.StatusCode != http.StatusOK { + return nil, errors.New("failed to load images from DevGuard: " + resp.Status) } - for el := range res { - slog.Info("Project", "name", res[el]["name"]) + var assets []projectAssetsResponse + if err := json.NewDecoder(resp.Body).Decode(&assets); err != nil { + return nil, err } - // fetch all assets for each project. - // we can do that concurrently - wg := sync.WaitGroup{} - - // channel to collect all images - images := make(chan kubernetes.ImageInNamespace) - - for _, project := range res { - wg.Add(1) - go func(project map[string]interface{}) { - defer wg.Done() - // fetch all assets - req, err := http.NewRequest("GET", fmt.Sprintf("/api/v1/organizations/%s/projects/%s/assets/", g.organizationSlug, project["slug"].(string)), nil) - if err != nil { - slog.Error("Could not fetch assets", "err", err) - return - } - - resp, err := g.client.Do(req) - if err != nil { - slog.Error("Could not fetch assets", "err", err) - return - } - - var assets []map[string]interface{} - err = json.NewDecoder(resp.Body).Decode(&assets) - if err != nil { - slog.Error("Could not fetch assets", "err", err) - return - } - - for _, asset := range assets { - images <- kubernetes.ImageInNamespace{ - Namespace: project["name"].(string), + result := make([]kubernetes.ImageInNamespace, 0) + for _, a := range assets { + for _, asset := range a.Assets { + for _, version := range asset.Versions { + fullImage := asset.AssetExternalEntityID + ":" + version + result = append(result, kubernetes.ImageInNamespace{ + Namespace: a.ProjectExternalEntityID, Image: &libk8s.RegistryImage{ - ImageID: asset["name"].(string), - Image: asset["name"].(string), + ImageID: fullImage, + Image: fullImage, }, - } + }) } - }(project) - } - - go func() { - wg.Wait() - close(images) - }() - - var imagesInNamespace []kubernetes.ImageInNamespace = []kubernetes.ImageInNamespace{} - - for img := range images { - imagesInNamespace = append(imagesInNamespace, img) + } } - return imagesInNamespace, nil + return result, nil } func (g *DevGuardTarget) ProcessSbom(ctx *TargetContext) error { - assetName := "" - version := "" - - // Set custom project name by kubernetes annotation? - if g.assetNameAnnotationKey != "" { - slog.Debug(`Try to set project name by configured annotationkey`, "assetNameAnnotationKey", g.assetNameAnnotationKey) - for podAnnotationKey, podAnnotationValue := range ctx.Pod.Annotations { - if strings.HasPrefix(podAnnotationKey, g.assetNameAnnotationKey) { - if podAnnotationValue != "" { - // determine container name from annotation key - containerName := getContainerNameFromAnnotationKey(podAnnotationKey, "/") - if containerName != "" { - slog.Debug(`ContainerName found"`, "name", containerName) - // correct container? - if containerName == ctx.Container.Name { - assetName, version = getNameAndVersionFromString(podAnnotationValue, ":") - slog.Info(`Custom project name found`, "podAnnotationKey", podAnnotationKey, "containerName", containerName, "assetName", assetName, "version", version) - break - } - } else { - slog.Error(`Containername could not be determined from annotation. Skip setting project name.`, "podAnnotationKey", podAnnotationKey) - - } - } else { - slog.Error(`Empty value for custom project name annotation. Skip setting custom project name.`, "podAnnotationKey", podAnnotationKey) - } - } - } - } - // If assetNameAnnotationKey is not set or could not be parsed correctly, use image instead - if assetName == "" || version == "" { - assetName, version = getRepoWithVersion(ctx.Image) - } + assetName, version := getRepoWithVersion(ctx.Image) if ctx.Sbom == "" { slog.Info("Empty SBOM - skip image", "image", ctx.Image.ImageID) return nil } - client := devguard.NewHTTPClient(g.token, g.apiUrl) - // make sure the namespace project exists inside the root project - s := slug.Make(ctx.Pod.PodNamespace) - project, err := g.getProjectBySlug(s) - - slog.Debug("checking project existence", "projectSlug", s, "err", err, "project", project) - if err != nil { - // the project does not exist yet - // create it - slog.Debug("Creating project", "projectSlug", s) - project, err = g.createChildNamespaceProject(s) - if err != nil { - slog.Error("Could not create project", "err", err) - return err - } + payload := DevGuardRequest{ + Verb: "update", + ProjectExternalEntityID: ctx.Pod.PodNamespace, + AssetExternalEntityID: assetName, + AssetVersion: version, + Sbom: json.RawMessage(ctx.Sbom), } - // check if the asset does already exist inside the project - asset, err := g.getAssetBySlug(s, slug.Make(assetName)) + jsonBody, err := json.Marshal(payload) if err != nil { - // the asset does not exist yet - // create it now - asset, err = g.createAssetInsideProject(project["slug"].(string), assetName) - if err != nil { - slog.Error("Could not create asset", "err", err) - return err - } + return err } - // asset exists now. - // upload the SBOM to the asset - req, err := http.NewRequest("POST", "/api/v1/scan/", strings.NewReader(ctx.Sbom)) + req, err := http.NewRequest("POST", g.projectURL, strings.NewReader(string(jsonBody))) if err != nil { - slog.Error("Could not upload BOM", "err", err) return err } req.Header.Set("Content-Type", "application/json") - req.Header.Set("X-Risk-Management", "true") - req.Header.Set("X-Asset-Name", fmt.Sprintf("%s/projects/%s/assets/%s", g.organizationSlug, s, asset["slug"].(string))) - req.Header.Set("X-Asset-Version", version) - req.Header.Set("X-Scan-Type", "container-scanning") - req.Header.Set("X-Scanner", "github.com/l3montree-dev/devguard-operator") slog.Info("Sending SBOM to DevGuard", "assetName", assetName, "version", version) - _, err = client.Do(req) + _, err = g.client.Do(req) if err != nil { - slog.Error("Could not upload BOM", "err", err) + slog.Error("Could not upload SBOM", "err", err) return err } @@ -419,7 +131,6 @@ func (g *DevGuardTarget) ProcessSbom(ctx *TargetContext) error { } func (g *DevGuardTarget) Remove(images []kubernetes.ImageInNamespace) error { - wg := sync.WaitGroup{} for _, img := range images { @@ -427,105 +138,41 @@ func (g *DevGuardTarget) Remove(images []kubernetes.ImageInNamespace) error { go func(img kubernetes.ImageInNamespace) { defer wg.Done() - name, _ := getRepoWithVersion(img.Image) - - projectSlug := slug.Make(img.Namespace) - assetSlug := slug.Make(name) + name, version := getRepoWithVersion(img.Image) - req, err := http.NewRequest("DELETE", fmt.Sprintf("/api/v1/organizations/%s/projects/%s/assets/%s/", g.organizationSlug, projectSlug, assetSlug), nil) - if err != nil { - slog.Error("could not delete asset", "err", err) - return + payload := DevGuardRequest{ + Verb: "delete", + ProjectExternalEntityID: img.Namespace, + AssetExternalEntityID: name, + AssetVersion: version, } - slog.Info("Deleting asset", "projectSlug", projectSlug, "assetSlug", assetSlug) - - req.Header.Set("Content-Type", "application/json") - _, err = g.client.Do(req) + jsonBody, err := json.Marshal(payload) if err != nil { - slog.Error("could not delete asset", "err", err) + slog.Error("could not marshal delete request", "err", err) return } - }(img) - } - - wg.Wait() - - // check if there are empty projects now. We can archive those too - namespaces := map[string]bool{} - for _, img := range images { - namespaces[img.Namespace] = true - } - - wg = sync.WaitGroup{} - // fetch all assets of those projects. - // if empty, archive the project - for namespace := range namespaces { - wg.Add(1) - go func(namespace string) { - defer wg.Done() - projectSlug := slug.Make(namespace) - req, err := http.NewRequest("GET", fmt.Sprintf("/api/v1/organizations/%s/projects/%s/assets/", g.organizationSlug, projectSlug), nil) + req, err := http.NewRequest("POST", g.projectURL, strings.NewReader(string(jsonBody))) if err != nil { - slog.Error("Could not fetch assets", "err", err) + slog.Error("could not create delete request", "err", err) return } - resp, err := g.client.Do(req) - if err != nil { - slog.Error("Could not fetch assets", "err", err) - return - } + req.Header.Set("Content-Type", "application/json") + + slog.Info("Deleting asset", "projectName", img.Namespace, "assetName", name, "assetVersion", version) - var assets []map[string]interface{} - err = json.NewDecoder(resp.Body).Decode(&assets) + _, err = g.client.Do(req) if err != nil { - slog.Error("Could not fetch assets", "err", err) + slog.Error("could not delete asset", "err", err) return } - - if len(assets) == 0 { - req, err := http.NewRequest("DELETE", fmt.Sprintf("/api/v1/organizations/%s/projects/%s/", g.organizationSlug, projectSlug), nil) - if err != nil { - slog.Error("Could not delete project", "err", err) - return - } - - req.Header.Set("Content-Type", "application/json") - _, err = g.client.Do(req) - if err != nil { - slog.Error("Could not delete project", "err", err) - return - } - - slog.Info("Deleted project", "projectSlug", projectSlug) - } - }(namespace) + }(img) } wg.Wait() return nil - -} - -func getNameAndVersionFromString(input string, delimiter string) (string, string) { - parts := strings.Split(input, delimiter) - name := parts[0] - version := "latest" - if len(parts) == 2 { - version = parts[1] - } - return name, version -} - -func getContainerNameFromAnnotationKey(annotationKey string, delimiter string) string { - parts := strings.Split(annotationKey, delimiter) - containerName := "" - if len(parts) == 2 { - containerName = parts[1] - } - return containerName } func getRepoWithVersion(image *libk8s.RegistryImage) (string, string) { diff --git a/go.mod b/go.mod index 30161bf9..07d10e0f 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,10 @@ module github.com/l3montree-dev/devguard-operator -go 1.23.4 +go 1.26.3 require ( github.com/ckotzbauer/libk8soci v0.0.0-20240810135526-c1ac5a827c6b github.com/ckotzbauer/libstandard v0.0.0-20240714072944-bb20d4a8e76a - github.com/gosimple/slug v1.14.0 github.com/l3montree-dev/devguard v0.5.15 github.com/lmittmann/tint v1.0.5 github.com/novln/docker-parser v1.0.0 @@ -52,7 +51,6 @@ require ( github.com/golang-sql/sqlexp v0.1.0 // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/gosimple/unidecode v1.0.1 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/iancoleman/strcase v0.3.0 // indirect diff --git a/go.sum b/go.sum index 7ef99d92..a0ba5f38 100644 --- a/go.sum +++ b/go.sum @@ -150,10 +150,6 @@ github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/gookit/color v1.2.5/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= -github.com/gosimple/slug v1.14.0 h1:RtTL/71mJNDfpUbCOmnf/XFkzKRtD6wL6Uy+3akm4Es= -github.com/gosimple/slug v1.14.0/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ= -github.com/gosimple/unidecode v1.0.1 h1:hZzFTMMqSswvf0LBJZCZgThIZrpDHFXux9KeGmn6T/o= -github.com/gosimple/unidecode v1.0.1/go.mod h1:CP0Cr1Y1kogOtx0bJblKzsVWrqYaqfNOnHzpgWw4Awc= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= diff --git a/main.go b/main.go index 7474f51b..0add585d 100644 --- a/main.go +++ b/main.go @@ -85,8 +85,7 @@ func newRootCmd() *cobra.Command { rootCmd.PersistentFlags().Int64(ConfigKeyJobTimeout, 60*60, "Job-Timeout") rootCmd.PersistentFlags().String(ConfigDevGuardToken, "", "DevGuard-Token") - rootCmd.PersistentFlags().String(ConfigDevGuardApiURL, "", "DevGuard Api URL") - rootCmd.PersistentFlags().String(ConfigDevGuardProjectName, "", "DevGuard Project Name (eg. l3montree-cybersecurity/projects/devguard)") + rootCmd.PersistentFlags().String(ConfigDevGuardProjectURL, "", "DevGuard Project URL") return rootCmd } diff --git a/processor.go b/processor.go index 86967ec1..ab028280 100644 --- a/processor.go +++ b/processor.go @@ -10,6 +10,7 @@ import ( libk8s "github.com/ckotzbauer/libk8soci/pkg/kubernetes" "github.com/ckotzbauer/libk8soci/pkg/oci" "github.com/l3montree-dev/devguard-operator/kubernetes" + parser "github.com/novln/docker-parser" "k8s.io/client-go/tools/cache" @@ -105,7 +106,7 @@ func (p *Processor) scanPod(pod libk8s.PodInfo) { func initTargets() []Target { targets := make([]Target, 0) - t := NewDevGuardTarget(OperatorConfig.DevGuardToken, OperatorConfig.DevGuardApiURL, OperatorConfig.DevGuardProjectName, nil) + t := NewDevGuardTarget(OperatorConfig.DevGuardToken, OperatorConfig.DevGuardProjectURL, nil) targets = append(targets, t) return targets @@ -159,9 +160,18 @@ func getChangedContainers(oldPod, newPod libk8s.PodInfo) ([]*libk8s.ContainerInf return addedContainers, removedContainers } -func containsImage(images []kubernetes.ImageInNamespace, image kubernetes.ImageInNamespace) bool { - for _, i := range images { - if i.String() == image.String() { +func containsImage(images []kubernetes.ImageInNamespace, target kubernetes.ImageInNamespace) bool { + targetParsed, err := parser.Parse(target.Image.Image) + if err != nil { + return false + } + + for _, candidate := range images { + candidateParsed, err := parser.Parse(candidate.Image.Image) + if err != nil { + continue + } + if candidate.Namespace == target.Namespace && candidateParsed.Remote() == targetParsed.Remote() { return true } } @@ -226,13 +236,6 @@ func (p *Processor) runInformerAsync(informer cache.SharedIndexInformer) { go func() { - for _, t := range p.Targets { - err := t.Initialize() - if err != nil { - slog.Error("Target could not be initialized", "err", err) - } - } - slog.Info("Start pod-informer") informer.Run(stop) slog.Info("Pod-informer has stopped") @@ -265,6 +268,7 @@ func (p *Processor) runInformerAsync(informer cache.SharedIndexInformer) { info := p.K8s.Client.ExtractPodInfos(*pod) for _, c := range info.Containers { allImages = append(allImages, kubernetes.ImageInNamespace{Namespace: info.PodNamespace, Image: c.Image}) + if !containsImage(targetImages, kubernetes.ImageInNamespace{ Image: c.Image, Namespace: info.PodNamespace, diff --git a/target.go b/target.go index cf2d06d9..b21b793b 100644 --- a/target.go +++ b/target.go @@ -14,8 +14,6 @@ type TargetContext struct { } type Target interface { - Initialize() error - ValidateConfig() error ProcessSbom(ctx *TargetContext) error LoadImages() ([]kubernetes.ImageInNamespace, error) Remove(images []kubernetes.ImageInNamespace) error