diff --git a/pkg/security/probe/opts_linux.go b/pkg/security/probe/opts_linux.go index 984c8f1c4bb3e..4e3f295403760 100644 --- a/pkg/security/probe/opts_linux.go +++ b/pkg/security/probe/opts_linux.go @@ -23,8 +23,8 @@ type Opts struct { PathResolutionEnabled bool // EnvsVarResolutionEnabled defines if environment variables resolution is enabled EnvsVarResolutionEnabled bool - // TagsResolver will override the default one. Mainly here for tests. - TagsResolver tags.Resolver + // Tagger will override the default one. Mainly here for tests. + Tagger tags.Tagger // SyscallsMonitorEnabled enable syscalls map monitor SyscallsMonitorEnabled bool // TTYFallbackEnabled enable the tty procfs fallback diff --git a/pkg/security/probe/probe_ebpf.go b/pkg/security/probe/probe_ebpf.go index cf28d72d46ccc..9aad81cf24775 100644 --- a/pkg/security/probe/probe_ebpf.go +++ b/pkg/security/probe/probe_ebpf.go @@ -2019,7 +2019,7 @@ func NewEBPFProbe(probe *Probe, config *config.Config, opts Opts, telemetry tele resolversOpts := resolvers.Opts{ PathResolutionEnabled: probe.Opts.PathResolutionEnabled, EnvVarsResolutionEnabled: probe.Opts.EnvsVarResolutionEnabled, - TagsResolver: probe.Opts.TagsResolver, + Tagger: probe.Opts.Tagger, UseRingBuffer: useRingBuffers, TTYFallbackEnabled: probe.Opts.TTYFallbackEnabled, } diff --git a/pkg/security/probe/probe_ebpfless.go b/pkg/security/probe/probe_ebpfless.go index 58298eb8299f3..0d6ede88571ec 100644 --- a/pkg/security/probe/probe_ebpfless.go +++ b/pkg/security/probe/probe_ebpfless.go @@ -679,7 +679,7 @@ func NewEBPFLessProbe(probe *Probe, config *config.Config, opts Opts, telemetry } resolversOpts := resolvers.Opts{ - TagsResolver: opts.TagsResolver, + Tagger: opts.Tagger, } p.Resolvers, err = resolvers.NewEBPFLessResolvers(config, p.statsdClient, probe.scrubber, resolversOpts, telemetry) diff --git a/pkg/security/resolvers/cgroup/resolver.go b/pkg/security/resolvers/cgroup/resolver.go index 1e60d8de0c1f8..28a85576fee8a 100644 --- a/pkg/security/resolvers/cgroup/resolver.go +++ b/pkg/security/resolvers/cgroup/resolver.go @@ -10,62 +10,56 @@ package cgroup import ( "context" - "fmt" "sync" - "time" "github.com/hashicorp/golang-lru/v2/simplelru" 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" + "github.com/DataDog/datadog-agent/pkg/security/utils" ) // Event defines the cgroup event type type Event int const ( - // WorkloadSelectorResolved is used to notify that a new cgroup with a resolved workload selector is ready - WorkloadSelectorResolved Event = iota // CGroupDeleted is used to notify that a cgroup was deleted - CGroupDeleted + CGroupDeleted Event = iota + 1 // CGroupCreated new croup created CGroupCreated // CGroupMaxEvent is used cap the event ID CGroupMaxEvent ) -// Listener is used to propagate CGroup events -type Listener func(workload *cgroupModel.CacheEntry) +// ResolverInterface defines the interface implemented by a cgroup resolver +type ResolverInterface interface { + Start(context.Context) + AddPID(*model.ProcessCacheEntry) + GetWorkload(string) (*cgroupModel.CacheEntry, bool) + DelPID(uint32) + DelPIDWithID(string, uint32) + Len() int + RegisterListener(Event, utils.Listener[*cgroupModel.CacheEntry]) error +} // Resolver defines a cgroup monitor type Resolver struct { + *utils.Notifier[Event, *cgroupModel.CacheEntry] sync.RWMutex - workloads *simplelru.LRU[string, *cgroupModel.CacheEntry] - tagsResolver tags.Resolver - workloadsWithoutTags chan *cgroupModel.CacheEntry - - listenersLock sync.Mutex - listeners map[Event][]Listener + workloads *simplelru.LRU[string, *cgroupModel.CacheEntry] } // NewResolver returns a new cgroups monitor -func NewResolver(tagsResolver tags.Resolver) (*Resolver, error) { +func NewResolver() (*Resolver, error) { cr := &Resolver{ - tagsResolver: tagsResolver, - workloadsWithoutTags: make(chan *cgroupModel.CacheEntry, 100), - listeners: make(map[Event][]Listener), + Notifier: utils.NewNotifier[Event, *cgroupModel.CacheEntry](), } workloads, err := simplelru.NewLRU(1024, func(_ string, value *cgroupModel.CacheEntry) { value.CallReleaseCallback() value.Deleted.Store(true) - cr.listenersLock.Lock() - defer cr.listenersLock.Unlock() - for _, l := range cr.listeners[CGroupDeleted] { - l(value) - } + cr.NotifyListeners(CGroupDeleted, value) }) if err != nil { return nil, err @@ -75,45 +69,7 @@ func NewResolver(tagsResolver tags.Resolver) (*Resolver, error) { } // Start starts the goroutine of the SBOM resolver -func (cr *Resolver) Start(ctx context.Context) { - go func() { - ctx, cancel := context.WithCancel(ctx) - defer cancel() - - delayerTick := time.NewTicker(10 * time.Second) - defer delayerTick.Stop() - - for { - select { - case <-ctx.Done(): - return - case <-delayerTick.C: - select { - case workload := <-cr.workloadsWithoutTags: - cr.checkTags(workload) - default: - } - - } - } - }() -} - -// RegisterListener registers a CGroup event listener -func (cr *Resolver) RegisterListener(event Event, listener Listener) error { - if event >= CGroupMaxEvent || event < 0 { - return fmt.Errorf("invalid Event: %v", event) - } - - cr.listenersLock.Lock() - defer cr.listenersLock.Unlock() - - if cr.listeners != nil { - cr.listeners[event] = append(cr.listeners[event], listener) - } else { - return fmt.Errorf("a Listener was inserted before initialization") - } - return nil +func (cr *Resolver) Start(_ context.Context) { } // AddPID associates a container id and a pid which is expected to be the pid 1 @@ -139,48 +95,7 @@ func (cr *Resolver) AddPID(process *model.ProcessCacheEntry) { // add the new CGroup to the cache cr.workloads.Add(string(process.ContainerID), newCGroup) - // notify listeners - cr.listenersLock.Lock() - for _, l := range cr.listeners[CGroupCreated] { - l(newCGroup) - } - cr.listenersLock.Unlock() - - // check the tags of this workload - cr.checkTags(newCGroup) -} - -// checkTags checks if the tags of a workload were properly set -func (cr *Resolver) checkTags(workload *cgroupModel.CacheEntry) { - // check if the workload tags were found - if workload.NeedsTagsResolution() { - // this is a container, try to resolve its tags now - if err := cr.fetchTags(workload); err != nil || workload.NeedsTagsResolution() { - // push to the workloadsWithoutTags chan so that its tags can be resolved later - select { - case cr.workloadsWithoutTags <- workload: - default: - } - return - } - } - - // notify listeners - cr.listenersLock.Lock() - defer cr.listenersLock.Unlock() - for _, l := range cr.listeners[WorkloadSelectorResolved] { - l(workload) - } -} - -// fetchTags fetches tags for the provided workload -func (cr *Resolver) fetchTags(workload *cgroupModel.CacheEntry) error { - newTags, err := cr.tagsResolver.ResolveWithErr(string(workload.ContainerID)) - if err != nil { - return fmt.Errorf("failed to resolve %s: %w", workload.ContainerID, err) - } - workload.SetTags(newTags) - return nil + cr.NotifyListeners(CGroupCreated, newCGroup) } // GetWorkload returns the workload referenced by the provided ID diff --git a/pkg/security/resolvers/mount/resolver_test.go b/pkg/security/resolvers/mount/resolver_test.go index e4b5894cf465a..3182b8c72c9ae 100644 --- a/pkg/security/resolvers/mount/resolver_test.go +++ b/pkg/security/resolvers/mount/resolver_test.go @@ -436,7 +436,7 @@ func TestMountResolver(t *testing.T) { pid uint32 = 1 ) - cr, _ := cgroup.NewResolver(nil) + cr, _ := cgroup.NewResolver() // Create mount resolver mr, _ := NewResolver(nil, cr, ResolverOpts{}) diff --git a/pkg/security/resolvers/opts_linux.go b/pkg/security/resolvers/opts_linux.go index fa17ac94dab9f..fbc5441a45089 100644 --- a/pkg/security/resolvers/opts_linux.go +++ b/pkg/security/resolvers/opts_linux.go @@ -12,7 +12,7 @@ import "github.com/DataDog/datadog-agent/pkg/security/resolvers/tags" type Opts struct { PathResolutionEnabled bool EnvVarsResolutionEnabled bool - TagsResolver tags.Resolver + Tagger tags.Tagger UseRingBuffer bool TTYFallbackEnabled bool } diff --git a/pkg/security/resolvers/resolvers_ebpf.go b/pkg/security/resolvers/resolvers_ebpf.go index 0ea241aac7195..39343334d1b8d 100644 --- a/pkg/security/resolvers/resolvers_ebpf.go +++ b/pkg/security/resolvers/resolvers_ebpf.go @@ -50,7 +50,7 @@ type EBPFResolvers struct { ContainerResolver *container.Resolver TimeResolver *ktime.Resolver UserGroupResolver *usergroup.Resolver - TagsResolver tags.Resolver + TagsResolver *tags.LinuxResolver DentryResolver *dentry.Resolver ProcessResolver *process.EBPFResolver NamespaceResolver *netns.Resolver @@ -91,18 +91,13 @@ func NewEBPFResolvers(config *config.Config, manager *manager.Manager, statsdCli } } - var tagsResolver tags.Resolver - if opts.TagsResolver != nil { - tagsResolver = opts.TagsResolver - } else { - tagsResolver = tags.NewResolver(telemetry) - } - - cgroupsResolver, err := cgroup.NewResolver(tagsResolver) + cgroupsResolver, err := cgroup.NewResolver() if err != nil { return nil, err } + tagsResolver := tags.NewResolver(telemetry, opts.Tagger, cgroupsResolver) + userGroupResolver, err := usergroup.NewResolver(cgroupsResolver) if err != nil { return nil, err @@ -112,7 +107,7 @@ func NewEBPFResolvers(config *config.Config, manager *manager.Manager, statsdCli if err := cgroupsResolver.RegisterListener(cgroup.CGroupDeleted, sbomResolver.OnCGroupDeletedEvent); err != nil { return nil, err } - if err := cgroupsResolver.RegisterListener(cgroup.WorkloadSelectorResolved, sbomResolver.OnWorkloadSelectorResolvedEvent); err != nil { + if err := tagsResolver.RegisterListener(tags.WorkloadSelectorResolved, sbomResolver.OnWorkloadSelectorResolvedEvent); err != nil { return nil, err } } diff --git a/pkg/security/resolvers/resolvers_ebpfless.go b/pkg/security/resolvers/resolvers_ebpfless.go index 93a8d97f0ead7..6713106a1f86c 100644 --- a/pkg/security/resolvers/resolvers_ebpfless.go +++ b/pkg/security/resolvers/resolvers_ebpfless.go @@ -26,20 +26,19 @@ import ( // EBPFLessResolvers holds the list of the event attribute resolvers type EBPFLessResolvers struct { ContainerResolver *container.Resolver - TagsResolver tags.Resolver + TagsResolver *tags.LinuxResolver ProcessResolver *process.EBPFLessResolver HashResolver *hash.Resolver } // NewEBPFLessResolvers creates a new instance of EBPFLessResolvers func NewEBPFLessResolvers(config *config.Config, statsdClient statsd.ClientInterface, scrubber *procutil.DataScrubber, opts Opts, telemetry telemetry.Component) (*EBPFLessResolvers, error) { - var tagsResolver tags.Resolver - if opts.TagsResolver != nil { - tagsResolver = opts.TagsResolver - } else { - tagsResolver = tags.NewResolver(telemetry) + cgroupsResolver, err := cgroup.NewResolver() + if err != nil { + return nil, err } + tagsResolver := tags.NewResolver(telemetry, opts.Tagger, cgroupsResolver) processOpts := process.NewResolverOpts() processOpts.WithEnvsValue(config.Probe.EnvsWithValue) @@ -48,10 +47,6 @@ func NewEBPFLessResolvers(config *config.Config, statsdClient statsd.ClientInter return nil, err } - cgroupsResolver, err := cgroup.NewResolver(tagsResolver) - if err != nil { - return nil, err - } hashResolver, err := hash.NewResolver(config.RuntimeSecurity, statsdClient, cgroupsResolver) if err != nil { return nil, err diff --git a/pkg/security/resolvers/resolvers_windows.go b/pkg/security/resolvers/resolvers_windows.go index 762e2abed0d9e..2df2a38813070 100644 --- a/pkg/security/resolvers/resolvers_windows.go +++ b/pkg/security/resolvers/resolvers_windows.go @@ -35,7 +35,7 @@ func NewResolvers(config *config.Config, statsdClient statsd.ClientInterface, sc return nil, err } - tagsResolver := tags.NewResolver(telemetry) + tagsResolver := tags.NewResolver(telemetry, nil) userSessionsResolver, err := usersessions.NewResolver(config.RuntimeSecurity) if err != nil { diff --git a/pkg/security/resolvers/tags/resolver.go b/pkg/security/resolvers/tags/resolver.go index 58255c69396e8..87e10bdfea9a7 100644 --- a/pkg/security/resolvers/tags/resolver.go +++ b/pkg/security/resolvers/tags/resolver.go @@ -12,8 +12,8 @@ import ( "strings" coreconfig "github.com/DataDog/datadog-agent/comp/core/config" - tagger "github.com/DataDog/datadog-agent/comp/core/tagger/def" - remoteTagger "github.com/DataDog/datadog-agent/comp/core/tagger/impl-remote" + taggerdef "github.com/DataDog/datadog-agent/comp/core/tagger/def" + remotetagger "github.com/DataDog/datadog-agent/comp/core/tagger/impl-remote" "github.com/DataDog/datadog-agent/comp/core/tagger/types" "github.com/DataDog/datadog-agent/comp/core/telemetry" "github.com/DataDog/datadog-agent/pkg/api/security" @@ -22,6 +22,14 @@ import ( "github.com/DataDog/datadog-agent/pkg/util/log" ) +// Event defines the tags event type +type Event int + +const ( + // WorkloadSelectorResolved is used to notify that a new cgroup with a resolved workload selector is ready + WorkloadSelectorResolved Event = iota +) + // Tagger defines a Tagger for the Tags Resolver type Tagger interface { Start(ctx context.Context) error @@ -34,7 +42,7 @@ type Resolver interface { Start(ctx context.Context) error Stop() error Resolve(id string) []string - ResolveWithErr(id string) ([]string, error) + ResolveWithErr(fid string) ([]string, error) GetValue(id string, tag string) string } @@ -43,22 +51,6 @@ type DefaultResolver struct { tagger Tagger } -// Start the resolver -func (t *DefaultResolver) Start(ctx context.Context) error { - go func() { - if err := t.tagger.Start(ctx); err != nil { - log.Errorf("failed to init tagger: %s", err) - } - }() - - go func() { - <-ctx.Done() - _ = t.tagger.Stop() - }() - - return nil -} - // Resolve returns the tags for the given id func (t *DefaultResolver) Resolve(id string) []string { // container id for ecs task are composed of task id + container id. @@ -83,16 +75,35 @@ func (t *DefaultResolver) GetValue(id string, tag string) string { return utils.GetTagValue(tag, t.Resolve(id)) } +// Start the resolver +func (t *DefaultResolver) Start(ctx context.Context) error { + go func() { + if err := t.tagger.Start(ctx); err != nil { + log.Errorf("failed to init tagger: %s", err) + } + }() + + go func() { + <-ctx.Done() + _ = t.tagger.Stop() + }() + + return nil +} + // Stop the resolver func (t *DefaultResolver) Stop() error { return t.tagger.Stop() } -// NewResolver returns a new tags resolver -func NewResolver(telemetry telemetry.Component) Resolver { +// NewDefaultResolver returns a new default tags resolver +func NewDefaultResolver(telemetry telemetry.Component, tagger Tagger) *DefaultResolver { ddConfig := pkgconfigsetup.Datadog() + resolver := &DefaultResolver{ + tagger: tagger, + } - params := tagger.RemoteParams{ + params := taggerdef.RemoteParams{ RemoteFilter: types.NewMatchAllFilter(), RemoteTarget: func(c coreconfig.Component) (string, error) { return fmt.Sprintf(":%v", c.GetInt("cmd_port")), nil }, RemoteTokenFetcher: func(c coreconfig.Component) func() (string, error) { @@ -102,11 +113,9 @@ func NewResolver(telemetry telemetry.Component) Resolver { }, } - tagger, _ := remoteTagger.NewRemoteTagger(params, ddConfig, log.NewWrapper(2), telemetry) - - return &DefaultResolver{ - // TODO: (components) use the actual remote tagger instance from the Fx entry point - tagger: tagger, + if tagger == nil { + resolver.tagger, _ = remotetagger.NewRemoteTagger(params, ddConfig, log.NewWrapper(2), telemetry) } + return resolver } diff --git a/pkg/security/resolvers/tags/resolver_linux.go b/pkg/security/resolvers/tags/resolver_linux.go new file mode 100644 index 0000000000000..7730902212f87 --- /dev/null +++ b/pkg/security/resolvers/tags/resolver_linux.go @@ -0,0 +1,100 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016-present Datadog, Inc. + +// Package tags holds tags related files +package tags + +import ( + "context" + "fmt" + "time" + + "github.com/DataDog/datadog-agent/comp/core/telemetry" + "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/utils" +) + +// LinuxResolver represents a default resolver based directly on the underlying tagger +type LinuxResolver struct { + *DefaultResolver + *utils.Notifier[Event, *cgroupModel.CacheEntry] + workloadsWithoutTags chan *cgroupModel.CacheEntry + cgroupResolver *cgroup.Resolver +} + +// Start the resolver +func (t *LinuxResolver) Start(ctx context.Context) error { + if err := t.DefaultResolver.Start(ctx); err != nil { + return err + } + + if err := t.cgroupResolver.RegisterListener(cgroup.CGroupCreated, t.checkTags); err != nil { + return err + } + + go func() { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + delayerTick := time.NewTicker(10 * time.Second) + defer delayerTick.Stop() + + for { + select { + case <-ctx.Done(): + return + case <-delayerTick.C: + select { + case workload := <-t.workloadsWithoutTags: + t.checkTags(workload) + default: + } + + } + } + }() + + return nil +} + +// checkTags checks if the tags of a workload were properly set +func (t *LinuxResolver) checkTags(workload *cgroupModel.CacheEntry) { + // check if the workload tags were found + if workload.NeedsTagsResolution() { + // this is a container, try to resolve its tags now + if err := t.fetchTags(workload); err != nil || workload.NeedsTagsResolution() { + // push to the workloadsWithoutTags chan so that its tags can be resolved later + select { + case t.workloadsWithoutTags <- workload: + default: + } + return + } + } + + t.NotifyListeners(WorkloadSelectorResolved, workload) +} + +// fetchTags fetches tags for the provided workload +func (t *LinuxResolver) fetchTags(container *cgroupModel.CacheEntry) error { + newTags, err := t.ResolveWithErr(string(container.ContainerID)) + if err != nil { + return fmt.Errorf("failed to resolve %s: %w", container.ContainerID, err) + } + container.SetTags(newTags) + return nil +} + +// NewResolver returns a new tags resolver +func NewResolver(telemetry telemetry.Component, tagger Tagger, cgroupsResolver *cgroup.Resolver) *LinuxResolver { + resolver := &LinuxResolver{ + Notifier: utils.NewNotifier[Event, *cgroupModel.CacheEntry](), + DefaultResolver: NewDefaultResolver(telemetry, tagger), + workloadsWithoutTags: make(chan *cgroupModel.CacheEntry, 100), + cgroupResolver: cgroupsResolver, + } + return resolver +} diff --git a/pkg/security/resolvers/tags/resolver_other.go b/pkg/security/resolvers/tags/resolver_other.go new file mode 100644 index 0000000000000..d00f07ce02113 --- /dev/null +++ b/pkg/security/resolvers/tags/resolver_other.go @@ -0,0 +1,18 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016-present Datadog, Inc. + +//go:build !linux + +// Package tags holds tags related files +package tags + +import ( + "github.com/DataDog/datadog-agent/comp/core/telemetry" +) + +// NewResolver returns a new tags resolver +func NewResolver(telemetry telemetry.Component, tagger Tagger) Resolver { + return NewDefaultResolver(telemetry, tagger) +} diff --git a/pkg/security/security_profile/profile/manager.go b/pkg/security/security_profile/profile/manager.go index c256ae847b488..8f8797ba3bd23 100644 --- a/pkg/security/security_profile/profile/manager.go +++ b/pkg/security/security_profile/profile/manager.go @@ -31,6 +31,7 @@ import ( "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" "github.com/DataDog/datadog-agent/pkg/security/secl/model" "github.com/DataDog/datadog-agent/pkg/security/seclog" @@ -226,7 +227,7 @@ func (m *SecurityProfileManager) Start(ctx context.Context) { } // register the manager to the CGroup resolver - _ = m.resolvers.CGroupResolver.RegisterListener(cgroup.WorkloadSelectorResolved, m.OnWorkloadSelectorResolvedEvent) + _ = m.resolvers.TagsResolver.RegisterListener(tags.WorkloadSelectorResolved, m.OnWorkloadSelectorResolvedEvent) _ = m.resolvers.CGroupResolver.RegisterListener(cgroup.CGroupDeleted, m.OnCGroupDeletedEvent) seclog.Infof("security profile manager started") diff --git a/pkg/security/tests/fake_tags_resolver.go b/pkg/security/tests/fake_tags_resolver.go index 9c97502c8d266..a26859a29ad76 100644 --- a/pkg/security/tests/fake_tags_resolver.go +++ b/pkg/security/tests/fake_tags_resolver.go @@ -13,31 +13,32 @@ import ( "fmt" "sync" + "github.com/DataDog/datadog-agent/comp/core/tagger/types" 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/utils" ) -// This fake resolver will give a different image_name for each different container ID +// This fake tagger will give a different image_name for each different container ID -// FakeResolver represents a fake cache resolver -type FakeResolver struct { +// FakeTagger represents a fake tagger +type FakeTagger struct { sync.Mutex containerIDs []string } -// Start the resolver -func (fr *FakeResolver) Start(_ context.Context) error { +// Start the tagger +func (fr *FakeTagger) Start(_ context.Context) error { return nil } -// Stop the resolver -func (fr *FakeResolver) Stop() error { +// Stop the tagger +func (fr *FakeTagger) Stop() error { return nil } -// Resolve returns the tags for the given id -func (fr *FakeResolver) Resolve(containerID string) []string { +// Tag returns the tags for the given id +func (fr *FakeTagger) Tag(entity types.EntityID, _ types.TagCardinality) ([]string, error) { + containerID := entity.GetID() fakeTags := []string{ "image_tag:latest", } @@ -45,93 +46,72 @@ func (fr *FakeResolver) Resolve(containerID string) []string { defer fr.Unlock() for index, id := range fr.containerIDs { if id == containerID { - return append(fakeTags, fmt.Sprintf("image_name:fake_ubuntu_%d", index+1)) + return append(fakeTags, fmt.Sprintf("image_name:fake_ubuntu_%d", index+1)), nil } } fr.containerIDs = append(fr.containerIDs, containerID) - return append(fakeTags, fmt.Sprintf("image_name:fake_ubuntu_%d", len(fr.containerIDs))) + return append(fakeTags, fmt.Sprintf("image_name:fake_ubuntu_%d", len(fr.containerIDs))), nil } -// ResolveWithErr returns the tags for the given id -func (fr *FakeResolver) ResolveWithErr(id string) ([]string, error) { - return fr.Resolve(id), nil +// NewFakeTaggerDifferentImageNames returns a new tagger +func NewFakeTaggerDifferentImageNames() tags.Tagger { + return &FakeTagger{} } -// GetValue return the tag value for the given id and tag name -func (fr *FakeResolver) GetValue(id string, tag string) string { - return utils.GetTagValue(tag, fr.Resolve(id)) -} - -// NewFakeResolverDifferentImageNames returns a new tags resolver -func NewFakeResolverDifferentImageNames() tags.Resolver { - return &FakeResolver{} -} +// This fake tagger will allways give the same image_name, no matter the container ID -// This fake resolver will allways give the same image_name, no matter the container ID +// FakeMonoTagger represents a fake mono tagger +type FakeMonoTagger struct{} -// FakeMonoResolver represents a fake mono resolver -type FakeMonoResolver struct { -} - -// Start the resolver -func (fmr *FakeMonoResolver) Start(_ context.Context) error { +// Start the tagger +func (fmr *FakeMonoTagger) Start(_ context.Context) error { return nil } -// Stop the resolver -func (fmr *FakeMonoResolver) Stop() error { +// Stop the tagger +func (fmr *FakeMonoTagger) Stop() error { return nil } -// Resolve returns the tags for the given id -func (fmr *FakeMonoResolver) Resolve(containerID string) []string { - return []string{"container_id:" + containerID, "image_name:fake_ubuntu", "image_tag:latest"} +// Tag returns the tags for the given id +func (fmr *FakeMonoTagger) Tag(entity types.EntityID, _ types.TagCardinality) ([]string, error) { + return []string{"container_id:" + entity.GetID(), "image_name:fake_ubuntu", "image_tag:latest"}, nil } -// ResolveWithErr returns the tags for the given id -func (fmr *FakeMonoResolver) ResolveWithErr(id string) ([]string, error) { - return fmr.Resolve(id), nil +// NewFakeMonoTagger returns a new tags tagger +func NewFakeMonoTagger() tags.Tagger { + return &FakeMonoTagger{} } -// GetValue return the tag value for the given id and tag name -func (fmr *FakeMonoResolver) GetValue(id string, tag string) string { - return utils.GetTagValue(tag, fmr.Resolve(id)) -} +// This fake tagger will let us specify the next containerID to be resolved -// NewFakeMonoResolver returns a new tags resolver -func NewFakeMonoResolver() tags.Resolver { - return &FakeMonoResolver{} -} - -// This fake resolver will let us specify the next containerID to be resolved - -// FakeManualResolver represents a fake manual resolver -type FakeManualResolver struct { +// FakeManualTagger represents a fake manual tagger +type FakeManualTagger struct { sync.Mutex containerToSelector map[string]*cgroupModel.WorkloadSelector cpt int nextSelectors []*cgroupModel.WorkloadSelector } -// Start the resolver -func (fmr *FakeManualResolver) Start(_ context.Context) error { +// Start the tagger +func (fmr *FakeManualTagger) Start(_ context.Context) error { return nil } -// Stop the resolver -func (fmr *FakeManualResolver) Stop() error { +// Stop the tagger +func (fmr *FakeManualTagger) Stop() error { return nil } // SpecifyNextSelector specifies the next image name and tag to be resolved -func (fmr *FakeManualResolver) SpecifyNextSelector(selector *cgroupModel.WorkloadSelector) { +func (fmr *FakeManualTagger) SpecifyNextSelector(selector *cgroupModel.WorkloadSelector) { fmr.Lock() defer fmr.Unlock() fmr.nextSelectors = append(fmr.nextSelectors, selector) } // GetContainerSelector returns the container selector -func (fmr *FakeManualResolver) GetContainerSelector(containerID string) *cgroupModel.WorkloadSelector { +func (fmr *FakeManualTagger) GetContainerSelector(containerID string) *cgroupModel.WorkloadSelector { fmr.Lock() defer fmr.Unlock() if selector, found := fmr.containerToSelector[containerID]; found { @@ -140,15 +120,16 @@ func (fmr *FakeManualResolver) GetContainerSelector(containerID string) *cgroupM return nil } -// Resolve returns the tags for the given id -func (fmr *FakeManualResolver) Resolve(containerID string) []string { +// Tag returns the tags for the given id +func (fmr *FakeManualTagger) Tag(entity types.EntityID, _ types.TagCardinality) ([]string, error) { fmr.Lock() defer fmr.Unlock() + containerID := entity.GetID() // first, use cache if any selector, alreadyResolved := fmr.containerToSelector[containerID] if alreadyResolved { - return []string{"container_id:" + containerID, "image_name:" + selector.Image, "image_tag:" + selector.Tag} + return []string{"container_id:" + containerID, "image_name:" + selector.Image, "image_tag:" + selector.Tag}, nil } // if no cache and there is a pending list, use it @@ -156,7 +137,7 @@ func (fmr *FakeManualResolver) Resolve(containerID string) []string { selector = fmr.nextSelectors[0] fmr.nextSelectors = fmr.nextSelectors[1:] fmr.containerToSelector[containerID] = selector - return []string{"container_id:" + containerID, "image_name:" + selector.Image, "image_tag:" + selector.Tag} + return []string{"container_id:" + containerID, "image_name:" + selector.Image, "image_tag:" + selector.Tag}, nil } // otherwise generate a new selector @@ -166,22 +147,12 @@ func (fmr *FakeManualResolver) Resolve(containerID string) []string { Tag: "fake_tag", } fmr.containerToSelector[containerID] = selector - return []string{"container_id:" + containerID, "image_name:" + selector.Image, "image_tag:" + selector.Tag} -} - -// ResolveWithErr returns the tags for the given id -func (fmr *FakeManualResolver) ResolveWithErr(id string) ([]string, error) { - return fmr.Resolve(id), nil -} - -// GetValue return the tag value for the given id and tag name -func (fmr *FakeManualResolver) GetValue(id string, tag string) string { - return utils.GetTagValue(tag, fmr.Resolve(id)) + return []string{"container_id:" + containerID, "image_name:" + selector.Image, "image_tag:" + selector.Tag}, nil } -// NewFakeManualResolver returns a new tags resolver -func NewFakeManualResolver() *FakeManualResolver { - return &FakeManualResolver{ +// NewFakeManualTagger returns a new tagger +func NewFakeManualTagger() *FakeManualTagger { + return &FakeManualTagger{ containerToSelector: make(map[string]*cgroupModel.WorkloadSelector), } } diff --git a/pkg/security/tests/module_tester_linux.go b/pkg/security/tests/module_tester_linux.go index 485a37422841a..1b20ff18cd690 100644 --- a/pkg/security/tests/module_tester_linux.go +++ b/pkg/security/tests/module_tester_linux.go @@ -728,10 +728,11 @@ func newTestModuleWithOnDemandProbes(t testing.TB, onDemandHooks []rules.OnDeman EBPFLessEnabled: ebpfLessEnabled, }, } - if opts.staticOpts.tagsResolver != nil { - emopts.ProbeOpts.TagsResolver = opts.staticOpts.tagsResolver + + if opts.staticOpts.tagger != nil { + emopts.ProbeOpts.Tagger = opts.staticOpts.tagger } else { - emopts.ProbeOpts.TagsResolver = NewFakeResolverDifferentImageNames() + emopts.ProbeOpts.Tagger = NewFakeTaggerDifferentImageNames() } if opts.staticOpts.discardRuntime { diff --git a/pkg/security/tests/security_profile_test.go b/pkg/security/tests/security_profile_test.go index f652ef3c3f80d..7fa43c6ddc0cb 100644 --- a/pkg/security/tests/security_profile_test.go +++ b/pkg/security/tests/security_profile_test.go @@ -426,7 +426,7 @@ func TestAnomalyDetectionWarmup(t *testing.T) { anomalyDetectionMinimumStablePeriodExec: 0, anomalyDetectionMinimumStablePeriodDNS: 0, anomalyDetectionWarmupPeriod: 3 * time.Second, - tagsResolver: NewFakeMonoResolver(), + tagger: NewFakeMonoTagger(), })) if err != nil { t.Fatal(err) @@ -1069,7 +1069,7 @@ func TestSecurityProfileLifeCycleExecs(t *testing.T) { os.MkdirAll(outputDir, 0755) defer os.RemoveAll(outputDir) - fakeManualResolver := NewFakeManualResolver() + fakeManualTagger := NewFakeManualTagger() test, err := newTestModule(t, nil, []*rules.RuleDefinition{}, withStaticOpts(testOpts{ enableActivityDump: true, @@ -1088,7 +1088,7 @@ func TestSecurityProfileLifeCycleExecs(t *testing.T) { anomalyDetectionMinimumStablePeriodExec: 10 * time.Second, anomalyDetectionMinimumStablePeriodDNS: 10 * time.Second, anomalyDetectionWarmupPeriod: 1 * time.Second, - tagsResolver: fakeManualResolver, + tagger: fakeManualTagger, })) if err != nil { t.Fatal(err) @@ -1131,7 +1131,7 @@ func TestSecurityProfileLifeCycleExecs(t *testing.T) { }, time.Second*2, model.ExecEventType) }) - selector := fakeManualResolver.GetContainerSelector(dockerInstanceV1.containerID) + selector := fakeManualTagger.GetContainerSelector(dockerInstanceV1.containerID) if err := test.SetProfileVersionState(&cgroupModel.WorkloadSelector{ Image: selector.Image, Tag: "*", @@ -1155,7 +1155,7 @@ func TestSecurityProfileLifeCycleExecs(t *testing.T) { } }) - fakeManualResolver.SpecifyNextSelector(&cgroupModel.WorkloadSelector{ + fakeManualTagger.SpecifyNextSelector(&cgroupModel.WorkloadSelector{ Image: selector.Image, Tag: selector.Tag + "+", }) @@ -1245,7 +1245,7 @@ func TestSecurityProfileLifeCycleDNS(t *testing.T) { os.MkdirAll(outputDir, 0755) defer os.RemoveAll(outputDir) - fakeManualResolver := NewFakeManualResolver() + fakeManualTagger := NewFakeManualTagger() test, err := newTestModule(t, nil, []*rules.RuleDefinition{}, withStaticOpts(testOpts{ enableActivityDump: true, @@ -1264,7 +1264,7 @@ func TestSecurityProfileLifeCycleDNS(t *testing.T) { anomalyDetectionMinimumStablePeriodExec: 10 * time.Second, anomalyDetectionMinimumStablePeriodDNS: 10 * time.Second, anomalyDetectionWarmupPeriod: 1 * time.Second, - tagsResolver: fakeManualResolver, + tagger: fakeManualTagger, })) if err != nil { t.Fatal(err) @@ -1325,8 +1325,8 @@ func TestSecurityProfileLifeCycleDNS(t *testing.T) { } }) - selector := fakeManualResolver.GetContainerSelector(dockerInstanceV1.containerID) - fakeManualResolver.SpecifyNextSelector(&cgroupModel.WorkloadSelector{ + selector := fakeManualTagger.GetContainerSelector(dockerInstanceV1.containerID) + fakeManualTagger.SpecifyNextSelector(&cgroupModel.WorkloadSelector{ Image: selector.Image, Tag: selector.Tag + "+", }) @@ -1419,7 +1419,7 @@ func TestSecurityProfileLifeCycleEvictitonProcess(t *testing.T) { os.MkdirAll(outputDir, 0755) defer os.RemoveAll(outputDir) - fakeManualResolver := NewFakeManualResolver() + fakeManualTagger := NewFakeManualTagger() test, err := newTestModule(t, nil, []*rules.RuleDefinition{}, withStaticOpts(testOpts{ enableActivityDump: true, @@ -1438,7 +1438,7 @@ func TestSecurityProfileLifeCycleEvictitonProcess(t *testing.T) { anomalyDetectionMinimumStablePeriodExec: 10 * time.Second, anomalyDetectionMinimumStablePeriodDNS: 10 * time.Second, anomalyDetectionWarmupPeriod: 1 * time.Second, - tagsResolver: fakeManualResolver, + tagger: fakeManualTagger, securityProfileMaxImageTags: 2, })) if err != nil { @@ -1482,7 +1482,7 @@ func TestSecurityProfileLifeCycleEvictitonProcess(t *testing.T) { }, time.Second*2, model.ExecEventType) }) - selector := fakeManualResolver.GetContainerSelector(dockerInstanceV1.containerID) + selector := fakeManualTagger.GetContainerSelector(dockerInstanceV1.containerID) if err := test.SetProfileVersionState(&cgroupModel.WorkloadSelector{ Image: selector.Image, Tag: "*", @@ -1506,7 +1506,7 @@ func TestSecurityProfileLifeCycleEvictitonProcess(t *testing.T) { } }) - fakeManualResolver.SpecifyNextSelector(&cgroupModel.WorkloadSelector{ + fakeManualTagger.SpecifyNextSelector(&cgroupModel.WorkloadSelector{ Image: selector.Image, Tag: selector.Tag + "v2", }) @@ -1532,7 +1532,7 @@ func TestSecurityProfileLifeCycleEvictitonProcess(t *testing.T) { } }) - fakeManualResolver.SpecifyNextSelector(&cgroupModel.WorkloadSelector{ + fakeManualTagger.SpecifyNextSelector(&cgroupModel.WorkloadSelector{ Image: selector.Image, Tag: selector.Tag + "v3", }) @@ -1600,7 +1600,7 @@ func TestSecurityProfileLifeCycleEvictitonDNS(t *testing.T) { os.MkdirAll(outputDir, 0755) defer os.RemoveAll(outputDir) - fakeManualResolver := NewFakeManualResolver() + fakeManualTagger := NewFakeManualTagger() test, err := newTestModule(t, nil, []*rules.RuleDefinition{}, withStaticOpts(testOpts{ enableActivityDump: true, @@ -1619,7 +1619,7 @@ func TestSecurityProfileLifeCycleEvictitonDNS(t *testing.T) { anomalyDetectionMinimumStablePeriodExec: 10 * time.Second, anomalyDetectionMinimumStablePeriodDNS: 10 * time.Second, anomalyDetectionWarmupPeriod: 1 * time.Second, - tagsResolver: fakeManualResolver, + tagger: fakeManualTagger, securityProfileMaxImageTags: 2, })) if err != nil { @@ -1663,7 +1663,7 @@ func TestSecurityProfileLifeCycleEvictitonDNS(t *testing.T) { }, time.Second*2, model.DNSEventType) }) - selector := fakeManualResolver.GetContainerSelector(dockerInstanceV1.containerID) + selector := fakeManualTagger.GetContainerSelector(dockerInstanceV1.containerID) if err := test.SetProfileVersionState(&cgroupModel.WorkloadSelector{ Image: selector.Image, Tag: "*", @@ -1687,7 +1687,7 @@ func TestSecurityProfileLifeCycleEvictitonDNS(t *testing.T) { } }) - fakeManualResolver.SpecifyNextSelector(&cgroupModel.WorkloadSelector{ + fakeManualTagger.SpecifyNextSelector(&cgroupModel.WorkloadSelector{ Image: selector.Image, Tag: selector.Tag + "v2", }) @@ -1713,7 +1713,7 @@ func TestSecurityProfileLifeCycleEvictitonDNS(t *testing.T) { } }) - fakeManualResolver.SpecifyNextSelector(&cgroupModel.WorkloadSelector{ + fakeManualTagger.SpecifyNextSelector(&cgroupModel.WorkloadSelector{ Image: selector.Image, Tag: selector.Tag + "v3", }) @@ -1781,7 +1781,7 @@ func TestSecurityProfileLifeCycleEvictitonProcessUnstable(t *testing.T) { os.MkdirAll(outputDir, 0755) defer os.RemoveAll(outputDir) - fakeManualResolver := NewFakeManualResolver() + fakeManualTagger := NewFakeManualTagger() test, err := newTestModule(t, nil, []*rules.RuleDefinition{}, withStaticOpts(testOpts{ enableActivityDump: true, @@ -1800,7 +1800,7 @@ func TestSecurityProfileLifeCycleEvictitonProcessUnstable(t *testing.T) { anomalyDetectionMinimumStablePeriodExec: 10 * time.Second, anomalyDetectionMinimumStablePeriodDNS: 10 * time.Second, anomalyDetectionWarmupPeriod: 1 * time.Second, - tagsResolver: fakeManualResolver, + tagger: fakeManualTagger, securityProfileMaxImageTags: 2, })) if err != nil { @@ -1844,7 +1844,7 @@ func TestSecurityProfileLifeCycleEvictitonProcessUnstable(t *testing.T) { }, time.Second*2, model.ExecEventType) }) - selector := fakeManualResolver.GetContainerSelector(dockerInstanceV1.containerID) + selector := fakeManualTagger.GetContainerSelector(dockerInstanceV1.containerID) if err := test.SetProfileVersionState(&cgroupModel.WorkloadSelector{ Image: selector.Image, Tag: "*", @@ -1865,7 +1865,7 @@ func TestSecurityProfileLifeCycleEvictitonProcessUnstable(t *testing.T) { }, time.Second*2, model.ExecEventType) }) - fakeManualResolver.SpecifyNextSelector(&cgroupModel.WorkloadSelector{ + fakeManualTagger.SpecifyNextSelector(&cgroupModel.WorkloadSelector{ Image: selector.Image, Tag: selector.Tag + "v2", }) @@ -1888,7 +1888,7 @@ func TestSecurityProfileLifeCycleEvictitonProcessUnstable(t *testing.T) { }, time.Second*2, model.ExecEventType) }) - fakeManualResolver.SpecifyNextSelector(&cgroupModel.WorkloadSelector{ + fakeManualTagger.SpecifyNextSelector(&cgroupModel.WorkloadSelector{ Image: selector.Image, Tag: selector.Tag + "v3", }) @@ -1964,7 +1964,7 @@ func TestSecurityProfilePersistence(t *testing.T) { }, } - fakeManualResolver := NewFakeManualResolver() + fakeManualTagger := NewFakeManualTagger() test, err := newTestModule(t, nil, rulesDef, withStaticOpts(testOpts{ enableActivityDump: true, @@ -1984,7 +1984,7 @@ func TestSecurityProfilePersistence(t *testing.T) { anomalyDetectionEventTypes: []string{"exec"}, anomalyDetectionMinimumStablePeriodExec: 10 * time.Second, anomalyDetectionWarmupPeriod: 1 * time.Second, - tagsResolver: fakeManualResolver, + tagger: fakeManualTagger, })) if err != nil { t.Fatal(err) @@ -2025,7 +2025,7 @@ func TestSecurityProfilePersistence(t *testing.T) { dockerInstance1.stop() // make sure the next instance has the same image name as the previous one - fakeManualResolver.SpecifyNextSelector(fakeManualResolver.GetContainerSelector(dockerInstance1.containerID)) + fakeManualTagger.SpecifyNextSelector(fakeManualTagger.GetContainerSelector(dockerInstance1.containerID)) dockerInstance2, err := test.StartADocker() if err != nil { t.Fatal(err) @@ -2194,7 +2194,7 @@ func TestSecurityProfileSyscallDrift(t *testing.T) { enableSecurityProfile: true, enableAnomalyDetection: true, securityProfileDir: outputDir, - tagsResolver: NewFakeMonoResolver(), + tagger: NewFakeMonoTagger(), })) if err != nil { t.Fatal(err) @@ -2319,7 +2319,7 @@ func TestSecurityProfileSyscallDriftExecExitInProfile(t *testing.T) { enableSecurityProfile: true, enableAnomalyDetection: true, securityProfileDir: outputDir, - tagsResolver: NewFakeMonoResolver(), + tagger: NewFakeMonoTagger(), })) if err != nil { t.Fatal(err) @@ -2442,7 +2442,7 @@ func TestSecurityProfileSyscallDriftNoNewSyscall(t *testing.T) { enableSecurityProfile: true, enableAnomalyDetection: true, securityProfileDir: outputDir, - tagsResolver: NewFakeMonoResolver(), + tagger: NewFakeMonoTagger(), })) if err != nil { t.Fatal(err) diff --git a/pkg/security/tests/testopts.go b/pkg/security/tests/testopts.go index 7b6b34d1a5b80..f1a37fc300b57 100644 --- a/pkg/security/tests/testopts.go +++ b/pkg/security/tests/testopts.go @@ -57,7 +57,7 @@ type testOpts struct { enableSBOM bool enableHostSBOM bool preStartCallback func(test *testModule) - tagsResolver tags.Resolver + tagger tags.Tagger snapshotRuleMatchHandler func(*testModule, *model.Event, *rules.Rule) enableFIM bool // only valid on windows networkIngressEnabled bool diff --git a/pkg/security/utils/notifier.go b/pkg/security/utils/notifier.go new file mode 100644 index 0000000000000..8a28e686bdcf5 --- /dev/null +++ b/pkg/security/utils/notifier.go @@ -0,0 +1,54 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016-present Datadog, Inc. + +// Package utils holds utils related files +package utils + +import ( + "fmt" + "sync" +) + +type event interface{ comparable } + +// Listener describes the callback called by a notifier +type Listener[O any] func(obj O) + +// Notifier describes a type that calls back listener that registered for a specific set of events +type Notifier[E event, O any] struct { + listenersLock sync.RWMutex + listeners map[E][]Listener[O] +} + +// RegisterListener registers an event listener +func (n *Notifier[E, O]) RegisterListener(event E, listener Listener[O]) error { + n.listenersLock.Lock() + defer n.listenersLock.Unlock() + + if n.listeners != nil { + n.listeners[event] = append(n.listeners[event], listener) + } else { + return fmt.Errorf("a listener was inserted before initialization") + } + return nil +} + +// NotifyListeners notifies all listeners of an event type +func (n *Notifier[E, O]) NotifyListeners(event E, obj O) { + // notify listeners + n.listenersLock.RLock() + for _, l := range n.listeners[event] { + l(obj) + } + n.listenersLock.RUnlock() + +} + +// NewNotifier returns a new notifier +func NewNotifier[E event, O any]() *Notifier[E, O] { + return &Notifier[E, O]{ + listeners: make(map[E][]Listener[O]), + } +}