diff --git a/pkg/security/secl/model/model.go b/pkg/security/secl/model/model.go
index b7a7a7df50cc4..9e3ba2db7b5ba 100644
--- a/pkg/security/secl/model/model.go
+++ b/pkg/security/secl/model/model.go
@@ -230,6 +230,13 @@ func (e *Event) AddToFlags(flag uint32) {
e.Flags |= flag
}
+// ResetAnomalyDetectionEvent removes the anomaly detection event flag
+func (e *Event) ResetAnomalyDetectionEvent() {
+ if e.IsAnomalyDetectionEvent() {
+ e.RemoveFromFlags(EventFlagsAnomalyDetectionEvent)
+ }
+}
+
// RemoveFromFlags remove a flag to the event
func (e *Event) RemoveFromFlags(flag uint32) {
e.Flags ^= flag
diff --git a/pkg/security/seclwin/model/model.go b/pkg/security/seclwin/model/model.go
index b7a7a7df50cc4..9e3ba2db7b5ba 100644
--- a/pkg/security/seclwin/model/model.go
+++ b/pkg/security/seclwin/model/model.go
@@ -230,6 +230,13 @@ func (e *Event) AddToFlags(flag uint32) {
e.Flags |= flag
}
+// ResetAnomalyDetectionEvent removes the anomaly detection event flag
+func (e *Event) ResetAnomalyDetectionEvent() {
+ if e.IsAnomalyDetectionEvent() {
+ e.RemoveFromFlags(EventFlagsAnomalyDetectionEvent)
+ }
+}
+
// RemoveFromFlags remove a flag to the event
func (e *Event) RemoveFromFlags(flag uint32) {
e.Flags ^= flag
diff --git a/pkg/security/security_profile/activity_tree/activity_tree.go b/pkg/security/security_profile/activity_tree/activity_tree.go
index 9a1110dd5882e..7987e8061f753 100644
--- a/pkg/security/security_profile/activity_tree/activity_tree.go
+++ b/pkg/security/security_profile/activity_tree/activity_tree.go
@@ -401,7 +401,7 @@ func (at *ActivityTree) insertEvent(event *model.Event, dryRun bool, insertMissi
case model.BindEventType:
return node.InsertBindEvent(event, imageTag, generationType, at.Stats, dryRun), nil
case model.SyscallsEventType:
- return node.InsertSyscalls(event, at.SyscallsMask), nil
+ return node.InsertSyscalls(event, imageTag, at.SyscallsMask, at.Stats, dryRun), nil
case model.ExitEventType:
// Update the exit time of the process (this is purely informative, do not rely on timestamps to detect
// execed children)
@@ -936,7 +936,7 @@ func (at *ActivityTree) ExtractSyscalls(arch string) []string {
at.visit(func(processNode *ProcessNode) {
for _, s := range processNode.Syscalls {
- sycallKey := utils.SyscallKey{Arch: arch, ID: s}
+ sycallKey := utils.SyscallKey{Arch: arch, ID: s.Syscall}
syscall, ok := utils.Syscalls[sycallKey]
if ok {
syscalls = append(syscalls, syscall)
diff --git a/pkg/security/security_profile/activity_tree/activity_tree_graph.go b/pkg/security/security_profile/activity_tree/activity_tree_graph.go
index 4ac254f015276..14f70a5a98ddd 100644
--- a/pkg/security/security_profile/activity_tree/activity_tree_graph.go
+++ b/pkg/security/security_profile/activity_tree/activity_tree_graph.go
@@ -293,7 +293,7 @@ func (at *ActivityTree) prepareFileNode(f *FileNode, data *utils.Graph, prefix s
func (at *ActivityTree) prepareSyscallsNode(p *ProcessNode, data *utils.Graph) utils.GraphID {
label := "<
"
for _, s := range p.Syscalls {
- label += "" + model.Syscall(s).String() + " |
"
+ label += "" + model.Syscall(s.Syscall).String() + " |
"
}
label += "
>"
diff --git a/pkg/security/security_profile/activity_tree/activity_tree_proto_dec_v1.go b/pkg/security/security_profile/activity_tree/activity_tree_proto_dec_v1.go
index 95671149ad057..09ad5a25a4c9a 100644
--- a/pkg/security/security_profile/activity_tree/activity_tree_proto_dec_v1.go
+++ b/pkg/security/security_profile/activity_tree/activity_tree_proto_dec_v1.go
@@ -39,7 +39,7 @@ func protoDecodeProcessActivityNode(parent ProcessNodeParent, pan *adproto.Proce
DNSNames: make(map[string]*DNSNode, len(pan.DnsNames)),
IMDSEvents: make(map[model.IMDSEvent]*IMDSNode, len(pan.ImdsEvents)),
Sockets: make([]*SocketNode, 0, len(pan.Sockets)),
- Syscalls: make([]int, 0, len(pan.Syscalls)),
+ Syscalls: make([]*SyscallNode, 0, len(pan.Syscalls)),
ImageTags: pan.ImageTags,
}
@@ -74,7 +74,7 @@ func protoDecodeProcessActivityNode(parent ProcessNodeParent, pan *adproto.Proce
}
for _, sysc := range pan.Syscalls {
- ppan.Syscalls = append(ppan.Syscalls, int(sysc))
+ ppan.Syscalls = append(ppan.Syscalls, NewSyscallNode(int(sysc), "", Unknown))
}
return ppan
diff --git a/pkg/security/security_profile/activity_tree/activity_tree_proto_enc_v1.go b/pkg/security/security_profile/activity_tree/activity_tree_proto_enc_v1.go
index b00ec1a6c0055..30b0b46e29382 100644
--- a/pkg/security/security_profile/activity_tree/activity_tree_proto_enc_v1.go
+++ b/pkg/security/security_profile/activity_tree/activity_tree_proto_enc_v1.go
@@ -71,7 +71,7 @@ func processActivityNodeToProto(pan *ProcessNode) *adproto.ProcessActivityNode {
}
for _, sysc := range pan.Syscalls {
- ppan.Syscalls = append(ppan.Syscalls, uint32(sysc))
+ ppan.Syscalls = append(ppan.Syscalls, uint32(sysc.Syscall))
}
return ppan
diff --git a/pkg/security/security_profile/activity_tree/activity_tree_stats.go b/pkg/security/security_profile/activity_tree/activity_tree_stats.go
index 0ee7dbbc1b82f..7f10be52327b4 100644
--- a/pkg/security/security_profile/activity_tree/activity_tree_stats.go
+++ b/pkg/security/security_profile/activity_tree/activity_tree_stats.go
@@ -26,6 +26,7 @@ type Stats struct {
DNSNodes int64
SocketNodes int64
IMDSNodes int64
+ SyscallNodes int64
counts map[model.EventType]*statsPerEventType
}
@@ -72,6 +73,7 @@ func (stats *Stats) ApproximateSize() int64 {
total += stats.DNSNodes * int64(unsafe.Sizeof(DNSNode{})) // 24
total += stats.SocketNodes * int64(unsafe.Sizeof(SocketNode{})) // 40
total += stats.IMDSNodes * int64(unsafe.Sizeof(IMDSNode{}))
+ total += stats.SyscallNodes * int64(unsafe.Sizeof(SyscallNode{}))
return total
}
diff --git a/pkg/security/security_profile/activity_tree/process_node.go b/pkg/security/security_profile/activity_tree/process_node.go
index a51951c3a7dfd..11525c9eacc79 100644
--- a/pkg/security/security_profile/activity_tree/process_node.go
+++ b/pkg/security/security_profile/activity_tree/process_node.go
@@ -43,7 +43,7 @@ type ProcessNode struct {
IMDSEvents map[model.IMDSEvent]*IMDSNode
Sockets []*SocketNode
- Syscalls []int
+ Syscalls []*SyscallNode
Children []*ProcessNode
}
@@ -200,20 +200,29 @@ func (pn *ProcessNode) Matches(entry *model.Process, matchArgs bool, normalize b
}
// InsertSyscalls inserts the syscall of the process in the dump
-func (pn *ProcessNode) InsertSyscalls(e *model.Event, syscallMask map[int]int) bool {
+func (pn *ProcessNode) InsertSyscalls(e *model.Event, imageTag string, syscallMask map[int]int, stats *Stats, dryRun bool) bool {
var hasNewSyscalls bool
newSyscallLoop:
for _, newSyscall := range e.Syscalls.Syscalls {
for _, existingSyscall := range pn.Syscalls {
- if existingSyscall == int(newSyscall) {
+ if existingSyscall.Syscall == int(newSyscall) {
+ if imageTag != "" && !slices.Contains(existingSyscall.ImageTags, imageTag) {
+ existingSyscall.ImageTags = append(existingSyscall.ImageTags, imageTag)
+ }
continue newSyscallLoop
}
}
- pn.Syscalls = append(pn.Syscalls, int(newSyscall))
- syscallMask[int(newSyscall)] = int(newSyscall)
hasNewSyscalls = true
+ if dryRun {
+ // exit early
+ break
+ }
+ pn.Syscalls = append(pn.Syscalls, NewSyscallNode(int(newSyscall), imageTag, Runtime))
+ syscallMask[int(newSyscall)] = int(newSyscall)
+ stats.SyscallNodes++
}
+
return hasNewSyscalls
}
@@ -389,6 +398,9 @@ func (pn *ProcessNode) TagAllNodes(imageTag string) {
for _, sock := range pn.Sockets {
sock.appendImageTag(imageTag)
}
+ for _, scall := range pn.Syscalls {
+ scall.appendImageTag(imageTag)
+ }
for _, child := range pn.Children {
child.TagAllNodes(imageTag)
}
@@ -449,6 +461,15 @@ func (pn *ProcessNode) EvictImageTag(imageTag string, DNSNames *utils.StringKeys
}
pn.Sockets = newSockets
+ newSyscalls := []*SyscallNode{}
+ for _, scall := range pn.Syscalls {
+ if shouldRemove := scall.evictImageTag(imageTag); !shouldRemove {
+ newSyscalls = append(newSyscalls, scall)
+ SyscallsMask[scall.Syscall] = scall.Syscall
+ }
+ }
+ pn.Syscalls = newSyscalls
+
newChildren := []*ProcessNode{}
for _, child := range pn.Children {
if shouldRemoveNode := child.EvictImageTag(imageTag, DNSNames, SyscallsMask); !shouldRemoveNode {
@@ -456,9 +477,5 @@ func (pn *ProcessNode) EvictImageTag(imageTag string, DNSNames *utils.StringKeys
}
}
pn.Children = newChildren
-
- for _, id := range pn.Syscalls {
- SyscallsMask[id] = id
- }
return false
}
diff --git a/pkg/security/security_profile/activity_tree/syscalls_node.go b/pkg/security/security_profile/activity_tree/syscalls_node.go
new file mode 100644
index 0000000000000..3ccd457796253
--- /dev/null
+++ b/pkg/security/security_profile/activity_tree/syscalls_node.go
@@ -0,0 +1,46 @@
+// 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 activitytree holds activitytree related files
+package activitytree
+
+// SyscallNode is used to store a syscall node
+type SyscallNode struct {
+ ImageTags []string
+ GenerationType NodeGenerationType
+
+ Syscall int
+}
+
+func (sn *SyscallNode) appendImageTag(imageTag string) {
+ sn.ImageTags, _ = AppendIfNotPresent(sn.ImageTags, imageTag)
+}
+
+func (sn *SyscallNode) evictImageTag(imageTag string) bool {
+ imageTags, removed := removeImageTagFromList(sn.ImageTags, imageTag)
+ if !removed {
+ return false
+ }
+ if len(imageTags) == 0 {
+ return true
+ }
+ sn.ImageTags = imageTags
+ return false
+}
+
+// NewSyscallNode returns a new SyscallNode instance
+func NewSyscallNode(syscall int, imageTag string, generationType NodeGenerationType) *SyscallNode {
+ var imageTags []string
+ if len(imageTag) != 0 {
+ imageTags = append(imageTags, imageTag)
+ }
+ return &SyscallNode{
+ Syscall: syscall,
+ GenerationType: generationType,
+ ImageTags: imageTags,
+ }
+}
diff --git a/pkg/security/security_profile/profile/manager.go b/pkg/security/security_profile/profile/manager.go
index 8f8797ba3bd23..aadd9b94cd88b 100644
--- a/pkg/security/security_profile/profile/manager.go
+++ b/pkg/security/security_profile/profile/manager.go
@@ -767,6 +767,10 @@ func (m *SecurityProfileManager) LookupEventInProfiles(event *model.Event) {
globalEventTypeProfilState := profile.GetGlobalEventTypeState(event.GetEventType())
if globalEventTypeProfilState == model.UnstableEventType {
m.incrementEventFilteringStat(event.GetEventType(), model.UnstableEventType, NA)
+ // The anomaly flag can be set in kernel space by our eBPF programs (currently applies only to syscalls), reset
+ // the anomaly flag if the user space profile considers it to not be an anomaly. Here, when a version is unstable,
+ // we don't want to generate anomalies for this profile anymore.
+ event.ResetAnomalyDetectionEvent()
return
}
@@ -778,6 +782,13 @@ func (m *SecurityProfileManager) LookupEventInProfiles(event *model.Event) {
case model.NoProfile, model.ProfileAtMaxSize, model.UnstableEventType:
// an error occurred or we are in unstable state
// do not link the profile to avoid sending anomalies
+
+ // The anomaly flag can be set in kernel space by our eBPF programs (currently applies only to syscalls), reset
+ // the anomaly flag if the user space profile considers it to not be an anomaly.
+ // We can also get a syscall anomaly detection kernel space for runc, which is ignored in the activity tree
+ // (i.e. tryAutolearn returns NoProfile) because "runc" can't be a root node.
+ event.ResetAnomalyDetectionEvent()
+
return
case model.AutoLearning, model.WorkloadWarmup:
// the event was either already in the profile, or has just been inserted
@@ -798,12 +809,20 @@ func (m *SecurityProfileManager) LookupEventInProfiles(event *model.Event) {
if err != nil {
// ignore, evaluation failed
m.incrementEventFilteringStat(event.GetEventType(), model.NoProfile, NA)
+
+ // The anomaly flag can be set in kernel space by our eBPF programs (currently applies only to syscalls), reset
+ // the anomaly flag if the user space profile considers it to not be an anomaly.
+ event.ResetAnomalyDetectionEvent()
return
}
FillProfileContextFromProfile(&event.SecurityProfileContext, profile, imageTag, profileState)
if found {
event.AddToFlags(model.EventFlagsSecurityProfileInProfile)
m.incrementEventFilteringStat(event.GetEventType(), profileState, InProfile)
+
+ // The anomaly flag can be set in kernel space by our eBPF programs (currently applies only to syscalls), reset
+ // the anomaly flag if the user space profile considers it to not be an anomaly.
+ event.ResetAnomalyDetectionEvent()
} else {
m.incrementEventFilteringStat(event.GetEventType(), profileState, NotInProfile)
if m.canGenerateAnomaliesFor(event) {
@@ -851,11 +870,19 @@ func (m *SecurityProfileManager) tryAutolearn(profile *SecurityProfile, ctx *Ver
globalEventTypeState := profile.GetGlobalEventTypeState(event.GetEventType())
if globalEventTypeState == model.StableEventType && m.canGenerateAnomaliesFor(event) {
event.AddToFlags(model.EventFlagsAnomalyDetectionEvent)
+ } else {
+ // The anomaly flag can be set in kernel space by our eBPF programs (currently applies only to syscalls), reset
+ // the anomaly flag if the user space profile considers it to not be an anomaly: there is a new entry and no
+ // previous version is in stable state.
+ event.ResetAnomalyDetectionEvent()
}
m.incrementEventFilteringStat(event.GetEventType(), profileState, NotInProfile)
} else { // no newEntry
m.incrementEventFilteringStat(event.GetEventType(), profileState, InProfile)
+ // The anomaly flag can be set in kernel space by our eBPF programs (currently applies only to syscalls), reset
+ // the anomaly flag if the user space profile considers it to not be an anomaly
+ event.ResetAnomalyDetectionEvent()
}
return profileState
}
diff --git a/pkg/security/tests/activity_dumps_test.go b/pkg/security/tests/activity_dumps_test.go
index c64e05a3b76ca..0a1bd7a530055 100644
--- a/pkg/security/tests/activity_dumps_test.go
+++ b/pkg/security/tests/activity_dumps_test.go
@@ -351,10 +351,10 @@ func TestActivityDumps(t *testing.T) {
var exitOK, bindOK bool
for _, node := range nodes {
for _, s := range node.Syscalls {
- if s == int(model.SysExit) || s == int(model.SysExitGroup) {
+ if s.Syscall == int(model.SysExit) || s.Syscall == int(model.SysExitGroup) {
exitOK = true
}
- if s == int(model.SysBind) {
+ if s.Syscall == int(model.SysBind) {
bindOK = true
}
}
diff --git a/pkg/security/tests/security_profile_test.go b/pkg/security/tests/security_profile_test.go
index ad87dc7e0560c..2936854c55758 100644
--- a/pkg/security/tests/security_profile_test.go
+++ b/pkg/security/tests/security_profile_test.go
@@ -10,6 +10,7 @@ package tests
import (
"errors"
+ "fmt"
"os"
"slices"
"strings"
@@ -1372,7 +1373,196 @@ func TestSecurityProfileLifeCycleDNS(t *testing.T) {
})
}
-func TestSecurityProfileLifeCycleEvictitonProcess(t *testing.T) {
+func TestSecurityProfileLifeCycleSyscall(t *testing.T) {
+ SkipIfNotAvailable(t)
+
+ // skip test that are about to be run on docker (to avoid trying spawning docker in docker)
+ if testEnvironment == DockerEnvironment {
+ t.Skip("Skip test spawning docker containers on docker")
+ }
+ if _, err := whichNonFatal("docker"); err != nil {
+ t.Skip("Skip test where docker is unavailable")
+ }
+ if !IsDedicatedNodeForAD() {
+ t.Skip("Skip test when not run in dedicated env")
+ }
+
+ var expectedFormats = []string{"profile"}
+ var testActivityDumpTracedEventTypes = []string{"exec", "syscalls"}
+
+ outputDir := t.TempDir()
+ os.MkdirAll(outputDir, 0755)
+ defer os.RemoveAll(outputDir)
+
+ fakeManualResolver := NewFakeManualResolver()
+
+ test, err := newTestModule(t, nil, []*rules.RuleDefinition{}, withStaticOpts(testOpts{
+ enableActivityDump: true,
+ activityDumpRateLimiter: 200,
+ activityDumpTracedCgroupsCount: 10,
+ activityDumpDuration: testActivityDumpDuration,
+ activityDumpLocalStorageDirectory: outputDir,
+ activityDumpLocalStorageCompression: false,
+ activityDumpLocalStorageFormats: expectedFormats,
+ activityDumpTracedEventTypes: testActivityDumpTracedEventTypes,
+ enableSecurityProfile: true,
+ securityProfileDir: outputDir,
+ securityProfileWatchDir: true,
+ enableAnomalyDetection: true,
+ anomalyDetectionEventTypes: testActivityDumpTracedEventTypes,
+ anomalyDetectionMinimumStablePeriodExec: 10 * time.Second,
+ anomalyDetectionMinimumStablePeriodDNS: 10 * time.Second,
+ anomalyDetectionDefaultMinimumStablePeriod: 10 * time.Second,
+ anomalyDetectionWarmupPeriod: 1 * time.Second,
+ tagsResolver: fakeManualResolver,
+ }))
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer test.Close()
+ syscallTester, err := loadSyscallTester(t, test, "syscall_tester")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ dockerInstanceV1, err := test.StartADocker()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer dockerInstanceV1.stop()
+
+ cmd := dockerInstanceV1.Command(syscallTester, []string{"sleep", "1"}, []string{})
+ _, err = cmd.CombinedOutput()
+ if err != nil {
+ t.Fatal(err)
+ }
+ time.Sleep(1 * time.Second) // a quick sleep to let events be added to the dump
+
+ err = test.StopActivityDump("", dockerInstanceV1.containerID)
+ if err != nil {
+ t.Fatal(err)
+ }
+ time.Sleep(6 * time.Second) // a quick sleep to let the profile be loaded (5sec debounce + 1sec spare)
+
+ // HERE: V1 is learning
+
+ // Some syscall will be missing from the initial dump because they had no way to come back to user space
+ // (i.e. no new syscall to flush the dirty entry + no new exec + no new exit)
+ t.Run("life-cycle-v1-learning", func(t *testing.T) {
+ err = test.GetCustomEventSent(t, func() error {
+ cmd := dockerInstanceV1.Command("sleep", []string{"1"}, []string{})
+ _, err = cmd.CombinedOutput()
+ return err
+ }, func(r *rules.Rule, event *events.CustomEvent) bool {
+ // We shouldn't see anything: the profile is still learning
+ data, _ := event.MarshalJSON()
+ t.Fatal(fmt.Errorf("syscall anomaly detected when it should have been ignored: %s", string(data)))
+ // we answer false on purpose: we might have 2 or more syscall anomaly events
+ return false
+ }, time.Second*2, model.SyscallsEventType, events.AnomalyDetectionRuleID)
+ })
+
+ time.Sleep(time.Second * 10) // waiting for the stable period
+
+ // HERE: V1 is stable
+
+ t.Run("life-cycle-v1-stable-no-anomaly", func(t *testing.T) {
+ err = test.GetCustomEventSent(t, func() error {
+ cmd := dockerInstanceV1.Command("sleep", []string{"1"}, []string{})
+ _, err = cmd.CombinedOutput()
+ return err
+ }, func(r *rules.Rule, event *events.CustomEvent) bool {
+ // this time we shouldn't see anything new.
+ data, _ := event.MarshalJSON()
+ t.Fatal(fmt.Errorf("syscall anomaly detected when it should have been ignored: %s", string(data)))
+ return false
+ }, time.Second*2, model.SyscallsEventType, events.AnomalyDetectionRuleID)
+ })
+
+ t.Run("life-cycle-v1-stable-anomaly", func(t *testing.T) {
+ err = test.GetCustomEventSent(t, func() error {
+ // this will generate new syscalls, and should therefore generate an anomaly
+ cmd := dockerInstanceV1.Command("nslookup", []string{"google.com"}, []string{})
+ _, _ = cmd.CombinedOutput()
+ return nil
+ }, func(r *rules.Rule, event *events.CustomEvent) bool {
+ assert.Equal(t, events.AnomalyDetectionRuleID, r.Rule.ID, "wrong custom event rule ID")
+ return true
+ }, time.Second*3, model.SyscallsEventType, events.AnomalyDetectionRuleID)
+ if err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ selector := fakeManualResolver.GetContainerSelector(dockerInstanceV1.containerID)
+ fakeManualResolver.SpecifyNextSelector(&cgroupModel.WorkloadSelector{
+ Image: selector.Image,
+ Tag: selector.Tag + "+",
+ })
+ dockerInstanceV2, err := test.StartADocker()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer dockerInstanceV2.stop()
+
+ // HERE: V1 is stable and V2 is learning
+
+ t.Run("life-cycle-v1-stable-v2-learning-anomaly", func(t *testing.T) {
+ var gotSyscallsEvent bool
+ err = test.GetCustomEventSent(t, func() error {
+ cmd := dockerInstanceV2.Command("date", []string{}, []string{})
+ _, _ = cmd.CombinedOutput()
+ return nil
+ }, func(r *rules.Rule, event *events.CustomEvent) bool {
+ // we should see an anomaly that will be inserted in the profile
+ assert.Equal(t, events.AnomalyDetectionRuleID, r.Rule.ID, "wrong custom event rule ID")
+ gotSyscallsEvent = true
+ // there may be multiple syscalls events
+ return false
+ }, time.Second*3, model.SyscallsEventType, events.AnomalyDetectionRuleID)
+ if !gotSyscallsEvent {
+ t.Fatal(err)
+ }
+ })
+
+ t.Run("life-cycle-v1-stable-v2-learning-no-anomaly", func(t *testing.T) {
+ err = test.GetCustomEventSent(t, func() error {
+ cmd := dockerInstanceV2.Command("date", []string{}, []string{})
+ _, _ = cmd.CombinedOutput()
+ return nil
+ }, func(r *rules.Rule, event *events.CustomEvent) bool {
+ // this time we shouldn't see anything new.
+ data, _ := event.MarshalJSON()
+ t.Fatal(fmt.Errorf("syscall anomaly detected when it should have been ignored: %s", string(data)))
+ return false
+ }, time.Second*2, model.SyscallsEventType, events.AnomalyDetectionRuleID)
+ })
+
+ if err := test.SetProfileVersionState(&cgroupModel.WorkloadSelector{
+ Image: selector.Image,
+ Tag: "*",
+ }, selector.Tag, model.UnstableEventType); err != nil {
+ t.Fatal(err)
+ }
+
+ // HERE: V1 is unstable and V2 is learning
+
+ t.Run("life-cycle-v1-unstable-v2-learning", func(t *testing.T) {
+ err = test.GetCustomEventSent(t, func() error {
+ cmd := dockerInstanceV1.Command("nslookup", []string{"google.com"}, []string{})
+ _, _ = cmd.CombinedOutput()
+ return nil
+ }, func(r *rules.Rule, event *events.CustomEvent) bool {
+ // We shouldn't see anything: the profile is unstable
+ data, _ := event.MarshalJSON()
+ t.Fatal(fmt.Errorf("syscall anomaly detected when it should have been ignored: %s", string(data)))
+ // we answer false on purpose: we might have 2 or more syscall anomaly events
+ return false
+ }, time.Second*2, model.SyscallsEventType, events.AnomalyDetectionRuleID)
+ })
+}
+
+func TestSecurityProfileLifeCycleEvictionProcess(t *testing.T) {
SkipIfNotAvailable(t)
// skip test that are about to be run on docker (to avoid trying spawning docker in docker)
@@ -1550,7 +1740,7 @@ func TestSecurityProfileLifeCycleEvictitonProcess(t *testing.T) {
})
}
-func TestSecurityProfileLifeCycleEvictitonDNS(t *testing.T) {
+func TestSecurityProfileLifeCycleEvictionDNS(t *testing.T) {
SkipIfNotAvailable(t)
// skip test that are about to be run on docker (to avoid trying spawning docker in docker)
@@ -1728,7 +1918,7 @@ func TestSecurityProfileLifeCycleEvictitonDNS(t *testing.T) {
})
}
-func TestSecurityProfileLifeCycleEvictitonProcessUnstable(t *testing.T) {
+func TestSecurityProfileLifeCycleEvictionProcessUnstable(t *testing.T) {
SkipIfNotAvailable(t)
// skip test that are about to be run on docker (to avoid trying spawning docker in docker)