Skip to content

Commit

Permalink
[CWS] Move tags from cgroup to tags resolver (#31874)
Browse files Browse the repository at this point in the history
  • Loading branch information
lebauce authored Dec 10, 2024
1 parent 385f25a commit 5ff3a47
Show file tree
Hide file tree
Showing 11 changed files with 88 additions and 79 deletions.
30 changes: 2 additions & 28 deletions pkg/security/resolvers/cgroup/model/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,15 @@ import (

"github.com/DataDog/datadog-agent/pkg/security/secl/containerutils"
"github.com/DataDog/datadog-agent/pkg/security/secl/model"
"github.com/DataDog/datadog-agent/pkg/security/utils"
)

// CacheEntry cgroup resolver cache entry
type CacheEntry struct {
model.CGroupContext
model.ContainerContext
sync.RWMutex
Deleted *atomic.Bool
WorkloadSelector WorkloadSelector
PIDs map[uint32]bool
Deleted *atomic.Bool
PIDs map[uint32]bool
}

// NewCacheEntry returns a new instance of a CacheEntry
Expand Down Expand Up @@ -78,27 +76,3 @@ func (cgce *CacheEntry) AddPID(pid uint32) {

cgce.PIDs[pid] = true
}

// SetTags sets the tags for the provided workload
func (cgce *CacheEntry) SetTags(tags []string) {
cgce.Lock()
defer cgce.Unlock()

cgce.Tags = tags
cgce.WorkloadSelector.Image = utils.GetTagValue("image_name", tags)
cgce.WorkloadSelector.Tag = utils.GetTagValue("image_tag", tags)
if len(cgce.WorkloadSelector.Image) != 0 && len(cgce.WorkloadSelector.Tag) == 0 {
cgce.WorkloadSelector.Tag = "latest"
}
}

// GetWorkloadSelectorCopy returns a copy of the workload selector of this cgroup
func (cgce *CacheEntry) GetWorkloadSelectorCopy() *WorkloadSelector {
cgce.Lock()
defer cgce.Unlock()

return &WorkloadSelector{
Image: cgce.WorkloadSelector.Image,
Tag: cgce.WorkloadSelector.Tag,
}
}
5 changes: 5 additions & 0 deletions pkg/security/resolvers/cgroup/model/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,8 @@ func (ws WorkloadSelector) ToTags() []string {
"image_tag:" + ws.Tag,
}
}

// Copy returns a copy of the selector
func (ws WorkloadSelector) Copy() *WorkloadSelector {
return &ws
}
11 changes: 6 additions & 5 deletions pkg/security/resolvers/sbom/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (
"github.com/DataDog/datadog-agent/pkg/security/config"
"github.com/DataDog/datadog-agent/pkg/security/metrics"
cgroupModel "github.com/DataDog/datadog-agent/pkg/security/resolvers/cgroup/model"
"github.com/DataDog/datadog-agent/pkg/security/resolvers/tags"
"github.com/DataDog/datadog-agent/pkg/security/secl/containerutils"
"github.com/DataDog/datadog-agent/pkg/security/secl/model"
"github.com/DataDog/datadog-agent/pkg/security/seclog"
Expand Down Expand Up @@ -486,24 +487,24 @@ func (r *Resolver) triggerScan(sbom *SBOM) {
}

// OnWorkloadSelectorResolvedEvent is used to handle the creation of a new cgroup with its resolved tags
func (r *Resolver) OnWorkloadSelectorResolvedEvent(cgroup *cgroupModel.CacheEntry) {
func (r *Resolver) OnWorkloadSelectorResolvedEvent(workload *tags.Workload) {
r.sbomsLock.Lock()
defer r.sbomsLock.Unlock()

if cgroup == nil {
if workload == nil {
return
}

id := cgroup.ContainerID
id := workload.ContainerID
// We don't scan hosts for now
if len(id) == 0 {
return
}

_, ok := r.sboms[id]
if !ok {
workloadKey := getWorkloadKey(cgroup.GetWorkloadSelectorCopy())
sbom, err := r.newWorkloadEntry(id, cgroup, workloadKey)
workloadKey := getWorkloadKey(workload.Selector.Copy())
sbom, err := r.newWorkloadEntry(id, workload.CacheEntry, workloadKey)
if err != nil {
seclog.Errorf("couldn't create new SBOM entry for sbom '%s': %v", id, err)
}
Expand Down
3 changes: 2 additions & 1 deletion pkg/security/resolvers/sbom/resolver_unsupported.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (

"github.com/DataDog/datadog-agent/pkg/security/config"
cgroupModel "github.com/DataDog/datadog-agent/pkg/security/resolvers/cgroup/model"
"github.com/DataDog/datadog-agent/pkg/security/resolvers/tags"
"github.com/DataDog/datadog-agent/pkg/security/secl/containerutils"
"github.com/DataDog/datadog-agent/pkg/security/secl/model"
)
Expand All @@ -33,7 +34,7 @@ func (r *Resolver) OnCGroupDeletedEvent(_ *cgroupModel.CacheEntry) {
}

// OnWorkloadSelectorResolvedEvent is used to handle the creation of a new cgroup with its resolved tags
func (r *Resolver) OnWorkloadSelectorResolvedEvent(_ *cgroupModel.CacheEntry) {
func (r *Resolver) OnWorkloadSelectorResolvedEvent(_ *tags.Workload) {
}

// ResolvePackage returns the Package that owns the provided file
Expand Down
2 changes: 2 additions & 0 deletions pkg/security/resolvers/tags/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ type Event int
const (
// WorkloadSelectorResolved is used to notify that a new cgroup with a resolved workload selector is ready
WorkloadSelectorResolved Event = iota
// WorkloadSelectorDeleted is used to notify that a cgroup with a resolved workload selector is deleted
WorkloadSelectorDeleted
)

// Tagger defines a Tagger for the Tags Resolver
Expand Down
52 changes: 36 additions & 16 deletions pkg/security/resolvers/tags/resolver_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,26 @@ import (

"github.com/DataDog/datadog-agent/pkg/security/resolvers/cgroup"
cgroupModel "github.com/DataDog/datadog-agent/pkg/security/resolvers/cgroup/model"
"github.com/DataDog/datadog-agent/pkg/security/secl/containerutils"
"github.com/DataDog/datadog-agent/pkg/security/seclog"
"github.com/DataDog/datadog-agent/pkg/security/utils"
)

type pendingWorkload struct {
// Workload represents a workload along with its tags
type Workload struct {
*cgroupModel.CacheEntry
retries int
Tags []string
Selector cgroupModel.WorkloadSelector
retries int
}

// LinuxResolver represents a default resolver based directly on the underlying tagger
type LinuxResolver struct {
*DefaultResolver
*utils.Notifier[Event, *cgroupModel.CacheEntry]
workloadsWithoutTags chan *pendingWorkload
*utils.Notifier[Event, *Workload]
workloadsWithoutTags chan *Workload
cgroupResolver *cgroup.Resolver
workloads map[containerutils.CGroupID]*Workload
}

// Start the resolver
Expand All @@ -37,12 +42,19 @@ func (t *LinuxResolver) Start(ctx context.Context) error {
}

if err := t.cgroupResolver.RegisterListener(cgroup.CGroupCreated, func(cgce *cgroupModel.CacheEntry) {
workload := &pendingWorkload{CacheEntry: cgce, retries: 3}
workload := &Workload{CacheEntry: cgce, retries: 3}
t.workloads[cgce.CGroupID] = workload
t.checkTags(workload)
}); err != nil {
return err
}

if err := t.cgroupResolver.RegisterListener(cgroup.CGroupDeleted, func(cgce *cgroupModel.CacheEntry) {
delete(t.workloads, cgce.CGroupID)
}); err != nil {
return err
}

go func() {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
Expand Down Expand Up @@ -73,17 +85,18 @@ func (t *LinuxResolver) Start(ctx context.Context) error {
return nil
}

func needsTagsResolution(cgce *cgroupModel.CacheEntry) bool {
return len(cgce.ContainerID) != 0 && !cgce.WorkloadSelector.IsReady()
func needsTagsResolution(workload *Workload) bool {
return len(workload.ContainerID) != 0 && !workload.Selector.IsReady()
}

// checkTags checks if the tags of a workload were properly set
func (t *LinuxResolver) checkTags(pendingWorkload *pendingWorkload) {
workload := pendingWorkload.CacheEntry
func (t *LinuxResolver) checkTags(pendingWorkload *Workload) {
workload := pendingWorkload
// check if the workload tags were found or if it was deleted
if !workload.Deleted.Load() && needsTagsResolution(workload) {
// this is an alive cgroup, try to resolve its tags now
if err := t.fetchTags(workload); err != nil || needsTagsResolution(workload) {
err := t.fetchTags(workload)
if err != nil || needsTagsResolution(workload) {
if pendingWorkload.retries--; pendingWorkload.retries >= 0 {
// push to the workloadsWithoutTags chan so that its tags can be resolved later
select {
Expand All @@ -102,22 +115,29 @@ func (t *LinuxResolver) checkTags(pendingWorkload *pendingWorkload) {
}

// fetchTags fetches tags for the provided workload
func (t *LinuxResolver) fetchTags(container *cgroupModel.CacheEntry) error {
newTags, err := t.ResolveWithErr(container.ContainerID)
func (t *LinuxResolver) fetchTags(workload *Workload) error {
newTags, err := t.ResolveWithErr(workload.ContainerID)
if err != nil {
return fmt.Errorf("failed to resolve %s: %w", container.ContainerID, err)
return fmt.Errorf("failed to resolve %s: %w", workload.ContainerID, err)
}
container.SetTags(newTags)

workload.Selector.Image = utils.GetTagValue("image_name", newTags)
workload.Selector.Tag = utils.GetTagValue("image_tag", newTags)
if len(workload.Selector.Image) != 0 && len(workload.Selector.Tag) == 0 {
workload.Selector.Tag = "latest"
}

return nil
}

// NewResolver returns a new tags resolver
func NewResolver(tagger Tagger, cgroupsResolver *cgroup.Resolver) *LinuxResolver {
resolver := &LinuxResolver{
Notifier: utils.NewNotifier[Event, *cgroupModel.CacheEntry](),
Notifier: utils.NewNotifier[Event, *Workload](),
DefaultResolver: NewDefaultResolver(tagger),
workloadsWithoutTags: make(chan *pendingWorkload, 100),
workloadsWithoutTags: make(chan *Workload, 100),
cgroupResolver: cgroupsResolver,
workloads: make(map[containerutils.CGroupID]*Workload),
}
return resolver
}
3 changes: 2 additions & 1 deletion pkg/security/security_profile/dump/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
"github.com/DataDog/datadog-agent/pkg/security/proto/api"
"github.com/DataDog/datadog-agent/pkg/security/resolvers"
cgroupModel "github.com/DataDog/datadog-agent/pkg/security/resolvers/cgroup/model"
"github.com/DataDog/datadog-agent/pkg/security/resolvers/tags"
"github.com/DataDog/datadog-agent/pkg/security/secl/model"
"github.com/DataDog/datadog-agent/pkg/security/seclog"
activity_tree "github.com/DataDog/datadog-agent/pkg/security/security_profile/activity_tree"
Expand All @@ -47,7 +48,7 @@ type ActivityDumpHandler interface {

// SecurityProfileManager is a generic interface used to communicate with the Security Profile manager
type SecurityProfileManager interface {
FetchSilentWorkloads() map[cgroupModel.WorkloadSelector][]*cgroupModel.CacheEntry
FetchSilentWorkloads() map[cgroupModel.WorkloadSelector][]*tags.Workload
OnLocalStorageCleanup(files []string)
}

Expand Down
33 changes: 16 additions & 17 deletions pkg/security/security_profile/profile/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import (
"github.com/DataDog/datadog-agent/pkg/security/metrics"
"github.com/DataDog/datadog-agent/pkg/security/proto/api"
"github.com/DataDog/datadog-agent/pkg/security/resolvers"
"github.com/DataDog/datadog-agent/pkg/security/resolvers/cgroup"
cgroupModel "github.com/DataDog/datadog-agent/pkg/security/resolvers/cgroup/model"
"github.com/DataDog/datadog-agent/pkg/security/resolvers/tags"
"github.com/DataDog/datadog-agent/pkg/security/secl/containerutils"
Expand Down Expand Up @@ -228,7 +227,7 @@ func (m *SecurityProfileManager) Start(ctx context.Context) {

// register the manager to the CGroup resolver
_ = m.resolvers.TagsResolver.RegisterListener(tags.WorkloadSelectorResolved, m.OnWorkloadSelectorResolvedEvent)
_ = m.resolvers.CGroupResolver.RegisterListener(cgroup.CGroupDeleted, m.OnCGroupDeletedEvent)
_ = m.resolvers.TagsResolver.RegisterListener(tags.WorkloadSelectorDeleted, m.OnWorkloadDeletedEvent)

seclog.Infof("security profile manager started")

Expand All @@ -250,7 +249,7 @@ func (m *SecurityProfileManager) propagateWorkloadSelectorsToProviders() {
}

// OnWorkloadSelectorResolvedEvent is used to handle the creation of a new cgroup with its resolved tags
func (m *SecurityProfileManager) OnWorkloadSelectorResolvedEvent(workload *cgroupModel.CacheEntry) {
func (m *SecurityProfileManager) OnWorkloadSelectorResolvedEvent(workload *tags.Workload) {
m.profilesLock.Lock()
defer m.profilesLock.Unlock()
workload.Lock()
Expand All @@ -261,7 +260,7 @@ func (m *SecurityProfileManager) OnWorkloadSelectorResolvedEvent(workload *cgrou
return
}

selector := workload.WorkloadSelector
selector := workload.Selector
selector.Tag = "*"

// check if the workload of this selector already exists
Expand Down Expand Up @@ -307,7 +306,7 @@ func (m *SecurityProfileManager) OnWorkloadSelectorResolvedEvent(workload *cgrou
}

// LinkProfile applies a profile to the provided workload
func (m *SecurityProfileManager) LinkProfile(profile *SecurityProfile, workload *cgroupModel.CacheEntry) {
func (m *SecurityProfileManager) LinkProfile(profile *SecurityProfile, workload *tags.Workload) {
profile.Lock()
defer profile.Unlock()

Expand All @@ -329,7 +328,7 @@ func (m *SecurityProfileManager) LinkProfile(profile *SecurityProfile, workload
}

// UnlinkProfile removes the link between a workload and a profile
func (m *SecurityProfileManager) UnlinkProfile(profile *SecurityProfile, workload *cgroupModel.CacheEntry) {
func (m *SecurityProfileManager) UnlinkProfile(profile *SecurityProfile, workload *tags.Workload) {
profile.Lock()
defer profile.Unlock()

Expand Down Expand Up @@ -394,11 +393,11 @@ func FillProfileContextFromProfile(ctx *model.SecurityProfileContext, profile *S
}
}

// OnCGroupDeletedEvent is used to handle a CGroupDeleted event
func (m *SecurityProfileManager) OnCGroupDeletedEvent(workload *cgroupModel.CacheEntry) {
// OnWorkloadDeletedEvent is used to handle a WorkloadDeleted event
func (m *SecurityProfileManager) OnWorkloadDeletedEvent(workload *tags.Workload) {
// lookup the profile
selector := cgroupModel.WorkloadSelector{
Image: workload.WorkloadSelector.Image,
Image: workload.Selector.Image,
Tag: "*",
}
profile := m.GetProfile(selector)
Expand Down Expand Up @@ -640,24 +639,24 @@ func (m *SecurityProfileManager) unloadProfile(profile *SecurityProfile) {
}

// linkProfile (thread unsafe) updates the kernel space mapping between a workload and its profile
func (m *SecurityProfileManager) linkProfile(profile *SecurityProfile, workload *cgroupModel.CacheEntry) {
func (m *SecurityProfileManager) linkProfile(profile *SecurityProfile, workload *tags.Workload) {
if err := m.securityProfileMap.Put([]byte(workload.ContainerID), profile.profileCookie); err != nil {
seclog.Errorf("couldn't link workload %s (selector: %s) with profile %s (check map size limit ?): %v", workload.ContainerID, workload.WorkloadSelector.String(), profile.Metadata.Name, err)
seclog.Errorf("couldn't link workload %s (selector: %s) with profile %s (check map size limit ?): %v", workload.ContainerID, workload.Selector.String(), profile.Metadata.Name, err)
return
}
seclog.Infof("workload %s (selector: %s) successfully linked to profile %s", workload.ContainerID, workload.WorkloadSelector.String(), profile.Metadata.Name)
seclog.Infof("workload %s (selector: %s) successfully linked to profile %s", workload.ContainerID, workload.Selector.String(), profile.Metadata.Name)
}

// unlinkProfile (thread unsafe) updates the kernel space mapping between a workload and its profile
func (m *SecurityProfileManager) unlinkProfile(profile *SecurityProfile, workload *cgroupModel.CacheEntry) {
func (m *SecurityProfileManager) unlinkProfile(profile *SecurityProfile, workload *tags.Workload) {
if !profile.loadedInKernel {
return
}

if err := m.securityProfileMap.Delete([]byte(workload.ContainerID)); err != nil {
seclog.Errorf("couldn't unlink workload %s (selector: %s) with profile %s: %v", workload.ContainerID, workload.WorkloadSelector.String(), profile.Metadata.Name, err)
seclog.Errorf("couldn't unlink workload %s (selector: %s) with profile %s: %v", workload.ContainerID, workload.Selector.String(), profile.Metadata.Name, err)
}
seclog.Infof("workload %s (selector: %s) successfully unlinked from profile %s", workload.ContainerID, workload.WorkloadSelector.String(), profile.Metadata.Name)
seclog.Infof("workload %s (selector: %s) successfully unlinked from profile %s", workload.ContainerID, workload.Selector.String(), profile.Metadata.Name)
}

func (m *SecurityProfileManager) canGenerateAnomaliesFor(e *model.Event) bool {
Expand Down Expand Up @@ -960,11 +959,11 @@ func (m *SecurityProfileManager) SaveSecurityProfile(params *api.SecurityProfile
}

// FetchSilentWorkloads returns the list of workloads for which we haven't received any profile
func (m *SecurityProfileManager) FetchSilentWorkloads() map[cgroupModel.WorkloadSelector][]*cgroupModel.CacheEntry {
func (m *SecurityProfileManager) FetchSilentWorkloads() map[cgroupModel.WorkloadSelector][]*tags.Workload {
m.profilesLock.Lock()
defer m.profilesLock.Unlock()

out := make(map[cgroupModel.WorkloadSelector][]*cgroupModel.CacheEntry)
out := make(map[cgroupModel.WorkloadSelector][]*tags.Workload)

for selector, profile := range m.profiles {
profile.Lock()
Expand Down
12 changes: 7 additions & 5 deletions pkg/security/security_profile/profile/manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (

"github.com/DataDog/datadog-agent/pkg/security/config"
cgroupModel "github.com/DataDog/datadog-agent/pkg/security/resolvers/cgroup/model"
"github.com/DataDog/datadog-agent/pkg/security/resolvers/tags"
"github.com/DataDog/datadog-agent/pkg/security/secl/containerutils"
"github.com/DataDog/datadog-agent/pkg/security/secl/model"
activity_tree "github.com/DataDog/datadog-agent/pkg/security/security_profile/activity_tree"
Expand Down Expand Up @@ -841,14 +842,15 @@ func TestSecurityProfileManager_tryAutolearn(t *testing.T) {
if ti.newProfile || profile == nil {
profile = NewSecurityProfile(cgroupModel.WorkloadSelector{Image: "image", Tag: "tag"}, []model.EventType{model.ExecEventType, model.DNSEventType}, nil)
profile.ActivityTree = activity_tree.NewActivityTree(profile, nil, "security_profile")
profile.Instances = append(profile.Instances, &cgroupModel.CacheEntry{
ContainerContext: model.ContainerContext{
profile.Instances = append(profile.Instances, &tags.Workload{
CacheEntry: &cgroupModel.CacheEntry{ContainerContext: model.ContainerContext{
ContainerID: containerutils.ContainerID(defaultContainerID),
},
CGroupContext: model.CGroupContext{
CGroupID: containerutils.CGroupID(defaultContainerID),
CGroupContext: model.CGroupContext{
CGroupID: containerutils.CGroupID(defaultContainerID),
},
},
WorkloadSelector: cgroupModel.WorkloadSelector{Image: "image", Tag: "tag"},
Selector: cgroupModel.WorkloadSelector{Image: "image", Tag: "tag"},
})
profile.loadedNano = uint64(t0.UnixNano())
}
Expand Down
Loading

0 comments on commit 5ff3a47

Please sign in to comment.