Skip to content

Commit

Permalink
feat: track process with annotations
Browse files Browse the repository at this point in the history
ref: #18
Signed-off-by: Christian Kotzbauer <[email protected]>
  • Loading branch information
ckotzbauer committed Jan 27, 2022
1 parent 3c60ff8 commit ed59b5a
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 48 deletions.
25 changes: 15 additions & 10 deletions internal/daemon/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,28 +56,33 @@ func (c *CronService) runBackgroundService() {
logrus.Debugf("Discovered %v namespaces", len(namespaces))

sy := syft.New(viper.GetString(internal.ConfigKeyGitWorkingTree), viper.GetString(internal.ConfigKeyGitPath), format)
allImages := []string{}

for _, ns := range namespaces {
pods := client.ListPods(ns.Name, viper.GetString(internal.ConfigKeyPodLabelSelector))
logrus.Debugf("Discovered %v pods in namespace %v", len(pods), ns.Name)
digests := client.GetContainerDigests(pods)
processedSbomFiles := []string{}

for _, d := range digests {
sbomPath, err := sy.ExecuteSyft(d)
// Error is already handled from syft module.
if err == nil {
processedSbomFiles = append(processedSbomFiles, sbomPath)

for _, pod := range pods {
filteredDigests, allPodImages := client.GetContainerDigests(pod)
allImages = append(allImages, allPodImages...)

for _, d := range filteredDigests {
// TODO: Avoid duplicate scans of the same image in different pods.
_, err := sy.ExecuteSyft(d)
// Error is already handled from syft module.
if err == nil {
client.UpdatePodAnnotation(pod)
}
}
}

for _, t := range c.targets {
t.ProcessSboms(processedSbomFiles, ns.Name)
t.ProcessSboms(ns.Name)
}
}

for _, t := range c.targets {
t.Cleanup()
t.Cleanup(allImages)
}

c.printNextExecution()
Expand Down
89 changes: 78 additions & 11 deletions internal/kubernetes/kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package kubernetes

import (
"context"
"fmt"

"github.com/sirupsen/logrus"
corev1 "k8s.io/api/core/v1"
Expand All @@ -21,6 +22,10 @@ type KubeClient struct {
Client *kubernetes.Clientset
}

var (
annotationTemplate = "ckotzbauer.sbom-operator.io/%s"
)

func NewClient() *KubeClient {
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()

Expand Down Expand Up @@ -71,31 +76,93 @@ func (client *KubeClient) ListPods(namespace, labelSelector string) []corev1.Pod
return list.Items
}

func (client *KubeClient) GetContainerDigests(pods []corev1.Pod) []ImageDigest {
func (client *KubeClient) UpdatePodAnnotation(pod corev1.Pod) {
newPod, err := client.Client.CoreV1().Pods(pod.Namespace).Get(context.Background(), pod.Name, meta.GetOptions{})

if err != nil {
logrus.WithError(err).Errorf("Pod %s/%s could not be fetched!", pod.Namespace, pod.Name)
}

ann := newPod.Annotations
if ann == nil {
ann = make(map[string]string)
}

for _, c := range pod.Status.ContainerStatuses {
ann[fmt.Sprintf(annotationTemplate, c.Name)] = c.ImageID
}

for _, c := range pod.Status.InitContainerStatuses {
ann[fmt.Sprintf(annotationTemplate, c.Name)] = c.ImageID
}

for _, c := range pod.Status.EphemeralContainerStatuses {
ann[fmt.Sprintf(annotationTemplate, c.Name)] = c.ImageID
}

newPod.Annotations = ann

_, err = client.Client.CoreV1().Pods(newPod.Namespace).Update(context.Background(), newPod, meta.UpdateOptions{})
if err != nil {
logrus.WithError(err).Errorf("Pod %s/%s could not be updated!", pod.Namespace, pod.Name)
}
}

func (client *KubeClient) GetContainerDigests(pod corev1.Pod) ([]ImageDigest, []string) {
digests := []ImageDigest{}
allImages := []string{}

for _, p := range pods {
pullSecrets, err := client.loadSecrets(p.Namespace, p.Spec.ImagePullSecrets)
annotations := pod.Annotations
pullSecrets, err := client.loadSecrets(pod.Namespace, pod.Spec.ImagePullSecrets)

if err != nil {
logrus.WithError(err).Error("PullSecrets could not be retrieved!")
return []ImageDigest{}
}
if err != nil {
logrus.WithError(err).Error("PullSecrets could not be retrieved!")
return []ImageDigest{}, []string{}
}

for _, c := range p.Status.ContainerStatuses {
for _, c := range pod.Status.ContainerStatuses {
if !hasAnnotation(annotations, c) {
digests = append(digests, ImageDigest{Digest: c.ImageID, Auth: pullSecrets})
} else {
logrus.Debugf("Skip image %s", c.ImageID)
}

for _, c := range p.Status.InitContainerStatuses {
allImages = append(allImages, c.ImageID)
}

for _, c := range pod.Status.InitContainerStatuses {
if !hasAnnotation(annotations, c) {
digests = append(digests, ImageDigest{Digest: c.ImageID, Auth: pullSecrets})
} else {
logrus.Debugf("Skip image %s", c.ImageID)
}

for _, c := range p.Status.EphemeralContainerStatuses {
allImages = append(allImages, c.ImageID)
}

for _, c := range pod.Status.EphemeralContainerStatuses {
if !hasAnnotation(annotations, c) {
digests = append(digests, ImageDigest{Digest: c.ImageID, Auth: pullSecrets})
} else {
logrus.Debugf("Skip image %s", c.ImageID)
}

allImages = append(allImages, c.ImageID)
}

return removeDuplicateValues(digests), allImages
}

func hasAnnotation(annotations map[string]string, status corev1.ContainerStatus) bool {
if annotations == nil {
return false
}

if val, ok := annotations[fmt.Sprintf(annotationTemplate, status.Name)]; ok {
return val == status.ImageID
}

return removeDuplicateValues(digests)
return false
}

func removeDuplicateValues(slice []ImageDigest) []ImageDigest {
Expand Down
53 changes: 35 additions & 18 deletions internal/target/git_target.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"os"
"path"
"path/filepath"
"strings"

"github.com/ckotzbauer/sbom-operator/internal"
"github.com/ckotzbauer/sbom-operator/internal/syft"
Expand All @@ -14,12 +15,11 @@ import (
)

type GitTarget struct {
workingTree string
workPath string
repository string
branch string
gitAccount git.GitAccount
processedSbomFiles []string
workingTree string
workPath string
repository string
branch string
gitAccount git.GitAccount
}

func NewGitTarget() *GitTarget {
Expand All @@ -34,12 +34,11 @@ func NewGitTarget() *GitTarget {
viper.GetString(internal.ConfigKeyGitAuthorEmail))

return &GitTarget{
workingTree: workingTree,
workPath: workPath,
repository: repository,
branch: branch,
gitAccount: gitAccount,
processedSbomFiles: []string{},
workingTree: workingTree,
workPath: workPath,
repository: repository,
branch: branch,
gitAccount: gitAccount,
}
}

Expand Down Expand Up @@ -77,27 +76,45 @@ func (g *GitTarget) Initialize() {
viper.GetString(internal.ConfigKeyGitBranch))
}

func (g *GitTarget) ProcessSboms(sbomFiles []string, namespace string) {
func (g *GitTarget) ProcessSboms(namespace string) {
g.gitAccount.CommitAll(g.workingTree, fmt.Sprintf("Created new SBOMs for pods in namespace %s", namespace))
g.processedSbomFiles = append(g.processedSbomFiles, sbomFiles...)
}

func (g *GitTarget) Cleanup() {
func (g *GitTarget) Cleanup(allImages []string) {
logrus.Debug("Start to remove old SBOMs")
ignoreDirs := []string{".git"}
format := viper.GetString(internal.ConfigKeyFormat)

fileName := syft.GetFileName(format)
allProcessedFiles := g.mapToFiles(allImages)

err := filepath.Walk(g.workPath, g.deleteObsoleteFiles(fileName, ignoreDirs))
err := filepath.Walk(g.workPath, g.deleteObsoleteFiles(fileName, ignoreDirs, allProcessedFiles))
if err != nil {
logrus.WithError(err).Error("Could not cleanup old SBOMs")
} else {
g.gitAccount.CommitAndPush(g.workingTree, "Deleted old SBOMs")
}
}

func (g *GitTarget) deleteObsoleteFiles(fileName string, ignoreDirs []string) filepath.WalkFunc {
func (g *GitTarget) mapToFiles(allImages []string) []string {
paths := []string{}

gitWorkingTree := viper.GetString(internal.ConfigKeyGitWorkingTree)
gitPath := viper.GetString(internal.ConfigKeyGitPath)
sbomFormat := viper.GetString(internal.ConfigKeyFormat)

// TODO: Refactor this, syft-package should not rely on git-logic.
for _, img := range allImages {
fileName := syft.GetFileName(sbomFormat)
filePath := strings.ReplaceAll(img, "@", "/")
filePath = strings.ReplaceAll(path.Join(gitWorkingTree, gitPath, filePath, fileName), ":", "_")
paths = append(paths, filePath)
}

return paths
}

func (g *GitTarget) deleteObsoleteFiles(fileName string, ignoreDirs, allProcessedFiles []string) filepath.WalkFunc {
return func(p string, info os.FileInfo, err error) error {
if err != nil {
logrus.WithError(err).Errorf("An error occurred while processing %s", p)
Expand All @@ -115,7 +132,7 @@ func (g *GitTarget) deleteObsoleteFiles(fileName string, ignoreDirs []string) fi

if info.Name() == fileName {
found := false
for _, f := range g.processedSbomFiles {
for _, f := range allProcessedFiles {
if f == p {
found = true
break
Expand Down
4 changes: 2 additions & 2 deletions internal/target/target.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ package target
type Target interface {
Initialize()
ValidateConfig() error
ProcessSboms(sbomFiles []string, namespace string)
Cleanup()
ProcessSboms(namespace string)
Cleanup(allImages []string)
}
7 changes: 0 additions & 7 deletions internal/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"fmt"
"io"
"math/rand"
"os"
"strings"

"github.com/sirupsen/logrus"
Expand Down Expand Up @@ -51,12 +50,6 @@ func Unescape(s string) string {
return s
}

// PathExists checks if the specificed path exists on the FS
func PathExists(path string) bool {
_, err := os.Stat(path)
return err == nil
}

const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

// RandStringBytes generates a random string with the given length
Expand Down

0 comments on commit ed59b5a

Please sign in to comment.