Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions deploy/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,8 @@ spec:
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: DEVGUARD_API_URL
value: "https://api.devguard.org"
- name: DEVGUARD_PROJECT_NAME
value: "devguard-operator"
- name: DEVGUARD_PROJECT_URL
value: "https://api.main.devguard.org/api/v1/organizations/l3montree-cybersecurity/projects/cluster/dynamic-project"
ports:
- containerPort: 8081
name: http
Expand Down
7 changes: 7 additions & 0 deletions deploy/rbac.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ apiVersion: v1
kind: ServiceAccount
metadata:
name: devguard-operator
namespace: devguard
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
Expand Down Expand Up @@ -29,6 +30,12 @@ rules:
- get
- update
- watch
- apiGroups:
- apps
resources:
- replicasets
verbs:
- get
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
Expand Down
178 changes: 129 additions & 49 deletions devguard_target.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,44 @@ type DevGuardTarget struct {
}

type DevGuardRequest struct {
Verb string `json:"verb"`
ProjectExternalEntityID string `json:"projectExternalEntityId"`
ProjectName string `json:"projectName"`
AssetExternalEntityID string `json:"assetExternalEntityId"`
AssetName string `json:"assetName"`
AssetVersion string `json:"assetVersion"`
Sbom json.RawMessage `json:"sbom,omitempty"`
Verb string `json:"verb"`
ProjectExternalEntityID string `json:"projectExternalEntityId"`
ProjectName string `json:"projectName"`
ProjectDescription string `json:"projectDescription,omitempty"`
SubProjectExternalEntityID string `json:"subProjectExternalEntityId,omitempty"`
SubProjectName string `json:"subProjectName,omitempty"`
SubProjectDescription string `json:"subProjectDescription,omitempty"`
AssetExternalEntityID string `json:"assetExternalEntityId"`
AssetName string `json:"assetName"`
AssetDescription string `json:"assetDescription,omitempty"`
AssetVersionName string `json:"assetVersionName,omitempty"`
Artifact string `json:"artifact,omitempty"`
Sbom json.RawMessage `json:"sbom,omitempty"`
}

type projectAssetsResponse struct {
ProjectExternalEntityID string `json:"projectExternalEntityId"`
ProjectName string `json:"projectName"`
Assets []struct {
AssetExternalEntityID string `json:"assetExternalEntityId"`
AssetName string `json:"assetName"`
Versions []string `json:"versions"`
SubProjects []struct {
SubProjectExternalEntityID string `json:"subProjectExternalEntityId,omitempty"`
SubProjectName string `json:"subProjectName,omitempty"`
SubProjectDescription string `json:"subProjectDescription,omitempty"`
Assets []struct {
AssetExternalEntityID string `json:"assetExternalEntityId"`
AssetName string `json:"assetName"`
AssetVersions []struct {
AssetVersionName string `json:"assetVersionName"`
Artifacts []string `json:"artifacts"`
} `json:"assetVersions,omitempty"`
} `json:"assets"`
} `json:"subProjects,omitempty"`
Assets []struct {
AssetExternalEntityID string `json:"assetExternalEntityId"`
AssetName string `json:"assetName"`
AssetVersions []struct {
AssetVersionName string `json:"assetVersionName"`
Artifacts []string `json:"artifacts"`
} `json:"assetVersions,omitempty"`
} `json:"assets"`
}

Expand Down Expand Up @@ -77,25 +99,76 @@ func (g *DevGuardTarget) LoadImages() ([]kubernetes.ImageInNamespace, error) {
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: fullImage,
Image: fullImage,
},
})
for _, version := range asset.AssetVersions {
for _, artifact := range version.Artifacts {
result = append(result, kubernetes.ImageInNamespace{
Namespace: a.ProjectExternalEntityID,
ContainerName: asset.AssetExternalEntityID,
Image: &libk8s.RegistryImage{
ImageID: g.buildImageNameFromArtifact(artifact),
Image: g.buildImageNameFromArtifact(artifact),
},
})
}
}
}
for _, sp := range a.SubProjects {
for _, asset := range sp.Assets {
for _, version := range asset.AssetVersions {
for _, artifact := range version.Artifacts {
result = append(result, kubernetes.ImageInNamespace{
Namespace: a.ProjectExternalEntityID,
ControllerName: &sp.SubProjectExternalEntityID,
ContainerName: asset.AssetExternalEntityID,
Image: &libk8s.RegistryImage{
Image: g.buildImageNameFromArtifact(artifact),
ImageID: g.buildImageNameFromArtifact(artifact),
},
},
)
}
}
}
}
}

return result, nil
}

func (g *DevGuardTarget) ProcessSbom(ctx *TargetContext) error {
func (g *DevGuardTarget) buildArtifactName(image *libk8s.RegistryImage) string {
if strings.HasPrefix(image.Image, "pkg:oci/") {
return image.Image
}

imageRepo, tag, shortName, err := getRepoWithVersion(image)
if err != nil {
slog.Error("Could not parse image!!!", "image", image.Image)
return image.Image
}
if tag == "" {
tag = "latest"
}
return "pkg:oci/" + shortName + "@" + tag + "?repository_url=" + imageRepo
}

func (g *DevGuardTarget) buildImageNameFromArtifact(artifact string) string {
if !strings.HasPrefix(artifact, "pkg:oci/") {
return artifact
}
parts := strings.SplitN(artifact, "@", 2)
if len(parts) != 2 {
return artifact
}
p := strings.SplitN(parts[1], "?repository_url=", 2)
if len(p) != 2 {
return artifact
}
digest := p[0]
repo := p[1]
return repo + ":" + digest
}

assetName, version := getRepoWithVersion(ctx.Image)
func (g *DevGuardTarget) ProcessSbom(ctx *TargetContext) error {

if ctx.Sbom == "" {
slog.Info("Empty SBOM - skip image", "image", ctx.Image.ImageID)
Expand All @@ -106,11 +179,22 @@ func (g *DevGuardTarget) ProcessSbom(ctx *TargetContext) error {
Verb: "update",
ProjectExternalEntityID: ctx.Pod.PodNamespace,
ProjectName: ctx.Pod.PodNamespace,
AssetExternalEntityID: assetName,
AssetName: assetName,
AssetVersion: version,
ProjectDescription: "Namespace",
AssetExternalEntityID: ctx.Container.Name,
AssetName: ctx.Container.Name,
AssetVersionName: "latest",
Artifact: g.buildArtifactName(ctx.Image),
Sbom: json.RawMessage(ctx.Sbom),
}
if ctx.Pod.OwnerReferences.Kind == "Deployment" || ctx.Pod.OwnerReferences.Kind == "DaemonSet" || ctx.Pod.OwnerReferences.Kind == "StatefulSet" {
payload.SubProjectExternalEntityID = ctx.Pod.OwnerReferences.Name
payload.SubProjectName = ctx.Pod.OwnerReferences.Name
payload.SubProjectDescription = ctx.Pod.OwnerReferences.Kind

payload.AssetDescription = "container"
} else {
payload.AssetDescription = "container controlled by " + ctx.Pod.OwnerReferences.Kind + " " + ctx.Pod.OwnerReferences.Name
}

jsonBody, err := json.Marshal(payload)
if err != nil {
Expand All @@ -124,15 +208,15 @@ func (g *DevGuardTarget) ProcessSbom(ctx *TargetContext) error {

req.Header.Set("Content-Type", "application/json")

slog.Info("Sending SBOM to DevGuard", "assetName", assetName, "version", version)
slog.Info("Sending SBOM to DevGuard", "Namespace", ctx.Pod.PodNamespace, "Container", ctx.Container.Name)

_, err = g.client.Do(req)
if err != nil {
slog.Error("Could not upload SBOM", "err", err)
return err
}

slog.Info("Uploaded SBOM to DevGuard", "assetName", assetName, "version", version)
slog.Info("Uploaded SBOM to DevGuard", "Namespace", ctx.Pod.PodNamespace, "Container", ctx.Container.Name)
return nil
}

Expand All @@ -143,16 +227,23 @@ func (g *DevGuardTarget) Remove(images []kubernetes.ImageInNamespace) error {
wg.Add(1)
go func(img kubernetes.ImageInNamespace) {
defer wg.Done()
slog.Debug("Removing asset from DevGuard", "Namespace", img.Namespace, "Container", img.ContainerName)

name, version := getRepoWithVersion(img.Image)
controllerName := ""
if img.ControllerName != nil {
controllerName = *img.ControllerName

}

payload := DevGuardRequest{
Verb: "delete",
ProjectExternalEntityID: img.Namespace,
ProjectName: img.Namespace,
AssetExternalEntityID: name,
AssetName: name,
AssetVersion: version,
Verb: "delete",
ProjectExternalEntityID: img.Namespace,
ProjectName: img.Namespace,
SubProjectExternalEntityID: controllerName,
AssetExternalEntityID: img.ContainerName,
AssetName: img.ContainerName,
AssetVersionName: "latest",
Artifact: g.buildArtifactName(img.Image),
}

jsonBody, err := json.Marshal(payload)
Expand All @@ -169,7 +260,7 @@ func (g *DevGuardTarget) Remove(images []kubernetes.ImageInNamespace) error {

req.Header.Set("Content-Type", "application/json")

slog.Info("Deleting asset", "projectName", img.Namespace, "assetName", name, "assetVersion", version)
slog.Info("Deleting asset", "Namespace", img.Namespace, "Container", img.ContainerName)

_, err = g.client.Do(req)
if err != nil {
Expand All @@ -183,23 +274,12 @@ func (g *DevGuardTarget) Remove(images []kubernetes.ImageInNamespace) error {
return nil
}

func getRepoWithVersion(image *libk8s.RegistryImage) (string, string) {
func getRepoWithVersion(image *libk8s.RegistryImage) (string, string, string, error) {
imageRef, err := parser.Parse(image.Image)
if err != nil {
slog.Error("Could not parse image", "image", image.Image)
return "", ""
}

projectName := imageRef.Repository()

if strings.Index(image.Image, "sha256") != 0 {
imageRef, err = parser.Parse(image.Image)
if err != nil {
slog.Error("Could not parse image", "image", image.Image)
return "", ""
}
return "", "", "", err
}

version := imageRef.Tag()
return projectName, version
return imageRef.Repository(), imageRef.Tag(), imageRef.ShortName(), nil
}
6 changes: 4 additions & 2 deletions kubernetes/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ import (
)

type ImageInNamespace struct {
Namespace string
Image *oci.RegistryImage
Namespace string
ControllerName *string
ContainerName string
Image *oci.RegistryImage
}

func (i ImageInNamespace) String() string {
Expand Down
Loading
Loading