From a26554b5ddc15f7a64fcc65095a8e16d3593539b Mon Sep 17 00:00:00 2001 From: Amit Schendel Date: Mon, 27 Jan 2025 11:14:56 +0000 Subject: [PATCH 01/40] Using storage commit Signed-off-by: Amit Schendel --- go.mod | 3 +++ go.sum | 6 ++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index ef8fcba9..9014a8e9 100644 --- a/go.mod +++ b/go.mod @@ -295,6 +295,7 @@ require ( github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect github.com/xlab/treeprint v1.2.0 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + github.com/yl2chen/cidranger v1.0.2 // indirect go.mongodb.org/mongo-driver v1.17.1 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect @@ -351,3 +352,5 @@ require ( replace github.com/vishvananda/netns => github.com/inspektor-gadget/netns v0.0.5-0.20230524185006-155d84c555d6 replace github.com/mholt/archiver/v3 v3.5.1 => github.com/anchore/archiver/v3 v3.5.2 + +replace github.com/kubescape/storage => github.com/kubescape/storage v0.0.154-0.20250126153504-e6f6cb58d361 diff --git a/go.sum b/go.sum index 0bcd4316..b0306e72 100644 --- a/go.sum +++ b/go.sum @@ -692,8 +692,8 @@ github.com/kubescape/go-logger v0.0.23 h1:5xh+Nm8eGImhFbtippRKLaFgsvlKE1ufvQhNM2 github.com/kubescape/go-logger v0.0.23/go.mod h1:Ayg7g769c7sXVB+P3fkJmbsJpoEmMmaUf9jeo+XuC3U= github.com/kubescape/k8s-interface v0.0.180 h1:0Rhh8KBKGZXjn+2g5CKBq7B54IMAyEixd+aOrH2qcnw= github.com/kubescape/k8s-interface v0.0.180/go.mod h1:rfrA5pX86V3LP8JSH5HOcunYt43s4vWpCZ5UkPvr6q0= -github.com/kubescape/storage v0.0.141 h1:dck2qWHtlui6T4rUoV3U9O/BNZybxlDN7Vrlnow4CSg= -github.com/kubescape/storage v0.0.141/go.mod h1:oqdNN8gANL58jagGDsEbRiWskvKK0s/ckdqrHZnG+Vw= +github.com/kubescape/storage v0.0.154-0.20250126153504-e6f6cb58d361 h1:Mu6OEeCCjo+m4BNKcahrYGhP0GJ9CznOa8K4kXT+K80= +github.com/kubescape/storage v0.0.154-0.20250126153504-e6f6cb58d361/go.mod h1:7ai5ePqTXdSTCGjpEHVFXKggrbey/guM5e13w7Y3yMw= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= @@ -1077,6 +1077,8 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavM github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= +github.com/yl2chen/cidranger v1.0.2 h1:lbOWZVCG1tCRX4u24kuM1Tb4nHqWkDxwLdoS+SevawU= +github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= From 1e0387909f611814a527b0df2cf6950f95dd6927 Mon Sep 17 00:00:00 2001 From: Amit Schendel Date: Mon, 27 Jan 2025 11:15:15 +0000 Subject: [PATCH 02/40] Adding application profile support for call stack Signed-off-by: Amit Schendel --- .../v1/applicationprofile_manager.go | 153 +++-- .../v1/applicationprofile_manager_test.go | 570 ++++++++++++++++++ pkg/applicationprofilemanager/v1/helpers.go | 43 ++ pkg/utils/applicationprofile.go | 17 +- pkg/utils/applicationprofile_test.go | 8 +- 5 files changed, 735 insertions(+), 56 deletions(-) diff --git a/pkg/applicationprofilemanager/v1/applicationprofile_manager.go b/pkg/applicationprofilemanager/v1/applicationprofile_manager.go index d13f2d15..46b3ee5e 100644 --- a/pkg/applicationprofilemanager/v1/applicationprofile_manager.go +++ b/pkg/applicationprofilemanager/v1/applicationprofile_manager.go @@ -42,22 +42,24 @@ type ApplicationProfileManager struct { cfg config.Config clusterName string ctx context.Context - containerMutexes storageUtils.MapMutex[string] // key is k8sContainerID - trackedContainers mapset.Set[string] // key is k8sContainerID - removedContainers mapset.Set[string] // key is k8sContainerID - droppedEventsContainers mapset.Set[string] // key is k8sContainerID - savedCapabilities maps.SafeMap[string, cache.ExpiringCache] // key is k8sContainerID - savedEndpoints maps.SafeMap[string, cache.ExpiringCache] // key is k8sContainerID - savedExecs maps.SafeMap[string, cache.ExpiringCache] // key is k8sContainerID - savedOpens maps.SafeMap[string, cache.ExpiringCache] // key is k8sContainerID - savedSyscalls maps.SafeMap[string, mapset.Set[string]] // key is k8sContainerID - savedRulePolicies maps.SafeMap[string, cache.ExpiringCache] // key is k8sContainerID - toSaveCapabilities maps.SafeMap[string, mapset.Set[string]] // key is k8sContainerID - toSaveEndpoints maps.SafeMap[string, *maps.SafeMap[string, *v1beta1.HTTPEndpoint]] // key is k8sContainerID - toSaveExecs maps.SafeMap[string, *maps.SafeMap[string, []string]] // key is k8sContainerID - toSaveOpens maps.SafeMap[string, *maps.SafeMap[string, mapset.Set[string]]] // key is k8sContainerID - toSaveRulePolicies maps.SafeMap[string, *maps.SafeMap[string, *v1beta1.RulePolicy]] // key is k8sContainerID - watchedContainerChannels maps.SafeMap[string, chan error] // key is ContainerID + containerMutexes storageUtils.MapMutex[string] // key is k8sContainerID + trackedContainers mapset.Set[string] // key is k8sContainerID + removedContainers mapset.Set[string] // key is k8sContainerID + droppedEventsContainers mapset.Set[string] // key is k8sContainerID + savedCapabilities maps.SafeMap[string, cache.ExpiringCache] // key is k8sContainerID + savedEndpoints maps.SafeMap[string, cache.ExpiringCache] // key is k8sContainerID + savedExecs maps.SafeMap[string, cache.ExpiringCache] // key is k8sContainerID + savedOpens maps.SafeMap[string, cache.ExpiringCache] // key is k8sContainerID + savedSyscalls maps.SafeMap[string, mapset.Set[string]] // key is k8sContainerID + savedRulePolicies maps.SafeMap[string, cache.ExpiringCache] // key is k8sContainerID + savedCallStacks maps.SafeMap[string, cache.ExpiringCache] // key is k8sContainerID + toSaveCapabilities maps.SafeMap[string, mapset.Set[string]] // key is k8sContainerID + toSaveEndpoints maps.SafeMap[string, *maps.SafeMap[string, *v1beta1.HTTPEndpoint]] // key is k8sContainerID + toSaveExecs maps.SafeMap[string, *maps.SafeMap[string, []string]] // key is k8sContainerID + toSaveOpens maps.SafeMap[string, *maps.SafeMap[string, mapset.Set[string]]] // key is k8sContainerID + toSaveRulePolicies maps.SafeMap[string, *maps.SafeMap[string, *v1beta1.RulePolicy]] // key is k8sContainerID + toSaveCallStacks maps.SafeMap[string, *maps.SafeMap[string, *v1beta1.IdentifiedCallStack]] // key is k8sContainerID + watchedContainerChannels maps.SafeMap[string, chan error] // key is ContainerID k8sClient k8sclient.K8sClientInterface k8sObjectCache objectcache.K8sObjectCache storageClient storage.StorageClient @@ -103,6 +105,8 @@ func (am *ApplicationProfileManager) deleteResources(watchedContainer *utils.Wat am.toSaveExecs.Delete(watchedContainer.K8sContainerID) am.toSaveOpens.Delete(watchedContainer.K8sContainerID) am.toSaveRulePolicies.Delete(watchedContainer.K8sContainerID) + am.savedCallStacks.Delete(watchedContainer.K8sContainerID) + am.toSaveCallStacks.Delete(watchedContainer.K8sContainerID) am.watchedContainerChannels.Delete(watchedContainer.ContainerID) } @@ -286,6 +290,17 @@ func (am *ApplicationProfileManager) saveProfile(ctx context.Context, watchedCon return true }) + // Get call stacks + callStacks := make([]v1beta1.IdentifiedCallStack, 0) + toSaveCallStacks := am.toSaveCallStacks.Get(watchedContainer.K8sContainerID) + // Point IG to a new call stacks map + am.toSaveCallStacks.Set(watchedContainer.K8sContainerID, new(maps.SafeMap[string, *v1beta1.IdentifiedCallStack])) + // Prepare call stacks slice + toSaveCallStacks.Range(func(identifier string, callStack *v1beta1.IdentifiedCallStack) bool { + callStacks = append(callStacks, *callStack) + return true + }) + // new activity // the process tries to use JSON patching to avoid conflicts between updates on the same object from different containers // 0. create both a patch and a new object @@ -295,9 +310,9 @@ func (am *ApplicationProfileManager) saveProfile(ctx context.Context, watchedCon // 3a. the object is missing its container slice - ADD one with the container profile at the right index // 3b. the object is missing the container profile - ADD the container profile at the right index // 3c. default - patch the container ourselves and REPLACE it at the right index - if len(capabilities) > 0 || len(endpoints) > 0 || len(execs) > 0 || len(opens) > 0 || len(toSaveSyscalls) > 0 || len(initalizeOperations) > 0 || watchedContainer.StatusUpdated() { + if len(capabilities) > 0 || len(endpoints) > 0 || len(execs) > 0 || len(opens) > 0 || len(toSaveSyscalls) > 0 || len(initalizeOperations) > 0 || len(callStacks) > 0 || watchedContainer.StatusUpdated() { // 0. calculate patch - operations := utils.CreateCapabilitiesPatchOperations(capabilities, observedSyscalls, execs, opens, endpoints, rulePolicies, watchedContainer.ContainerType.String(), watchedContainer.ContainerIndex) + operations := utils.CreateCapabilitiesPatchOperations(capabilities, observedSyscalls, execs, opens, endpoints, rulePolicies, callStacks, watchedContainer.ContainerType.String(), watchedContainer.ContainerIndex) if len(initalizeOperations) > 0 { operations = append(operations, initalizeOperations...) } @@ -337,16 +352,17 @@ func (am *ApplicationProfileManager) saveProfile(ctx context.Context, watchedCon helpers.String("k8s workload", watchedContainer.K8sContainerID)) } containers = append(containers, v1beta1.ApplicationProfileContainer{ - Name: info.Name, - Endpoints: make([]v1beta1.HTTPEndpoint, 0), - Execs: make([]v1beta1.ExecCalls, 0), - Opens: make([]v1beta1.OpenCalls, 0), - Capabilities: make([]string, 0), - Syscalls: make([]string, 0), - PolicyByRuleId: make(map[string]v1beta1.RulePolicy), - SeccompProfile: seccompProfile, - ImageTag: info.ImageTag, - ImageID: info.ImageID, + Name: info.Name, + Endpoints: make([]v1beta1.HTTPEndpoint, 0), + Execs: make([]v1beta1.ExecCalls, 0), + Opens: make([]v1beta1.OpenCalls, 0), + Capabilities: make([]string, 0), + Syscalls: make([]string, 0), + PolicyByRuleId: make(map[string]v1beta1.RulePolicy), + IdentifiedCallStacks: make([]v1beta1.IdentifiedCallStack, 0), + SeccompProfile: seccompProfile, + ImageTag: info.ImageTag, + ImageID: info.ImageID, }) } return containers @@ -357,7 +373,7 @@ func (am *ApplicationProfileManager) saveProfile(ctx context.Context, watchedCon newObject.Spec.EphemeralContainers = addContainers(newObject.Spec.EphemeralContainers, watchedContainer.ContainerInfos[utils.EphemeralContainer]) // enrich container newContainer := utils.GetApplicationProfileContainer(newObject, watchedContainer.ContainerType, watchedContainer.ContainerIndex) - utils.EnrichApplicationProfileContainer(newContainer, capabilities, observedSyscalls, execs, opens, endpoints, rulePolicies, watchedContainer.ImageID, watchedContainer.ImageTag) + utils.EnrichApplicationProfileContainer(newContainer, capabilities, observedSyscalls, execs, opens, endpoints, rulePolicies, callStacks, watchedContainer.ImageID, watchedContainer.ImageTag) // try to create object if err := am.storageClient.CreateApplicationProfile(newObject, namespace); err != nil { gotErr = err @@ -398,20 +414,21 @@ func (am *ApplicationProfileManager) saveProfile(ctx context.Context, watchedCon helpers.String("k8s workload", watchedContainer.K8sContainerID)) } existingContainer = &v1beta1.ApplicationProfileContainer{ - Name: info.Name, - Endpoints: make([]v1beta1.HTTPEndpoint, 0), - Execs: make([]v1beta1.ExecCalls, 0), - Opens: make([]v1beta1.OpenCalls, 0), - Capabilities: make([]string, 0), - Syscalls: make([]string, 0), - PolicyByRuleId: make(map[string]v1beta1.RulePolicy), - SeccompProfile: seccompProfile, - ImageTag: info.ImageTag, - ImageID: info.ImageID, + Name: info.Name, + Endpoints: make([]v1beta1.HTTPEndpoint, 0), + Execs: make([]v1beta1.ExecCalls, 0), + Opens: make([]v1beta1.OpenCalls, 0), + Capabilities: make([]string, 0), + Syscalls: make([]string, 0), + PolicyByRuleId: make(map[string]v1beta1.RulePolicy), + IdentifiedCallStacks: make([]v1beta1.IdentifiedCallStack, 0), + SeccompProfile: seccompProfile, + ImageTag: info.ImageTag, + ImageID: info.ImageID, } } // update it - utils.EnrichApplicationProfileContainer(existingContainer, capabilities, observedSyscalls, execs, opens, endpoints, rulePolicies, watchedContainer.ImageID, watchedContainer.ImageTag) + utils.EnrichApplicationProfileContainer(existingContainer, capabilities, observedSyscalls, execs, opens, endpoints, rulePolicies, callStacks, watchedContainer.ImageID, watchedContainer.ImageTag) // get existing containers var existingContainers []v1beta1.ApplicationProfileContainer if watchedContainer.ContainerType == utils.Container { @@ -445,16 +462,17 @@ func (am *ApplicationProfileManager) saveProfile(ctx context.Context, watchedCon Op: "add", Path: fmt.Sprintf("/spec/%s/%d", watchedContainer.ContainerType, i), Value: v1beta1.ApplicationProfileContainer{ - Name: info.Name, - Endpoints: make([]v1beta1.HTTPEndpoint, 0), - Execs: make([]v1beta1.ExecCalls, 0), - Opens: make([]v1beta1.OpenCalls, 0), - Capabilities: make([]string, 0), - Syscalls: make([]string, 0), - PolicyByRuleId: make(map[string]v1beta1.RulePolicy), - SeccompProfile: seccompProfile, - ImageTag: info.ImageTag, - ImageID: info.ImageID, + Name: info.Name, + Endpoints: make([]v1beta1.HTTPEndpoint, 0), + Execs: make([]v1beta1.ExecCalls, 0), + Opens: make([]v1beta1.OpenCalls, 0), + Capabilities: make([]string, 0), + Syscalls: make([]string, 0), + PolicyByRuleId: make(map[string]v1beta1.RulePolicy), + IdentifiedCallStacks: make([]v1beta1.IdentifiedCallStack, 0), + SeccompProfile: seccompProfile, + ImageTag: info.ImageTag, + ImageID: info.ImageID, }, }) } @@ -510,6 +528,13 @@ func (am *ApplicationProfileManager) saveProfile(ctx context.Context, watchedCon }) // restore opens map entries toSaveOpens.Range(utils.SetInMap(am.toSaveOpens.Get(watchedContainer.K8sContainerID))) + // restore call stacks + toSaveCallStacks.Range(func(identifier string, callStack *v1beta1.IdentifiedCallStack) bool { + if !am.toSaveCallStacks.Get(watchedContainer.K8sContainerID).Has(identifier) { + am.toSaveCallStacks.Get(watchedContainer.K8sContainerID).Set(identifier, callStack) + } + return true + }) } else { // for status updates to be tracked, we reset the update flag watchedContainer.ResetStatusUpdatedFlag() @@ -552,12 +577,21 @@ func (am *ApplicationProfileManager) saveProfile(ctx context.Context, watchedCon return true }) + // record saved call stacks + toSaveCallStacks.Range(func(identifier string, callStack *v1beta1.IdentifiedCallStack) bool { + if !am.toSaveCallStacks.Get(watchedContainer.K8sContainerID).Has(identifier) { + am.savedCallStacks.Get(watchedContainer.K8sContainerID).Set(identifier, callStack) + } + return true + }) + logger.L().Debug("ApplicationProfileManager - saved application profile", helpers.Int("capabilities", len(capabilities)), helpers.Int("endpoints", toSaveEndpoints.Len()), helpers.Int("execs", toSaveExecs.Len()), helpers.Int("opens", toSaveOpens.Len()), helpers.Int("rule policies", toSaveRulePolicies.Len()), + helpers.Int("call stacks", toSaveCallStacks.Len()), helpers.Int("init operations", len(initalizeOperations)), helpers.String("slug", slug), helpers.Int("container index", watchedContainer.ContainerIndex), @@ -665,6 +699,8 @@ func (am *ApplicationProfileManager) ContainerCallback(notif containercollection am.toSaveExecs.Set(k8sContainerID, new(maps.SafeMap[string, []string])) am.toSaveOpens.Set(k8sContainerID, new(maps.SafeMap[string, mapset.Set[string]])) am.toSaveRulePolicies.Set(k8sContainerID, new(maps.SafeMap[string, *v1beta1.RulePolicy])) + am.savedCallStacks.Set(k8sContainerID, cache.NewTTL(5*am.cfg.UpdateDataPeriod, am.cfg.UpdateDataPeriod)) + am.toSaveCallStacks.Set(k8sContainerID, new(maps.SafeMap[string, *v1beta1.IdentifiedCallStack])) am.removedContainers.Remove(k8sContainerID) // make sure container is not in the removed list am.trackedContainers.Add(k8sContainerID) @@ -799,3 +835,20 @@ func (am *ApplicationProfileManager) ReportRulePolicy(k8sContainerID, ruleId, al toBeSavedPolicies.Set(ruleId, finalPolicy) } + +func (am *ApplicationProfileManager) ReportIdentifiedCallStack(k8sContainerID string, callStack *v1beta1.IdentifiedCallStack) { + if err := am.waitForContainer(k8sContainerID); err != nil { + return + } + + // Generate unique identifier for the call stack + callStackIdentifier := CalculateSHA256CallStackHash(callStack) + + // Check if we already have this call stack + if _, ok := am.savedCallStacks.Get(k8sContainerID).Get(callStackIdentifier); ok { + return + } + + // Add to call stacks map + am.toSaveCallStacks.Get(k8sContainerID).Set(callStackIdentifier, callStack) +} diff --git a/pkg/applicationprofilemanager/v1/applicationprofile_manager_test.go b/pkg/applicationprofilemanager/v1/applicationprofile_manager_test.go index c6eb8dfb..477d3c23 100644 --- a/pkg/applicationprofilemanager/v1/applicationprofile_manager_test.go +++ b/pkg/applicationprofilemanager/v1/applicationprofile_manager_test.go @@ -518,3 +518,573 @@ func TestReportRulePolicy(t *testing.T) { }) } } + +func TestReportIdentifiedCallStack(t *testing.T) { + // Setup common test environment + cfg := config.Config{ + InitialDelay: 1 * time.Second, + MaxSniffingTime: 5 * time.Minute, + UpdateDataPeriod: 1 * time.Second, + } + ctx := context.TODO() + k8sClient := &k8sclient.K8sClientMock{} + storageClient := &storage.StorageHttpClientMock{} + k8sObjectCacheMock := &objectcache.K8sObjectCacheMock{} + seccompManagerMock := &seccompmanager.SeccompManagerMock{} + + tests := []struct { + name string + k8sContainerID string + callStacks []*v1beta1.IdentifiedCallStack + expected []v1beta1.IdentifiedCallStack + }{ + { + name: "Single callstack", + k8sContainerID: "ns/pod/cont", + callStacks: []*v1beta1.IdentifiedCallStack{ + { + CallID: "open", + CallStack: v1beta1.CallStack{ + Root: &v1beta1.CallStackNode{ + Frame: &v1beta1.StackFrame{ + FileID: 1, + Lineno: 42, + }, + }, + }, + }, + }, + expected: []v1beta1.IdentifiedCallStack{ + { + CallID: "open", + CallStack: v1beta1.CallStack{ + Root: &v1beta1.CallStackNode{ + Frame: &v1beta1.StackFrame{ + FileID: 1, + Lineno: 42, + }, + }, + }, + }, + }, + }, + { + name: "Multiple callstacks", + k8sContainerID: "ns/pod/cont", + callStacks: []*v1beta1.IdentifiedCallStack{ + { + CallID: "open", + CallStack: v1beta1.CallStack{ + Root: &v1beta1.CallStackNode{ + Frame: &v1beta1.StackFrame{ + FileID: 1, + Lineno: 42, + }, + Children: []*v1beta1.CallStackNode{ + { + Frame: &v1beta1.StackFrame{ + FileID: 2, + Lineno: 84, + }, + }, + }, + }, + }, + }, + { + CallID: "exec", + CallStack: v1beta1.CallStack{ + Root: &v1beta1.CallStackNode{ + Frame: &v1beta1.StackFrame{ + FileID: 3, + Lineno: 120, + }, + }, + }, + }, + }, + expected: []v1beta1.IdentifiedCallStack{ + { + CallID: "open", + CallStack: v1beta1.CallStack{ + Root: &v1beta1.CallStackNode{ + Frame: &v1beta1.StackFrame{ + FileID: 1, + Lineno: 42, + }, + Children: []*v1beta1.CallStackNode{ + { + Frame: &v1beta1.StackFrame{ + FileID: 2, + Lineno: 84, + }, + }, + }, + }, + }, + }, + { + CallID: "exec", + CallStack: v1beta1.CallStack{ + Root: &v1beta1.CallStackNode{ + Frame: &v1beta1.StackFrame{ + FileID: 3, + Lineno: 120, + }, + }, + }, + }, + }, + }, + { + name: "Duplicate callstack", + k8sContainerID: "ns/pod/cont", + callStacks: []*v1beta1.IdentifiedCallStack{ + { + CallID: "open", + CallStack: v1beta1.CallStack{ + Root: &v1beta1.CallStackNode{ + Frame: &v1beta1.StackFrame{ + FileID: 1, + Lineno: 42, + }, + }, + }, + }, + { + CallID: "open", + CallStack: v1beta1.CallStack{ + Root: &v1beta1.CallStackNode{ + Frame: &v1beta1.StackFrame{ + FileID: 1, + Lineno: 42, + }, + }, + }, + }, + }, + expected: []v1beta1.IdentifiedCallStack{ + { + CallID: "open", + CallStack: v1beta1.CallStack{ + Root: &v1beta1.CallStackNode{ + Frame: &v1beta1.StackFrame{ + FileID: 1, + Lineno: 42, + }, + }, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + am, err := CreateApplicationProfileManager(ctx, cfg, "cluster", k8sClient, storageClient, k8sObjectCacheMock, seccompManagerMock) + assert.NoError(t, err) + + // Initialize container tracking + am.savedCallStacks.Set(tt.k8sContainerID, istiocache.NewTTL(5*am.cfg.UpdateDataPeriod, am.cfg.UpdateDataPeriod)) + am.toSaveCallStacks.Set(tt.k8sContainerID, new(maps.SafeMap[string, *v1beta1.IdentifiedCallStack])) + am.trackedContainers.Add(tt.k8sContainerID) + + // Report each callstack + for _, callStack := range tt.callStacks { + am.ReportIdentifiedCallStack(tt.k8sContainerID, callStack) + } + + // Collect all callstacks that were queued to be saved + var resultCallStacks []v1beta1.IdentifiedCallStack + am.toSaveCallStacks.Get(tt.k8sContainerID).Range(func(identifier string, callStack *v1beta1.IdentifiedCallStack) bool { + resultCallStacks = append(resultCallStacks, *callStack) + return true + }) + + // Verify results + assert.Equal(t, len(tt.expected), len(resultCallStacks)) + for _, expectedCallStack := range tt.expected { + found := false + for _, resultCallStack := range resultCallStacks { + if compareCallStacks(&expectedCallStack, &resultCallStack) { + found = true + break + } + } + assert.True(t, found, "Expected call stack not found in results") + } + }) + } +} + +// Helper function to deeply compare two call stacks +func compareCallStacks(a, b *v1beta1.IdentifiedCallStack) bool { + if a.CallID != b.CallID { + return false + } + return compareCallStackNodes(a.CallStack.Root, b.CallStack.Root) +} + +func compareCallStackNodes(a, b *v1beta1.CallStackNode) bool { + if a == nil && b == nil { + return true + } + if a == nil || b == nil { + return false + } + if !compareStackFrames(a.Frame, b.Frame) { + return false + } + if len(a.Children) != len(b.Children) { + return false + } + for i := range a.Children { + if !compareCallStackNodes(a.Children[i], b.Children[i]) { + return false + } + } + return true +} + +func compareStackFrames(a, b *v1beta1.StackFrame) bool { + if a == nil && b == nil { + return true + } + if a == nil || b == nil { + return false + } + return a.FileID == b.FileID && a.Lineno == b.Lineno +} + +func TestApplicationProfileManagerWithCallStacks(t *testing.T) { + // Setup test environment + cfg := config.Config{ + InitialDelay: 1 * time.Second, + MaxSniffingTime: 5 * time.Minute, + UpdateDataPeriod: 5 * time.Second, + } + ctx := context.TODO() + k8sClient := &k8sclient.K8sClientMock{} + storageClient := &storage.StorageHttpClientMock{} + k8sObjectCacheMock := &objectcache.K8sObjectCacheMock{} + seccompManagerMock := &seccompmanager.SeccompManagerMock{} + + am, err := CreateApplicationProfileManager(ctx, cfg, "cluster", k8sClient, storageClient, k8sObjectCacheMock, seccompManagerMock) + assert.NoError(t, err) + + // Prepare container + container := &containercollection.Container{ + K8s: containercollection.K8sMetadata{ + BasicK8sMetadata: types.BasicK8sMetadata{ + Namespace: "ns", + PodName: "pod", + ContainerName: "cont", + }, + }, + Runtime: containercollection.RuntimeMetadata{ + BasicRuntimeMetadata: types.BasicRuntimeMetadata{ + ContainerID: "5fff6a395ce4e6984a9447cc6cfb09f473eaf278498243963fcc944889bc8400", + ContainerStartedAt: types.Time(time.Now().UnixNano()), + }, + }, + } + + sharedWatchedContainerData := &utils.WatchedContainerData{} + err = ensureInstanceID(container, sharedWatchedContainerData, k8sClient, "cluster") + assert.NoError(t, err) + k8sObjectCacheMock.SetSharedContainerData(container.Runtime.ContainerID, sharedWatchedContainerData) + + // Start container monitoring + am.ContainerCallback(containercollection.PubSubEvent{ + Type: containercollection.EventTypeAddContainer, + Container: container, + }) + + // Wait for initialization + time.Sleep(2 * time.Second) + + // Report call stacks + callStack1 := &v1beta1.IdentifiedCallStack{ + CallID: "open", + CallStack: v1beta1.CallStack{ + Root: &v1beta1.CallStackNode{ + Frame: &v1beta1.StackFrame{ + FileID: 1, + Lineno: 42, + }, + }, + }, + } + + callStack2 := &v1beta1.IdentifiedCallStack{ + CallID: "exec", + CallStack: v1beta1.CallStack{ + Root: &v1beta1.CallStackNode{ + Frame: &v1beta1.StackFrame{ + FileID: 2, + Lineno: 84, + }, + Children: []*v1beta1.CallStackNode{ + { + Frame: &v1beta1.StackFrame{ + FileID: 3, + Lineno: 120, + }, + }, + }, + }, + }, + } + + // Report call stacks and verify they're stored in toSaveCallStacks + am.ReportIdentifiedCallStack("ns/pod/cont", callStack1) + time.Sleep(100 * time.Millisecond) + + // Debug logging for first call stack + toSaveCallStacks := am.toSaveCallStacks.Get("ns/pod/cont") + t.Logf("After first report - Number of call stacks in toSave: %d", toSaveCallStacks.Len()) + toSaveCallStacks.Range(func(identifier string, stack *v1beta1.IdentifiedCallStack) bool { + t.Logf("Found call stack with ID: %s", stack.CallID) + return true + }) + + am.ReportIdentifiedCallStack("ns/pod/cont", callStack2) + time.Sleep(100 * time.Millisecond) + + // Debug logging for second call stack + t.Logf("After second report - Number of call stacks in toSave: %d", toSaveCallStacks.Len()) + toSaveCallStacks.Range(func(identifier string, stack *v1beta1.IdentifiedCallStack) bool { + t.Logf("Found call stack with ID: %s", stack.CallID) + return true + }) + + // Let monitoring run and trigger a save + time.Sleep(2 * time.Second) + + // Check saved call stacks after first save + savedCallStacks := am.savedCallStacks.Get("ns/pod/cont") + t.Logf("After first save - Number of call stacks in saved: %v", savedCallStacks) + + // Let it run for the remaining time + time.Sleep(2 * time.Second) + + // Report container stopped + am.ContainerCallback(containercollection.PubSubEvent{ + Type: containercollection.EventTypeRemoveContainer, + Container: container, + }) + + // Wait for final processing + time.Sleep(2 * time.Second) + + // Debug logging for profiles + for i, profile := range storageClient.ApplicationProfiles { + t.Logf("Profile %d:", i) + t.Logf(" Number of containers: %d", len(profile.Spec.Containers)) + if len(profile.Spec.Containers) > 1 { + t.Logf(" Call stacks in container[1]: %d", len(profile.Spec.Containers[1].IdentifiedCallStacks)) + for _, cs := range profile.Spec.Containers[1].IdentifiedCallStacks { + t.Logf(" Call stack ID: %s", cs.CallID) + } + } + } + + // Verify results + assert.Greater(t, len(storageClient.ApplicationProfiles), 0, "No application profiles were created") + + // Get the latest profile + latestProfile := storageClient.ApplicationProfiles[len(storageClient.ApplicationProfiles)-1] + assert.NotNil(t, latestProfile.Spec.Containers, "Containers slice is nil") + assert.Greater(t, len(latestProfile.Spec.Containers), 1, "Not enough containers in profile") + + foundCallStacks := latestProfile.Spec.Containers[1].IdentifiedCallStacks + + // Sort both expected and found call stacks + sortCallStacks := func(cs []v1beta1.IdentifiedCallStack) { + sort.Slice(cs, func(i, j int) bool { + return string(cs[i].CallID) < string(cs[j].CallID) + }) + } + + expectedCallStacks := []v1beta1.IdentifiedCallStack{*callStack1, *callStack2} + sortCallStacks(expectedCallStacks) + sortCallStacks(foundCallStacks) + + t.Logf("Number of profiles: %d", len(storageClient.ApplicationProfiles)) + t.Logf("Latest profile containers: %d", len(latestProfile.Spec.Containers)) + t.Logf("Found call stacks: %d", len(foundCallStacks)) + + assert.Equal(t, len(expectedCallStacks), len(foundCallStacks), "Number of call stacks doesn't match") + if len(foundCallStacks) == len(expectedCallStacks) { + for i := range expectedCallStacks { + assert.True(t, compareCallStacks(&expectedCallStacks[i], &foundCallStacks[i]), + "Call stack mismatch at index %d\nExpected: %+v\nGot: %+v", i, expectedCallStacks[i], foundCallStacks[i]) + } + } +} + +func TestCallStackComparison(t *testing.T) { + tests := []struct { + name string + stack1 *v1beta1.IdentifiedCallStack + stack2 *v1beta1.IdentifiedCallStack + expected bool + }{ + { + name: "identical single frame stacks", + stack1: &v1beta1.IdentifiedCallStack{ + CallID: "test", + CallStack: v1beta1.CallStack{ + Root: &v1beta1.CallStackNode{ + Frame: &v1beta1.StackFrame{ + FileID: 1, + Lineno: 42, + }, + }, + }, + }, + stack2: &v1beta1.IdentifiedCallStack{ + CallID: "test", + CallStack: v1beta1.CallStack{ + Root: &v1beta1.CallStackNode{ + Frame: &v1beta1.StackFrame{ + FileID: 1, + Lineno: 42, + }, + }, + }, + }, + expected: true, + }, + { + name: "different call IDs", + stack1: &v1beta1.IdentifiedCallStack{ + CallID: "test1", + CallStack: v1beta1.CallStack{ + Root: &v1beta1.CallStackNode{ + Frame: &v1beta1.StackFrame{ + FileID: 1, + Lineno: 42, + }, + }, + }, + }, + stack2: &v1beta1.IdentifiedCallStack{ + CallID: "test2", + CallStack: v1beta1.CallStack{ + Root: &v1beta1.CallStackNode{ + Frame: &v1beta1.StackFrame{ + FileID: 1, + Lineno: 42, + }, + }, + }, + }, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := compareCallStacks(tt.stack1, tt.stack2) + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestCalculateSHA256CallStackHash(t *testing.T) { + callStack1 := &v1beta1.IdentifiedCallStack{ + CallID: "open", + CallStack: v1beta1.CallStack{ + Root: &v1beta1.CallStackNode{ + Frame: &v1beta1.StackFrame{ + FileID: 1, + Lineno: 42, + }, + }, + }, + } + + callStack2 := &v1beta1.IdentifiedCallStack{ + CallID: "exec", + CallStack: v1beta1.CallStack{ + Root: &v1beta1.CallStackNode{ + Frame: &v1beta1.StackFrame{ + FileID: 2, + Lineno: 84, + }, + Children: []*v1beta1.CallStackNode{ + { + Frame: &v1beta1.StackFrame{ + FileID: 3, + Lineno: 120, + }, + }, + }, + }, + }, + } + + hash1 := CalculateSHA256CallStackHash(callStack1) + hash2 := CalculateSHA256CallStackHash(callStack2) + + t.Logf("Hash for callStack1: %s", hash1) + t.Logf("Hash for callStack2: %s", hash2) + + // Different call stacks should produce different hashes + assert.NotEqual(t, hash1, hash2, "Different call stacks should have different hashes") + + // Same call stack should produce same hash + hash1Again := CalculateSHA256CallStackHash(callStack1) + assert.Equal(t, hash1, hash1Again, "Same call stack should produce same hash") + + // Test with children nodes + childrenCallStack := &v1beta1.IdentifiedCallStack{ + CallID: "exec", + CallStack: v1beta1.CallStack{ + Root: &v1beta1.CallStackNode{ + Frame: &v1beta1.StackFrame{ + FileID: 2, + Lineno: 84, + }, + Children: []*v1beta1.CallStackNode{ + { + Frame: &v1beta1.StackFrame{ + FileID: 3, + Lineno: 120, + }, + }, + }, + }, + }, + } + + hashWithChildren := CalculateSHA256CallStackHash(childrenCallStack) + t.Logf("Hash for childrenCallStack: %s", hashWithChildren) + + // Different call stack with same root but different children should have different hash + differentChildrenCallStack := &v1beta1.IdentifiedCallStack{ + CallID: "exec", + CallStack: v1beta1.CallStack{ + Root: &v1beta1.CallStackNode{ + Frame: &v1beta1.StackFrame{ + FileID: 2, + Lineno: 84, + }, + Children: []*v1beta1.CallStackNode{ + { + Frame: &v1beta1.StackFrame{ + FileID: 4, // Different FileID + Lineno: 120, + }, + }, + }, + }, + }, + } + + hashWithDifferentChildren := CalculateSHA256CallStackHash(differentChildrenCallStack) + t.Logf("Hash for differentChildrenCallStack: %s", hashWithDifferentChildren) + assert.NotEqual(t, hashWithChildren, hashWithDifferentChildren, + "Call stacks with different children should have different hashes") +} diff --git a/pkg/applicationprofilemanager/v1/helpers.go b/pkg/applicationprofilemanager/v1/helpers.go index fa92ae3a..6e7a0137 100644 --- a/pkg/applicationprofilemanager/v1/helpers.go +++ b/pkg/applicationprofilemanager/v1/helpers.go @@ -2,6 +2,7 @@ package applicationprofilemanager import ( "crypto/sha256" + "encoding/binary" "encoding/hex" "encoding/json" "fmt" @@ -128,3 +129,45 @@ func GetInitOperations(containerType string, containerIndex int) []utils.PatchOp return operations } + +func CalculateSHA256CallStackHash(callStack *v1beta1.IdentifiedCallStack) string { + hash := sha256.New() + + // Write CallID + hash.Write([]byte(callStack.CallID)) + + // Helper function to write frame data + writeFrame := func(frame *v1beta1.StackFrame) { + if frame != nil { + fileIDBytes := make([]byte, 8) + binary.LittleEndian.PutUint64(fileIDBytes, frame.FileID) + hash.Write(fileIDBytes) + + linenoBytes := make([]byte, 8) + binary.LittleEndian.PutUint64(linenoBytes, frame.Lineno) + hash.Write(linenoBytes) + } + } + + // Helper function to recursively process node and its children + var processNode func(*v1beta1.CallStackNode) + processNode = func(node *v1beta1.CallStackNode) { + if node == nil { + return + } + + writeFrame(node.Frame) + + // Process children + for _, child := range node.Children { + processNode(child) + } + } + + // Process the entire call stack + if callStack.CallStack.Root != nil { + processNode(callStack.CallStack.Root) + } + + return hex.EncodeToString(hash.Sum(nil)) +} diff --git a/pkg/utils/applicationprofile.go b/pkg/utils/applicationprofile.go index 406a7ca0..393ca22e 100644 --- a/pkg/utils/applicationprofile.go +++ b/pkg/utils/applicationprofile.go @@ -13,7 +13,7 @@ const ( ContainerAllowed = "containerAllowed" ) -func CreateCapabilitiesPatchOperations(capabilities, syscalls []string, execs map[string][]string, opens map[string]mapset.Set[string], endpoints map[string]*v1beta1.HTTPEndpoint, rulePolicies map[string]v1beta1.RulePolicy, containerType string, containerIndex int) []PatchOperation { +func CreateCapabilitiesPatchOperations(capabilities, syscalls []string, execs map[string][]string, opens map[string]mapset.Set[string], endpoints map[string]*v1beta1.HTTPEndpoint, rulePolicies map[string]v1beta1.RulePolicy, callStacks []v1beta1.IdentifiedCallStack, containerType string, containerIndex int) []PatchOperation { var profileOperations []PatchOperation // add capabilities sort.Strings(capabilities) @@ -81,10 +81,20 @@ func CreateCapabilitiesPatchOperations(capabilities, syscalls []string, execs ma // add rule policies profileOperations = append(profileOperations, createRulePolicyOperations(rulePolicies, containerType, containerIndex)...) + // add call stacks + callStacksPath := fmt.Sprintf("/spec/%s/%d/identifiedCallStacks/-", containerType, containerIndex) + for _, callStack := range callStacks { + profileOperations = append(profileOperations, PatchOperation{ + Op: "add", + Path: callStacksPath, + Value: callStack, + }) + } + return profileOperations } -func EnrichApplicationProfileContainer(container *v1beta1.ApplicationProfileContainer, observedCapabilities, observedSyscalls []string, execs map[string][]string, opens map[string]mapset.Set[string], endpoints map[string]*v1beta1.HTTPEndpoint, rulePolicies map[string]v1beta1.RulePolicy, imageID, imageTag string) { +func EnrichApplicationProfileContainer(container *v1beta1.ApplicationProfileContainer, observedCapabilities, observedSyscalls []string, execs map[string][]string, opens map[string]mapset.Set[string], endpoints map[string]*v1beta1.HTTPEndpoint, rulePolicies map[string]v1beta1.RulePolicy, callStacks []v1beta1.IdentifiedCallStack, imageID, imageTag string) { // add image metadata container.ImageID = imageID container.ImageTag = imageTag @@ -141,6 +151,9 @@ func EnrichApplicationProfileContainer(container *v1beta1.ApplicationProfileCont } } + // add call stacks + container.IdentifiedCallStacks = append(container.IdentifiedCallStacks, callStacks...) + } // TODO make generic? diff --git a/pkg/utils/applicationprofile_test.go b/pkg/utils/applicationprofile_test.go index 9b5f01bf..97bd78dc 100644 --- a/pkg/utils/applicationprofile_test.go +++ b/pkg/utils/applicationprofile_test.go @@ -43,21 +43,21 @@ func Test_EnrichApplicationProfileContainer(t *testing.T) { var test map[string]*v1beta1.HTTPEndpoint // empty enrich - EnrichApplicationProfileContainer(existingContainer, []string{}, []string{}, map[string][]string{}, map[string]mapset.Set[string]{}, test, map[string]v1beta1.RulePolicy{}, "", "") + EnrichApplicationProfileContainer(existingContainer, []string{}, []string{}, map[string][]string{}, map[string]mapset.Set[string]{}, test, map[string]v1beta1.RulePolicy{}, []v1beta1.IdentifiedCallStack{}, "", "") assert.Equal(t, 5, len(existingContainer.Capabilities)) assert.Equal(t, 2, len(existingContainer.Execs)) assert.Equal(t, 5, len(existingContainer.Syscalls)) assert.Equal(t, 0, len(existingContainer.Opens)) // enrich with existing capabilities, syscalls - no change - EnrichApplicationProfileContainer(existingContainer, []string{"SETGID"}, []string{"listen"}, map[string][]string{}, map[string]mapset.Set[string]{}, test, map[string]v1beta1.RulePolicy{}, "", "") + EnrichApplicationProfileContainer(existingContainer, []string{"SETGID"}, []string{"listen"}, map[string][]string{}, map[string]mapset.Set[string]{}, test, map[string]v1beta1.RulePolicy{}, []v1beta1.IdentifiedCallStack{}, "", "") assert.Equal(t, 5, len(existingContainer.Capabilities)) assert.Equal(t, 2, len(existingContainer.Execs)) assert.Equal(t, 5, len(existingContainer.Syscalls)) assert.Equal(t, 0, len(existingContainer.Opens)) // enrich with new capabilities, syscalls - add - EnrichApplicationProfileContainer(existingContainer, []string{"NEW"}, []string{"xxx", "yyy"}, map[string][]string{}, map[string]mapset.Set[string]{}, test, map[string]v1beta1.RulePolicy{}, "", "") + EnrichApplicationProfileContainer(existingContainer, []string{"NEW"}, []string{"xxx", "yyy"}, map[string][]string{}, map[string]mapset.Set[string]{}, test, map[string]v1beta1.RulePolicy{}, []v1beta1.IdentifiedCallStack{}, "", "") assert.Equal(t, 6, len(existingContainer.Capabilities)) assert.Equal(t, 2, len(existingContainer.Execs)) assert.Equal(t, 7, len(existingContainer.Syscalls)) @@ -67,7 +67,7 @@ func Test_EnrichApplicationProfileContainer(t *testing.T) { opens := map[string]mapset.Set[string]{ "/checkoutservice": mapset.NewSet("O_RDONLY", "O_WRONLY"), } - EnrichApplicationProfileContainer(existingContainer, []string{"NEW"}, []string{"xxx", "yyy"}, map[string][]string{}, opens, test, map[string]v1beta1.RulePolicy{}, "", "") + EnrichApplicationProfileContainer(existingContainer, []string{"NEW"}, []string{"xxx", "yyy"}, map[string][]string{}, opens, test, map[string]v1beta1.RulePolicy{}, []v1beta1.IdentifiedCallStack{}, "", "") assert.Equal(t, 6, len(existingContainer.Capabilities)) assert.Equal(t, 2, len(existingContainer.Execs)) assert.Equal(t, 7, len(existingContainer.Syscalls)) From 797851d5e6443d7cfbfb94377f70eacd3c74f654 Mon Sep 17 00:00:00 2001 From: Amit Schendel Date: Mon, 27 Jan 2025 11:29:17 +0000 Subject: [PATCH 03/40] Adding Report function to interface Signed-off-by: Amit Schendel --- .../applicationprofile_manager_interface.go | 2 ++ .../applicationprofile_manager_mock.go | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/pkg/applicationprofilemanager/applicationprofile_manager_interface.go b/pkg/applicationprofilemanager/applicationprofile_manager_interface.go index 8e0e4732..6bebecce 100644 --- a/pkg/applicationprofilemanager/applicationprofile_manager_interface.go +++ b/pkg/applicationprofilemanager/applicationprofile_manager_interface.go @@ -3,6 +3,7 @@ package applicationprofilemanager import ( containercollection "github.com/inspektor-gadget/inspektor-gadget/pkg/container-collection" tracerhttptype "github.com/kubescape/node-agent/pkg/ebpf/gadgets/http/types" + "github.com/kubescape/storage/pkg/apis/softwarecomposition/v1beta1" ) type ApplicationProfileManagerClient interface { @@ -13,6 +14,7 @@ type ApplicationProfileManagerClient interface { ReportFileOpen(k8sContainerID, path string, flags []string) ReportHTTPEvent(k8sContainerID string, event *tracerhttptype.Event) ReportRulePolicy(k8sContainerID, ruleId, allowedProcess string, allowedContainer bool) + ReportIdentifiedCallStack(k8sContainerID string, callStack *v1beta1.IdentifiedCallStack) ReportDroppedEvent(k8sContainerID string) ContainerReachedMaxTime(containerID string) } diff --git a/pkg/applicationprofilemanager/applicationprofile_manager_mock.go b/pkg/applicationprofilemanager/applicationprofile_manager_mock.go index b1bd0528..56bdb6a2 100644 --- a/pkg/applicationprofilemanager/applicationprofile_manager_mock.go +++ b/pkg/applicationprofilemanager/applicationprofile_manager_mock.go @@ -3,6 +3,7 @@ package applicationprofilemanager import ( containercollection "github.com/inspektor-gadget/inspektor-gadget/pkg/container-collection" tracerhttptype "github.com/kubescape/node-agent/pkg/ebpf/gadgets/http/types" + "github.com/kubescape/storage/pkg/apis/softwarecomposition/v1beta1" ) type ApplicationProfileManagerMock struct { @@ -46,6 +47,10 @@ func (a ApplicationProfileManagerMock) ReportRulePolicy(_, _, _ string, _ bool) // noop } +func (a ApplicationProfileManagerMock) ReportIdentifiedCallStack(_ string, _ *v1beta1.IdentifiedCallStack) { + // noop +} + func (a ApplicationProfileManagerMock) ContainerReachedMaxTime(_ string) { // noop } From ab390159f63d2f263f86e79099c42480e9498408 Mon Sep 17 00:00:00 2001 From: Amit Schendel Date: Mon, 27 Jan 2025 14:16:44 +0000 Subject: [PATCH 04/40] Adding enricher Signed-off-by: Amit Schendel --- main.go | 2 +- .../applicationprofile_manager_interface.go | 10 ++- .../applicationprofile_manager_mock.go | 5 +- .../v1/applicationprofile_manager.go | 35 +++++++-- .../v1/applicationprofile_manager_test.go | 73 +++++++++++++++---- pkg/containerwatcher/v1/container_watcher.go | 7 +- pkg/utils/utils.go | 7 ++ 7 files changed, 106 insertions(+), 33 deletions(-) diff --git a/main.go b/main.go index 6c78f9d5..180ea93a 100644 --- a/main.go +++ b/main.go @@ -176,7 +176,7 @@ func main() { // Create the application profile manager var applicationProfileManager applicationprofilemanager.ApplicationProfileManagerClient if cfg.EnableApplicationProfile { - applicationProfileManager, err = applicationprofilemanagerv1.CreateApplicationProfileManager(ctx, cfg, clusterData.ClusterName, k8sClient, storageClient, k8sObjectCache, seccompManager) + applicationProfileManager, err = applicationprofilemanagerv1.CreateApplicationProfileManager(ctx, cfg, clusterData.ClusterName, k8sClient, storageClient, k8sObjectCache, seccompManager, nil) if err != nil { logger.L().Ctx(ctx).Fatal("error creating the application profile manager", helpers.Error(err)) } diff --git a/pkg/applicationprofilemanager/applicationprofile_manager_interface.go b/pkg/applicationprofilemanager/applicationprofile_manager_interface.go index 6bebecce..ae1b9512 100644 --- a/pkg/applicationprofilemanager/applicationprofile_manager_interface.go +++ b/pkg/applicationprofilemanager/applicationprofile_manager_interface.go @@ -2,7 +2,9 @@ package applicationprofilemanager import ( containercollection "github.com/inspektor-gadget/inspektor-gadget/pkg/container-collection" + "github.com/kubescape/node-agent/pkg/ebpf/events" tracerhttptype "github.com/kubescape/node-agent/pkg/ebpf/gadgets/http/types" + "github.com/kubescape/node-agent/pkg/utils" "github.com/kubescape/storage/pkg/apis/softwarecomposition/v1beta1" ) @@ -10,11 +12,15 @@ type ApplicationProfileManagerClient interface { ContainerCallback(notif containercollection.PubSubEvent) RegisterPeekFunc(peek func(mntns uint64) ([]string, error)) ReportCapability(k8sContainerID, capability string) - ReportFileExec(k8sContainerID, path string, args []string) - ReportFileOpen(k8sContainerID, path string, flags []string) + ReportFileExec(k8sContainerID string, event events.ExecEvent) + ReportFileOpen(k8sContainerID string, event events.OpenEvent) ReportHTTPEvent(k8sContainerID string, event *tracerhttptype.Event) ReportRulePolicy(k8sContainerID, ruleId, allowedProcess string, allowedContainer bool) ReportIdentifiedCallStack(k8sContainerID string, callStack *v1beta1.IdentifiedCallStack) ReportDroppedEvent(k8sContainerID string) ContainerReachedMaxTime(containerID string) } + +type Enricher interface { + EnrichEvent(event utils.EnrichEvent, callID string) +} diff --git a/pkg/applicationprofilemanager/applicationprofile_manager_mock.go b/pkg/applicationprofilemanager/applicationprofile_manager_mock.go index 56bdb6a2..1b42c178 100644 --- a/pkg/applicationprofilemanager/applicationprofile_manager_mock.go +++ b/pkg/applicationprofilemanager/applicationprofile_manager_mock.go @@ -2,6 +2,7 @@ package applicationprofilemanager import ( containercollection "github.com/inspektor-gadget/inspektor-gadget/pkg/container-collection" + "github.com/kubescape/node-agent/pkg/ebpf/events" tracerhttptype "github.com/kubescape/node-agent/pkg/ebpf/gadgets/http/types" "github.com/kubescape/storage/pkg/apis/softwarecomposition/v1beta1" ) @@ -27,11 +28,11 @@ func (a ApplicationProfileManagerMock) ReportCapability(_, _ string) { // noop } -func (a ApplicationProfileManagerMock) ReportFileExec(_, _ string, _ []string) { +func (a ApplicationProfileManagerMock) ReportFileExec(_ string, _ events.ExecEvent) { // noop } -func (a ApplicationProfileManagerMock) ReportFileOpen(_, _ string, _ []string) { +func (a ApplicationProfileManagerMock) ReportFileOpen(_ string, _ events.OpenEvent) { // noop } diff --git a/pkg/applicationprofilemanager/v1/applicationprofile_manager.go b/pkg/applicationprofilemanager/v1/applicationprofile_manager.go index 46b3ee5e..66530c0d 100644 --- a/pkg/applicationprofilemanager/v1/applicationprofile_manager.go +++ b/pkg/applicationprofilemanager/v1/applicationprofile_manager.go @@ -19,6 +19,7 @@ import ( helpersv1 "github.com/kubescape/k8s-interface/instanceidhandler/v1/helpers" "github.com/kubescape/node-agent/pkg/applicationprofilemanager" "github.com/kubescape/node-agent/pkg/config" + "github.com/kubescape/node-agent/pkg/ebpf/events" tracerhttptype "github.com/kubescape/node-agent/pkg/ebpf/gadgets/http/types" "github.com/kubescape/node-agent/pkg/k8sclient" "github.com/kubescape/node-agent/pkg/objectcache" @@ -65,11 +66,12 @@ type ApplicationProfileManager struct { storageClient storage.StorageClient syscallPeekFunc func(nsMountId uint64) ([]string, error) seccompManager seccompmanager.SeccompManagerClient + enricher applicationprofilemanager.Enricher } var _ applicationprofilemanager.ApplicationProfileManagerClient = (*ApplicationProfileManager)(nil) -func CreateApplicationProfileManager(ctx context.Context, cfg config.Config, clusterName string, k8sClient k8sclient.K8sClientInterface, storageClient storage.StorageClient, k8sObjectCache objectcache.K8sObjectCache, seccompManager seccompmanager.SeccompManagerClient) (*ApplicationProfileManager, error) { +func CreateApplicationProfileManager(ctx context.Context, cfg config.Config, clusterName string, k8sClient k8sclient.K8sClientInterface, storageClient storage.StorageClient, k8sObjectCache objectcache.K8sObjectCache, seccompManager seccompmanager.SeccompManagerClient, enricher applicationprofilemanager.Enricher) (*ApplicationProfileManager, error) { return &ApplicationProfileManager{ cfg: cfg, clusterName: clusterName, @@ -82,6 +84,7 @@ func CreateApplicationProfileManager(ctx context.Context, cfg config.Config, clu removedContainers: mapset.NewSet[string](), droppedEventsContainers: mapset.NewSet[string](), seccompManager: seccompManager, + enricher: enricher, }, nil } @@ -730,39 +733,55 @@ func (am *ApplicationProfileManager) ReportCapability(k8sContainerID, capability am.toSaveCapabilities.Get(k8sContainerID).Add(capability) } -func (am *ApplicationProfileManager) ReportFileExec(k8sContainerID, path string, args []string) { +func (am *ApplicationProfileManager) ReportFileExec(k8sContainerID string, event events.ExecEvent) { if err := am.waitForContainer(k8sContainerID); err != nil { return } + + path := event.Comm + if len(event.Args) > 0 { + path = event.Args[0] + } + // check if we already have this exec // we use a SHA256 hash of the exec to identify it uniquely (path + args, in the order they were provided) - execIdentifier := utils.CalculateSHA256FileExecHash(path, args) + execIdentifier := utils.CalculateSHA256FileExecHash(path, event.Args) if _, ok := am.savedExecs.Get(k8sContainerID).Get(execIdentifier); ok { return } // add to exec map, first element is the path, the rest are the args - am.toSaveExecs.Get(k8sContainerID).Set(execIdentifier, append([]string{path}, args...)) + am.toSaveExecs.Get(k8sContainerID).Set(execIdentifier, append([]string{path}, event.Args...)) + + if am.enricher != nil { + go am.enricher.EnrichEvent(&event, execIdentifier) + } } -func (am *ApplicationProfileManager) ReportFileOpen(k8sContainerID, path string, flags []string) { +func (am *ApplicationProfileManager) ReportFileOpen(k8sContainerID string, event events.OpenEvent) { if err := am.waitForContainer(k8sContainerID); err != nil { return } // deduplicate /proc/1234/* into /proc/.../* (quite a common case) // we perform it here instead of waiting for compression + path := event.Path if strings.HasPrefix(path, "/proc/") { path = procRegex.ReplaceAllString(path, "/proc/"+dynamicpathdetector.DynamicIdentifier) } // check if we already have this open - if opens, ok := am.savedOpens.Get(k8sContainerID).Get(path); ok && opens.(mapset.Set[string]).Contains(flags...) { + if opens, ok := am.savedOpens.Get(k8sContainerID).Get(path); ok && opens.(mapset.Set[string]).Contains(event.Flags...) { return } // add to open map openMap := am.toSaveOpens.Get(k8sContainerID) if openMap.Has(path) { - openMap.Get(path).Append(flags...) + openMap.Get(path).Append(event.Flags...) } else { - openMap.Set(path, mapset.NewSet[string](flags...)) + openMap.Set(path, mapset.NewSet[string](event.Flags...)) + } + + if am.enricher != nil { + openIdentifier := utils.CalculateSHA256FileOpenHash(path) + go am.enricher.EnrichEvent(&event, openIdentifier) } } diff --git a/pkg/applicationprofilemanager/v1/applicationprofile_manager_test.go b/pkg/applicationprofilemanager/v1/applicationprofile_manager_test.go index 477d3c23..29c8a18f 100644 --- a/pkg/applicationprofilemanager/v1/applicationprofile_manager_test.go +++ b/pkg/applicationprofilemanager/v1/applicationprofile_manager_test.go @@ -16,10 +16,14 @@ import ( "github.com/goradd/maps" containercollection "github.com/inspektor-gadget/inspektor-gadget/pkg/container-collection" + tracerexectype "github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets/trace/exec/types" + traceropentype "github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets/trace/open/types" "github.com/inspektor-gadget/inspektor-gadget/pkg/types" + eventtypes "github.com/inspektor-gadget/inspektor-gadget/pkg/types" "github.com/kubescape/k8s-interface/instanceidhandler/v1" "github.com/kubescape/k8s-interface/workloadinterface" "github.com/kubescape/node-agent/pkg/config" + "github.com/kubescape/node-agent/pkg/ebpf/events" tracerhttptype "github.com/kubescape/node-agent/pkg/ebpf/gadgets/http/types" "github.com/kubescape/node-agent/pkg/k8sclient" "github.com/kubescape/node-agent/pkg/objectcache" @@ -92,7 +96,7 @@ func TestApplicationProfileManager(t *testing.T) { storageClient := &storage.StorageHttpClientMock{} k8sObjectCacheMock := &objectcache.K8sObjectCacheMock{} seccompManagerMock := &seccompmanager.SeccompManagerMock{} - am, err := CreateApplicationProfileManager(ctx, cfg, "cluster", k8sClient, storageClient, k8sObjectCacheMock, seccompManagerMock) + am, err := CreateApplicationProfileManager(ctx, cfg, "cluster", k8sClient, storageClient, k8sObjectCacheMock, seccompManagerMock, nil) assert.NoError(t, err) // prepare container container := &containercollection.Container{ @@ -121,13 +125,47 @@ func TestApplicationProfileManager(t *testing.T) { // report capability go am.ReportCapability("ns/pod/cont", "NET_BIND_SERVICE") // report file exec - go am.ReportFileExec("ns/pod/cont", "/bin/bash", []string{"-c", "ls"}) - go am.ReportFileExec("ns/pod/cont", "/bin/bash", []string{"-c", "ls"}) // duplicate - not reported - go am.ReportFileExec("ns/pod/cont", "/bin/bash", []string{"-c", "ls", "-l"}) // additional arg - reported - go am.ReportFileExec("ns/pod/cont", "/bin/bash", []string{"ls", "-c"}) // different order of args - reported - go am.ReportFileExec("ns/pod/cont", "/bin/ls", []string{"-l"}) + e := &events.ExecEvent{ + Event: tracerexectype.Event{ + Event: eventtypes.Event{ + CommonData: eventtypes.CommonData{ + K8s: eventtypes.K8sMetadata{ + BasicK8sMetadata: eventtypes.BasicK8sMetadata{ + ContainerName: "cont", + }, + }, + }, + }, + Comm: "/bin/bash", + Args: []string{"/bin/bash", "-c", "ls"}, + }, + } + go am.ReportFileExec("ns/pod/cont", *e) + go am.ReportFileExec("ns/pod/cont", *e) // duplicate - not reported + e.Args = []string{"/bin/bash", "-c", "ls", "-l"} + go am.ReportFileExec("ns/pod/cont", *e) // additional arg - reported + e.Args = []string{"/bin/bash", "ls", "-c"} + go am.ReportFileExec("ns/pod/cont", *e) // different order of args - reported + e.Args = []string{"/bin/ls", "-l"} + go am.ReportFileExec("ns/pod/cont", *e) // report file open - go am.ReportFileOpen("ns/pod/cont", "/etc/passwd", []string{"O_RDONLY"}) + f := &events.OpenEvent{ + Event: traceropentype.Event{ + Event: eventtypes.Event{ + CommonData: eventtypes.CommonData{ + K8s: eventtypes.K8sMetadata{ + BasicK8sMetadata: eventtypes.BasicK8sMetadata{ + ContainerName: "cont", + }, + }, + }, + }, + Path: "/etc/passwd", + FullPath: "/etc/passwd", + Flags: []string{"O_RDONLY"}, + }, + } + go am.ReportFileOpen("ns/pod/cont", *f) // report container started (race condition with reports) am.ContainerCallback(containercollection.PubSubEvent{ @@ -137,9 +175,12 @@ func TestApplicationProfileManager(t *testing.T) { // let it run for a while time.Sleep(15 * time.Second) // need to sleep longer because of AddRandomDuration in startApplicationProfiling // report another file open - go am.ReportFileOpen("ns/pod/cont", "/etc/hosts", []string{"O_RDONLY"}) + f.Path = "/etc/hosts" + f.FullPath = "/etc/hosts" + go am.ReportFileOpen("ns/pod/cont", *f) // report another file open - go am.ReportFileExec("ns/pod/cont", "/bin/bash", []string{"-c", "ls"}) // duplicate - will not be reported + e.Args = []string{"/bin/bash", "-c", "ls"} + go am.ReportFileExec("ns/pod/cont", *e) // duplicate - will not be reported // report endpoint @@ -251,10 +292,10 @@ func TestApplicationProfileManager(t *testing.T) { reportedExecs := storageClient.ApplicationProfiles[0].Spec.Containers[1].Execs expectedExecs := []v1beta1.ExecCalls{ - {Path: "/bin/bash", Args: []string{"-c", "ls"}, Envs: []string(nil)}, - {Path: "/bin/bash", Args: []string{"-c", "ls", "-l"}, Envs: []string(nil)}, - {Path: "/bin/bash", Args: []string{"ls", "-c"}, Envs: []string(nil)}, - {Path: "/bin/ls", Args: []string{"-l"}, Envs: []string(nil)}, + {Path: "/bin/bash", Args: []string{"/bin/bash", "-c", "ls"}, Envs: []string(nil)}, + {Path: "/bin/bash", Args: []string{"/bin/bash", "-c", "ls", "-l"}, Envs: []string(nil)}, + {Path: "/bin/bash", Args: []string{"/bin/bash", "ls", "-c"}, Envs: []string(nil)}, + {Path: "/bin/ls", Args: []string{"/bin/ls", "-l"}, Envs: []string(nil)}, } assert.Len(t, reportedExecs, len(expectedExecs)) for _, expectedExec := range expectedExecs { @@ -485,7 +526,7 @@ func TestReportRulePolicy(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - am, err := CreateApplicationProfileManager(ctx, cfg, "cluster", k8sClient, storageClient, k8sObjectCacheMock, seccompManagerMock) + am, err := CreateApplicationProfileManager(ctx, cfg, "cluster", k8sClient, storageClient, k8sObjectCacheMock, seccompManagerMock, nil) assert.NoError(t, err) am.savedRulePolicies.Set(tt.k8sContainerID, istiocache.NewTTL(5*am.cfg.UpdateDataPeriod, am.cfg.UpdateDataPeriod)) @@ -681,7 +722,7 @@ func TestReportIdentifiedCallStack(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - am, err := CreateApplicationProfileManager(ctx, cfg, "cluster", k8sClient, storageClient, k8sObjectCacheMock, seccompManagerMock) + am, err := CreateApplicationProfileManager(ctx, cfg, "cluster", k8sClient, storageClient, k8sObjectCacheMock, seccompManagerMock, nil) assert.NoError(t, err) // Initialize container tracking @@ -769,7 +810,7 @@ func TestApplicationProfileManagerWithCallStacks(t *testing.T) { k8sObjectCacheMock := &objectcache.K8sObjectCacheMock{} seccompManagerMock := &seccompmanager.SeccompManagerMock{} - am, err := CreateApplicationProfileManager(ctx, cfg, "cluster", k8sClient, storageClient, k8sObjectCacheMock, seccompManagerMock) + am, err := CreateApplicationProfileManager(ctx, cfg, "cluster", k8sClient, storageClient, k8sObjectCacheMock, seccompManagerMock, nil) assert.NoError(t, err) // Prepare container diff --git a/pkg/containerwatcher/v1/container_watcher.go b/pkg/containerwatcher/v1/container_watcher.go index 50c4309b..01b07da1 100644 --- a/pkg/containerwatcher/v1/container_watcher.go +++ b/pkg/containerwatcher/v1/container_watcher.go @@ -222,7 +222,7 @@ func CreateIGContainerWatcher(cfg config.Config, applicationProfileManager appli metrics.ReportEvent(utils.ExecveEventType) processManager.ReportEvent(utils.ExecveEventType, &event) - applicationProfileManager.ReportFileExec(k8sContainerID, path, event.Args) + applicationProfileManager.ReportFileExec(k8sContainerID, event) rulePolicyReporter.ReportEvent(utils.ExecveEventType, &event, k8sContainerID, event.Comm) // Report exec events to event receivers @@ -245,13 +245,12 @@ func CreateIGContainerWatcher(cfg config.Config, applicationProfileManager appli return } - path := event.Path if cfg.EnableFullPathTracing { - path = event.FullPath + event.Path = event.FullPath } metrics.ReportEvent(utils.OpenEventType) - applicationProfileManager.ReportFileOpen(k8sContainerID, path, event.Flags) + applicationProfileManager.ReportFileOpen(k8sContainerID, event) ruleManager.ReportEvent(utils.OpenEventType, &event) malwareManager.ReportEvent(utils.OpenEventType, &event) diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index a481a1ad..c7225861 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -490,6 +490,13 @@ func CalculateSHA256FileExecHash(path string, args []string) string { return hex.EncodeToString(hashInBytes) } +func CalculateSHA256FileOpenHash(path string) string { + hsh := sha256.New() + hsh.Write([]byte(path)) + hashInBytes := hsh.Sum(nil) + return hex.EncodeToString(hashInBytes) +} + // CalculateFileHashes calculates both SHA1 and MD5 hashes of the given file. func CalculateFileHashes(path string) (sha1Hash string, md5Hash string, err error) { file, err := os.Open(path) From 9f78549ba9b345c0cf455dc3cd67a6353b0a653e Mon Sep 17 00:00:00 2001 From: Amit Schendel Date: Mon, 27 Jan 2025 14:23:28 +0000 Subject: [PATCH 05/40] Adding k8sContainerID Signed-off-by: Amit Schendel --- .../applicationprofile_manager_interface.go | 2 +- .../v1/applicationprofile_manager.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/applicationprofilemanager/applicationprofile_manager_interface.go b/pkg/applicationprofilemanager/applicationprofile_manager_interface.go index ae1b9512..e9477f6b 100644 --- a/pkg/applicationprofilemanager/applicationprofile_manager_interface.go +++ b/pkg/applicationprofilemanager/applicationprofile_manager_interface.go @@ -22,5 +22,5 @@ type ApplicationProfileManagerClient interface { } type Enricher interface { - EnrichEvent(event utils.EnrichEvent, callID string) + EnrichEvent(k8sContainerID string, event utils.EnrichEvent, callID string) } diff --git a/pkg/applicationprofilemanager/v1/applicationprofile_manager.go b/pkg/applicationprofilemanager/v1/applicationprofile_manager.go index 66530c0d..2831ce14 100644 --- a/pkg/applicationprofilemanager/v1/applicationprofile_manager.go +++ b/pkg/applicationprofilemanager/v1/applicationprofile_manager.go @@ -753,7 +753,7 @@ func (am *ApplicationProfileManager) ReportFileExec(k8sContainerID string, event am.toSaveExecs.Get(k8sContainerID).Set(execIdentifier, append([]string{path}, event.Args...)) if am.enricher != nil { - go am.enricher.EnrichEvent(&event, execIdentifier) + go am.enricher.EnrichEvent(k8sContainerID, &event, execIdentifier) } } @@ -781,7 +781,7 @@ func (am *ApplicationProfileManager) ReportFileOpen(k8sContainerID string, event if am.enricher != nil { openIdentifier := utils.CalculateSHA256FileOpenHash(path) - go am.enricher.EnrichEvent(&event, openIdentifier) + go am.enricher.EnrichEvent(k8sContainerID, &event, openIdentifier) } } From e5610dfb0049a95cb3801356065f82da928b9df0 Mon Sep 17 00:00:00 2001 From: Amit Schendel Date: Tue, 28 Jan 2025 09:35:35 +0000 Subject: [PATCH 06/40] Bumping storage to use strings instead of uint64 Signed-off-by: Amit Schendel --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 9014a8e9..9a444b0d 100644 --- a/go.mod +++ b/go.mod @@ -353,4 +353,4 @@ replace github.com/vishvananda/netns => github.com/inspektor-gadget/netns v0.0.5 replace github.com/mholt/archiver/v3 v3.5.1 => github.com/anchore/archiver/v3 v3.5.2 -replace github.com/kubescape/storage => github.com/kubescape/storage v0.0.154-0.20250126153504-e6f6cb58d361 +replace github.com/kubescape/storage => github.com/kubescape/storage v0.0.154-0.20250128093120-9e60c021537a diff --git a/go.sum b/go.sum index b0306e72..295c8403 100644 --- a/go.sum +++ b/go.sum @@ -692,8 +692,8 @@ github.com/kubescape/go-logger v0.0.23 h1:5xh+Nm8eGImhFbtippRKLaFgsvlKE1ufvQhNM2 github.com/kubescape/go-logger v0.0.23/go.mod h1:Ayg7g769c7sXVB+P3fkJmbsJpoEmMmaUf9jeo+XuC3U= github.com/kubescape/k8s-interface v0.0.180 h1:0Rhh8KBKGZXjn+2g5CKBq7B54IMAyEixd+aOrH2qcnw= github.com/kubescape/k8s-interface v0.0.180/go.mod h1:rfrA5pX86V3LP8JSH5HOcunYt43s4vWpCZ5UkPvr6q0= -github.com/kubescape/storage v0.0.154-0.20250126153504-e6f6cb58d361 h1:Mu6OEeCCjo+m4BNKcahrYGhP0GJ9CznOa8K4kXT+K80= -github.com/kubescape/storage v0.0.154-0.20250126153504-e6f6cb58d361/go.mod h1:7ai5ePqTXdSTCGjpEHVFXKggrbey/guM5e13w7Y3yMw= +github.com/kubescape/storage v0.0.154-0.20250128093120-9e60c021537a h1:8h7z8nJMKdvF57V+QLdU5CO53fnmYyozTnz+Bv+RwM0= +github.com/kubescape/storage v0.0.154-0.20250128093120-9e60c021537a/go.mod h1:7ai5ePqTXdSTCGjpEHVFXKggrbey/guM5e13w7Y3yMw= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= From 51f930b1699472da738cc7aa1c8f2e4892fc5128 Mon Sep 17 00:00:00 2001 From: Amit Schendel Date: Tue, 28 Jan 2025 09:39:41 +0000 Subject: [PATCH 07/40] Updating types Signed-off-by: Amit Schendel --- .../v1/applicationprofile_manager_test.go | 100 +++++++++--------- pkg/applicationprofilemanager/v1/helpers.go | 10 +- 2 files changed, 52 insertions(+), 58 deletions(-) diff --git a/pkg/applicationprofilemanager/v1/applicationprofile_manager_test.go b/pkg/applicationprofilemanager/v1/applicationprofile_manager_test.go index 29c8a18f..b3d466e6 100644 --- a/pkg/applicationprofilemanager/v1/applicationprofile_manager_test.go +++ b/pkg/applicationprofilemanager/v1/applicationprofile_manager_test.go @@ -588,8 +588,8 @@ func TestReportIdentifiedCallStack(t *testing.T) { CallStack: v1beta1.CallStack{ Root: &v1beta1.CallStackNode{ Frame: &v1beta1.StackFrame{ - FileID: 1, - Lineno: 42, + FileID: "1", + Lineno: "42", }, }, }, @@ -601,8 +601,8 @@ func TestReportIdentifiedCallStack(t *testing.T) { CallStack: v1beta1.CallStack{ Root: &v1beta1.CallStackNode{ Frame: &v1beta1.StackFrame{ - FileID: 1, - Lineno: 42, + FileID: "1", + Lineno: "42", }, }, }, @@ -618,14 +618,14 @@ func TestReportIdentifiedCallStack(t *testing.T) { CallStack: v1beta1.CallStack{ Root: &v1beta1.CallStackNode{ Frame: &v1beta1.StackFrame{ - FileID: 1, - Lineno: 42, + FileID: "1", + Lineno: "42", }, Children: []*v1beta1.CallStackNode{ { Frame: &v1beta1.StackFrame{ - FileID: 2, - Lineno: 84, + FileID: "2", + Lineno: "84", }, }, }, @@ -637,8 +637,8 @@ func TestReportIdentifiedCallStack(t *testing.T) { CallStack: v1beta1.CallStack{ Root: &v1beta1.CallStackNode{ Frame: &v1beta1.StackFrame{ - FileID: 3, - Lineno: 120, + FileID: "3", + Lineno: "120", }, }, }, @@ -650,14 +650,14 @@ func TestReportIdentifiedCallStack(t *testing.T) { CallStack: v1beta1.CallStack{ Root: &v1beta1.CallStackNode{ Frame: &v1beta1.StackFrame{ - FileID: 1, - Lineno: 42, + FileID: "1", + Lineno: "42", }, Children: []*v1beta1.CallStackNode{ { Frame: &v1beta1.StackFrame{ - FileID: 2, - Lineno: 84, + FileID: "2", + Lineno: "84", }, }, }, @@ -669,8 +669,8 @@ func TestReportIdentifiedCallStack(t *testing.T) { CallStack: v1beta1.CallStack{ Root: &v1beta1.CallStackNode{ Frame: &v1beta1.StackFrame{ - FileID: 3, - Lineno: 120, + FileID: "3", + Lineno: "120", }, }, }, @@ -686,8 +686,8 @@ func TestReportIdentifiedCallStack(t *testing.T) { CallStack: v1beta1.CallStack{ Root: &v1beta1.CallStackNode{ Frame: &v1beta1.StackFrame{ - FileID: 1, - Lineno: 42, + FileID: "1", + Lineno: "42", }, }, }, @@ -697,8 +697,8 @@ func TestReportIdentifiedCallStack(t *testing.T) { CallStack: v1beta1.CallStack{ Root: &v1beta1.CallStackNode{ Frame: &v1beta1.StackFrame{ - FileID: 1, - Lineno: 42, + FileID: "1", + Lineno: "42", }, }, }, @@ -710,8 +710,8 @@ func TestReportIdentifiedCallStack(t *testing.T) { CallStack: v1beta1.CallStack{ Root: &v1beta1.CallStackNode{ Frame: &v1beta1.StackFrame{ - FileID: 1, - Lineno: 42, + FileID: "1", + Lineno: "42", }, }, }, @@ -850,8 +850,8 @@ func TestApplicationProfileManagerWithCallStacks(t *testing.T) { CallStack: v1beta1.CallStack{ Root: &v1beta1.CallStackNode{ Frame: &v1beta1.StackFrame{ - FileID: 1, - Lineno: 42, + FileID: "1", + Lineno: "42", }, }, }, @@ -862,14 +862,14 @@ func TestApplicationProfileManagerWithCallStacks(t *testing.T) { CallStack: v1beta1.CallStack{ Root: &v1beta1.CallStackNode{ Frame: &v1beta1.StackFrame{ - FileID: 2, - Lineno: 84, + FileID: "2", + Lineno: "84", }, Children: []*v1beta1.CallStackNode{ { Frame: &v1beta1.StackFrame{ - FileID: 3, - Lineno: 120, + FileID: "3", + Lineno: "120", }, }, }, @@ -978,8 +978,8 @@ func TestCallStackComparison(t *testing.T) { CallStack: v1beta1.CallStack{ Root: &v1beta1.CallStackNode{ Frame: &v1beta1.StackFrame{ - FileID: 1, - Lineno: 42, + FileID: "1", + Lineno: "42", }, }, }, @@ -989,8 +989,8 @@ func TestCallStackComparison(t *testing.T) { CallStack: v1beta1.CallStack{ Root: &v1beta1.CallStackNode{ Frame: &v1beta1.StackFrame{ - FileID: 1, - Lineno: 42, + FileID: "1", + Lineno: "42", }, }, }, @@ -1004,8 +1004,8 @@ func TestCallStackComparison(t *testing.T) { CallStack: v1beta1.CallStack{ Root: &v1beta1.CallStackNode{ Frame: &v1beta1.StackFrame{ - FileID: 1, - Lineno: 42, + FileID: "1", + Lineno: "42", }, }, }, @@ -1015,8 +1015,8 @@ func TestCallStackComparison(t *testing.T) { CallStack: v1beta1.CallStack{ Root: &v1beta1.CallStackNode{ Frame: &v1beta1.StackFrame{ - FileID: 1, - Lineno: 42, + FileID: "1", + Lineno: "42", }, }, }, @@ -1039,8 +1039,8 @@ func TestCalculateSHA256CallStackHash(t *testing.T) { CallStack: v1beta1.CallStack{ Root: &v1beta1.CallStackNode{ Frame: &v1beta1.StackFrame{ - FileID: 1, - Lineno: 42, + FileID: "1", + Lineno: "42", }, }, }, @@ -1051,14 +1051,14 @@ func TestCalculateSHA256CallStackHash(t *testing.T) { CallStack: v1beta1.CallStack{ Root: &v1beta1.CallStackNode{ Frame: &v1beta1.StackFrame{ - FileID: 2, - Lineno: 84, + FileID: "2", + Lineno: "84", }, Children: []*v1beta1.CallStackNode{ { Frame: &v1beta1.StackFrame{ - FileID: 3, - Lineno: 120, + FileID: "3", + Lineno: "120", }, }, }, @@ -1085,14 +1085,14 @@ func TestCalculateSHA256CallStackHash(t *testing.T) { CallStack: v1beta1.CallStack{ Root: &v1beta1.CallStackNode{ Frame: &v1beta1.StackFrame{ - FileID: 2, - Lineno: 84, + FileID: "2", + Lineno: "84", }, Children: []*v1beta1.CallStackNode{ { Frame: &v1beta1.StackFrame{ - FileID: 3, - Lineno: 120, + FileID: "3", + Lineno: "120", }, }, }, @@ -1109,14 +1109,14 @@ func TestCalculateSHA256CallStackHash(t *testing.T) { CallStack: v1beta1.CallStack{ Root: &v1beta1.CallStackNode{ Frame: &v1beta1.StackFrame{ - FileID: 2, - Lineno: 84, + FileID: "2", + Lineno: "84", }, Children: []*v1beta1.CallStackNode{ { Frame: &v1beta1.StackFrame{ - FileID: 4, // Different FileID - Lineno: 120, + FileID: "4", // Different FileID + Lineno: "120", }, }, }, diff --git a/pkg/applicationprofilemanager/v1/helpers.go b/pkg/applicationprofilemanager/v1/helpers.go index 6e7a0137..80bfd1f9 100644 --- a/pkg/applicationprofilemanager/v1/helpers.go +++ b/pkg/applicationprofilemanager/v1/helpers.go @@ -2,7 +2,6 @@ package applicationprofilemanager import ( "crypto/sha256" - "encoding/binary" "encoding/hex" "encoding/json" "fmt" @@ -139,13 +138,8 @@ func CalculateSHA256CallStackHash(callStack *v1beta1.IdentifiedCallStack) string // Helper function to write frame data writeFrame := func(frame *v1beta1.StackFrame) { if frame != nil { - fileIDBytes := make([]byte, 8) - binary.LittleEndian.PutUint64(fileIDBytes, frame.FileID) - hash.Write(fileIDBytes) - - linenoBytes := make([]byte, 8) - binary.LittleEndian.PutUint64(linenoBytes, frame.Lineno) - hash.Write(linenoBytes) + hash.Write([]byte(frame.FileID)) + hash.Write([]byte(frame.Lineno)) } } From ba249dae9b144c4730c7d6a6f126edadeef45a2d Mon Sep 17 00:00:00 2001 From: Amit Schendel Date: Tue, 28 Jan 2025 10:57:22 +0000 Subject: [PATCH 08/40] Commenting open enrichment Signed-off-by: Amit Schendel --- .../v1/applicationprofile_manager.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/applicationprofilemanager/v1/applicationprofile_manager.go b/pkg/applicationprofilemanager/v1/applicationprofile_manager.go index 2831ce14..1061c55a 100644 --- a/pkg/applicationprofilemanager/v1/applicationprofile_manager.go +++ b/pkg/applicationprofilemanager/v1/applicationprofile_manager.go @@ -779,10 +779,10 @@ func (am *ApplicationProfileManager) ReportFileOpen(k8sContainerID string, event openMap.Set(path, mapset.NewSet[string](event.Flags...)) } - if am.enricher != nil { - openIdentifier := utils.CalculateSHA256FileOpenHash(path) - go am.enricher.EnrichEvent(k8sContainerID, &event, openIdentifier) - } + // if am.enricher != nil { + // openIdentifier := utils.CalculateSHA256FileOpenHash(path) + // go am.enricher.EnrichEvent(k8sContainerID, &event, openIdentifier) + // } } func (am *ApplicationProfileManager) ReportDroppedEvent(k8sContainerID string) { From 89e7287fb8e48f17269de7e2a8d57da76ef02f5c Mon Sep 17 00:00:00 2001 From: Amit Schendel Date: Tue, 28 Jan 2025 12:30:54 +0000 Subject: [PATCH 09/40] Adding print Signed-off-by: Amit Schendel --- .../v1/applicationprofile_manager.go | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/pkg/applicationprofilemanager/v1/applicationprofile_manager.go b/pkg/applicationprofilemanager/v1/applicationprofile_manager.go index 1061c55a..c467315b 100644 --- a/pkg/applicationprofilemanager/v1/applicationprofile_manager.go +++ b/pkg/applicationprofilemanager/v1/applicationprofile_manager.go @@ -178,6 +178,43 @@ func (am *ApplicationProfileManager) monitorContainer(ctx context.Context, conta } } +func PrintCallStackVerbose(ics *v1beta1.IdentifiedCallStack) { + fmt.Printf("IdentifiedCallStack:\n") + fmt.Printf(" CallID: %s\n", ics.CallID) + + if ics.CallStack.Root == nil { + fmt.Println(" Root: nil") + return + } + + var printNode func(*v1beta1.CallStackNode, int) + printNode = func(node *v1beta1.CallStackNode, depth int) { + if node == nil { + return + } + + indent := strings.Repeat(" ", depth+1) + fmt.Printf("%sNode:\n", indent) + + if node.Frame != nil { + fmt.Printf("%s Frame:\n", indent) + fmt.Printf("%s FileID: %s\n", indent, node.Frame.FileID) + fmt.Printf("%s Lineno: %s\n", indent, node.Frame.Lineno) + } else { + fmt.Printf("%s Frame: nil\n", indent) + } + + fmt.Printf("%s Children: %d\n", indent, len(node.Children)) + for i, child := range node.Children { + fmt.Printf("%s Child %d:\n", indent, i) + printNode(child, depth+1) + } + } + + fmt.Println(" Root:") + printNode(ics.CallStack.Root, 1) +} + func (am *ApplicationProfileManager) saveProfile(ctx context.Context, watchedContainer *utils.WatchedContainerData, namespace string, initalizeOperations []utils.PatchOperation) { ctx, span := otel.Tracer("").Start(ctx, "ApplicationProfileManager.saveProfile") defer span.End() @@ -303,6 +340,10 @@ func (am *ApplicationProfileManager) saveProfile(ctx context.Context, watchedCon callStacks = append(callStacks, *callStack) return true }) + // Print the call stacks + for _, callStack := range callStacks { + PrintCallStackVerbose(&callStack) + } // new activity // the process tries to use JSON patching to avoid conflicts between updates on the same object from different containers From 32841801d114295687d44e8906661fa091c45375 Mon Sep 17 00:00:00 2001 From: Amit Schendel Date: Tue, 28 Jan 2025 13:53:35 +0000 Subject: [PATCH 10/40] Changing storage no ptr Signed-off-by: Amit Schendel --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 9a444b0d..84531ea4 100644 --- a/go.mod +++ b/go.mod @@ -353,4 +353,4 @@ replace github.com/vishvananda/netns => github.com/inspektor-gadget/netns v0.0.5 replace github.com/mholt/archiver/v3 v3.5.1 => github.com/anchore/archiver/v3 v3.5.2 -replace github.com/kubescape/storage => github.com/kubescape/storage v0.0.154-0.20250128093120-9e60c021537a +replace github.com/kubescape/storage => github.com/kubescape/storage v0.0.154-0.20250128135200-7865ee9203d6 diff --git a/go.sum b/go.sum index 295c8403..3171ab98 100644 --- a/go.sum +++ b/go.sum @@ -692,8 +692,8 @@ github.com/kubescape/go-logger v0.0.23 h1:5xh+Nm8eGImhFbtippRKLaFgsvlKE1ufvQhNM2 github.com/kubescape/go-logger v0.0.23/go.mod h1:Ayg7g769c7sXVB+P3fkJmbsJpoEmMmaUf9jeo+XuC3U= github.com/kubescape/k8s-interface v0.0.180 h1:0Rhh8KBKGZXjn+2g5CKBq7B54IMAyEixd+aOrH2qcnw= github.com/kubescape/k8s-interface v0.0.180/go.mod h1:rfrA5pX86V3LP8JSH5HOcunYt43s4vWpCZ5UkPvr6q0= -github.com/kubescape/storage v0.0.154-0.20250128093120-9e60c021537a h1:8h7z8nJMKdvF57V+QLdU5CO53fnmYyozTnz+Bv+RwM0= -github.com/kubescape/storage v0.0.154-0.20250128093120-9e60c021537a/go.mod h1:7ai5ePqTXdSTCGjpEHVFXKggrbey/guM5e13w7Y3yMw= +github.com/kubescape/storage v0.0.154-0.20250128135200-7865ee9203d6 h1:72jalEG8+pX7+ZrA0hChq0Jtf+8OBqolXuj8ORDuJJE= +github.com/kubescape/storage v0.0.154-0.20250128135200-7865ee9203d6/go.mod h1:7ai5ePqTXdSTCGjpEHVFXKggrbey/guM5e13w7Y3yMw= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= From 7c439e2ff8abecf94c0865ea18abc9e689dff602 Mon Sep 17 00:00:00 2001 From: Amit Schendel Date: Tue, 28 Jan 2025 13:56:13 +0000 Subject: [PATCH 11/40] Adding new storage Signed-off-by: Amit Schendel --- .../v1/applicationprofile_manager.go | 2 +- .../v1/applicationprofile_manager_test.go | 14 +++++++------- pkg/applicationprofilemanager/v1/helpers.go | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pkg/applicationprofilemanager/v1/applicationprofile_manager.go b/pkg/applicationprofilemanager/v1/applicationprofile_manager.go index c467315b..4f98d683 100644 --- a/pkg/applicationprofilemanager/v1/applicationprofile_manager.go +++ b/pkg/applicationprofilemanager/v1/applicationprofile_manager.go @@ -207,7 +207,7 @@ func PrintCallStackVerbose(ics *v1beta1.IdentifiedCallStack) { fmt.Printf("%s Children: %d\n", indent, len(node.Children)) for i, child := range node.Children { fmt.Printf("%s Child %d:\n", indent, i) - printNode(child, depth+1) + printNode(&child, depth+1) } } diff --git a/pkg/applicationprofilemanager/v1/applicationprofile_manager_test.go b/pkg/applicationprofilemanager/v1/applicationprofile_manager_test.go index b3d466e6..7554ebd0 100644 --- a/pkg/applicationprofilemanager/v1/applicationprofile_manager_test.go +++ b/pkg/applicationprofilemanager/v1/applicationprofile_manager_test.go @@ -621,7 +621,7 @@ func TestReportIdentifiedCallStack(t *testing.T) { FileID: "1", Lineno: "42", }, - Children: []*v1beta1.CallStackNode{ + Children: []v1beta1.CallStackNode{ { Frame: &v1beta1.StackFrame{ FileID: "2", @@ -653,7 +653,7 @@ func TestReportIdentifiedCallStack(t *testing.T) { FileID: "1", Lineno: "42", }, - Children: []*v1beta1.CallStackNode{ + Children: []v1beta1.CallStackNode{ { Frame: &v1beta1.StackFrame{ FileID: "2", @@ -780,7 +780,7 @@ func compareCallStackNodes(a, b *v1beta1.CallStackNode) bool { return false } for i := range a.Children { - if !compareCallStackNodes(a.Children[i], b.Children[i]) { + if !compareCallStackNodes(&a.Children[i], &b.Children[i]) { return false } } @@ -865,7 +865,7 @@ func TestApplicationProfileManagerWithCallStacks(t *testing.T) { FileID: "2", Lineno: "84", }, - Children: []*v1beta1.CallStackNode{ + Children: []v1beta1.CallStackNode{ { Frame: &v1beta1.StackFrame{ FileID: "3", @@ -1054,7 +1054,7 @@ func TestCalculateSHA256CallStackHash(t *testing.T) { FileID: "2", Lineno: "84", }, - Children: []*v1beta1.CallStackNode{ + Children: []v1beta1.CallStackNode{ { Frame: &v1beta1.StackFrame{ FileID: "3", @@ -1088,7 +1088,7 @@ func TestCalculateSHA256CallStackHash(t *testing.T) { FileID: "2", Lineno: "84", }, - Children: []*v1beta1.CallStackNode{ + Children: []v1beta1.CallStackNode{ { Frame: &v1beta1.StackFrame{ FileID: "3", @@ -1112,7 +1112,7 @@ func TestCalculateSHA256CallStackHash(t *testing.T) { FileID: "2", Lineno: "84", }, - Children: []*v1beta1.CallStackNode{ + Children: []v1beta1.CallStackNode{ { Frame: &v1beta1.StackFrame{ FileID: "4", // Different FileID diff --git a/pkg/applicationprofilemanager/v1/helpers.go b/pkg/applicationprofilemanager/v1/helpers.go index 80bfd1f9..0413f063 100644 --- a/pkg/applicationprofilemanager/v1/helpers.go +++ b/pkg/applicationprofilemanager/v1/helpers.go @@ -154,7 +154,7 @@ func CalculateSHA256CallStackHash(callStack *v1beta1.IdentifiedCallStack) string // Process children for _, child := range node.Children { - processNode(child) + processNode(&child) } } From a9be1964b7535045da0c2d5755e485695f934162 Mon Sep 17 00:00:00 2001 From: Amit Schendel Date: Tue, 28 Jan 2025 15:11:50 +0000 Subject: [PATCH 12/40] Change storage Signed-off-by: Amit Schendel --- go.mod | 2 +- go.sum | 4 +- .../v1/applicationprofile_manager.go | 31 +- .../v1/applicationprofile_manager_test.go | 1134 ++++++++--------- pkg/applicationprofilemanager/v1/helpers.go | 27 +- 5 files changed, 597 insertions(+), 601 deletions(-) diff --git a/go.mod b/go.mod index 84531ea4..be9deb28 100644 --- a/go.mod +++ b/go.mod @@ -353,4 +353,4 @@ replace github.com/vishvananda/netns => github.com/inspektor-gadget/netns v0.0.5 replace github.com/mholt/archiver/v3 v3.5.1 => github.com/anchore/archiver/v3 v3.5.2 -replace github.com/kubescape/storage => github.com/kubescape/storage v0.0.154-0.20250128135200-7865ee9203d6 +replace github.com/kubescape/storage => github.com/kubescape/storage v0.0.154-0.20250128150729-f5cb1a341394 diff --git a/go.sum b/go.sum index 3171ab98..67694227 100644 --- a/go.sum +++ b/go.sum @@ -692,8 +692,8 @@ github.com/kubescape/go-logger v0.0.23 h1:5xh+Nm8eGImhFbtippRKLaFgsvlKE1ufvQhNM2 github.com/kubescape/go-logger v0.0.23/go.mod h1:Ayg7g769c7sXVB+P3fkJmbsJpoEmMmaUf9jeo+XuC3U= github.com/kubescape/k8s-interface v0.0.180 h1:0Rhh8KBKGZXjn+2g5CKBq7B54IMAyEixd+aOrH2qcnw= github.com/kubescape/k8s-interface v0.0.180/go.mod h1:rfrA5pX86V3LP8JSH5HOcunYt43s4vWpCZ5UkPvr6q0= -github.com/kubescape/storage v0.0.154-0.20250128135200-7865ee9203d6 h1:72jalEG8+pX7+ZrA0hChq0Jtf+8OBqolXuj8ORDuJJE= -github.com/kubescape/storage v0.0.154-0.20250128135200-7865ee9203d6/go.mod h1:7ai5ePqTXdSTCGjpEHVFXKggrbey/guM5e13w7Y3yMw= +github.com/kubescape/storage v0.0.154-0.20250128150729-f5cb1a341394 h1:tZw2UA164uMf1ADUz1f0d3SrSp5l8EWWgVmvuXcMtJU= +github.com/kubescape/storage v0.0.154-0.20250128150729-f5cb1a341394/go.mod h1:7ai5ePqTXdSTCGjpEHVFXKggrbey/guM5e13w7Y3yMw= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= diff --git a/pkg/applicationprofilemanager/v1/applicationprofile_manager.go b/pkg/applicationprofilemanager/v1/applicationprofile_manager.go index 4f98d683..2ce43dcb 100644 --- a/pkg/applicationprofilemanager/v1/applicationprofile_manager.go +++ b/pkg/applicationprofilemanager/v1/applicationprofile_manager.go @@ -178,36 +178,34 @@ func (am *ApplicationProfileManager) monitorContainer(ctx context.Context, conta } } -func PrintCallStackVerbose(ics *v1beta1.IdentifiedCallStack) { +func PrintCallStackVerbose(ics v1beta1.IdentifiedCallStack) { fmt.Printf("IdentifiedCallStack:\n") fmt.Printf(" CallID: %s\n", ics.CallID) - if ics.CallStack.Root == nil { - fmt.Println(" Root: nil") + // No need to check for nil Root as it's a value type + // Check if it's an empty frame and has no children instead + if len(ics.CallStack.Root.Children) == 0 && isEmptyFrame(ics.CallStack.Root.Frame) { + fmt.Println(" Root: empty") return } - var printNode func(*v1beta1.CallStackNode, int) - printNode = func(node *v1beta1.CallStackNode, depth int) { - if node == nil { - return - } - + var printNode func(v1beta1.CallStackNode, int) + printNode = func(node v1beta1.CallStackNode, depth int) { indent := strings.Repeat(" ", depth+1) fmt.Printf("%sNode:\n", indent) - if node.Frame != nil { + if !isEmptyFrame(node.Frame) { fmt.Printf("%s Frame:\n", indent) fmt.Printf("%s FileID: %s\n", indent, node.Frame.FileID) fmt.Printf("%s Lineno: %s\n", indent, node.Frame.Lineno) } else { - fmt.Printf("%s Frame: nil\n", indent) + fmt.Printf("%s Frame: empty\n", indent) } fmt.Printf("%s Children: %d\n", indent, len(node.Children)) for i, child := range node.Children { fmt.Printf("%s Child %d:\n", indent, i) - printNode(&child, depth+1) + printNode(child, depth+1) } } @@ -215,6 +213,11 @@ func PrintCallStackVerbose(ics *v1beta1.IdentifiedCallStack) { printNode(ics.CallStack.Root, 1) } +// Helper function to check if a frame is empty +func isEmptyFrame(frame v1beta1.StackFrame) bool { + return frame.FileID == "" && frame.Lineno == "" +} + func (am *ApplicationProfileManager) saveProfile(ctx context.Context, watchedContainer *utils.WatchedContainerData, namespace string, initalizeOperations []utils.PatchOperation) { ctx, span := otel.Tracer("").Start(ctx, "ApplicationProfileManager.saveProfile") defer span.End() @@ -342,7 +345,7 @@ func (am *ApplicationProfileManager) saveProfile(ctx context.Context, watchedCon }) // Print the call stacks for _, callStack := range callStacks { - PrintCallStackVerbose(&callStack) + PrintCallStackVerbose(callStack) } // new activity @@ -902,7 +905,7 @@ func (am *ApplicationProfileManager) ReportIdentifiedCallStack(k8sContainerID st } // Generate unique identifier for the call stack - callStackIdentifier := CalculateSHA256CallStackHash(callStack) + callStackIdentifier := CalculateSHA256CallStackHash(*callStack) // Check if we already have this call stack if _, ok := am.savedCallStacks.Get(k8sContainerID).Get(callStackIdentifier); ok { diff --git a/pkg/applicationprofilemanager/v1/applicationprofile_manager_test.go b/pkg/applicationprofilemanager/v1/applicationprofile_manager_test.go index 7554ebd0..1561db9f 100644 --- a/pkg/applicationprofilemanager/v1/applicationprofile_manager_test.go +++ b/pkg/applicationprofilemanager/v1/applicationprofile_manager_test.go @@ -560,572 +560,572 @@ func TestReportRulePolicy(t *testing.T) { } } -func TestReportIdentifiedCallStack(t *testing.T) { - // Setup common test environment - cfg := config.Config{ - InitialDelay: 1 * time.Second, - MaxSniffingTime: 5 * time.Minute, - UpdateDataPeriod: 1 * time.Second, - } - ctx := context.TODO() - k8sClient := &k8sclient.K8sClientMock{} - storageClient := &storage.StorageHttpClientMock{} - k8sObjectCacheMock := &objectcache.K8sObjectCacheMock{} - seccompManagerMock := &seccompmanager.SeccompManagerMock{} - - tests := []struct { - name string - k8sContainerID string - callStacks []*v1beta1.IdentifiedCallStack - expected []v1beta1.IdentifiedCallStack - }{ - { - name: "Single callstack", - k8sContainerID: "ns/pod/cont", - callStacks: []*v1beta1.IdentifiedCallStack{ - { - CallID: "open", - CallStack: v1beta1.CallStack{ - Root: &v1beta1.CallStackNode{ - Frame: &v1beta1.StackFrame{ - FileID: "1", - Lineno: "42", - }, - }, - }, - }, - }, - expected: []v1beta1.IdentifiedCallStack{ - { - CallID: "open", - CallStack: v1beta1.CallStack{ - Root: &v1beta1.CallStackNode{ - Frame: &v1beta1.StackFrame{ - FileID: "1", - Lineno: "42", - }, - }, - }, - }, - }, - }, - { - name: "Multiple callstacks", - k8sContainerID: "ns/pod/cont", - callStacks: []*v1beta1.IdentifiedCallStack{ - { - CallID: "open", - CallStack: v1beta1.CallStack{ - Root: &v1beta1.CallStackNode{ - Frame: &v1beta1.StackFrame{ - FileID: "1", - Lineno: "42", - }, - Children: []v1beta1.CallStackNode{ - { - Frame: &v1beta1.StackFrame{ - FileID: "2", - Lineno: "84", - }, - }, - }, - }, - }, - }, - { - CallID: "exec", - CallStack: v1beta1.CallStack{ - Root: &v1beta1.CallStackNode{ - Frame: &v1beta1.StackFrame{ - FileID: "3", - Lineno: "120", - }, - }, - }, - }, - }, - expected: []v1beta1.IdentifiedCallStack{ - { - CallID: "open", - CallStack: v1beta1.CallStack{ - Root: &v1beta1.CallStackNode{ - Frame: &v1beta1.StackFrame{ - FileID: "1", - Lineno: "42", - }, - Children: []v1beta1.CallStackNode{ - { - Frame: &v1beta1.StackFrame{ - FileID: "2", - Lineno: "84", - }, - }, - }, - }, - }, - }, - { - CallID: "exec", - CallStack: v1beta1.CallStack{ - Root: &v1beta1.CallStackNode{ - Frame: &v1beta1.StackFrame{ - FileID: "3", - Lineno: "120", - }, - }, - }, - }, - }, - }, - { - name: "Duplicate callstack", - k8sContainerID: "ns/pod/cont", - callStacks: []*v1beta1.IdentifiedCallStack{ - { - CallID: "open", - CallStack: v1beta1.CallStack{ - Root: &v1beta1.CallStackNode{ - Frame: &v1beta1.StackFrame{ - FileID: "1", - Lineno: "42", - }, - }, - }, - }, - { - CallID: "open", - CallStack: v1beta1.CallStack{ - Root: &v1beta1.CallStackNode{ - Frame: &v1beta1.StackFrame{ - FileID: "1", - Lineno: "42", - }, - }, - }, - }, - }, - expected: []v1beta1.IdentifiedCallStack{ - { - CallID: "open", - CallStack: v1beta1.CallStack{ - Root: &v1beta1.CallStackNode{ - Frame: &v1beta1.StackFrame{ - FileID: "1", - Lineno: "42", - }, - }, - }, - }, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - am, err := CreateApplicationProfileManager(ctx, cfg, "cluster", k8sClient, storageClient, k8sObjectCacheMock, seccompManagerMock, nil) - assert.NoError(t, err) - - // Initialize container tracking - am.savedCallStacks.Set(tt.k8sContainerID, istiocache.NewTTL(5*am.cfg.UpdateDataPeriod, am.cfg.UpdateDataPeriod)) - am.toSaveCallStacks.Set(tt.k8sContainerID, new(maps.SafeMap[string, *v1beta1.IdentifiedCallStack])) - am.trackedContainers.Add(tt.k8sContainerID) - - // Report each callstack - for _, callStack := range tt.callStacks { - am.ReportIdentifiedCallStack(tt.k8sContainerID, callStack) - } - - // Collect all callstacks that were queued to be saved - var resultCallStacks []v1beta1.IdentifiedCallStack - am.toSaveCallStacks.Get(tt.k8sContainerID).Range(func(identifier string, callStack *v1beta1.IdentifiedCallStack) bool { - resultCallStacks = append(resultCallStacks, *callStack) - return true - }) - - // Verify results - assert.Equal(t, len(tt.expected), len(resultCallStacks)) - for _, expectedCallStack := range tt.expected { - found := false - for _, resultCallStack := range resultCallStacks { - if compareCallStacks(&expectedCallStack, &resultCallStack) { - found = true - break - } - } - assert.True(t, found, "Expected call stack not found in results") - } - }) - } -} +// func TestReportIdentifiedCallStack(t *testing.T) { +// // Setup common test environment +// cfg := config.Config{ +// InitialDelay: 1 * time.Second, +// MaxSniffingTime: 5 * time.Minute, +// UpdateDataPeriod: 1 * time.Second, +// } +// ctx := context.TODO() +// k8sClient := &k8sclient.K8sClientMock{} +// storageClient := &storage.StorageHttpClientMock{} +// k8sObjectCacheMock := &objectcache.K8sObjectCacheMock{} +// seccompManagerMock := &seccompmanager.SeccompManagerMock{} + +// tests := []struct { +// name string +// k8sContainerID string +// callStacks []*v1beta1.IdentifiedCallStack +// expected []v1beta1.IdentifiedCallStack +// }{ +// { +// name: "Single callstack", +// k8sContainerID: "ns/pod/cont", +// callStacks: []*v1beta1.IdentifiedCallStack{ +// { +// CallID: "open", +// CallStack: v1beta1.CallStack{ +// Root: &v1beta1.CallStackNode{ +// Frame: &v1beta1.StackFrame{ +// FileID: "1", +// Lineno: "42", +// }, +// }, +// }, +// }, +// }, +// expected: []v1beta1.IdentifiedCallStack{ +// { +// CallID: "open", +// CallStack: v1beta1.CallStack{ +// Root: &v1beta1.CallStackNode{ +// Frame: &v1beta1.StackFrame{ +// FileID: "1", +// Lineno: "42", +// }, +// }, +// }, +// }, +// }, +// }, +// { +// name: "Multiple callstacks", +// k8sContainerID: "ns/pod/cont", +// callStacks: []*v1beta1.IdentifiedCallStack{ +// { +// CallID: "open", +// CallStack: v1beta1.CallStack{ +// Root: &v1beta1.CallStackNode{ +// Frame: &v1beta1.StackFrame{ +// FileID: "1", +// Lineno: "42", +// }, +// Children: []v1beta1.CallStackNode{ +// { +// Frame: &v1beta1.StackFrame{ +// FileID: "2", +// Lineno: "84", +// }, +// }, +// }, +// }, +// }, +// }, +// { +// CallID: "exec", +// CallStack: v1beta1.CallStack{ +// Root: &v1beta1.CallStackNode{ +// Frame: &v1beta1.StackFrame{ +// FileID: "3", +// Lineno: "120", +// }, +// }, +// }, +// }, +// }, +// expected: []v1beta1.IdentifiedCallStack{ +// { +// CallID: "open", +// CallStack: v1beta1.CallStack{ +// Root: &v1beta1.CallStackNode{ +// Frame: &v1beta1.StackFrame{ +// FileID: "1", +// Lineno: "42", +// }, +// Children: []v1beta1.CallStackNode{ +// { +// Frame: &v1beta1.StackFrame{ +// FileID: "2", +// Lineno: "84", +// }, +// }, +// }, +// }, +// }, +// }, +// { +// CallID: "exec", +// CallStack: v1beta1.CallStack{ +// Root: &v1beta1.CallStackNode{ +// Frame: &v1beta1.StackFrame{ +// FileID: "3", +// Lineno: "120", +// }, +// }, +// }, +// }, +// }, +// }, +// { +// name: "Duplicate callstack", +// k8sContainerID: "ns/pod/cont", +// callStacks: []*v1beta1.IdentifiedCallStack{ +// { +// CallID: "open", +// CallStack: v1beta1.CallStack{ +// Root: &v1beta1.CallStackNode{ +// Frame: &v1beta1.StackFrame{ +// FileID: "1", +// Lineno: "42", +// }, +// }, +// }, +// }, +// { +// CallID: "open", +// CallStack: v1beta1.CallStack{ +// Root: &v1beta1.CallStackNode{ +// Frame: &v1beta1.StackFrame{ +// FileID: "1", +// Lineno: "42", +// }, +// }, +// }, +// }, +// }, +// expected: []v1beta1.IdentifiedCallStack{ +// { +// CallID: "open", +// CallStack: v1beta1.CallStack{ +// Root: &v1beta1.CallStackNode{ +// Frame: &v1beta1.StackFrame{ +// FileID: "1", +// Lineno: "42", +// }, +// }, +// }, +// }, +// }, +// }, +// } + +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// am, err := CreateApplicationProfileManager(ctx, cfg, "cluster", k8sClient, storageClient, k8sObjectCacheMock, seccompManagerMock, nil) +// assert.NoError(t, err) + +// // Initialize container tracking +// am.savedCallStacks.Set(tt.k8sContainerID, istiocache.NewTTL(5*am.cfg.UpdateDataPeriod, am.cfg.UpdateDataPeriod)) +// am.toSaveCallStacks.Set(tt.k8sContainerID, new(maps.SafeMap[string, *v1beta1.IdentifiedCallStack])) +// am.trackedContainers.Add(tt.k8sContainerID) + +// // Report each callstack +// for _, callStack := range tt.callStacks { +// am.ReportIdentifiedCallStack(tt.k8sContainerID, callStack) +// } + +// // Collect all callstacks that were queued to be saved +// var resultCallStacks []v1beta1.IdentifiedCallStack +// am.toSaveCallStacks.Get(tt.k8sContainerID).Range(func(identifier string, callStack *v1beta1.IdentifiedCallStack) bool { +// resultCallStacks = append(resultCallStacks, *callStack) +// return true +// }) + +// // Verify results +// assert.Equal(t, len(tt.expected), len(resultCallStacks)) +// for _, expectedCallStack := range tt.expected { +// found := false +// for _, resultCallStack := range resultCallStacks { +// if compareCallStacks(&expectedCallStack, &resultCallStack) { +// found = true +// break +// } +// } +// assert.True(t, found, "Expected call stack not found in results") +// } +// }) +// } +// } // Helper function to deeply compare two call stacks -func compareCallStacks(a, b *v1beta1.IdentifiedCallStack) bool { - if a.CallID != b.CallID { - return false - } - return compareCallStackNodes(a.CallStack.Root, b.CallStack.Root) -} - -func compareCallStackNodes(a, b *v1beta1.CallStackNode) bool { - if a == nil && b == nil { - return true - } - if a == nil || b == nil { - return false - } - if !compareStackFrames(a.Frame, b.Frame) { - return false - } - if len(a.Children) != len(b.Children) { - return false - } - for i := range a.Children { - if !compareCallStackNodes(&a.Children[i], &b.Children[i]) { - return false - } - } - return true -} - -func compareStackFrames(a, b *v1beta1.StackFrame) bool { - if a == nil && b == nil { - return true - } - if a == nil || b == nil { - return false - } - return a.FileID == b.FileID && a.Lineno == b.Lineno -} - -func TestApplicationProfileManagerWithCallStacks(t *testing.T) { - // Setup test environment - cfg := config.Config{ - InitialDelay: 1 * time.Second, - MaxSniffingTime: 5 * time.Minute, - UpdateDataPeriod: 5 * time.Second, - } - ctx := context.TODO() - k8sClient := &k8sclient.K8sClientMock{} - storageClient := &storage.StorageHttpClientMock{} - k8sObjectCacheMock := &objectcache.K8sObjectCacheMock{} - seccompManagerMock := &seccompmanager.SeccompManagerMock{} - - am, err := CreateApplicationProfileManager(ctx, cfg, "cluster", k8sClient, storageClient, k8sObjectCacheMock, seccompManagerMock, nil) - assert.NoError(t, err) - - // Prepare container - container := &containercollection.Container{ - K8s: containercollection.K8sMetadata{ - BasicK8sMetadata: types.BasicK8sMetadata{ - Namespace: "ns", - PodName: "pod", - ContainerName: "cont", - }, - }, - Runtime: containercollection.RuntimeMetadata{ - BasicRuntimeMetadata: types.BasicRuntimeMetadata{ - ContainerID: "5fff6a395ce4e6984a9447cc6cfb09f473eaf278498243963fcc944889bc8400", - ContainerStartedAt: types.Time(time.Now().UnixNano()), - }, - }, - } - - sharedWatchedContainerData := &utils.WatchedContainerData{} - err = ensureInstanceID(container, sharedWatchedContainerData, k8sClient, "cluster") - assert.NoError(t, err) - k8sObjectCacheMock.SetSharedContainerData(container.Runtime.ContainerID, sharedWatchedContainerData) - - // Start container monitoring - am.ContainerCallback(containercollection.PubSubEvent{ - Type: containercollection.EventTypeAddContainer, - Container: container, - }) - - // Wait for initialization - time.Sleep(2 * time.Second) - - // Report call stacks - callStack1 := &v1beta1.IdentifiedCallStack{ - CallID: "open", - CallStack: v1beta1.CallStack{ - Root: &v1beta1.CallStackNode{ - Frame: &v1beta1.StackFrame{ - FileID: "1", - Lineno: "42", - }, - }, - }, - } - - callStack2 := &v1beta1.IdentifiedCallStack{ - CallID: "exec", - CallStack: v1beta1.CallStack{ - Root: &v1beta1.CallStackNode{ - Frame: &v1beta1.StackFrame{ - FileID: "2", - Lineno: "84", - }, - Children: []v1beta1.CallStackNode{ - { - Frame: &v1beta1.StackFrame{ - FileID: "3", - Lineno: "120", - }, - }, - }, - }, - }, - } - - // Report call stacks and verify they're stored in toSaveCallStacks - am.ReportIdentifiedCallStack("ns/pod/cont", callStack1) - time.Sleep(100 * time.Millisecond) - - // Debug logging for first call stack - toSaveCallStacks := am.toSaveCallStacks.Get("ns/pod/cont") - t.Logf("After first report - Number of call stacks in toSave: %d", toSaveCallStacks.Len()) - toSaveCallStacks.Range(func(identifier string, stack *v1beta1.IdentifiedCallStack) bool { - t.Logf("Found call stack with ID: %s", stack.CallID) - return true - }) - - am.ReportIdentifiedCallStack("ns/pod/cont", callStack2) - time.Sleep(100 * time.Millisecond) - - // Debug logging for second call stack - t.Logf("After second report - Number of call stacks in toSave: %d", toSaveCallStacks.Len()) - toSaveCallStacks.Range(func(identifier string, stack *v1beta1.IdentifiedCallStack) bool { - t.Logf("Found call stack with ID: %s", stack.CallID) - return true - }) - - // Let monitoring run and trigger a save - time.Sleep(2 * time.Second) - - // Check saved call stacks after first save - savedCallStacks := am.savedCallStacks.Get("ns/pod/cont") - t.Logf("After first save - Number of call stacks in saved: %v", savedCallStacks) - - // Let it run for the remaining time - time.Sleep(2 * time.Second) - - // Report container stopped - am.ContainerCallback(containercollection.PubSubEvent{ - Type: containercollection.EventTypeRemoveContainer, - Container: container, - }) - - // Wait for final processing - time.Sleep(2 * time.Second) - - // Debug logging for profiles - for i, profile := range storageClient.ApplicationProfiles { - t.Logf("Profile %d:", i) - t.Logf(" Number of containers: %d", len(profile.Spec.Containers)) - if len(profile.Spec.Containers) > 1 { - t.Logf(" Call stacks in container[1]: %d", len(profile.Spec.Containers[1].IdentifiedCallStacks)) - for _, cs := range profile.Spec.Containers[1].IdentifiedCallStacks { - t.Logf(" Call stack ID: %s", cs.CallID) - } - } - } - - // Verify results - assert.Greater(t, len(storageClient.ApplicationProfiles), 0, "No application profiles were created") - - // Get the latest profile - latestProfile := storageClient.ApplicationProfiles[len(storageClient.ApplicationProfiles)-1] - assert.NotNil(t, latestProfile.Spec.Containers, "Containers slice is nil") - assert.Greater(t, len(latestProfile.Spec.Containers), 1, "Not enough containers in profile") - - foundCallStacks := latestProfile.Spec.Containers[1].IdentifiedCallStacks - - // Sort both expected and found call stacks - sortCallStacks := func(cs []v1beta1.IdentifiedCallStack) { - sort.Slice(cs, func(i, j int) bool { - return string(cs[i].CallID) < string(cs[j].CallID) - }) - } - - expectedCallStacks := []v1beta1.IdentifiedCallStack{*callStack1, *callStack2} - sortCallStacks(expectedCallStacks) - sortCallStacks(foundCallStacks) - - t.Logf("Number of profiles: %d", len(storageClient.ApplicationProfiles)) - t.Logf("Latest profile containers: %d", len(latestProfile.Spec.Containers)) - t.Logf("Found call stacks: %d", len(foundCallStacks)) - - assert.Equal(t, len(expectedCallStacks), len(foundCallStacks), "Number of call stacks doesn't match") - if len(foundCallStacks) == len(expectedCallStacks) { - for i := range expectedCallStacks { - assert.True(t, compareCallStacks(&expectedCallStacks[i], &foundCallStacks[i]), - "Call stack mismatch at index %d\nExpected: %+v\nGot: %+v", i, expectedCallStacks[i], foundCallStacks[i]) - } - } -} - -func TestCallStackComparison(t *testing.T) { - tests := []struct { - name string - stack1 *v1beta1.IdentifiedCallStack - stack2 *v1beta1.IdentifiedCallStack - expected bool - }{ - { - name: "identical single frame stacks", - stack1: &v1beta1.IdentifiedCallStack{ - CallID: "test", - CallStack: v1beta1.CallStack{ - Root: &v1beta1.CallStackNode{ - Frame: &v1beta1.StackFrame{ - FileID: "1", - Lineno: "42", - }, - }, - }, - }, - stack2: &v1beta1.IdentifiedCallStack{ - CallID: "test", - CallStack: v1beta1.CallStack{ - Root: &v1beta1.CallStackNode{ - Frame: &v1beta1.StackFrame{ - FileID: "1", - Lineno: "42", - }, - }, - }, - }, - expected: true, - }, - { - name: "different call IDs", - stack1: &v1beta1.IdentifiedCallStack{ - CallID: "test1", - CallStack: v1beta1.CallStack{ - Root: &v1beta1.CallStackNode{ - Frame: &v1beta1.StackFrame{ - FileID: "1", - Lineno: "42", - }, - }, - }, - }, - stack2: &v1beta1.IdentifiedCallStack{ - CallID: "test2", - CallStack: v1beta1.CallStack{ - Root: &v1beta1.CallStackNode{ - Frame: &v1beta1.StackFrame{ - FileID: "1", - Lineno: "42", - }, - }, - }, - }, - expected: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result := compareCallStacks(tt.stack1, tt.stack2) - assert.Equal(t, tt.expected, result) - }) - } -} - -func TestCalculateSHA256CallStackHash(t *testing.T) { - callStack1 := &v1beta1.IdentifiedCallStack{ - CallID: "open", - CallStack: v1beta1.CallStack{ - Root: &v1beta1.CallStackNode{ - Frame: &v1beta1.StackFrame{ - FileID: "1", - Lineno: "42", - }, - }, - }, - } - - callStack2 := &v1beta1.IdentifiedCallStack{ - CallID: "exec", - CallStack: v1beta1.CallStack{ - Root: &v1beta1.CallStackNode{ - Frame: &v1beta1.StackFrame{ - FileID: "2", - Lineno: "84", - }, - Children: []v1beta1.CallStackNode{ - { - Frame: &v1beta1.StackFrame{ - FileID: "3", - Lineno: "120", - }, - }, - }, - }, - }, - } - - hash1 := CalculateSHA256CallStackHash(callStack1) - hash2 := CalculateSHA256CallStackHash(callStack2) - - t.Logf("Hash for callStack1: %s", hash1) - t.Logf("Hash for callStack2: %s", hash2) - - // Different call stacks should produce different hashes - assert.NotEqual(t, hash1, hash2, "Different call stacks should have different hashes") - - // Same call stack should produce same hash - hash1Again := CalculateSHA256CallStackHash(callStack1) - assert.Equal(t, hash1, hash1Again, "Same call stack should produce same hash") - - // Test with children nodes - childrenCallStack := &v1beta1.IdentifiedCallStack{ - CallID: "exec", - CallStack: v1beta1.CallStack{ - Root: &v1beta1.CallStackNode{ - Frame: &v1beta1.StackFrame{ - FileID: "2", - Lineno: "84", - }, - Children: []v1beta1.CallStackNode{ - { - Frame: &v1beta1.StackFrame{ - FileID: "3", - Lineno: "120", - }, - }, - }, - }, - }, - } - - hashWithChildren := CalculateSHA256CallStackHash(childrenCallStack) - t.Logf("Hash for childrenCallStack: %s", hashWithChildren) - - // Different call stack with same root but different children should have different hash - differentChildrenCallStack := &v1beta1.IdentifiedCallStack{ - CallID: "exec", - CallStack: v1beta1.CallStack{ - Root: &v1beta1.CallStackNode{ - Frame: &v1beta1.StackFrame{ - FileID: "2", - Lineno: "84", - }, - Children: []v1beta1.CallStackNode{ - { - Frame: &v1beta1.StackFrame{ - FileID: "4", // Different FileID - Lineno: "120", - }, - }, - }, - }, - }, - } - - hashWithDifferentChildren := CalculateSHA256CallStackHash(differentChildrenCallStack) - t.Logf("Hash for differentChildrenCallStack: %s", hashWithDifferentChildren) - assert.NotEqual(t, hashWithChildren, hashWithDifferentChildren, - "Call stacks with different children should have different hashes") -} +// func compareCallStacks(a, b *v1beta1.IdentifiedCallStack) bool { +// if a.CallID != b.CallID { +// return false +// } +// return compareCallStackNodes(a.CallStack.Root, b.CallStack.Root) +// } + +// func compareCallStackNodes(a, b *v1beta1.CallStackNode) bool { +// if a == nil && b == nil { +// return true +// } +// if a == nil || b == nil { +// return false +// } +// if !compareStackFrames(a.Frame, b.Frame) { +// return false +// } +// if len(a.Children) != len(b.Children) { +// return false +// } +// for i := range a.Children { +// if !compareCallStackNodes(&a.Children[i], &b.Children[i]) { +// return false +// } +// } +// return true +// } + +// func compareStackFrames(a, b *v1beta1.StackFrame) bool { +// if a == nil && b == nil { +// return true +// } +// if a == nil || b == nil { +// return false +// } +// return a.FileID == b.FileID && a.Lineno == b.Lineno +// } + +// func TestApplicationProfileManagerWithCallStacks(t *testing.T) { +// // Setup test environment +// cfg := config.Config{ +// InitialDelay: 1 * time.Second, +// MaxSniffingTime: 5 * time.Minute, +// UpdateDataPeriod: 5 * time.Second, +// } +// ctx := context.TODO() +// k8sClient := &k8sclient.K8sClientMock{} +// storageClient := &storage.StorageHttpClientMock{} +// k8sObjectCacheMock := &objectcache.K8sObjectCacheMock{} +// seccompManagerMock := &seccompmanager.SeccompManagerMock{} + +// am, err := CreateApplicationProfileManager(ctx, cfg, "cluster", k8sClient, storageClient, k8sObjectCacheMock, seccompManagerMock, nil) +// assert.NoError(t, err) + +// // Prepare container +// container := &containercollection.Container{ +// K8s: containercollection.K8sMetadata{ +// BasicK8sMetadata: types.BasicK8sMetadata{ +// Namespace: "ns", +// PodName: "pod", +// ContainerName: "cont", +// }, +// }, +// Runtime: containercollection.RuntimeMetadata{ +// BasicRuntimeMetadata: types.BasicRuntimeMetadata{ +// ContainerID: "5fff6a395ce4e6984a9447cc6cfb09f473eaf278498243963fcc944889bc8400", +// ContainerStartedAt: types.Time(time.Now().UnixNano()), +// }, +// }, +// } + +// sharedWatchedContainerData := &utils.WatchedContainerData{} +// err = ensureInstanceID(container, sharedWatchedContainerData, k8sClient, "cluster") +// assert.NoError(t, err) +// k8sObjectCacheMock.SetSharedContainerData(container.Runtime.ContainerID, sharedWatchedContainerData) + +// // Start container monitoring +// am.ContainerCallback(containercollection.PubSubEvent{ +// Type: containercollection.EventTypeAddContainer, +// Container: container, +// }) + +// // Wait for initialization +// time.Sleep(2 * time.Second) + +// // Report call stacks +// callStack1 := &v1beta1.IdentifiedCallStack{ +// CallID: "open", +// CallStack: v1beta1.CallStack{ +// Root: &v1beta1.CallStackNode{ +// Frame: &v1beta1.StackFrame{ +// FileID: "1", +// Lineno: "42", +// }, +// }, +// }, +// } + +// callStack2 := &v1beta1.IdentifiedCallStack{ +// CallID: "exec", +// CallStack: v1beta1.CallStack{ +// Root: &v1beta1.CallStackNode{ +// Frame: &v1beta1.StackFrame{ +// FileID: "2", +// Lineno: "84", +// }, +// Children: []v1beta1.CallStackNode{ +// { +// Frame: &v1beta1.StackFrame{ +// FileID: "3", +// Lineno: "120", +// }, +// }, +// }, +// }, +// }, +// } + +// // Report call stacks and verify they're stored in toSaveCallStacks +// am.ReportIdentifiedCallStack("ns/pod/cont", callStack1) +// time.Sleep(100 * time.Millisecond) + +// // Debug logging for first call stack +// toSaveCallStacks := am.toSaveCallStacks.Get("ns/pod/cont") +// t.Logf("After first report - Number of call stacks in toSave: %d", toSaveCallStacks.Len()) +// toSaveCallStacks.Range(func(identifier string, stack *v1beta1.IdentifiedCallStack) bool { +// t.Logf("Found call stack with ID: %s", stack.CallID) +// return true +// }) + +// am.ReportIdentifiedCallStack("ns/pod/cont", callStack2) +// time.Sleep(100 * time.Millisecond) + +// // Debug logging for second call stack +// t.Logf("After second report - Number of call stacks in toSave: %d", toSaveCallStacks.Len()) +// toSaveCallStacks.Range(func(identifier string, stack *v1beta1.IdentifiedCallStack) bool { +// t.Logf("Found call stack with ID: %s", stack.CallID) +// return true +// }) + +// // Let monitoring run and trigger a save +// time.Sleep(2 * time.Second) + +// // Check saved call stacks after first save +// savedCallStacks := am.savedCallStacks.Get("ns/pod/cont") +// t.Logf("After first save - Number of call stacks in saved: %v", savedCallStacks) + +// // Let it run for the remaining time +// time.Sleep(2 * time.Second) + +// // Report container stopped +// am.ContainerCallback(containercollection.PubSubEvent{ +// Type: containercollection.EventTypeRemoveContainer, +// Container: container, +// }) + +// // Wait for final processing +// time.Sleep(2 * time.Second) + +// // Debug logging for profiles +// for i, profile := range storageClient.ApplicationProfiles { +// t.Logf("Profile %d:", i) +// t.Logf(" Number of containers: %d", len(profile.Spec.Containers)) +// if len(profile.Spec.Containers) > 1 { +// t.Logf(" Call stacks in container[1]: %d", len(profile.Spec.Containers[1].IdentifiedCallStacks)) +// for _, cs := range profile.Spec.Containers[1].IdentifiedCallStacks { +// t.Logf(" Call stack ID: %s", cs.CallID) +// } +// } +// } + +// // Verify results +// assert.Greater(t, len(storageClient.ApplicationProfiles), 0, "No application profiles were created") + +// // Get the latest profile +// latestProfile := storageClient.ApplicationProfiles[len(storageClient.ApplicationProfiles)-1] +// assert.NotNil(t, latestProfile.Spec.Containers, "Containers slice is nil") +// assert.Greater(t, len(latestProfile.Spec.Containers), 1, "Not enough containers in profile") + +// foundCallStacks := latestProfile.Spec.Containers[1].IdentifiedCallStacks + +// // Sort both expected and found call stacks +// sortCallStacks := func(cs []v1beta1.IdentifiedCallStack) { +// sort.Slice(cs, func(i, j int) bool { +// return string(cs[i].CallID) < string(cs[j].CallID) +// }) +// } + +// expectedCallStacks := []v1beta1.IdentifiedCallStack{*callStack1, *callStack2} +// sortCallStacks(expectedCallStacks) +// sortCallStacks(foundCallStacks) + +// t.Logf("Number of profiles: %d", len(storageClient.ApplicationProfiles)) +// t.Logf("Latest profile containers: %d", len(latestProfile.Spec.Containers)) +// t.Logf("Found call stacks: %d", len(foundCallStacks)) + +// assert.Equal(t, len(expectedCallStacks), len(foundCallStacks), "Number of call stacks doesn't match") +// if len(foundCallStacks) == len(expectedCallStacks) { +// for i := range expectedCallStacks { +// assert.True(t, compareCallStacks(&expectedCallStacks[i], &foundCallStacks[i]), +// "Call stack mismatch at index %d\nExpected: %+v\nGot: %+v", i, expectedCallStacks[i], foundCallStacks[i]) +// } +// } +// } + +// func TestCallStackComparison(t *testing.T) { +// tests := []struct { +// name string +// stack1 *v1beta1.IdentifiedCallStack +// stack2 *v1beta1.IdentifiedCallStack +// expected bool +// }{ +// { +// name: "identical single frame stacks", +// stack1: &v1beta1.IdentifiedCallStack{ +// CallID: "test", +// CallStack: v1beta1.CallStack{ +// Root: &v1beta1.CallStackNode{ +// Frame: &v1beta1.StackFrame{ +// FileID: "1", +// Lineno: "42", +// }, +// }, +// }, +// }, +// stack2: &v1beta1.IdentifiedCallStack{ +// CallID: "test", +// CallStack: v1beta1.CallStack{ +// Root: &v1beta1.CallStackNode{ +// Frame: &v1beta1.StackFrame{ +// FileID: "1", +// Lineno: "42", +// }, +// }, +// }, +// }, +// expected: true, +// }, +// { +// name: "different call IDs", +// stack1: &v1beta1.IdentifiedCallStack{ +// CallID: "test1", +// CallStack: v1beta1.CallStack{ +// Root: &v1beta1.CallStackNode{ +// Frame: &v1beta1.StackFrame{ +// FileID: "1", +// Lineno: "42", +// }, +// }, +// }, +// }, +// stack2: &v1beta1.IdentifiedCallStack{ +// CallID: "test2", +// CallStack: v1beta1.CallStack{ +// Root: &v1beta1.CallStackNode{ +// Frame: &v1beta1.StackFrame{ +// FileID: "1", +// Lineno: "42", +// }, +// }, +// }, +// }, +// expected: false, +// }, +// } + +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// result := compareCallStacks(tt.stack1, tt.stack2) +// assert.Equal(t, tt.expected, result) +// }) +// } +// } + +// func TestCalculateSHA256CallStackHash(t *testing.T) { +// callStack1 := &v1beta1.IdentifiedCallStack{ +// CallID: "open", +// CallStack: v1beta1.CallStack{ +// Root: &v1beta1.CallStackNode{ +// Frame: &v1beta1.StackFrame{ +// FileID: "1", +// Lineno: "42", +// }, +// }, +// }, +// } + +// callStack2 := &v1beta1.IdentifiedCallStack{ +// CallID: "exec", +// CallStack: v1beta1.CallStack{ +// Root: &v1beta1.CallStackNode{ +// Frame: &v1beta1.StackFrame{ +// FileID: "2", +// Lineno: "84", +// }, +// Children: []v1beta1.CallStackNode{ +// { +// Frame: &v1beta1.StackFrame{ +// FileID: "3", +// Lineno: "120", +// }, +// }, +// }, +// }, +// }, +// } + +// hash1 := CalculateSHA256CallStackHash(callStack1) +// hash2 := CalculateSHA256CallStackHash(callStack2) + +// t.Logf("Hash for callStack1: %s", hash1) +// t.Logf("Hash for callStack2: %s", hash2) + +// // Different call stacks should produce different hashes +// assert.NotEqual(t, hash1, hash2, "Different call stacks should have different hashes") + +// // Same call stack should produce same hash +// hash1Again := CalculateSHA256CallStackHash(callStack1) +// assert.Equal(t, hash1, hash1Again, "Same call stack should produce same hash") + +// // Test with children nodes +// childrenCallStack := &v1beta1.IdentifiedCallStack{ +// CallID: "exec", +// CallStack: v1beta1.CallStack{ +// Root: &v1beta1.CallStackNode{ +// Frame: &v1beta1.StackFrame{ +// FileID: "2", +// Lineno: "84", +// }, +// Children: []v1beta1.CallStackNode{ +// { +// Frame: &v1beta1.StackFrame{ +// FileID: "3", +// Lineno: "120", +// }, +// }, +// }, +// }, +// }, +// } + +// hashWithChildren := CalculateSHA256CallStackHash(childrenCallStack) +// t.Logf("Hash for childrenCallStack: %s", hashWithChildren) + +// // Different call stack with same root but different children should have different hash +// differentChildrenCallStack := &v1beta1.IdentifiedCallStack{ +// CallID: "exec", +// CallStack: v1beta1.CallStack{ +// Root: &v1beta1.CallStackNode{ +// Frame: &v1beta1.StackFrame{ +// FileID: "2", +// Lineno: "84", +// }, +// Children: []v1beta1.CallStackNode{ +// { +// Frame: &v1beta1.StackFrame{ +// FileID: "4", // Different FileID +// Lineno: "120", +// }, +// }, +// }, +// }, +// }, +// } + +// hashWithDifferentChildren := CalculateSHA256CallStackHash(differentChildrenCallStack) +// t.Logf("Hash for differentChildrenCallStack: %s", hashWithDifferentChildren) +// assert.NotEqual(t, hashWithChildren, hashWithDifferentChildren, +// "Call stacks with different children should have different hashes") +// } diff --git a/pkg/applicationprofilemanager/v1/helpers.go b/pkg/applicationprofilemanager/v1/helpers.go index 0413f063..a01164a7 100644 --- a/pkg/applicationprofilemanager/v1/helpers.go +++ b/pkg/applicationprofilemanager/v1/helpers.go @@ -129,39 +129,32 @@ func GetInitOperations(containerType string, containerIndex int) []utils.PatchOp return operations } -func CalculateSHA256CallStackHash(callStack *v1beta1.IdentifiedCallStack) string { +func CalculateSHA256CallStackHash(callStack v1beta1.IdentifiedCallStack) string { hash := sha256.New() // Write CallID hash.Write([]byte(callStack.CallID)) // Helper function to write frame data - writeFrame := func(frame *v1beta1.StackFrame) { - if frame != nil { - hash.Write([]byte(frame.FileID)) - hash.Write([]byte(frame.Lineno)) - } + writeFrame := func(frame v1beta1.StackFrame) { + // No need for nil check since it's a value type + hash.Write([]byte(frame.FileID)) + hash.Write([]byte(frame.Lineno)) } // Helper function to recursively process node and its children - var processNode func(*v1beta1.CallStackNode) - processNode = func(node *v1beta1.CallStackNode) { - if node == nil { - return - } - + var processNode func(v1beta1.CallStackNode) + processNode = func(node v1beta1.CallStackNode) { writeFrame(node.Frame) // Process children for _, child := range node.Children { - processNode(&child) + processNode(child) } } - // Process the entire call stack - if callStack.CallStack.Root != nil { - processNode(callStack.CallStack.Root) - } + // Process the entire call stack - no need for nil check since Root is a value type + processNode(callStack.CallStack.Root) return hex.EncodeToString(hash.Sum(nil)) } From 3a0ae09abaa0d0556b284404b5eddbccde548a51 Mon Sep 17 00:00:00 2001 From: Amit Schendel Date: Tue, 28 Jan 2025 15:54:38 +0000 Subject: [PATCH 13/40] Addingg fixed code Signed-off-by: Amit Schendel --- .../v1/applicationprofile_manager.go | 44 - .../v1/applicationprofile_manager_test.go | 1190 +++++++++-------- 2 files changed, 623 insertions(+), 611 deletions(-) diff --git a/pkg/applicationprofilemanager/v1/applicationprofile_manager.go b/pkg/applicationprofilemanager/v1/applicationprofile_manager.go index 2ce43dcb..78bb8437 100644 --- a/pkg/applicationprofilemanager/v1/applicationprofile_manager.go +++ b/pkg/applicationprofilemanager/v1/applicationprofile_manager.go @@ -178,46 +178,6 @@ func (am *ApplicationProfileManager) monitorContainer(ctx context.Context, conta } } -func PrintCallStackVerbose(ics v1beta1.IdentifiedCallStack) { - fmt.Printf("IdentifiedCallStack:\n") - fmt.Printf(" CallID: %s\n", ics.CallID) - - // No need to check for nil Root as it's a value type - // Check if it's an empty frame and has no children instead - if len(ics.CallStack.Root.Children) == 0 && isEmptyFrame(ics.CallStack.Root.Frame) { - fmt.Println(" Root: empty") - return - } - - var printNode func(v1beta1.CallStackNode, int) - printNode = func(node v1beta1.CallStackNode, depth int) { - indent := strings.Repeat(" ", depth+1) - fmt.Printf("%sNode:\n", indent) - - if !isEmptyFrame(node.Frame) { - fmt.Printf("%s Frame:\n", indent) - fmt.Printf("%s FileID: %s\n", indent, node.Frame.FileID) - fmt.Printf("%s Lineno: %s\n", indent, node.Frame.Lineno) - } else { - fmt.Printf("%s Frame: empty\n", indent) - } - - fmt.Printf("%s Children: %d\n", indent, len(node.Children)) - for i, child := range node.Children { - fmt.Printf("%s Child %d:\n", indent, i) - printNode(child, depth+1) - } - } - - fmt.Println(" Root:") - printNode(ics.CallStack.Root, 1) -} - -// Helper function to check if a frame is empty -func isEmptyFrame(frame v1beta1.StackFrame) bool { - return frame.FileID == "" && frame.Lineno == "" -} - func (am *ApplicationProfileManager) saveProfile(ctx context.Context, watchedContainer *utils.WatchedContainerData, namespace string, initalizeOperations []utils.PatchOperation) { ctx, span := otel.Tracer("").Start(ctx, "ApplicationProfileManager.saveProfile") defer span.End() @@ -343,10 +303,6 @@ func (am *ApplicationProfileManager) saveProfile(ctx context.Context, watchedCon callStacks = append(callStacks, *callStack) return true }) - // Print the call stacks - for _, callStack := range callStacks { - PrintCallStackVerbose(callStack) - } // new activity // the process tries to use JSON patching to avoid conflicts between updates on the same object from different containers diff --git a/pkg/applicationprofilemanager/v1/applicationprofile_manager_test.go b/pkg/applicationprofilemanager/v1/applicationprofile_manager_test.go index 1561db9f..253dabec 100644 --- a/pkg/applicationprofilemanager/v1/applicationprofile_manager_test.go +++ b/pkg/applicationprofilemanager/v1/applicationprofile_manager_test.go @@ -560,572 +560,628 @@ func TestReportRulePolicy(t *testing.T) { } } -// func TestReportIdentifiedCallStack(t *testing.T) { -// // Setup common test environment -// cfg := config.Config{ -// InitialDelay: 1 * time.Second, -// MaxSniffingTime: 5 * time.Minute, -// UpdateDataPeriod: 1 * time.Second, -// } -// ctx := context.TODO() -// k8sClient := &k8sclient.K8sClientMock{} -// storageClient := &storage.StorageHttpClientMock{} -// k8sObjectCacheMock := &objectcache.K8sObjectCacheMock{} -// seccompManagerMock := &seccompmanager.SeccompManagerMock{} - -// tests := []struct { -// name string -// k8sContainerID string -// callStacks []*v1beta1.IdentifiedCallStack -// expected []v1beta1.IdentifiedCallStack -// }{ -// { -// name: "Single callstack", -// k8sContainerID: "ns/pod/cont", -// callStacks: []*v1beta1.IdentifiedCallStack{ -// { -// CallID: "open", -// CallStack: v1beta1.CallStack{ -// Root: &v1beta1.CallStackNode{ -// Frame: &v1beta1.StackFrame{ -// FileID: "1", -// Lineno: "42", -// }, -// }, -// }, -// }, -// }, -// expected: []v1beta1.IdentifiedCallStack{ -// { -// CallID: "open", -// CallStack: v1beta1.CallStack{ -// Root: &v1beta1.CallStackNode{ -// Frame: &v1beta1.StackFrame{ -// FileID: "1", -// Lineno: "42", -// }, -// }, -// }, -// }, -// }, -// }, -// { -// name: "Multiple callstacks", -// k8sContainerID: "ns/pod/cont", -// callStacks: []*v1beta1.IdentifiedCallStack{ -// { -// CallID: "open", -// CallStack: v1beta1.CallStack{ -// Root: &v1beta1.CallStackNode{ -// Frame: &v1beta1.StackFrame{ -// FileID: "1", -// Lineno: "42", -// }, -// Children: []v1beta1.CallStackNode{ -// { -// Frame: &v1beta1.StackFrame{ -// FileID: "2", -// Lineno: "84", -// }, -// }, -// }, -// }, -// }, -// }, -// { -// CallID: "exec", -// CallStack: v1beta1.CallStack{ -// Root: &v1beta1.CallStackNode{ -// Frame: &v1beta1.StackFrame{ -// FileID: "3", -// Lineno: "120", -// }, -// }, -// }, -// }, -// }, -// expected: []v1beta1.IdentifiedCallStack{ -// { -// CallID: "open", -// CallStack: v1beta1.CallStack{ -// Root: &v1beta1.CallStackNode{ -// Frame: &v1beta1.StackFrame{ -// FileID: "1", -// Lineno: "42", -// }, -// Children: []v1beta1.CallStackNode{ -// { -// Frame: &v1beta1.StackFrame{ -// FileID: "2", -// Lineno: "84", -// }, -// }, -// }, -// }, -// }, -// }, -// { -// CallID: "exec", -// CallStack: v1beta1.CallStack{ -// Root: &v1beta1.CallStackNode{ -// Frame: &v1beta1.StackFrame{ -// FileID: "3", -// Lineno: "120", -// }, -// }, -// }, -// }, -// }, -// }, -// { -// name: "Duplicate callstack", -// k8sContainerID: "ns/pod/cont", -// callStacks: []*v1beta1.IdentifiedCallStack{ -// { -// CallID: "open", -// CallStack: v1beta1.CallStack{ -// Root: &v1beta1.CallStackNode{ -// Frame: &v1beta1.StackFrame{ -// FileID: "1", -// Lineno: "42", -// }, -// }, -// }, -// }, -// { -// CallID: "open", -// CallStack: v1beta1.CallStack{ -// Root: &v1beta1.CallStackNode{ -// Frame: &v1beta1.StackFrame{ -// FileID: "1", -// Lineno: "42", -// }, -// }, -// }, -// }, -// }, -// expected: []v1beta1.IdentifiedCallStack{ -// { -// CallID: "open", -// CallStack: v1beta1.CallStack{ -// Root: &v1beta1.CallStackNode{ -// Frame: &v1beta1.StackFrame{ -// FileID: "1", -// Lineno: "42", -// }, -// }, -// }, -// }, -// }, -// }, -// } - -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// am, err := CreateApplicationProfileManager(ctx, cfg, "cluster", k8sClient, storageClient, k8sObjectCacheMock, seccompManagerMock, nil) -// assert.NoError(t, err) - -// // Initialize container tracking -// am.savedCallStacks.Set(tt.k8sContainerID, istiocache.NewTTL(5*am.cfg.UpdateDataPeriod, am.cfg.UpdateDataPeriod)) -// am.toSaveCallStacks.Set(tt.k8sContainerID, new(maps.SafeMap[string, *v1beta1.IdentifiedCallStack])) -// am.trackedContainers.Add(tt.k8sContainerID) - -// // Report each callstack -// for _, callStack := range tt.callStacks { -// am.ReportIdentifiedCallStack(tt.k8sContainerID, callStack) -// } - -// // Collect all callstacks that were queued to be saved -// var resultCallStacks []v1beta1.IdentifiedCallStack -// am.toSaveCallStacks.Get(tt.k8sContainerID).Range(func(identifier string, callStack *v1beta1.IdentifiedCallStack) bool { -// resultCallStacks = append(resultCallStacks, *callStack) -// return true -// }) - -// // Verify results -// assert.Equal(t, len(tt.expected), len(resultCallStacks)) -// for _, expectedCallStack := range tt.expected { -// found := false -// for _, resultCallStack := range resultCallStacks { -// if compareCallStacks(&expectedCallStack, &resultCallStack) { -// found = true -// break -// } -// } -// assert.True(t, found, "Expected call stack not found in results") -// } -// }) -// } -// } +func TestReportIdentifiedCallStack(t *testing.T) { + // Setup common test environment + cfg := config.Config{ + InitialDelay: 1 * time.Second, + MaxSniffingTime: 5 * time.Minute, + UpdateDataPeriod: 1 * time.Second, + } + ctx := context.TODO() + k8sClient := &k8sclient.K8sClientMock{} + storageClient := &storage.StorageHttpClientMock{} + k8sObjectCacheMock := &objectcache.K8sObjectCacheMock{} + seccompManagerMock := &seccompmanager.SeccompManagerMock{} + + tests := []struct { + name string + k8sContainerID string + callStacks []v1beta1.IdentifiedCallStack + expected []v1beta1.IdentifiedCallStack + }{ + { + name: "Single callstack", + k8sContainerID: "ns/pod/cont", + callStacks: []v1beta1.IdentifiedCallStack{ + { + CallID: "open", + CallStack: v1beta1.CallStack{ + Root: v1beta1.CallStackNode{ + Frame: v1beta1.StackFrame{ + FileID: "1", + Lineno: "42", + }, + Children: []v1beta1.CallStackNode{}, + }, + }, + }, + }, + expected: []v1beta1.IdentifiedCallStack{ + { + CallID: "open", + CallStack: v1beta1.CallStack{ + Root: v1beta1.CallStackNode{ + Frame: v1beta1.StackFrame{ + FileID: "1", + Lineno: "42", + }, + Children: []v1beta1.CallStackNode{}, + }, + }, + }, + }, + }, + { + name: "Multiple callstacks", + k8sContainerID: "ns/pod/cont", + callStacks: []v1beta1.IdentifiedCallStack{ + { + CallID: "open", + CallStack: v1beta1.CallStack{ + Root: v1beta1.CallStackNode{ + Frame: v1beta1.StackFrame{ + FileID: "1", + Lineno: "42", + }, + Children: []v1beta1.CallStackNode{ + { + Frame: v1beta1.StackFrame{ + FileID: "2", + Lineno: "84", + }, + Children: []v1beta1.CallStackNode{}, + }, + }, + }, + }, + }, + { + CallID: "exec", + CallStack: v1beta1.CallStack{ + Root: v1beta1.CallStackNode{ + Frame: v1beta1.StackFrame{ + FileID: "3", + Lineno: "120", + }, + Children: []v1beta1.CallStackNode{}, + }, + }, + }, + }, + expected: []v1beta1.IdentifiedCallStack{ + { + CallID: "open", + CallStack: v1beta1.CallStack{ + Root: v1beta1.CallStackNode{ + Frame: v1beta1.StackFrame{ + FileID: "1", + Lineno: "42", + }, + Children: []v1beta1.CallStackNode{ + { + Frame: v1beta1.StackFrame{ + FileID: "2", + Lineno: "84", + }, + Children: []v1beta1.CallStackNode{}, + }, + }, + }, + }, + }, + { + CallID: "exec", + CallStack: v1beta1.CallStack{ + Root: v1beta1.CallStackNode{ + Frame: v1beta1.StackFrame{ + FileID: "3", + Lineno: "120", + }, + Children: []v1beta1.CallStackNode{}, + }, + }, + }, + }, + }, + { + name: "Duplicate callstack", + k8sContainerID: "ns/pod/cont", + callStacks: []v1beta1.IdentifiedCallStack{ + { + CallID: "open", + CallStack: v1beta1.CallStack{ + Root: v1beta1.CallStackNode{ + Frame: v1beta1.StackFrame{ + FileID: "1", + Lineno: "42", + }, + Children: []v1beta1.CallStackNode{}, + }, + }, + }, + { + CallID: "open", + CallStack: v1beta1.CallStack{ + Root: v1beta1.CallStackNode{ + Frame: v1beta1.StackFrame{ + FileID: "1", + Lineno: "42", + }, + Children: []v1beta1.CallStackNode{}, + }, + }, + }, + }, + expected: []v1beta1.IdentifiedCallStack{ + { + CallID: "open", + CallStack: v1beta1.CallStack{ + Root: v1beta1.CallStackNode{ + Frame: v1beta1.StackFrame{ + FileID: "1", + Lineno: "42", + }, + Children: []v1beta1.CallStackNode{}, + }, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + am, err := CreateApplicationProfileManager(ctx, cfg, "cluster", k8sClient, storageClient, k8sObjectCacheMock, seccompManagerMock, nil) + if err != nil { + t.Fatalf("Failed to create ApplicationProfileManager: %v", err) + } + + // Initialize container tracking + am.savedCallStacks.Set(tt.k8sContainerID, istiocache.NewTTL(5*am.cfg.UpdateDataPeriod, am.cfg.UpdateDataPeriod)) + am.toSaveCallStacks.Set(tt.k8sContainerID, new(maps.SafeMap[string, *v1beta1.IdentifiedCallStack])) + am.trackedContainers.Add(tt.k8sContainerID) + + // Report each callstack + for _, callStack := range tt.callStacks { + am.ReportIdentifiedCallStack(tt.k8sContainerID, &callStack) + } + + // Collect all callstacks that were queued to be saved + var resultCallStacks []v1beta1.IdentifiedCallStack + am.toSaveCallStacks.Get(tt.k8sContainerID).Range(func(identifier string, callStack *v1beta1.IdentifiedCallStack) bool { + resultCallStacks = append(resultCallStacks, *callStack) + return true + }) + + // Verify results + if len(tt.expected) != len(resultCallStacks) { + t.Errorf("Expected %d call stacks, got %d", len(tt.expected), len(resultCallStacks)) + return + } + + for _, expectedCallStack := range tt.expected { + found := false + for _, resultCallStack := range resultCallStacks { + if compareCallStacks(expectedCallStack, resultCallStack) { + found = true + break + } + } + if !found { + t.Error("Expected call stack not found in results") + } + } + }) + } +} + +// Helper function to compare two call stack nodes and their subtrees +func compareNodes(node1, node2 v1beta1.CallStackNode) bool { + if node1.Frame.FileID != node2.Frame.FileID || node1.Frame.Lineno != node2.Frame.Lineno { + return false + } + + if len(node1.Children) != len(node2.Children) { + return false + } + + for i := range node1.Children { + if !compareNodes(node1.Children[i], node2.Children[i]) { + return false + } + } + + return true +} // Helper function to deeply compare two call stacks -// func compareCallStacks(a, b *v1beta1.IdentifiedCallStack) bool { -// if a.CallID != b.CallID { -// return false -// } -// return compareCallStackNodes(a.CallStack.Root, b.CallStack.Root) -// } - -// func compareCallStackNodes(a, b *v1beta1.CallStackNode) bool { -// if a == nil && b == nil { -// return true -// } -// if a == nil || b == nil { -// return false -// } -// if !compareStackFrames(a.Frame, b.Frame) { -// return false -// } -// if len(a.Children) != len(b.Children) { -// return false -// } -// for i := range a.Children { -// if !compareCallStackNodes(&a.Children[i], &b.Children[i]) { -// return false -// } -// } -// return true -// } - -// func compareStackFrames(a, b *v1beta1.StackFrame) bool { -// if a == nil && b == nil { -// return true -// } -// if a == nil || b == nil { -// return false -// } -// return a.FileID == b.FileID && a.Lineno == b.Lineno -// } - -// func TestApplicationProfileManagerWithCallStacks(t *testing.T) { -// // Setup test environment -// cfg := config.Config{ -// InitialDelay: 1 * time.Second, -// MaxSniffingTime: 5 * time.Minute, -// UpdateDataPeriod: 5 * time.Second, -// } -// ctx := context.TODO() -// k8sClient := &k8sclient.K8sClientMock{} -// storageClient := &storage.StorageHttpClientMock{} -// k8sObjectCacheMock := &objectcache.K8sObjectCacheMock{} -// seccompManagerMock := &seccompmanager.SeccompManagerMock{} - -// am, err := CreateApplicationProfileManager(ctx, cfg, "cluster", k8sClient, storageClient, k8sObjectCacheMock, seccompManagerMock, nil) -// assert.NoError(t, err) - -// // Prepare container -// container := &containercollection.Container{ -// K8s: containercollection.K8sMetadata{ -// BasicK8sMetadata: types.BasicK8sMetadata{ -// Namespace: "ns", -// PodName: "pod", -// ContainerName: "cont", -// }, -// }, -// Runtime: containercollection.RuntimeMetadata{ -// BasicRuntimeMetadata: types.BasicRuntimeMetadata{ -// ContainerID: "5fff6a395ce4e6984a9447cc6cfb09f473eaf278498243963fcc944889bc8400", -// ContainerStartedAt: types.Time(time.Now().UnixNano()), -// }, -// }, -// } - -// sharedWatchedContainerData := &utils.WatchedContainerData{} -// err = ensureInstanceID(container, sharedWatchedContainerData, k8sClient, "cluster") -// assert.NoError(t, err) -// k8sObjectCacheMock.SetSharedContainerData(container.Runtime.ContainerID, sharedWatchedContainerData) - -// // Start container monitoring -// am.ContainerCallback(containercollection.PubSubEvent{ -// Type: containercollection.EventTypeAddContainer, -// Container: container, -// }) - -// // Wait for initialization -// time.Sleep(2 * time.Second) - -// // Report call stacks -// callStack1 := &v1beta1.IdentifiedCallStack{ -// CallID: "open", -// CallStack: v1beta1.CallStack{ -// Root: &v1beta1.CallStackNode{ -// Frame: &v1beta1.StackFrame{ -// FileID: "1", -// Lineno: "42", -// }, -// }, -// }, -// } - -// callStack2 := &v1beta1.IdentifiedCallStack{ -// CallID: "exec", -// CallStack: v1beta1.CallStack{ -// Root: &v1beta1.CallStackNode{ -// Frame: &v1beta1.StackFrame{ -// FileID: "2", -// Lineno: "84", -// }, -// Children: []v1beta1.CallStackNode{ -// { -// Frame: &v1beta1.StackFrame{ -// FileID: "3", -// Lineno: "120", -// }, -// }, -// }, -// }, -// }, -// } - -// // Report call stacks and verify they're stored in toSaveCallStacks -// am.ReportIdentifiedCallStack("ns/pod/cont", callStack1) -// time.Sleep(100 * time.Millisecond) - -// // Debug logging for first call stack -// toSaveCallStacks := am.toSaveCallStacks.Get("ns/pod/cont") -// t.Logf("After first report - Number of call stacks in toSave: %d", toSaveCallStacks.Len()) -// toSaveCallStacks.Range(func(identifier string, stack *v1beta1.IdentifiedCallStack) bool { -// t.Logf("Found call stack with ID: %s", stack.CallID) -// return true -// }) - -// am.ReportIdentifiedCallStack("ns/pod/cont", callStack2) -// time.Sleep(100 * time.Millisecond) - -// // Debug logging for second call stack -// t.Logf("After second report - Number of call stacks in toSave: %d", toSaveCallStacks.Len()) -// toSaveCallStacks.Range(func(identifier string, stack *v1beta1.IdentifiedCallStack) bool { -// t.Logf("Found call stack with ID: %s", stack.CallID) -// return true -// }) - -// // Let monitoring run and trigger a save -// time.Sleep(2 * time.Second) - -// // Check saved call stacks after first save -// savedCallStacks := am.savedCallStacks.Get("ns/pod/cont") -// t.Logf("After first save - Number of call stacks in saved: %v", savedCallStacks) - -// // Let it run for the remaining time -// time.Sleep(2 * time.Second) - -// // Report container stopped -// am.ContainerCallback(containercollection.PubSubEvent{ -// Type: containercollection.EventTypeRemoveContainer, -// Container: container, -// }) - -// // Wait for final processing -// time.Sleep(2 * time.Second) - -// // Debug logging for profiles -// for i, profile := range storageClient.ApplicationProfiles { -// t.Logf("Profile %d:", i) -// t.Logf(" Number of containers: %d", len(profile.Spec.Containers)) -// if len(profile.Spec.Containers) > 1 { -// t.Logf(" Call stacks in container[1]: %d", len(profile.Spec.Containers[1].IdentifiedCallStacks)) -// for _, cs := range profile.Spec.Containers[1].IdentifiedCallStacks { -// t.Logf(" Call stack ID: %s", cs.CallID) -// } -// } -// } - -// // Verify results -// assert.Greater(t, len(storageClient.ApplicationProfiles), 0, "No application profiles were created") - -// // Get the latest profile -// latestProfile := storageClient.ApplicationProfiles[len(storageClient.ApplicationProfiles)-1] -// assert.NotNil(t, latestProfile.Spec.Containers, "Containers slice is nil") -// assert.Greater(t, len(latestProfile.Spec.Containers), 1, "Not enough containers in profile") - -// foundCallStacks := latestProfile.Spec.Containers[1].IdentifiedCallStacks - -// // Sort both expected and found call stacks -// sortCallStacks := func(cs []v1beta1.IdentifiedCallStack) { -// sort.Slice(cs, func(i, j int) bool { -// return string(cs[i].CallID) < string(cs[j].CallID) -// }) -// } - -// expectedCallStacks := []v1beta1.IdentifiedCallStack{*callStack1, *callStack2} -// sortCallStacks(expectedCallStacks) -// sortCallStacks(foundCallStacks) - -// t.Logf("Number of profiles: %d", len(storageClient.ApplicationProfiles)) -// t.Logf("Latest profile containers: %d", len(latestProfile.Spec.Containers)) -// t.Logf("Found call stacks: %d", len(foundCallStacks)) - -// assert.Equal(t, len(expectedCallStacks), len(foundCallStacks), "Number of call stacks doesn't match") -// if len(foundCallStacks) == len(expectedCallStacks) { -// for i := range expectedCallStacks { -// assert.True(t, compareCallStacks(&expectedCallStacks[i], &foundCallStacks[i]), -// "Call stack mismatch at index %d\nExpected: %+v\nGot: %+v", i, expectedCallStacks[i], foundCallStacks[i]) -// } -// } -// } - -// func TestCallStackComparison(t *testing.T) { -// tests := []struct { -// name string -// stack1 *v1beta1.IdentifiedCallStack -// stack2 *v1beta1.IdentifiedCallStack -// expected bool -// }{ -// { -// name: "identical single frame stacks", -// stack1: &v1beta1.IdentifiedCallStack{ -// CallID: "test", -// CallStack: v1beta1.CallStack{ -// Root: &v1beta1.CallStackNode{ -// Frame: &v1beta1.StackFrame{ -// FileID: "1", -// Lineno: "42", -// }, -// }, -// }, -// }, -// stack2: &v1beta1.IdentifiedCallStack{ -// CallID: "test", -// CallStack: v1beta1.CallStack{ -// Root: &v1beta1.CallStackNode{ -// Frame: &v1beta1.StackFrame{ -// FileID: "1", -// Lineno: "42", -// }, -// }, -// }, -// }, -// expected: true, -// }, -// { -// name: "different call IDs", -// stack1: &v1beta1.IdentifiedCallStack{ -// CallID: "test1", -// CallStack: v1beta1.CallStack{ -// Root: &v1beta1.CallStackNode{ -// Frame: &v1beta1.StackFrame{ -// FileID: "1", -// Lineno: "42", -// }, -// }, -// }, -// }, -// stack2: &v1beta1.IdentifiedCallStack{ -// CallID: "test2", -// CallStack: v1beta1.CallStack{ -// Root: &v1beta1.CallStackNode{ -// Frame: &v1beta1.StackFrame{ -// FileID: "1", -// Lineno: "42", -// }, -// }, -// }, -// }, -// expected: false, -// }, -// } - -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// result := compareCallStacks(tt.stack1, tt.stack2) -// assert.Equal(t, tt.expected, result) -// }) -// } -// } - -// func TestCalculateSHA256CallStackHash(t *testing.T) { -// callStack1 := &v1beta1.IdentifiedCallStack{ -// CallID: "open", -// CallStack: v1beta1.CallStack{ -// Root: &v1beta1.CallStackNode{ -// Frame: &v1beta1.StackFrame{ -// FileID: "1", -// Lineno: "42", -// }, -// }, -// }, -// } - -// callStack2 := &v1beta1.IdentifiedCallStack{ -// CallID: "exec", -// CallStack: v1beta1.CallStack{ -// Root: &v1beta1.CallStackNode{ -// Frame: &v1beta1.StackFrame{ -// FileID: "2", -// Lineno: "84", -// }, -// Children: []v1beta1.CallStackNode{ -// { -// Frame: &v1beta1.StackFrame{ -// FileID: "3", -// Lineno: "120", -// }, -// }, -// }, -// }, -// }, -// } - -// hash1 := CalculateSHA256CallStackHash(callStack1) -// hash2 := CalculateSHA256CallStackHash(callStack2) - -// t.Logf("Hash for callStack1: %s", hash1) -// t.Logf("Hash for callStack2: %s", hash2) - -// // Different call stacks should produce different hashes -// assert.NotEqual(t, hash1, hash2, "Different call stacks should have different hashes") - -// // Same call stack should produce same hash -// hash1Again := CalculateSHA256CallStackHash(callStack1) -// assert.Equal(t, hash1, hash1Again, "Same call stack should produce same hash") - -// // Test with children nodes -// childrenCallStack := &v1beta1.IdentifiedCallStack{ -// CallID: "exec", -// CallStack: v1beta1.CallStack{ -// Root: &v1beta1.CallStackNode{ -// Frame: &v1beta1.StackFrame{ -// FileID: "2", -// Lineno: "84", -// }, -// Children: []v1beta1.CallStackNode{ -// { -// Frame: &v1beta1.StackFrame{ -// FileID: "3", -// Lineno: "120", -// }, -// }, -// }, -// }, -// }, -// } - -// hashWithChildren := CalculateSHA256CallStackHash(childrenCallStack) -// t.Logf("Hash for childrenCallStack: %s", hashWithChildren) - -// // Different call stack with same root but different children should have different hash -// differentChildrenCallStack := &v1beta1.IdentifiedCallStack{ -// CallID: "exec", -// CallStack: v1beta1.CallStack{ -// Root: &v1beta1.CallStackNode{ -// Frame: &v1beta1.StackFrame{ -// FileID: "2", -// Lineno: "84", -// }, -// Children: []v1beta1.CallStackNode{ -// { -// Frame: &v1beta1.StackFrame{ -// FileID: "4", // Different FileID -// Lineno: "120", -// }, -// }, -// }, -// }, -// }, -// } - -// hashWithDifferentChildren := CalculateSHA256CallStackHash(differentChildrenCallStack) -// t.Logf("Hash for differentChildrenCallStack: %s", hashWithDifferentChildren) -// assert.NotEqual(t, hashWithChildren, hashWithDifferentChildren, -// "Call stacks with different children should have different hashes") -// } +func compareCallStacks(a, b v1beta1.IdentifiedCallStack) bool { + if a.CallID != b.CallID { + return false + } + return compareCallStackNodes(a.CallStack.Root, b.CallStack.Root) +} + +func compareCallStackNodes(a, b v1beta1.CallStackNode) bool { + if !compareStackFrames(a.Frame, b.Frame) { + return false + } + if len(a.Children) != len(b.Children) { + return false + } + for i := range a.Children { + if !compareCallStackNodes(a.Children[i], b.Children[i]) { + return false + } + } + return true +} + +func compareStackFrames(a, b v1beta1.StackFrame) bool { + return a.FileID == b.FileID && a.Lineno == b.Lineno +} + +func TestApplicationProfileManagerWithCallStacks(t *testing.T) { + // Setup test environment + cfg := config.Config{ + InitialDelay: 1 * time.Second, + MaxSniffingTime: 5 * time.Minute, + UpdateDataPeriod: 5 * time.Second, + } + ctx := context.TODO() + k8sClient := &k8sclient.K8sClientMock{} + storageClient := &storage.StorageHttpClientMock{} + k8sObjectCacheMock := &objectcache.K8sObjectCacheMock{} + seccompManagerMock := &seccompmanager.SeccompManagerMock{} + + am, err := CreateApplicationProfileManager(ctx, cfg, "cluster", k8sClient, storageClient, k8sObjectCacheMock, seccompManagerMock, nil) + if err != nil { + t.Fatalf("Failed to create ApplicationProfileManager: %v", err) + } + + // Prepare container + container := &containercollection.Container{ + K8s: containercollection.K8sMetadata{ + BasicK8sMetadata: types.BasicK8sMetadata{ + Namespace: "ns", + PodName: "pod", + ContainerName: "cont", + }, + }, + Runtime: containercollection.RuntimeMetadata{ + BasicRuntimeMetadata: types.BasicRuntimeMetadata{ + ContainerID: "5fff6a395ce4e6984a9447cc6cfb09f473eaf278498243963fcc944889bc8400", + ContainerStartedAt: types.Time(time.Now().UnixNano()), + }, + }, + } + + sharedWatchedContainerData := &utils.WatchedContainerData{} + err = ensureInstanceID(container, sharedWatchedContainerData, k8sClient, "cluster") + if err != nil { + t.Fatalf("Failed to ensure instance ID: %v", err) + } + k8sObjectCacheMock.SetSharedContainerData(container.Runtime.ContainerID, sharedWatchedContainerData) + + // Start container monitoring + am.ContainerCallback(containercollection.PubSubEvent{ + Type: containercollection.EventTypeAddContainer, + Container: container, + }) + + // Wait for initialization + time.Sleep(2 * time.Second) + + // Report call stacks + callStack1 := v1beta1.IdentifiedCallStack{ + CallID: "open", + CallStack: v1beta1.CallStack{ + Root: v1beta1.CallStackNode{ + Frame: v1beta1.StackFrame{ + FileID: "1", + Lineno: "42", + }, + Children: []v1beta1.CallStackNode{}, + }, + }, + } + + callStack2 := v1beta1.IdentifiedCallStack{ + CallID: "exec", + CallStack: v1beta1.CallStack{ + Root: v1beta1.CallStackNode{ + Frame: v1beta1.StackFrame{ + FileID: "2", + Lineno: "84", + }, + Children: []v1beta1.CallStackNode{ + { + Frame: v1beta1.StackFrame{ + FileID: "3", + Lineno: "120", + }, + Children: []v1beta1.CallStackNode{}, + }, + }, + }, + }, + } + + // Report call stacks and verify they're stored in toSaveCallStacks + am.ReportIdentifiedCallStack("ns/pod/cont", &callStack1) + time.Sleep(100 * time.Millisecond) + + // Debug logging for first call stack + toSaveCallStacks := am.toSaveCallStacks.Get("ns/pod/cont") + t.Logf("After first report - Number of call stacks in toSave: %d", toSaveCallStacks.Len()) + toSaveCallStacks.Range(func(identifier string, stack *v1beta1.IdentifiedCallStack) bool { + t.Logf("Found call stack with ID: %s", stack.CallID) + return true + }) + + am.ReportIdentifiedCallStack("ns/pod/cont", &callStack2) + time.Sleep(100 * time.Millisecond) + + // Debug logging for second call stack + t.Logf("After second report - Number of call stacks in toSave: %d", toSaveCallStacks.Len()) + toSaveCallStacks.Range(func(identifier string, stack *v1beta1.IdentifiedCallStack) bool { + t.Logf("Found call stack with ID: %s", stack.CallID) + return true + }) + + // Let monitoring run and trigger a save + time.Sleep(2 * time.Second) + + // Check saved call stacks after first save + savedCallStacks := am.savedCallStacks.Get("ns/pod/cont") + t.Logf("After first save - Number of call stacks in saved: %v", savedCallStacks) + + // Let it run for the remaining time + time.Sleep(2 * time.Second) + + // Report container stopped + am.ContainerCallback(containercollection.PubSubEvent{ + Type: containercollection.EventTypeRemoveContainer, + Container: container, + }) + + // Wait for final processing + time.Sleep(2 * time.Second) + + // Debug logging for profiles + for i, profile := range storageClient.ApplicationProfiles { + t.Logf("Profile %d:", i) + t.Logf(" Number of containers: %d", len(profile.Spec.Containers)) + if len(profile.Spec.Containers) > 1 { + t.Logf(" Call stacks in container[1]: %d", len(profile.Spec.Containers[1].IdentifiedCallStacks)) + for _, cs := range profile.Spec.Containers[1].IdentifiedCallStacks { + t.Logf(" Call stack ID: %s", cs.CallID) + } + } + } + + // Verify results + if len(storageClient.ApplicationProfiles) == 0 { + t.Fatal("No application profiles were created") + } + + // Get the latest profile + latestProfile := storageClient.ApplicationProfiles[len(storageClient.ApplicationProfiles)-1] + if latestProfile.Spec.Containers == nil { + t.Fatal("Containers slice is nil") + } + if len(latestProfile.Spec.Containers) <= 1 { + t.Fatal("Not enough containers in profile") + } + + foundCallStacks := latestProfile.Spec.Containers[1].IdentifiedCallStacks + + // Sort both expected and found call stacks + sortCallStacks := func(cs []v1beta1.IdentifiedCallStack) { + sort.Slice(cs, func(i, j int) bool { + return string(cs[i].CallID) < string(cs[j].CallID) + }) + } + + expectedCallStacks := []v1beta1.IdentifiedCallStack{callStack1, callStack2} + sortCallStacks(expectedCallStacks) + sortCallStacks(foundCallStacks) + + t.Logf("Number of profiles: %d", len(storageClient.ApplicationProfiles)) + t.Logf("Latest profile containers: %d", len(latestProfile.Spec.Containers)) + t.Logf("Found call stacks: %d", len(foundCallStacks)) + + if len(expectedCallStacks) != len(foundCallStacks) { + t.Errorf("Number of call stacks doesn't match. Expected %d, got %d", + len(expectedCallStacks), len(foundCallStacks)) + return + } + + for i := range expectedCallStacks { + if !compareCallStacks(expectedCallStacks[i], foundCallStacks[i]) { + t.Errorf("Call stack mismatch at index %d\nExpected: %+v\nGot: %+v", + i, expectedCallStacks[i], foundCallStacks[i]) + } + } +} + +func TestCallStackComparison(t *testing.T) { + tests := []struct { + name string + stack1 v1beta1.IdentifiedCallStack + stack2 v1beta1.IdentifiedCallStack + expected bool + }{ + { + name: "identical single frame stacks", + stack1: v1beta1.IdentifiedCallStack{ + CallID: "test", + CallStack: v1beta1.CallStack{ + Root: v1beta1.CallStackNode{ + Frame: v1beta1.StackFrame{ + FileID: "1", + Lineno: "42", + }, + Children: []v1beta1.CallStackNode{}, + }, + }, + }, + stack2: v1beta1.IdentifiedCallStack{ + CallID: "test", + CallStack: v1beta1.CallStack{ + Root: v1beta1.CallStackNode{ + Frame: v1beta1.StackFrame{ + FileID: "1", + Lineno: "42", + }, + Children: []v1beta1.CallStackNode{}, + }, + }, + }, + expected: true, + }, + { + name: "different call IDs", + stack1: v1beta1.IdentifiedCallStack{ + CallID: "test1", + CallStack: v1beta1.CallStack{ + Root: v1beta1.CallStackNode{ + Frame: v1beta1.StackFrame{ + FileID: "1", + Lineno: "42", + }, + Children: []v1beta1.CallStackNode{}, + }, + }, + }, + stack2: v1beta1.IdentifiedCallStack{ + CallID: "test2", + CallStack: v1beta1.CallStack{ + Root: v1beta1.CallStackNode{ + Frame: v1beta1.StackFrame{ + FileID: "1", + Lineno: "42", + }, + Children: []v1beta1.CallStackNode{}, + }, + }, + }, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := compareCallStacks(tt.stack1, tt.stack2) + if result != tt.expected { + t.Errorf("expected %v, got %v", tt.expected, result) + } + }) + } +} + +func TestCalculateSHA256CallStackHash(t *testing.T) { + callStack1 := v1beta1.IdentifiedCallStack{ + CallID: "open", + CallStack: v1beta1.CallStack{ + Root: v1beta1.CallStackNode{ + Frame: v1beta1.StackFrame{ + FileID: "1", + Lineno: "42", + }, + Children: []v1beta1.CallStackNode{}, + }, + }, + } + + callStack2 := v1beta1.IdentifiedCallStack{ + CallID: "exec", + CallStack: v1beta1.CallStack{ + Root: v1beta1.CallStackNode{ + Frame: v1beta1.StackFrame{ + FileID: "2", + Lineno: "84", + }, + Children: []v1beta1.CallStackNode{ + { + Frame: v1beta1.StackFrame{ + FileID: "3", + Lineno: "120", + }, + Children: []v1beta1.CallStackNode{}, + }, + }, + }, + }, + } + + hash1 := CalculateSHA256CallStackHash(callStack1) + hash2 := CalculateSHA256CallStackHash(callStack2) + + t.Logf("Hash for callStack1: %s", hash1) + t.Logf("Hash for callStack2: %s", hash2) + + // Different call stacks should produce different hashes + if hash1 == hash2 { + t.Error("Different call stacks should have different hashes") + } + + // Same call stack should produce same hash + hash1Again := CalculateSHA256CallStackHash(callStack1) + if hash1 != hash1Again { + t.Error("Same call stack should produce same hash") + } + + // Test with children nodes + childrenCallStack := v1beta1.IdentifiedCallStack{ + CallID: "exec", + CallStack: v1beta1.CallStack{ + Root: v1beta1.CallStackNode{ + Frame: v1beta1.StackFrame{ + FileID: "2", + Lineno: "84", + }, + Children: []v1beta1.CallStackNode{ + { + Frame: v1beta1.StackFrame{ + FileID: "3", + Lineno: "120", + }, + Children: []v1beta1.CallStackNode{}, + }, + }, + }, + }, + } + + hashWithChildren := CalculateSHA256CallStackHash(childrenCallStack) + t.Logf("Hash for childrenCallStack: %s", hashWithChildren) + + // Different call stack with same root but different children should have different hash + differentChildrenCallStack := v1beta1.IdentifiedCallStack{ + CallID: "exec", + CallStack: v1beta1.CallStack{ + Root: v1beta1.CallStackNode{ + Frame: v1beta1.StackFrame{ + FileID: "2", + Lineno: "84", + }, + Children: []v1beta1.CallStackNode{ + { + Frame: v1beta1.StackFrame{ + FileID: "4", // Different FileID + Lineno: "120", + }, + Children: []v1beta1.CallStackNode{}, + }, + }, + }, + }, + } + + hashWithDifferentChildren := CalculateSHA256CallStackHash(differentChildrenCallStack) + t.Logf("Hash for differentChildrenCallStack: %s", hashWithDifferentChildren) + if hashWithChildren == hashWithDifferentChildren { + t.Error("Call stacks with different children should have different hashes") + } +} From aefbfbc6b715984da3528575d3065174a775d5a7 Mon Sep 17 00:00:00 2001 From: Amit Schendel Date: Thu, 30 Jan 2025 11:08:13 +0000 Subject: [PATCH 14/40] Adding improved callstack cache Signed-off-by: Amit Schendel --- go.mod | 1 + go.sum | 2 + .../applicationprofilecache.go | 57 +++ .../applicationprofilecache/callstackcache.go | 183 ++++++++ .../callstackcache_test.go | 396 ++++++++++++++++++ 5 files changed, 639 insertions(+) create mode 100644 pkg/objectcache/applicationprofilecache/callstackcache.go create mode 100644 pkg/objectcache/applicationprofilecache/callstackcache_test.go diff --git a/go.mod b/go.mod index be9deb28..b4093872 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/crewjam/rfc5424 v0.1.0 github.com/cyphar/filepath-securejoin v0.4.0 github.com/deckarep/golang-set/v2 v2.7.0 + github.com/dghubble/trie v0.1.0 github.com/distribution/distribution v2.8.2+incompatible github.com/dustin/go-humanize v1.0.1 github.com/dutchcoders/go-clamd v0.0.0-20170520113014-b970184f4d9e diff --git a/go.sum b/go.sum index 67694227..357c24d5 100644 --- a/go.sum +++ b/go.sum @@ -287,6 +287,8 @@ github.com/deckarep/golang-set/v2 v2.7.0 h1:gIloKvD7yH2oip4VLhsv3JyLLFnC0Y2mlusg github.com/deckarep/golang-set/v2 v2.7.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da h1:ZOjWpVsFZ06eIhnh4mkaceTiVoktdU67+M7KDHJ268M= github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da/go.mod h1:B3tI9iGHi4imdLi4Asdha1Sc6feLMTfPLXh9IUYmysk= +github.com/dghubble/trie v0.1.0 h1:kJnjBLFFElBwS60N4tkPvnLhnpcDxbBjIulgI8CpNGM= +github.com/dghubble/trie v0.1.0/go.mod h1:sOmnzfBNH7H92ow2292dDFWNsVQuh/izuD7otCYb1ak= github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4= github.com/distribution/distribution v2.8.2+incompatible h1:k9+4DKdOG+quPFZXT/mUsiQrGu9vYCp+dXpuPkuqhk8= github.com/distribution/distribution v2.8.2+incompatible/go.mod h1:EgLm2NgWtdKgzF9NpMzUKgzmR7AMmb0VQi2B+ZzDRjc= diff --git a/pkg/objectcache/applicationprofilecache/applicationprofilecache.go b/pkg/objectcache/applicationprofilecache/applicationprofilecache.go index 4e946f6f..45aae2b1 100644 --- a/pkg/objectcache/applicationprofilecache/applicationprofilecache.go +++ b/pkg/objectcache/applicationprofilecache/applicationprofilecache.go @@ -47,6 +47,11 @@ func newApplicationProfileState(ap *v1beta1.ApplicationProfile) applicationProfi } } +// ContainerCallStackIndex maintains call stack search trees for a container +type ContainerCallStackIndex struct { + searchTree *CallStackSearchTree +} + type ApplicationProfileCacheImpl struct { containerToSlug maps.SafeMap[string, string] // cache the containerID to slug mapping, this will enable a quick lookup of the application profile slugToAppProfile maps.SafeMap[string, *v1beta1.ApplicationProfile] // cache the application profile @@ -57,6 +62,7 @@ type ApplicationProfileCacheImpl struct { nodeName string maxDelaySeconds int // maximum delay in seconds before getting the full object from the storage userManagedProfiles maps.SafeMap[string, *v1beta1.ApplicationProfile] + containerCallStacks maps.SafeMap[string, *ContainerCallStackIndex] // cache the containerID to call stack search tree mapping } func NewApplicationProfileCache(nodeName string, storageClient versioned.SpdxV1beta1Interface, maxDelaySeconds int) *ApplicationProfileCacheImpl { @@ -70,6 +76,7 @@ func NewApplicationProfileCache(nodeName string, storageClient versioned.SpdxV1b slugToState: maps.SafeMap[string, applicationProfileState]{}, allProfiles: mapset.NewSet[string](), userManagedProfiles: maps.SafeMap[string, *v1beta1.ApplicationProfile]{}, + containerCallStacks: maps.SafeMap[string, *ContainerCallStackIndex]{}, } } @@ -114,6 +121,54 @@ func (ap *ApplicationProfileCacheImpl) handleUserManagedProfile(appProfile *v1be } } +// indexContainerCallStacks builds the search index for a container's call stacks and removes them from the profile +func (ap *ApplicationProfileCacheImpl) indexContainerCallStacks(containerID string, appProfile *v1beta1.ApplicationProfile) { + // Initialize container index if needed + if !ap.containerCallStacks.Has(containerID) { + ap.containerCallStacks.Set(containerID, &ContainerCallStackIndex{ + searchTree: NewCallStackSearchTree(), + }) + } + + index := ap.containerCallStacks.Get(containerID) + + // Find the container in the profile and index its call stacks + for i := range appProfile.Spec.Containers { + if appProfile.Spec.Containers[i].ImageID == containerID { + // Index all call stacks + for _, stack := range appProfile.Spec.Containers[i].IdentifiedCallStacks { + index.searchTree.AddCallStack(stack) + } + + // Clear the call stacks to free memory + appProfile.Spec.Containers[i].IdentifiedCallStacks = nil + break + } + } + + // Also check init containers + for i := range appProfile.Spec.InitContainers { + if appProfile.Spec.InitContainers[i].ImageID == containerID { + for _, stack := range appProfile.Spec.InitContainers[i].IdentifiedCallStacks { + index.searchTree.AddCallStack(stack) + } + appProfile.Spec.InitContainers[i].IdentifiedCallStacks = nil + break + } + } + + // And ephemeral containers + for i := range appProfile.Spec.EphemeralContainers { + if appProfile.Spec.EphemeralContainers[i].ImageID == containerID { + for _, stack := range appProfile.Spec.EphemeralContainers[i].IdentifiedCallStacks { + index.searchTree.AddCallStack(stack) + } + appProfile.Spec.EphemeralContainers[i].IdentifiedCallStacks = nil + break + } + } +} + func (ap *ApplicationProfileCacheImpl) addApplicationProfile(obj runtime.Object) { appProfile := obj.(*v1beta1.ApplicationProfile) apName := objectcache.MetaUniqueName(appProfile) @@ -271,6 +326,7 @@ func (ap *ApplicationProfileCacheImpl) removeContainer(containerID string) { uniqueSlug := ap.containerToSlug.Get(containerID) ap.containerToSlug.Delete(containerID) + ap.containerCallStacks.Delete(containerID) // remove pod form the application profile mapping if ap.slugToContainers.Has(uniqueSlug) { @@ -306,6 +362,7 @@ func (ap *ApplicationProfileCacheImpl) addFullApplicationProfile(appProfile *v1b ap.slugToAppProfile.Set(apName, fullAP) for _, i := range ap.slugToContainers.Get(apName).ToSlice() { ap.containerToSlug.Set(i, apName) + ap.indexContainerCallStacks(i, fullAP) } logger.L().Debug("ApplicationProfileCacheImpl - added pod to application profile cache", helpers.String("name", apName)) } diff --git a/pkg/objectcache/applicationprofilecache/callstackcache.go b/pkg/objectcache/applicationprofilecache/callstackcache.go new file mode 100644 index 00000000..e384f905 --- /dev/null +++ b/pkg/objectcache/applicationprofilecache/callstackcache.go @@ -0,0 +1,183 @@ +package applicationprofilecache + +import ( + "strings" + + "github.com/dghubble/trie" + "github.com/kubescape/storage/pkg/apis/softwarecomposition/v1beta1" +) + +// BidirectionalNode extends the call stack node with parent reference +type BidirectionalNode struct { + Frame v1beta1.StackFrame + Parent *BidirectionalNode + Children []*BidirectionalNode +} + +// CallStackSearchTree provides efficient searching and comparison of call stacks +type CallStackSearchTree struct { + // Store forward paths in trie for prefix matching + forwardTrie *trie.PathTrie + // Store complete paths mapped by CallID for quick lookup + pathsByCallID map[v1beta1.CallID][][]v1beta1.StackFrame + // Store root nodes for bidirectional traversal + roots map[v1beta1.CallID]*BidirectionalNode +} + +func NewCallStackSearchTree() *CallStackSearchTree { + return &CallStackSearchTree{ + forwardTrie: trie.NewPathTrie(), + pathsByCallID: make(map[v1beta1.CallID][][]v1beta1.StackFrame), + roots: make(map[v1beta1.CallID]*BidirectionalNode), + } +} + +// AddCallStack adds a new identified call stack to the search tree +func (t *CallStackSearchTree) AddCallStack(stack v1beta1.IdentifiedCallStack) { + // Get all paths from the call stack + paths := getCallStackPaths(stack.CallStack) + if len(paths) == 0 { + return + } + + // First create root from first path + firstPath := paths[0] + firstFrames := make([]v1beta1.StackFrame, len(firstPath)) + for i, node := range firstPath { + firstFrames[i] = node.Frame + } + + root := t.buildBidirectionalTree(firstFrames) + t.roots[stack.CallID] = root + + // Store paths for this CallID + t.pathsByCallID[stack.CallID] = make([][]v1beta1.StackFrame, 0) + t.pathsByCallID[stack.CallID] = append(t.pathsByCallID[stack.CallID], firstFrames) + + // Add first path to trie + key := pathToKey(firstFrames) + t.forwardTrie.Put(key, firstFrames) + + // Process remaining paths + for i := 1; i < len(paths); i++ { + path := paths[i] + frames := make([]v1beta1.StackFrame, len(path)) + for j, node := range path { + frames[j] = node.Frame + } + + // Add to paths by CallID + t.pathsByCallID[stack.CallID] = append(t.pathsByCallID[stack.CallID], frames) + + // Add to trie + key = pathToKey(frames) + t.forwardTrie.Put(key, frames) + + // Merge into bidirectional tree + current := root + + for j := 1; j < len(frames); j++ { // Start from 1 since we know root matches + frame := frames[j] + + // Try to find matching child + var found *BidirectionalNode + for _, child := range current.Children { + if framesEqual(child.Frame, frame) { + found = child + break + } + } + + // If no matching child found, create new node + if found == nil { + newNode := &BidirectionalNode{ + Frame: frame, + Parent: current, + Children: make([]*BidirectionalNode, 0), + } + current.Children = append(current.Children, newNode) + current = newNode + } else { + current = found + } + } + } +} + +// buildBidirectionalTree creates a tree with parent references +func (t *CallStackSearchTree) buildBidirectionalTree(frames []v1beta1.StackFrame) *BidirectionalNode { + if len(frames) == 0 { + return nil + } + + root := &BidirectionalNode{ + Frame: frames[0], + Children: make([]*BidirectionalNode, 0), + } + + current := root + for i := 1; i < len(frames); i++ { + node := &BidirectionalNode{ + Frame: frames[i], + Parent: current, + Children: make([]*BidirectionalNode, 0), + } + current.Children = append(current.Children, node) + current = node + } + + return root +} + +// getCallStackPaths returns all complete paths in a call stack +func getCallStackPaths(cs v1beta1.CallStack) [][]v1beta1.CallStackNode { + var paths [][]v1beta1.CallStackNode + var traverse func(node v1beta1.CallStackNode, currentPath []v1beta1.CallStackNode) + + traverse = func(node v1beta1.CallStackNode, currentPath []v1beta1.CallStackNode) { + path := append([]v1beta1.CallStackNode{}, currentPath...) + path = append(path, node) + + if len(node.Children) == 0 { + paths = append(paths, path) + return + } + for _, child := range node.Children { + traverse(child, path) + } + } + + if isEmptyFrame(cs.Root.Frame) { + for _, child := range cs.Root.Children { + traverse(child, nil) + } + } else { + traverse(cs.Root, nil) + } + + return paths +} + +// isEmptyFrame checks if a StackFrame is empty +func isEmptyFrame(frame v1beta1.StackFrame) bool { + return frame.FileID == "" && frame.Lineno == "" +} + +// Helper functions +func pathToKey(frames []v1beta1.StackFrame) string { + parts := make([]string, len(frames)) + for i, frame := range frames { + parts[i] = frameKey(frame) + } + return strings.Join(parts, "/") +} + +// frameKey returns a unique key for a stack frame +func frameKey(frame v1beta1.StackFrame) string { + return frame.FileID + ":" + frame.Lineno +} + +// framesEqual checks if two call stacks are equal +func framesEqual(a, b v1beta1.StackFrame) bool { + return a.FileID == b.FileID && a.Lineno == b.Lineno +} diff --git a/pkg/objectcache/applicationprofilecache/callstackcache_test.go b/pkg/objectcache/applicationprofilecache/callstackcache_test.go new file mode 100644 index 00000000..d4c1c46e --- /dev/null +++ b/pkg/objectcache/applicationprofilecache/callstackcache_test.go @@ -0,0 +1,396 @@ +package applicationprofilecache + +import ( + "fmt" + "strings" + "testing" + + "github.com/kubescape/storage/pkg/apis/softwarecomposition/v1beta1" +) + +// Helper function to build unified call stacks for testing +func buildUnifiedStack(callID string, paths [][]struct{ fileID, lineno string }) v1beta1.IdentifiedCallStack { + stack := v1beta1.IdentifiedCallStack{ + CallID: v1beta1.CallID(callID), + CallStack: v1beta1.CallStack{ + Root: v1beta1.CallStackNode{ + Frame: v1beta1.StackFrame{}, + Children: make([]v1beta1.CallStackNode, 0), + }, + }, + } + + if len(paths) == 0 || len(paths[0]) == 0 { + return stack + } + + fmt.Printf("Building stack with %d paths\n", len(paths)) + for i, path := range paths { + fmt.Printf("Path %d: ", i) + for _, f := range path { + fmt.Printf("{%s %s} ", f.fileID, f.lineno) + } + fmt.Println() + } + + // Create and add the first node + firstNode := v1beta1.CallStackNode{ + Frame: v1beta1.StackFrame{ + FileID: paths[0][0].fileID, + Lineno: paths[0][0].lineno, + }, + Children: make([]v1beta1.CallStackNode, 0), + } + stack.CallStack.Root.Children = append(stack.CallStack.Root.Children, firstNode) + fmt.Printf("Created first node: {%s %s}\n", firstNode.Frame.FileID, firstNode.Frame.Lineno) + + // For each path + for pathIdx, path := range paths { + fmt.Printf("\nProcessing path %d:\n", pathIdx) + current := &stack.CallStack.Root.Children[0] // Start at the first real node + fmt.Printf("Starting at node: {%s %s}\n", current.Frame.FileID, current.Frame.Lineno) + + // Skip the first frame since we already added it + for frameIdx := 1; frameIdx < len(path); frameIdx++ { + frame := path[frameIdx] + fmt.Printf("Processing frame {%s %s}\n", frame.fileID, frame.lineno) + + // Check if this frame already exists in children + found := false + for i := range current.Children { + if current.Children[i].Frame.FileID == frame.fileID && + current.Children[i].Frame.Lineno == frame.lineno { + current = ¤t.Children[i] + found = true + fmt.Printf("Found existing node for frame\n") + break + } + } + + // If not found, add new node + if !found { + newNode := v1beta1.CallStackNode{ + Frame: v1beta1.StackFrame{ + FileID: frame.fileID, + Lineno: frame.lineno, + }, + Children: make([]v1beta1.CallStackNode, 0), + } + current.Children = append(current.Children, newNode) + current = ¤t.Children[len(current.Children)-1] + fmt.Printf("Created new node for frame\n") + } + + fmt.Printf("Current node now: {%s %s} with %d children\n", + current.Frame.FileID, current.Frame.Lineno, len(current.Children)) + } + } + + // Debug print final tree structure + fmt.Println("\nFinal tree structure:") + var printNode func(node v1beta1.CallStackNode, depth int) + printNode = func(node v1beta1.CallStackNode, depth int) { + indent := strings.Repeat(" ", depth) + fmt.Printf("%s{%s %s} (children: %d)\n", + indent, node.Frame.FileID, node.Frame.Lineno, len(node.Children)) + for _, child := range node.Children { + printNode(child, depth+1) + } + } + for _, child := range stack.CallStack.Root.Children { + printNode(child, 0) + } + + return stack +} + +func TestCallStackSearchTreeBranching(t *testing.T) { + tree := NewCallStackSearchTree() + + // Create a branching stack similar to the real example + branchingStack := buildUnifiedStack("test1", [][]struct{ fileID, lineno string }{ + { + {"10425069705252389217", "645761"}, + {"10425069705252389217", "653231"}, + {"2918313636494991837", "867979"}, + {"4298936378959959569", "390324"}, + }, + { + {"10425069705252389217", "645761"}, + {"10425069705252389217", "653231"}, + {"2918313636494991837", "867979"}, + {"0", "4012"}, + }, + }) + + tree.AddCallStack(branchingStack) + + // Debug print the stored tree before verification + fmt.Println("\nStored tree before verification:") + printBiNode(tree.roots["test1"], 0) + + fmt.Println("\nStarting verification:") + + // Start verification + root := tree.roots["test1"] + fmt.Printf("Root frame: {%s %s}\n", root.Frame.FileID, root.Frame.Lineno) + + current := root + expectedFrames := []struct{ fileID, lineno string }{ + {"10425069705252389217", "645761"}, + {"10425069705252389217", "653231"}, + {"2918313636494991837", "867979"}, + } + + for i := 0; i < len(expectedFrames); i++ { + expected := expectedFrames[i] + fmt.Printf("\nVerifying depth %d:\n", i) + fmt.Printf("Current frame: {%s %s}\n", current.Frame.FileID, current.Frame.Lineno) + fmt.Printf("Expected frame: {%s %s}\n", expected.fileID, expected.lineno) + fmt.Printf("Current has %d children\n", len(current.Children)) + + if !framesEqual(current.Frame, v1beta1.StackFrame{FileID: expected.fileID, Lineno: expected.lineno}) { + t.Errorf("At depth %d, unexpected frame: got {%s %s}, expected {%s %s}", + i, current.Frame.FileID, current.Frame.Lineno, expected.fileID, expected.lineno) + } + if i < len(expectedFrames)-1 { + if len(current.Children) != 1 { + t.Errorf("Expected 1 child at common path depth %d, got %d", i, len(current.Children)) + return + } + current = current.Children[0] + } + } + + // At last common node (867979), verify branching + fmt.Printf("\nVerifying branching at 867979 node:\n") + fmt.Printf("Current frame: {%s %s}\n", current.Frame.FileID, current.Frame.Lineno) + fmt.Printf("Has %d children\n", len(current.Children)) + + if len(current.Children) != 2 { + t.Errorf("Expected 2 branches at node 867979, got %d", len(current.Children)) + return + } + + // Verify both branch endpoints exist + foundBranch1 := false + foundBranch2 := false + for _, child := range current.Children { + fmt.Printf("Checking child: {%s %s}\n", child.Frame.FileID, child.Frame.Lineno) + if framesEqual(child.Frame, v1beta1.StackFrame{FileID: "4298936378959959569", Lineno: "390324"}) { + foundBranch1 = true + } + if framesEqual(child.Frame, v1beta1.StackFrame{FileID: "0", Lineno: "4012"}) { + foundBranch2 = true + } + } + + if !foundBranch1 { + t.Error("Missing branch with 390324") + } + if !foundBranch2 { + t.Error("Missing branch with 4012") + } +} + +func printBiNode(node *BidirectionalNode, depth int) { + indent := strings.Repeat(" ", depth) + fmt.Printf("%s{%s %s} (children: %d)", + indent, node.Frame.FileID, node.Frame.Lineno, len(node.Children)) + if node.Parent != nil { + fmt.Printf(" [parent: {%s %s}]", + node.Parent.Frame.FileID, node.Parent.Frame.Lineno) + } + fmt.Println() + for _, child := range node.Children { + printBiNode(child, depth+1) + } +} + +func TestCallStackSearchTreeBasic(t *testing.T) { + tree := NewCallStackSearchTree() + + // Test with simple linear path + simpleStack := buildUnifiedStack("test1", [][]struct{ fileID, lineno string }{ + { + {"1", "1"}, + {"2", "1"}, + {"3", "1"}, + }, + }) + + tree.AddCallStack(simpleStack) + + // Verify path storage + paths := tree.pathsByCallID["test1"] + if len(paths) != 1 { + t.Errorf("Expected 1 path, got %d", len(paths)) + } + + // Verify trie storage + expectedKey := "1:1/2:1/3:1" + value := tree.forwardTrie.Get(expectedKey) + if value == nil { + t.Error("Path not found in trie") + } + + // Verify bidirectional tree + root := tree.roots["test1"] + if root == nil { + t.Fatal("Root node not found") + } + + current := root + expectedFrames := []v1beta1.StackFrame{ + {FileID: "1", Lineno: "1"}, + {FileID: "2", Lineno: "1"}, + {FileID: "3", Lineno: "1"}, + } + + for i, expected := range expectedFrames { + if !framesEqual(current.Frame, expected) { + t.Errorf("At depth %d, expected frame %v, got %v", i, expected, current.Frame) + } + if i < len(expectedFrames)-1 { + if len(current.Children) != 1 { + t.Errorf("Expected 1 child at depth %d, got %d", i, len(current.Children)) + break + } + current = current.Children[0] + } + } +} + +func TestCallStackSearchTreeWithSpecialFrame(t *testing.T) { + tree := NewCallStackSearchTree() + + // Test with the [0:4012] case from the real data + stack := buildUnifiedStack("test1", [][]struct{ fileID, lineno string }{ + { + {"10425069705252389217", "645761"}, + {"10425069705252389217", "653231"}, + {"2918313636494991837", "867979"}, + {"0", "4012"}, + }, + }) + + tree.AddCallStack(stack) + + // Verify the path is stored correctly + paths := tree.pathsByCallID["test1"] + if len(paths) != 1 { + t.Errorf("Expected 1 path, got %d", len(paths)) + } + + // Verify path with special frame exists in trie + expectedKey := "10425069705252389217:645761/10425069705252389217:653231/2918313636494991837:867979/0:4012" + if value := tree.forwardTrie.Get(expectedKey); value == nil { + t.Error("Special frame path not found in trie") + } + + // Verify the node structure + root := tree.roots["test1"] + if root == nil { + t.Fatal("Root node not found") + } + + // Navigate to the special frame + current := root + frames := []struct{ fileID, lineno string }{ + {"10425069705252389217", "645761"}, + {"10425069705252389217", "653231"}, + {"2918313636494991837", "867979"}, + {"0", "4012"}, + } + + for i, expectedFrame := range frames { + if !framesEqual(current.Frame, v1beta1.StackFrame{ + FileID: expectedFrame.fileID, + Lineno: expectedFrame.lineno, + }) { + t.Errorf("Unexpected frame at depth %d", i) + } + + if i < len(frames)-1 { + if len(current.Children) != 1 { + t.Errorf("Expected single child at depth %d, got %d", i, len(current.Children)) + return + } + current = current.Children[0] + } + } + + // Verify parent references + for current.Parent != nil { + current = current.Parent + } + if current != root { + t.Error("Parent chain doesn't lead back to root") + } +} + +func TestCallStackSearchTreeEmpty(t *testing.T) { + tree := NewCallStackSearchTree() + + // Test with empty stack + emptyStack := buildUnifiedStack("test1", [][]struct{ fileID, lineno string }{{}}) + tree.AddCallStack(emptyStack) + + // Verify empty path handling + paths := tree.pathsByCallID["test1"] + if len(paths) != 0 { + t.Errorf("Expected 0 paths for empty stack, got %d", len(paths)) + } + + // Verify root handling + if root := tree.roots["test1"]; root != nil { + t.Error("Expected nil root for empty stack") + } +} + +func TestCallStackSearchTreeMultipleCallIDs(t *testing.T) { + tree := NewCallStackSearchTree() + + // Add stacks with different CallIDs + stack1 := buildUnifiedStack("id1", [][]struct{ fileID, lineno string }{ + { + {"1", "1"}, + {"2", "1"}, + }, + }) + stack2 := buildUnifiedStack("id2", [][]struct{ fileID, lineno string }{ + { + {"1", "1"}, + {"2", "2"}, + }, + }) + + tree.AddCallStack(stack1) + tree.AddCallStack(stack2) + + // Verify separate storage by CallID + if len(tree.pathsByCallID) != 2 { + t.Errorf("Expected 2 CallIDs, got %d", len(tree.pathsByCallID)) + } + + // Verify paths for each CallID + paths1 := tree.pathsByCallID["id1"] + paths2 := tree.pathsByCallID["id2"] + if len(paths1) != 1 || len(paths2) != 1 { + t.Error("Wrong number of paths per CallID") + } + + // Verify separate root nodes with correct structures + root1 := tree.roots["id1"] + root2 := tree.roots["id2"] + if root1 == nil || root2 == nil { + t.Fatal("Missing root nodes") + } + + if !framesEqual(root1.Children[0].Frame, v1beta1.StackFrame{FileID: "2", Lineno: "1"}) { + t.Error("Incorrect frame in first stack") + } + if !framesEqual(root2.Children[0].Frame, v1beta1.StackFrame{FileID: "2", Lineno: "2"}) { + t.Error("Incorrect frame in second stack") + } +} From b950c35dc7eefb01d8a3a55fa27a727f694f3a73 Mon Sep 17 00:00:00 2001 From: Amit Schendel Date: Thu, 30 Jan 2025 12:12:26 +0000 Subject: [PATCH 15/40] Adding get search tree option Signed-off-by: Amit Schendel --- .../applicationprofilecache/applicationprofilecache.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pkg/objectcache/applicationprofilecache/applicationprofilecache.go b/pkg/objectcache/applicationprofilecache/applicationprofilecache.go index 45aae2b1..61043166 100644 --- a/pkg/objectcache/applicationprofilecache/applicationprofilecache.go +++ b/pkg/objectcache/applicationprofilecache/applicationprofilecache.go @@ -206,6 +206,13 @@ func (ap *ApplicationProfileCacheImpl) GetApplicationProfile(containerID string) return nil } +func (ap *ApplicationProfileCacheImpl) GetCallStackSearchTree(containerID string) *CallStackSearchTree { + if index := ap.containerCallStacks.Get(containerID); index != nil { + return index.searchTree + } + return nil +} + // ------------------ watcher.Adaptor methods ----------------------- // ------------------ watcher.WatchResources methods ----------------------- From bb9ebbfe68e0eec67603e9b52c86000194b83d82 Mon Sep 17 00:00:00 2001 From: Amit Schendel Date: Thu, 30 Jan 2025 12:46:40 +0000 Subject: [PATCH 16/40] Public attrs Signed-off-by: Amit Schendel --- .../applicationprofilecache/callstackcache.go | 24 +++++++------- .../callstackcache_test.go | 32 +++++++++---------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/pkg/objectcache/applicationprofilecache/callstackcache.go b/pkg/objectcache/applicationprofilecache/callstackcache.go index e384f905..f4ffce85 100644 --- a/pkg/objectcache/applicationprofilecache/callstackcache.go +++ b/pkg/objectcache/applicationprofilecache/callstackcache.go @@ -17,18 +17,18 @@ type BidirectionalNode struct { // CallStackSearchTree provides efficient searching and comparison of call stacks type CallStackSearchTree struct { // Store forward paths in trie for prefix matching - forwardTrie *trie.PathTrie + ForwardTrie *trie.PathTrie // Store complete paths mapped by CallID for quick lookup - pathsByCallID map[v1beta1.CallID][][]v1beta1.StackFrame + PathsByCallID map[v1beta1.CallID][][]v1beta1.StackFrame // Store root nodes for bidirectional traversal - roots map[v1beta1.CallID]*BidirectionalNode + Roots map[v1beta1.CallID]*BidirectionalNode } func NewCallStackSearchTree() *CallStackSearchTree { return &CallStackSearchTree{ - forwardTrie: trie.NewPathTrie(), - pathsByCallID: make(map[v1beta1.CallID][][]v1beta1.StackFrame), - roots: make(map[v1beta1.CallID]*BidirectionalNode), + ForwardTrie: trie.NewPathTrie(), + PathsByCallID: make(map[v1beta1.CallID][][]v1beta1.StackFrame), + Roots: make(map[v1beta1.CallID]*BidirectionalNode), } } @@ -48,15 +48,15 @@ func (t *CallStackSearchTree) AddCallStack(stack v1beta1.IdentifiedCallStack) { } root := t.buildBidirectionalTree(firstFrames) - t.roots[stack.CallID] = root + t.Roots[stack.CallID] = root // Store paths for this CallID - t.pathsByCallID[stack.CallID] = make([][]v1beta1.StackFrame, 0) - t.pathsByCallID[stack.CallID] = append(t.pathsByCallID[stack.CallID], firstFrames) + t.PathsByCallID[stack.CallID] = make([][]v1beta1.StackFrame, 0) + t.PathsByCallID[stack.CallID] = append(t.PathsByCallID[stack.CallID], firstFrames) // Add first path to trie key := pathToKey(firstFrames) - t.forwardTrie.Put(key, firstFrames) + t.ForwardTrie.Put(key, firstFrames) // Process remaining paths for i := 1; i < len(paths); i++ { @@ -67,11 +67,11 @@ func (t *CallStackSearchTree) AddCallStack(stack v1beta1.IdentifiedCallStack) { } // Add to paths by CallID - t.pathsByCallID[stack.CallID] = append(t.pathsByCallID[stack.CallID], frames) + t.PathsByCallID[stack.CallID] = append(t.PathsByCallID[stack.CallID], frames) // Add to trie key = pathToKey(frames) - t.forwardTrie.Put(key, frames) + t.ForwardTrie.Put(key, frames) // Merge into bidirectional tree current := root diff --git a/pkg/objectcache/applicationprofilecache/callstackcache_test.go b/pkg/objectcache/applicationprofilecache/callstackcache_test.go index d4c1c46e..1f1c0cba 100644 --- a/pkg/objectcache/applicationprofilecache/callstackcache_test.go +++ b/pkg/objectcache/applicationprofilecache/callstackcache_test.go @@ -127,12 +127,12 @@ func TestCallStackSearchTreeBranching(t *testing.T) { // Debug print the stored tree before verification fmt.Println("\nStored tree before verification:") - printBiNode(tree.roots["test1"], 0) + printBiNode(tree.Roots["test1"], 0) fmt.Println("\nStarting verification:") // Start verification - root := tree.roots["test1"] + root := tree.Roots["test1"] fmt.Printf("Root frame: {%s %s}\n", root.Frame.FileID, root.Frame.Lineno) current := root @@ -222,20 +222,20 @@ func TestCallStackSearchTreeBasic(t *testing.T) { tree.AddCallStack(simpleStack) // Verify path storage - paths := tree.pathsByCallID["test1"] + paths := tree.PathsByCallID["test1"] if len(paths) != 1 { t.Errorf("Expected 1 path, got %d", len(paths)) } // Verify trie storage expectedKey := "1:1/2:1/3:1" - value := tree.forwardTrie.Get(expectedKey) + value := tree.ForwardTrie.Get(expectedKey) if value == nil { t.Error("Path not found in trie") } // Verify bidirectional tree - root := tree.roots["test1"] + root := tree.Roots["test1"] if root == nil { t.Fatal("Root node not found") } @@ -277,19 +277,19 @@ func TestCallStackSearchTreeWithSpecialFrame(t *testing.T) { tree.AddCallStack(stack) // Verify the path is stored correctly - paths := tree.pathsByCallID["test1"] + paths := tree.PathsByCallID["test1"] if len(paths) != 1 { t.Errorf("Expected 1 path, got %d", len(paths)) } // Verify path with special frame exists in trie expectedKey := "10425069705252389217:645761/10425069705252389217:653231/2918313636494991837:867979/0:4012" - if value := tree.forwardTrie.Get(expectedKey); value == nil { + if value := tree.ForwardTrie.Get(expectedKey); value == nil { t.Error("Special frame path not found in trie") } // Verify the node structure - root := tree.roots["test1"] + root := tree.Roots["test1"] if root == nil { t.Fatal("Root node not found") } @@ -337,13 +337,13 @@ func TestCallStackSearchTreeEmpty(t *testing.T) { tree.AddCallStack(emptyStack) // Verify empty path handling - paths := tree.pathsByCallID["test1"] + paths := tree.PathsByCallID["test1"] if len(paths) != 0 { t.Errorf("Expected 0 paths for empty stack, got %d", len(paths)) } // Verify root handling - if root := tree.roots["test1"]; root != nil { + if root := tree.Roots["test1"]; root != nil { t.Error("Expected nil root for empty stack") } } @@ -369,20 +369,20 @@ func TestCallStackSearchTreeMultipleCallIDs(t *testing.T) { tree.AddCallStack(stack2) // Verify separate storage by CallID - if len(tree.pathsByCallID) != 2 { - t.Errorf("Expected 2 CallIDs, got %d", len(tree.pathsByCallID)) + if len(tree.PathsByCallID) != 2 { + t.Errorf("Expected 2 CallIDs, got %d", len(tree.PathsByCallID)) } // Verify paths for each CallID - paths1 := tree.pathsByCallID["id1"] - paths2 := tree.pathsByCallID["id2"] + paths1 := tree.PathsByCallID["id1"] + paths2 := tree.PathsByCallID["id2"] if len(paths1) != 1 || len(paths2) != 1 { t.Error("Wrong number of paths per CallID") } // Verify separate root nodes with correct structures - root1 := tree.roots["id1"] - root2 := tree.roots["id2"] + root1 := tree.Roots["id1"] + root2 := tree.Roots["id2"] if root1 == nil || root2 == nil { t.Fatal("Missing root nodes") } From 7090d0dd10c08fcc7b0d7cea4dd018eafae97422 Mon Sep 17 00:00:00 2001 From: Amit Schendel Date: Thu, 30 Jan 2025 14:27:39 +0000 Subject: [PATCH 17/40] Putting callstack in a cache pkg Signed-off-by: Amit Schendel --- .../applicationprofilecache/applicationprofilecache.go | 7 ++++--- .../{ => callstackcache}/callstackcache.go | 2 +- .../{ => callstackcache}/callstackcache_test.go | 2 +- pkg/objectcache/applicationprofilecache_interface.go | 6 ++++++ pkg/ruleengine/v1/mock.go | 4 ++++ 5 files changed, 16 insertions(+), 5 deletions(-) rename pkg/objectcache/applicationprofilecache/{ => callstackcache}/callstackcache.go (99%) rename pkg/objectcache/applicationprofilecache/{ => callstackcache}/callstackcache_test.go (99%) diff --git a/pkg/objectcache/applicationprofilecache/applicationprofilecache.go b/pkg/objectcache/applicationprofilecache/applicationprofilecache.go index 61043166..4165c334 100644 --- a/pkg/objectcache/applicationprofilecache/applicationprofilecache.go +++ b/pkg/objectcache/applicationprofilecache/applicationprofilecache.go @@ -14,6 +14,7 @@ import ( helpersv1 "github.com/kubescape/k8s-interface/instanceidhandler/v1/helpers" "github.com/kubescape/k8s-interface/workloadinterface" "github.com/kubescape/node-agent/pkg/objectcache" + "github.com/kubescape/node-agent/pkg/objectcache/applicationprofilecache/callstackcache" "github.com/kubescape/node-agent/pkg/utils" "github.com/kubescape/node-agent/pkg/watcher" "github.com/kubescape/storage/pkg/apis/softwarecomposition/v1beta1" @@ -49,7 +50,7 @@ func newApplicationProfileState(ap *v1beta1.ApplicationProfile) applicationProfi // ContainerCallStackIndex maintains call stack search trees for a container type ContainerCallStackIndex struct { - searchTree *CallStackSearchTree + searchTree *callstackcache.CallStackSearchTree } type ApplicationProfileCacheImpl struct { @@ -126,7 +127,7 @@ func (ap *ApplicationProfileCacheImpl) indexContainerCallStacks(containerID stri // Initialize container index if needed if !ap.containerCallStacks.Has(containerID) { ap.containerCallStacks.Set(containerID, &ContainerCallStackIndex{ - searchTree: NewCallStackSearchTree(), + searchTree: callstackcache.NewCallStackSearchTree(), }) } @@ -206,7 +207,7 @@ func (ap *ApplicationProfileCacheImpl) GetApplicationProfile(containerID string) return nil } -func (ap *ApplicationProfileCacheImpl) GetCallStackSearchTree(containerID string) *CallStackSearchTree { +func (ap *ApplicationProfileCacheImpl) GetCallStackSearchTree(containerID string) *callstackcache.CallStackSearchTree { if index := ap.containerCallStacks.Get(containerID); index != nil { return index.searchTree } diff --git a/pkg/objectcache/applicationprofilecache/callstackcache.go b/pkg/objectcache/applicationprofilecache/callstackcache/callstackcache.go similarity index 99% rename from pkg/objectcache/applicationprofilecache/callstackcache.go rename to pkg/objectcache/applicationprofilecache/callstackcache/callstackcache.go index f4ffce85..8afcae3d 100644 --- a/pkg/objectcache/applicationprofilecache/callstackcache.go +++ b/pkg/objectcache/applicationprofilecache/callstackcache/callstackcache.go @@ -1,4 +1,4 @@ -package applicationprofilecache +package callstackcache import ( "strings" diff --git a/pkg/objectcache/applicationprofilecache/callstackcache_test.go b/pkg/objectcache/applicationprofilecache/callstackcache/callstackcache_test.go similarity index 99% rename from pkg/objectcache/applicationprofilecache/callstackcache_test.go rename to pkg/objectcache/applicationprofilecache/callstackcache/callstackcache_test.go index 1f1c0cba..85f72683 100644 --- a/pkg/objectcache/applicationprofilecache/callstackcache_test.go +++ b/pkg/objectcache/applicationprofilecache/callstackcache/callstackcache_test.go @@ -1,4 +1,4 @@ -package applicationprofilecache +package callstackcache import ( "fmt" diff --git a/pkg/objectcache/applicationprofilecache_interface.go b/pkg/objectcache/applicationprofilecache_interface.go index f8d24004..f4fdc34b 100644 --- a/pkg/objectcache/applicationprofilecache_interface.go +++ b/pkg/objectcache/applicationprofilecache_interface.go @@ -3,6 +3,7 @@ package objectcache import ( "context" + "github.com/kubescape/node-agent/pkg/objectcache/applicationprofilecache/callstackcache" "github.com/kubescape/node-agent/pkg/watcher" "github.com/kubescape/storage/pkg/apis/softwarecomposition/v1beta1" "k8s.io/apimachinery/pkg/runtime" @@ -10,6 +11,7 @@ import ( type ApplicationProfileCache interface { GetApplicationProfile(containerID string) *v1beta1.ApplicationProfile + GetCallStackSearchTree(containerID string) *callstackcache.CallStackSearchTree WatchResources() []watcher.WatchResource AddHandler(ctx context.Context, obj runtime.Object) ModifyHandler(ctx context.Context, obj runtime.Object) @@ -25,6 +27,10 @@ func (ap *ApplicationProfileCacheMock) GetApplicationProfile(_ string) *v1beta1. return nil } +func (ap *ApplicationProfileCacheMock) GetCallStackSearchTree(_ string) *callstackcache.CallStackSearchTree { + return nil +} + func (ap *ApplicationProfileCacheMock) WatchResources() []watcher.WatchResource { return nil } diff --git a/pkg/ruleengine/v1/mock.go b/pkg/ruleengine/v1/mock.go index f0bc5bb6..6b0559dc 100644 --- a/pkg/ruleengine/v1/mock.go +++ b/pkg/ruleengine/v1/mock.go @@ -30,6 +30,10 @@ func (r *RuleObjectCacheMock) GetApplicationProfile(string) *v1beta1.Application return r.profile } +func (r *RuleObjectCacheMock) GetCallStackSearchTree(string) *v1beta1.CallStackSearchTree { + return nil +} + func (r *RuleObjectCacheMock) SetApplicationProfile(profile *v1beta1.ApplicationProfile) { r.profile = profile } From 5364b35836f53800982dd3d82d0810b475950682 Mon Sep 17 00:00:00 2001 From: Amit Schendel Date: Thu, 30 Jan 2025 15:39:23 +0000 Subject: [PATCH 18/40] Adding unsaved file Signed-off-by: Amit Schendel --- pkg/ruleengine/v1/mock.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/ruleengine/v1/mock.go b/pkg/ruleengine/v1/mock.go index 6b0559dc..3dd11b51 100644 --- a/pkg/ruleengine/v1/mock.go +++ b/pkg/ruleengine/v1/mock.go @@ -5,6 +5,7 @@ import ( "github.com/goradd/maps" "github.com/kubescape/node-agent/pkg/objectcache" + "github.com/kubescape/node-agent/pkg/objectcache/applicationprofilecache/callstackcache" "github.com/kubescape/node-agent/pkg/utils" "github.com/kubescape/node-agent/pkg/watcher" "github.com/kubescape/storage/pkg/apis/softwarecomposition/v1beta1" @@ -30,7 +31,7 @@ func (r *RuleObjectCacheMock) GetApplicationProfile(string) *v1beta1.Application return r.profile } -func (r *RuleObjectCacheMock) GetCallStackSearchTree(string) *v1beta1.CallStackSearchTree { +func (r *RuleObjectCacheMock) GetCallStackSearchTree(string) *callstackcache.CallStackSearchTree { return nil } From e8ea0876239d5a387ca9d4f8bcc7043d1f6a9089 Mon Sep 17 00:00:00 2001 From: Amit Schendel Date: Sun, 2 Feb 2025 09:46:21 +0000 Subject: [PATCH 19/40] Fixing cache search Signed-off-by: Amit Schendel --- .../applicationprofilecache.go | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/pkg/objectcache/applicationprofilecache/applicationprofilecache.go b/pkg/objectcache/applicationprofilecache/applicationprofilecache.go index 4165c334..d033d380 100644 --- a/pkg/objectcache/applicationprofilecache/applicationprofilecache.go +++ b/pkg/objectcache/applicationprofilecache/applicationprofilecache.go @@ -64,6 +64,7 @@ type ApplicationProfileCacheImpl struct { maxDelaySeconds int // maximum delay in seconds before getting the full object from the storage userManagedProfiles maps.SafeMap[string, *v1beta1.ApplicationProfile] containerCallStacks maps.SafeMap[string, *ContainerCallStackIndex] // cache the containerID to call stack search tree mapping + containerToName maps.SafeMap[string, string] // cache the containerID to container name mapping } func NewApplicationProfileCache(nodeName string, storageClient versioned.SpdxV1beta1Interface, maxDelaySeconds int) *ApplicationProfileCacheImpl { @@ -78,6 +79,7 @@ func NewApplicationProfileCache(nodeName string, storageClient versioned.SpdxV1b allProfiles: mapset.NewSet[string](), userManagedProfiles: maps.SafeMap[string, *v1beta1.ApplicationProfile]{}, containerCallStacks: maps.SafeMap[string, *ContainerCallStackIndex]{}, + containerToName: maps.SafeMap[string, string]{}, } } @@ -123,7 +125,7 @@ func (ap *ApplicationProfileCacheImpl) handleUserManagedProfile(appProfile *v1be } // indexContainerCallStacks builds the search index for a container's call stacks and removes them from the profile -func (ap *ApplicationProfileCacheImpl) indexContainerCallStacks(containerID string, appProfile *v1beta1.ApplicationProfile) { +func (ap *ApplicationProfileCacheImpl) indexContainerCallStacks(containerID, containerName string, appProfile *v1beta1.ApplicationProfile) { // Initialize container index if needed if !ap.containerCallStacks.Has(containerID) { ap.containerCallStacks.Set(containerID, &ContainerCallStackIndex{ @@ -135,7 +137,7 @@ func (ap *ApplicationProfileCacheImpl) indexContainerCallStacks(containerID stri // Find the container in the profile and index its call stacks for i := range appProfile.Spec.Containers { - if appProfile.Spec.Containers[i].ImageID == containerID { + if appProfile.Spec.Containers[i].Name == containerName { // Index all call stacks for _, stack := range appProfile.Spec.Containers[i].IdentifiedCallStacks { index.searchTree.AddCallStack(stack) @@ -149,7 +151,7 @@ func (ap *ApplicationProfileCacheImpl) indexContainerCallStacks(containerID stri // Also check init containers for i := range appProfile.Spec.InitContainers { - if appProfile.Spec.InitContainers[i].ImageID == containerID { + if appProfile.Spec.InitContainers[i].Name == containerName { for _, stack := range appProfile.Spec.InitContainers[i].IdentifiedCallStacks { index.searchTree.AddCallStack(stack) } @@ -160,7 +162,7 @@ func (ap *ApplicationProfileCacheImpl) indexContainerCallStacks(containerID stri // And ephemeral containers for i := range appProfile.Spec.EphemeralContainers { - if appProfile.Spec.EphemeralContainers[i].ImageID == containerID { + if appProfile.Spec.EphemeralContainers[i].Name == containerName { for _, stack := range appProfile.Spec.EphemeralContainers[i].IdentifiedCallStacks { index.searchTree.AddCallStack(stack) } @@ -303,6 +305,7 @@ func (ap *ApplicationProfileCacheImpl) addPod(obj runtime.Object) { continue } ap.containerToSlug.Set(container, uniqueSlug) + ap.initContainerIdToName(pod) // if application profile exists but is not cached if ap.allProfiles.Contains(uniqueSlug) && !ap.slugToAppProfile.Has(uniqueSlug) { @@ -321,6 +324,19 @@ func (ap *ApplicationProfileCacheImpl) addPod(obj runtime.Object) { } +func (ap *ApplicationProfileCacheImpl) initContainerIdToName(pod *corev1.Pod) { + for i, container := range pod.Spec.Containers { + ap.containerToName.Set(utils.TrimRuntimePrefix(pod.Status.ContainerStatuses[i].ContainerID), container.Name) + } + for i, container := range pod.Spec.InitContainers { + ap.containerToName.Set(utils.TrimRuntimePrefix(pod.Status.InitContainerStatuses[i].ContainerID), container.Name) + } + for i, container := range pod.Spec.EphemeralContainers { + ap.containerToName.Set(utils.TrimRuntimePrefix(pod.Status.EphemeralContainerStatuses[i].ContainerID), container.Name) + } + +} + func (ap *ApplicationProfileCacheImpl) deletePod(obj runtime.Object) { pod := obj.(*corev1.Pod) @@ -335,6 +351,7 @@ func (ap *ApplicationProfileCacheImpl) removeContainer(containerID string) { uniqueSlug := ap.containerToSlug.Get(containerID) ap.containerToSlug.Delete(containerID) ap.containerCallStacks.Delete(containerID) + ap.containerToName.Delete(containerID) // remove pod form the application profile mapping if ap.slugToContainers.Has(uniqueSlug) { @@ -370,7 +387,7 @@ func (ap *ApplicationProfileCacheImpl) addFullApplicationProfile(appProfile *v1b ap.slugToAppProfile.Set(apName, fullAP) for _, i := range ap.slugToContainers.Get(apName).ToSlice() { ap.containerToSlug.Set(i, apName) - ap.indexContainerCallStacks(i, fullAP) + ap.indexContainerCallStacks(i, ap.containerToName.Get(i), fullAP) } logger.L().Debug("ApplicationProfileCacheImpl - added pod to application profile cache", helpers.String("name", apName)) } From 00a87e8f8abe107ec592cd70ee64bce0652319d4 Mon Sep 17 00:00:00 2001 From: Amit Schendel Date: Sun, 2 Feb 2025 11:55:20 +0000 Subject: [PATCH 20/40] Adding logs Signed-off-by: Amit Schendel --- .../applicationprofilecache/applicationprofilecache.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pkg/objectcache/applicationprofilecache/applicationprofilecache.go b/pkg/objectcache/applicationprofilecache/applicationprofilecache.go index d033d380..3e6ffc4f 100644 --- a/pkg/objectcache/applicationprofilecache/applicationprofilecache.go +++ b/pkg/objectcache/applicationprofilecache/applicationprofilecache.go @@ -140,6 +140,7 @@ func (ap *ApplicationProfileCacheImpl) indexContainerCallStacks(containerID, con if appProfile.Spec.Containers[i].Name == containerName { // Index all call stacks for _, stack := range appProfile.Spec.Containers[i].IdentifiedCallStacks { + logger.L().Info("ApplicationProfileCacheImpl - indexing call stack", helpers.String("containerID", containerID), helpers.String("containerName", containerName), helpers.String("CallID", string(stack.CallID))) index.searchTree.AddCallStack(stack) } @@ -170,6 +171,12 @@ func (ap *ApplicationProfileCacheImpl) indexContainerCallStacks(containerID, con break } } + + // Print the indexed call stacks + logger.L().Info("ApplicationProfileCacheImpl - indexed call stacks for container", + helpers.String("containerID", containerID), + helpers.String("containerName", containerName), + helpers.Int("callStackCount", len(index.searchTree.Roots))) } func (ap *ApplicationProfileCacheImpl) addApplicationProfile(obj runtime.Object) { @@ -210,7 +217,9 @@ func (ap *ApplicationProfileCacheImpl) GetApplicationProfile(containerID string) } func (ap *ApplicationProfileCacheImpl) GetCallStackSearchTree(containerID string) *callstackcache.CallStackSearchTree { + logger.L().Info("ApplicationProfileCacheImpl - getting call stack search tree", helpers.String("containerID", containerID)) if index := ap.containerCallStacks.Get(containerID); index != nil { + logger.L().Info("ApplicationProfileCacheImpl - found call stack search tree", helpers.String("containerID", containerID)) return index.searchTree } return nil From 67e3a09f40755c6fa6530741ed6bb759130c9e64 Mon Sep 17 00:00:00 2001 From: Amit Schendel Date: Sun, 2 Feb 2025 12:05:07 +0000 Subject: [PATCH 21/40] Adding logs Signed-off-by: Amit Schendel --- .../applicationprofilecache/applicationprofilecache.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/objectcache/applicationprofilecache/applicationprofilecache.go b/pkg/objectcache/applicationprofilecache/applicationprofilecache.go index 3e6ffc4f..3b05135f 100644 --- a/pkg/objectcache/applicationprofilecache/applicationprofilecache.go +++ b/pkg/objectcache/applicationprofilecache/applicationprofilecache.go @@ -126,6 +126,7 @@ func (ap *ApplicationProfileCacheImpl) handleUserManagedProfile(appProfile *v1be // indexContainerCallStacks builds the search index for a container's call stacks and removes them from the profile func (ap *ApplicationProfileCacheImpl) indexContainerCallStacks(containerID, containerName string, appProfile *v1beta1.ApplicationProfile) { + logger.L().Info("ApplicationProfileCacheImpl - indexing call stacks for container", helpers.String("containerID", containerID), helpers.String("containerName", containerName)) // Initialize container index if needed if !ap.containerCallStacks.Has(containerID) { ap.containerCallStacks.Set(containerID, &ContainerCallStackIndex{ From 3a778d06449b1c459d4565b8c572034523c7f4f5 Mon Sep 17 00:00:00 2001 From: Amit Schendel Date: Sun, 2 Feb 2025 12:30:08 +0000 Subject: [PATCH 22/40] Removing logs Signed-off-by: Amit Schendel --- .../applicationprofilecache/applicationprofilecache.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/pkg/objectcache/applicationprofilecache/applicationprofilecache.go b/pkg/objectcache/applicationprofilecache/applicationprofilecache.go index 3b05135f..da2d0f46 100644 --- a/pkg/objectcache/applicationprofilecache/applicationprofilecache.go +++ b/pkg/objectcache/applicationprofilecache/applicationprofilecache.go @@ -172,12 +172,6 @@ func (ap *ApplicationProfileCacheImpl) indexContainerCallStacks(containerID, con break } } - - // Print the indexed call stacks - logger.L().Info("ApplicationProfileCacheImpl - indexed call stacks for container", - helpers.String("containerID", containerID), - helpers.String("containerName", containerName), - helpers.Int("callStackCount", len(index.searchTree.Roots))) } func (ap *ApplicationProfileCacheImpl) addApplicationProfile(obj runtime.Object) { @@ -218,9 +212,7 @@ func (ap *ApplicationProfileCacheImpl) GetApplicationProfile(containerID string) } func (ap *ApplicationProfileCacheImpl) GetCallStackSearchTree(containerID string) *callstackcache.CallStackSearchTree { - logger.L().Info("ApplicationProfileCacheImpl - getting call stack search tree", helpers.String("containerID", containerID)) if index := ap.containerCallStacks.Get(containerID); index != nil { - logger.L().Info("ApplicationProfileCacheImpl - found call stack search tree", helpers.String("containerID", containerID)) return index.searchTree } return nil From 7fa19ff542e0d793bca3f1b97f574abb60ab087d Mon Sep 17 00:00:00 2001 From: Amit Schendel Date: Sun, 2 Feb 2025 13:21:33 +0000 Subject: [PATCH 23/40] Adding retry Signed-off-by: Amit Schendel --- .../applicationprofilecache.go | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/pkg/objectcache/applicationprofilecache/applicationprofilecache.go b/pkg/objectcache/applicationprofilecache/applicationprofilecache.go index da2d0f46..c3fdc984 100644 --- a/pkg/objectcache/applicationprofilecache/applicationprofilecache.go +++ b/pkg/objectcache/applicationprofilecache/applicationprofilecache.go @@ -6,6 +6,7 @@ import ( "strings" "time" + "github.com/cenkalti/backoff" mapset "github.com/deckarep/golang-set/v2" "github.com/goradd/maps" "github.com/kubescape/go-logger" @@ -197,11 +198,15 @@ func (ap *ApplicationProfileCacheImpl) addApplicationProfile(obj runtime.Object) ap.allProfiles.Add(apName) - if ap.slugToContainers.Has(apName) { - time.AfterFunc(utils.RandomDuration(ap.maxDelaySeconds, time.Second), func() { - ap.addFullApplicationProfile(appProfile, apName) - }) - } + backoff.Retry(func() error { + if ap.slugToContainers.Has(apName) { + time.AfterFunc(utils.RandomDuration(ap.maxDelaySeconds, time.Second), func() { + ap.addFullApplicationProfile(appProfile, apName) + }) + } + logger.L().Info("ApplicationProfileCacheImpl - retrying to add full application profile", helpers.String("name", apName)) + return fmt.Errorf("retry") + }, backoff.NewExponentialBackOff()) } func (ap *ApplicationProfileCacheImpl) GetApplicationProfile(containerID string) *v1beta1.ApplicationProfile { From 55e73cce1396fd0cff17ae5bf3d4aa562e9354c8 Mon Sep 17 00:00:00 2001 From: Amit Schendel Date: Sun, 2 Feb 2025 13:28:06 +0000 Subject: [PATCH 24/40] Fixing backoff Signed-off-by: Amit Schendel --- .../applicationprofilecache/applicationprofilecache.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/objectcache/applicationprofilecache/applicationprofilecache.go b/pkg/objectcache/applicationprofilecache/applicationprofilecache.go index c3fdc984..a4321682 100644 --- a/pkg/objectcache/applicationprofilecache/applicationprofilecache.go +++ b/pkg/objectcache/applicationprofilecache/applicationprofilecache.go @@ -203,9 +203,11 @@ func (ap *ApplicationProfileCacheImpl) addApplicationProfile(obj runtime.Object) time.AfterFunc(utils.RandomDuration(ap.maxDelaySeconds, time.Second), func() { ap.addFullApplicationProfile(appProfile, apName) }) + } else { + logger.L().Info("ApplicationProfileCacheImpl - retrying to add full application profile", helpers.String("name", apName)) + return fmt.Errorf("retry") } - logger.L().Info("ApplicationProfileCacheImpl - retrying to add full application profile", helpers.String("name", apName)) - return fmt.Errorf("retry") + return nil }, backoff.NewExponentialBackOff()) } From aaf8d01510484ddd504f77fdc27405cbfed8dac8 Mon Sep 17 00:00:00 2001 From: Amit Schendel Date: Sun, 2 Feb 2025 15:43:29 +0000 Subject: [PATCH 25/40] Enable open unwind Signed-off-by: Amit Schendel --- .../v1/applicationprofile_manager.go | 8 ++++---- .../applicationprofilecache/applicationprofilecache.go | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/pkg/applicationprofilemanager/v1/applicationprofile_manager.go b/pkg/applicationprofilemanager/v1/applicationprofile_manager.go index 78bb8437..17fe57a5 100644 --- a/pkg/applicationprofilemanager/v1/applicationprofile_manager.go +++ b/pkg/applicationprofilemanager/v1/applicationprofile_manager.go @@ -779,10 +779,10 @@ func (am *ApplicationProfileManager) ReportFileOpen(k8sContainerID string, event openMap.Set(path, mapset.NewSet[string](event.Flags...)) } - // if am.enricher != nil { - // openIdentifier := utils.CalculateSHA256FileOpenHash(path) - // go am.enricher.EnrichEvent(k8sContainerID, &event, openIdentifier) - // } + if am.enricher != nil { + openIdentifier := utils.CalculateSHA256FileOpenHash(path) + go am.enricher.EnrichEvent(k8sContainerID, &event, openIdentifier) + } } func (am *ApplicationProfileManager) ReportDroppedEvent(k8sContainerID string) { diff --git a/pkg/objectcache/applicationprofilecache/applicationprofilecache.go b/pkg/objectcache/applicationprofilecache/applicationprofilecache.go index a4321682..98df61b2 100644 --- a/pkg/objectcache/applicationprofilecache/applicationprofilecache.go +++ b/pkg/objectcache/applicationprofilecache/applicationprofilecache.go @@ -198,6 +198,7 @@ func (ap *ApplicationProfileCacheImpl) addApplicationProfile(obj runtime.Object) ap.allProfiles.Add(apName) + // TODO: FIX THIS BUG. This is a bug in the original code. backoff.Retry(func() error { if ap.slugToContainers.Has(apName) { time.AfterFunc(utils.RandomDuration(ap.maxDelaySeconds, time.Second), func() { From 15f82db269d2cb5251255d8371fd558c8fcbdd62 Mon Sep 17 00:00:00 2001 From: Amit Schendel Date: Mon, 3 Feb 2025 13:37:58 +0000 Subject: [PATCH 26/40] Moving index container to be on pod update Signed-off-by: Amit Schendel --- .../applicationprofilecache.go | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/pkg/objectcache/applicationprofilecache/applicationprofilecache.go b/pkg/objectcache/applicationprofilecache/applicationprofilecache.go index 98df61b2..56429297 100644 --- a/pkg/objectcache/applicationprofilecache/applicationprofilecache.go +++ b/pkg/objectcache/applicationprofilecache/applicationprofilecache.go @@ -6,7 +6,6 @@ import ( "strings" "time" - "github.com/cenkalti/backoff" mapset "github.com/deckarep/golang-set/v2" "github.com/goradd/maps" "github.com/kubescape/go-logger" @@ -198,18 +197,11 @@ func (ap *ApplicationProfileCacheImpl) addApplicationProfile(obj runtime.Object) ap.allProfiles.Add(apName) - // TODO: FIX THIS BUG. This is a bug in the original code. - backoff.Retry(func() error { - if ap.slugToContainers.Has(apName) { - time.AfterFunc(utils.RandomDuration(ap.maxDelaySeconds, time.Second), func() { - ap.addFullApplicationProfile(appProfile, apName) - }) - } else { - logger.L().Info("ApplicationProfileCacheImpl - retrying to add full application profile", helpers.String("name", apName)) - return fmt.Errorf("retry") - } - return nil - }, backoff.NewExponentialBackOff()) + if ap.slugToContainers.Has(apName) { + time.AfterFunc(utils.RandomDuration(ap.maxDelaySeconds, time.Second), func() { + ap.addFullApplicationProfile(appProfile, apName) + }) + } } func (ap *ApplicationProfileCacheImpl) GetApplicationProfile(containerID string) *v1beta1.ApplicationProfile { @@ -330,6 +322,7 @@ func (ap *ApplicationProfileCacheImpl) addPod(obj runtime.Object) { ap.slugToAppProfile.Set(uniqueSlug, appProfile) } + ap.indexContainerCallStacks(container, ap.containerToName.Get(container), ap.slugToAppProfile.Get(uniqueSlug)) } } @@ -397,7 +390,6 @@ func (ap *ApplicationProfileCacheImpl) addFullApplicationProfile(appProfile *v1b ap.slugToAppProfile.Set(apName, fullAP) for _, i := range ap.slugToContainers.Get(apName).ToSlice() { ap.containerToSlug.Set(i, apName) - ap.indexContainerCallStacks(i, ap.containerToName.Get(i), fullAP) } logger.L().Debug("ApplicationProfileCacheImpl - added pod to application profile cache", helpers.String("name", apName)) } From d53c06a4110a12783dc79dfcb1497a3b66e66b0f Mon Sep 17 00:00:00 2001 From: Amit Schendel Date: Tue, 4 Feb 2025 08:20:30 +0000 Subject: [PATCH 27/40] Adding symlink + hardlink enrichment Signed-off-by: Amit Schendel --- .../applicationprofile_manager_interface.go | 4 ++++ .../applicationprofile_manager_mock.go | 10 ++++++++ .../v1/applicationprofile_manager.go | 24 +++++++++++++++++++ pkg/containerwatcher/v1/container_watcher.go | 2 ++ 4 files changed, 40 insertions(+) diff --git a/pkg/applicationprofilemanager/applicationprofile_manager_interface.go b/pkg/applicationprofilemanager/applicationprofile_manager_interface.go index e9477f6b..a888c052 100644 --- a/pkg/applicationprofilemanager/applicationprofile_manager_interface.go +++ b/pkg/applicationprofilemanager/applicationprofile_manager_interface.go @@ -3,7 +3,9 @@ package applicationprofilemanager import ( containercollection "github.com/inspektor-gadget/inspektor-gadget/pkg/container-collection" "github.com/kubescape/node-agent/pkg/ebpf/events" + tracerhardlinktype "github.com/kubescape/node-agent/pkg/ebpf/gadgets/hardlink/types" tracerhttptype "github.com/kubescape/node-agent/pkg/ebpf/gadgets/http/types" + tracersymlinktype "github.com/kubescape/node-agent/pkg/ebpf/gadgets/symlink/types" "github.com/kubescape/node-agent/pkg/utils" "github.com/kubescape/storage/pkg/apis/softwarecomposition/v1beta1" ) @@ -17,6 +19,8 @@ type ApplicationProfileManagerClient interface { ReportHTTPEvent(k8sContainerID string, event *tracerhttptype.Event) ReportRulePolicy(k8sContainerID, ruleId, allowedProcess string, allowedContainer bool) ReportIdentifiedCallStack(k8sContainerID string, callStack *v1beta1.IdentifiedCallStack) + ReportSymlinkEvent(k8sContainerID string, event *tracersymlinktype.Event) + ReportHardlinkEvent(k8sContainerID string, event *tracerhardlinktype.Event) ReportDroppedEvent(k8sContainerID string) ContainerReachedMaxTime(containerID string) } diff --git a/pkg/applicationprofilemanager/applicationprofile_manager_mock.go b/pkg/applicationprofilemanager/applicationprofile_manager_mock.go index 1b42c178..68f24c76 100644 --- a/pkg/applicationprofilemanager/applicationprofile_manager_mock.go +++ b/pkg/applicationprofilemanager/applicationprofile_manager_mock.go @@ -3,7 +3,9 @@ package applicationprofilemanager import ( containercollection "github.com/inspektor-gadget/inspektor-gadget/pkg/container-collection" "github.com/kubescape/node-agent/pkg/ebpf/events" + tracerhardlinktype "github.com/kubescape/node-agent/pkg/ebpf/gadgets/hardlink/types" tracerhttptype "github.com/kubescape/node-agent/pkg/ebpf/gadgets/http/types" + tracersymlinktype "github.com/kubescape/node-agent/pkg/ebpf/gadgets/symlink/types" "github.com/kubescape/storage/pkg/apis/softwarecomposition/v1beta1" ) @@ -52,6 +54,14 @@ func (a ApplicationProfileManagerMock) ReportIdentifiedCallStack(_ string, _ *v1 // noop } +func (a ApplicationProfileManagerMock) ReportSymlinkEvent(_ string, _ *tracersymlinktype.Event) { + // noop +} + +func (a ApplicationProfileManagerMock) ReportHardlinkEvent(_ string, _ *tracerhardlinktype.Event) { + // noop +} + func (a ApplicationProfileManagerMock) ContainerReachedMaxTime(_ string) { // noop } diff --git a/pkg/applicationprofilemanager/v1/applicationprofile_manager.go b/pkg/applicationprofilemanager/v1/applicationprofile_manager.go index 17fe57a5..da68fb77 100644 --- a/pkg/applicationprofilemanager/v1/applicationprofile_manager.go +++ b/pkg/applicationprofilemanager/v1/applicationprofile_manager.go @@ -20,7 +20,9 @@ import ( "github.com/kubescape/node-agent/pkg/applicationprofilemanager" "github.com/kubescape/node-agent/pkg/config" "github.com/kubescape/node-agent/pkg/ebpf/events" + tracerhardlinktype "github.com/kubescape/node-agent/pkg/ebpf/gadgets/hardlink/types" tracerhttptype "github.com/kubescape/node-agent/pkg/ebpf/gadgets/http/types" + tracersymlinktype "github.com/kubescape/node-agent/pkg/ebpf/gadgets/symlink/types" "github.com/kubescape/node-agent/pkg/k8sclient" "github.com/kubescape/node-agent/pkg/objectcache" "github.com/kubescape/node-agent/pkg/seccompmanager" @@ -785,6 +787,28 @@ func (am *ApplicationProfileManager) ReportFileOpen(k8sContainerID string, event } } +func (am *ApplicationProfileManager) ReportSymlinkEvent(k8sContainerID string, event *tracersymlinktype.Event) { + if err := am.waitForContainer(k8sContainerID); err != nil { + return + } + + if am.enricher != nil { + symlinkIdentifier := utils.CalculateSHA256FileOpenHash(event.OldPath + event.NewPath) + go am.enricher.EnrichEvent(k8sContainerID, event, symlinkIdentifier) + } +} + +func (am *ApplicationProfileManager) ReportHardlinkEvent(k8sContainerID string, event *tracerhardlinktype.Event) { + if err := am.waitForContainer(k8sContainerID); err != nil { + return + } + + if am.enricher != nil { + hardlinkIdentifier := utils.CalculateSHA256FileOpenHash(event.OldPath + event.NewPath) + go am.enricher.EnrichEvent(k8sContainerID, event, hardlinkIdentifier) + } +} + func (am *ApplicationProfileManager) ReportDroppedEvent(k8sContainerID string) { am.droppedEventsContainers.Add(k8sContainerID) } diff --git a/pkg/containerwatcher/v1/container_watcher.go b/pkg/containerwatcher/v1/container_watcher.go index 01b07da1..c7f636cb 100644 --- a/pkg/containerwatcher/v1/container_watcher.go +++ b/pkg/containerwatcher/v1/container_watcher.go @@ -335,6 +335,7 @@ func CreateIGContainerWatcher(cfg config.Config, applicationProfileManager appli k8sContainerID := utils.CreateK8sContainerID(event.K8s.Namespace, event.K8s.PodName, event.K8s.ContainerName) metrics.ReportEvent(utils.SymlinkEventType) + applicationProfileManager.ReportSymlinkEvent(k8sContainerID, &event) ruleManager.ReportEvent(utils.SymlinkEventType, &event) rulePolicyReporter.ReportEvent(utils.SymlinkEventType, &event, k8sContainerID, event.Comm) // Report symlink events to event receivers @@ -353,6 +354,7 @@ func CreateIGContainerWatcher(cfg config.Config, applicationProfileManager appli k8sContainerID := utils.CreateK8sContainerID(event.K8s.Namespace, event.K8s.PodName, event.K8s.ContainerName) metrics.ReportEvent(utils.HardlinkEventType) + applicationProfileManager.ReportHardlinkEvent(k8sContainerID, &event) ruleManager.ReportEvent(utils.HardlinkEventType, &event) rulePolicyReporter.ReportEvent(utils.HardlinkEventType, &event, k8sContainerID, event.Comm) // Report hardlink events to event receivers From 1e567999a948ec5cac1917a95e2e35b425f068de Mon Sep 17 00:00:00 2001 From: Amit Schendel Date: Tue, 4 Feb 2025 14:01:56 +0000 Subject: [PATCH 28/40] Enrich on sensitive files Signed-off-by: Amit Schendel --- .../v1/applicationprofile_manager.go | 5 ++- .../v1/applicationprofile_manager_test.go | 32 ++++--------------- .../r0010_unexpected_sensitive_file_access.go | 31 +----------------- pkg/utils/utils.go | 27 ++++++++++++++++ 4 files changed, 38 insertions(+), 57 deletions(-) diff --git a/pkg/applicationprofilemanager/v1/applicationprofile_manager.go b/pkg/applicationprofilemanager/v1/applicationprofile_manager.go index da68fb77..14fe93fd 100644 --- a/pkg/applicationprofilemanager/v1/applicationprofile_manager.go +++ b/pkg/applicationprofilemanager/v1/applicationprofile_manager.go @@ -25,6 +25,7 @@ import ( tracersymlinktype "github.com/kubescape/node-agent/pkg/ebpf/gadgets/symlink/types" "github.com/kubescape/node-agent/pkg/k8sclient" "github.com/kubescape/node-agent/pkg/objectcache" + "github.com/kubescape/node-agent/pkg/ruleengine/v1" "github.com/kubescape/node-agent/pkg/seccompmanager" "github.com/kubescape/node-agent/pkg/storage" "github.com/kubescape/node-agent/pkg/utils" @@ -781,7 +782,9 @@ func (am *ApplicationProfileManager) ReportFileOpen(k8sContainerID string, event openMap.Set(path, mapset.NewSet[string](event.Flags...)) } - if am.enricher != nil { + isSensitive := utils.IsSensitivePath(path, ruleengine.SensitiveFiles) + + if am.enricher != nil && isSensitive { openIdentifier := utils.CalculateSHA256FileOpenHash(path) go am.enricher.EnrichEvent(k8sContainerID, &event, openIdentifier) } diff --git a/pkg/applicationprofilemanager/v1/applicationprofile_manager_test.go b/pkg/applicationprofilemanager/v1/applicationprofile_manager_test.go index 253dabec..3c84a2dd 100644 --- a/pkg/applicationprofilemanager/v1/applicationprofile_manager_test.go +++ b/pkg/applicationprofilemanager/v1/applicationprofile_manager_test.go @@ -18,7 +18,6 @@ import ( containercollection "github.com/inspektor-gadget/inspektor-gadget/pkg/container-collection" tracerexectype "github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets/trace/exec/types" traceropentype "github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets/trace/open/types" - "github.com/inspektor-gadget/inspektor-gadget/pkg/types" eventtypes "github.com/inspektor-gadget/inspektor-gadget/pkg/types" "github.com/kubescape/k8s-interface/instanceidhandler/v1" "github.com/kubescape/k8s-interface/workloadinterface" @@ -101,16 +100,16 @@ func TestApplicationProfileManager(t *testing.T) { // prepare container container := &containercollection.Container{ K8s: containercollection.K8sMetadata{ - BasicK8sMetadata: types.BasicK8sMetadata{ + BasicK8sMetadata: eventtypes.BasicK8sMetadata{ Namespace: "ns", PodName: "pod", ContainerName: "cont", }, }, Runtime: containercollection.RuntimeMetadata{ - BasicRuntimeMetadata: types.BasicRuntimeMetadata{ + BasicRuntimeMetadata: eventtypes.BasicRuntimeMetadata{ ContainerID: "5fff6a395ce4e6984a9447cc6cfb09f473eaf278498243963fcc944889bc8400", - ContainerStartedAt: types.Time(time.Now().UnixNano()), + ContainerStartedAt: eventtypes.Time(time.Now().UnixNano()), }, }, } @@ -775,25 +774,6 @@ func TestReportIdentifiedCallStack(t *testing.T) { } } -// Helper function to compare two call stack nodes and their subtrees -func compareNodes(node1, node2 v1beta1.CallStackNode) bool { - if node1.Frame.FileID != node2.Frame.FileID || node1.Frame.Lineno != node2.Frame.Lineno { - return false - } - - if len(node1.Children) != len(node2.Children) { - return false - } - - for i := range node1.Children { - if !compareNodes(node1.Children[i], node2.Children[i]) { - return false - } - } - - return true -} - // Helper function to deeply compare two call stacks func compareCallStacks(a, b v1beta1.IdentifiedCallStack) bool { if a.CallID != b.CallID { @@ -842,16 +822,16 @@ func TestApplicationProfileManagerWithCallStacks(t *testing.T) { // Prepare container container := &containercollection.Container{ K8s: containercollection.K8sMetadata{ - BasicK8sMetadata: types.BasicK8sMetadata{ + BasicK8sMetadata: eventtypes.BasicK8sMetadata{ Namespace: "ns", PodName: "pod", ContainerName: "cont", }, }, Runtime: containercollection.RuntimeMetadata{ - BasicRuntimeMetadata: types.BasicRuntimeMetadata{ + BasicRuntimeMetadata: eventtypes.BasicRuntimeMetadata{ ContainerID: "5fff6a395ce4e6984a9447cc6cfb09f473eaf278498243963fcc944889bc8400", - ContainerStartedAt: types.Time(time.Now().UnixNano()), + ContainerStartedAt: eventtypes.Time(time.Now().UnixNano()), }, }, } diff --git a/pkg/ruleengine/v1/r0010_unexpected_sensitive_file_access.go b/pkg/ruleengine/v1/r0010_unexpected_sensitive_file_access.go index 106075db..8e09d630 100644 --- a/pkg/ruleengine/v1/r0010_unexpected_sensitive_file_access.go +++ b/pkg/ruleengine/v1/r0010_unexpected_sensitive_file_access.go @@ -2,8 +2,6 @@ package ruleengine import ( "fmt" - "path/filepath" - "strings" events "github.com/kubescape/node-agent/pkg/ebpf/events" "github.com/kubescape/node-agent/pkg/objectcache" @@ -100,7 +98,7 @@ func (rule *R0010UnexpectedSensitiveFileAccess) ProcessEvent(eventType utils.Eve return nil } - if !isSensitivePath(openEvent.FullPath, rule.additionalPaths) { + if !utils.IsSensitivePath(openEvent.FullPath, rule.additionalPaths) { return nil } @@ -149,30 +147,3 @@ func (rule *R0010UnexpectedSensitiveFileAccess) Requirements() ruleengine.RuleSp EventTypes: R0010UnexpectedSensitiveFileAccessRuleDescriptor.Requirements.RequiredEventTypes(), } } - -// isSensitivePath checks if a given path matches or is within any sensitive paths -func isSensitivePath(fullPath string, paths []string) bool { - // Clean the path to handle "..", "//", etc. - fullPath = filepath.Clean(fullPath) - - for _, sensitivePath := range paths { - sensitivePath = filepath.Clean(sensitivePath) - - // Check if the path exactly matches - if fullPath == sensitivePath { - return true - } - - // Check if the path is a directory that contains sensitive files - if strings.HasPrefix(sensitivePath, fullPath+"/") { - return true - } - - // Check if the path is within a sensitive directory - if strings.HasPrefix(fullPath, sensitivePath+"/") { - return true - } - } - - return false -} diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 7d7e0896..c52fe388 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -723,3 +723,30 @@ func DiskUsage(path string) int64 { } return s } + +// IsSensitivePath checks if a given path matches or is within any sensitive paths +func IsSensitivePath(fullPath string, paths []string) bool { + // Clean the path to handle "..", "//", etc. + fullPath = filepath.Clean(fullPath) + + for _, sensitivePath := range paths { + sensitivePath = filepath.Clean(sensitivePath) + + // Check if the path exactly matches + if fullPath == sensitivePath { + return true + } + + // Check if the path is a directory that contains sensitive files + if strings.HasPrefix(sensitivePath, fullPath+"/") { + return true + } + + // Check if the path is within a sensitive directory + if strings.HasPrefix(fullPath, sensitivePath+"/") { + return true + } + } + + return false +} From 89e43330016701d043485895ed3155bd6d1fad09 Mon Sep 17 00:00:00 2001 From: Amit Schendel Date: Tue, 4 Feb 2025 18:25:16 +0000 Subject: [PATCH 29/40] Adding log to verify Signed-off-by: Amit Schendel --- pkg/applicationprofilemanager/v1/applicationprofile_manager.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/applicationprofilemanager/v1/applicationprofile_manager.go b/pkg/applicationprofilemanager/v1/applicationprofile_manager.go index 14fe93fd..61be195a 100644 --- a/pkg/applicationprofilemanager/v1/applicationprofile_manager.go +++ b/pkg/applicationprofilemanager/v1/applicationprofile_manager.go @@ -785,6 +785,7 @@ func (am *ApplicationProfileManager) ReportFileOpen(k8sContainerID string, event isSensitive := utils.IsSensitivePath(path, ruleengine.SensitiveFiles) if am.enricher != nil && isSensitive { + logger.L().Info("ApplicationProfileManager - sensitive file open event", helpers.String("path", path)) openIdentifier := utils.CalculateSHA256FileOpenHash(path) go am.enricher.EnrichEvent(k8sContainerID, &event, openIdentifier) } From c682ac1bf4d43b206dcea8dba0f4888cf34c7b1f Mon Sep 17 00:00:00 2001 From: Amit Schendel Date: Wed, 5 Feb 2025 08:38:55 +0000 Subject: [PATCH 30/40] Sending callstack on each call Signed-off-by: Amit Schendel --- .../v1/applicationprofile_manager.go | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/pkg/applicationprofilemanager/v1/applicationprofile_manager.go b/pkg/applicationprofilemanager/v1/applicationprofile_manager.go index 61be195a..3e10a001 100644 --- a/pkg/applicationprofilemanager/v1/applicationprofile_manager.go +++ b/pkg/applicationprofilemanager/v1/applicationprofile_manager.go @@ -749,15 +749,15 @@ func (am *ApplicationProfileManager) ReportFileExec(k8sContainerID string, event // check if we already have this exec // we use a SHA256 hash of the exec to identify it uniquely (path + args, in the order they were provided) execIdentifier := utils.CalculateSHA256FileExecHash(path, event.Args) + if am.enricher != nil { + go am.enricher.EnrichEvent(k8sContainerID, &event, execIdentifier) + } + if _, ok := am.savedExecs.Get(k8sContainerID).Get(execIdentifier); ok { return } // add to exec map, first element is the path, the rest are the args am.toSaveExecs.Get(k8sContainerID).Set(execIdentifier, append([]string{path}, event.Args...)) - - if am.enricher != nil { - go am.enricher.EnrichEvent(k8sContainerID, &event, execIdentifier) - } } func (am *ApplicationProfileManager) ReportFileOpen(k8sContainerID string, event events.OpenEvent) { @@ -770,6 +770,15 @@ func (am *ApplicationProfileManager) ReportFileOpen(k8sContainerID string, event if strings.HasPrefix(path, "/proc/") { path = procRegex.ReplaceAllString(path, "/proc/"+dynamicpathdetector.DynamicIdentifier) } + + isSensitive := utils.IsSensitivePath(path, ruleengine.SensitiveFiles) + + if am.enricher != nil && isSensitive { + logger.L().Info("ApplicationProfileManager - sensitive file open event", helpers.String("path", path)) + openIdentifier := utils.CalculateSHA256FileOpenHash(path) + go am.enricher.EnrichEvent(k8sContainerID, &event, openIdentifier) + } + // check if we already have this open if opens, ok := am.savedOpens.Get(k8sContainerID).Get(path); ok && opens.(mapset.Set[string]).Contains(event.Flags...) { return @@ -781,14 +790,6 @@ func (am *ApplicationProfileManager) ReportFileOpen(k8sContainerID string, event } else { openMap.Set(path, mapset.NewSet[string](event.Flags...)) } - - isSensitive := utils.IsSensitivePath(path, ruleengine.SensitiveFiles) - - if am.enricher != nil && isSensitive { - logger.L().Info("ApplicationProfileManager - sensitive file open event", helpers.String("path", path)) - openIdentifier := utils.CalculateSHA256FileOpenHash(path) - go am.enricher.EnrichEvent(k8sContainerID, &event, openIdentifier) - } } func (am *ApplicationProfileManager) ReportSymlinkEvent(k8sContainerID string, event *tracersymlinktype.Event) { From 151ef05f9c83ed79ae919528e1dd4ace0cf45bd6 Mon Sep 17 00:00:00 2001 From: Amit Schendel Date: Wed, 5 Feb 2025 11:54:38 +0000 Subject: [PATCH 31/40] Adding index callstacks Signed-off-by: Amit Schendel --- .../applicationprofilecache/applicationprofilecache.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/objectcache/applicationprofilecache/applicationprofilecache.go b/pkg/objectcache/applicationprofilecache/applicationprofilecache.go index 56429297..e62d276f 100644 --- a/pkg/objectcache/applicationprofilecache/applicationprofilecache.go +++ b/pkg/objectcache/applicationprofilecache/applicationprofilecache.go @@ -390,6 +390,7 @@ func (ap *ApplicationProfileCacheImpl) addFullApplicationProfile(appProfile *v1b ap.slugToAppProfile.Set(apName, fullAP) for _, i := range ap.slugToContainers.Get(apName).ToSlice() { ap.containerToSlug.Set(i, apName) + ap.indexContainerCallStacks(i, ap.containerToName.Get(i), fullAP) } logger.L().Debug("ApplicationProfileCacheImpl - added pod to application profile cache", helpers.String("name", apName)) } From 119c1aedf06c580e28d4ce6e653fa7fd421d5729 Mon Sep 17 00:00:00 2001 From: Amit Schendel Date: Wed, 5 Feb 2025 14:00:34 +0000 Subject: [PATCH 32/40] Adding logs Signed-off-by: Amit Schendel --- .../applicationprofilecache/applicationprofilecache.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/objectcache/applicationprofilecache/applicationprofilecache.go b/pkg/objectcache/applicationprofilecache/applicationprofilecache.go index e62d276f..46688efb 100644 --- a/pkg/objectcache/applicationprofilecache/applicationprofilecache.go +++ b/pkg/objectcache/applicationprofilecache/applicationprofilecache.go @@ -290,6 +290,7 @@ func (ap *ApplicationProfileCacheImpl) addPod(obj runtime.Object) { } containers := objectcache.ListContainersIDs(pod) + ap.initContainerIdToName(pod) for _, container := range containers { if !ap.slugToContainers.Has(uniqueSlug) { @@ -307,7 +308,6 @@ func (ap *ApplicationProfileCacheImpl) addPod(obj runtime.Object) { continue } ap.containerToSlug.Set(container, uniqueSlug) - ap.initContainerIdToName(pod) // if application profile exists but is not cached if ap.allProfiles.Contains(uniqueSlug) && !ap.slugToAppProfile.Has(uniqueSlug) { @@ -329,6 +329,7 @@ func (ap *ApplicationProfileCacheImpl) addPod(obj runtime.Object) { func (ap *ApplicationProfileCacheImpl) initContainerIdToName(pod *corev1.Pod) { for i, container := range pod.Spec.Containers { + logger.L().Info("ApplicationProfileCacheImpl - initContainerIdToName", helpers.String("containerID", utils.TrimRuntimePrefix(pod.Status.ContainerStatuses[i].ContainerID)), helpers.String("containerName", container.Name)) ap.containerToName.Set(utils.TrimRuntimePrefix(pod.Status.ContainerStatuses[i].ContainerID), container.Name) } for i, container := range pod.Spec.InitContainers { @@ -390,6 +391,7 @@ func (ap *ApplicationProfileCacheImpl) addFullApplicationProfile(appProfile *v1b ap.slugToAppProfile.Set(apName, fullAP) for _, i := range ap.slugToContainers.Get(apName).ToSlice() { ap.containerToSlug.Set(i, apName) + logger.L().Info("ApplicationProfileCacheImpl - indexing call stacks for container1", helpers.String("containerID", i)) ap.indexContainerCallStacks(i, ap.containerToName.Get(i), fullAP) } logger.L().Debug("ApplicationProfileCacheImpl - added pod to application profile cache", helpers.String("name", apName)) From a3211e361eb258f28773b9a62ce3703a046380c7 Mon Sep 17 00:00:00 2001 From: Amit Schendel Date: Wed, 5 Feb 2025 14:44:47 +0000 Subject: [PATCH 33/40] Removing logs Signed-off-by: Amit Schendel --- .../v1/applicationprofile_manager.go | 1 - .../applicationprofilecache/applicationprofilecache.go | 4 ---- 2 files changed, 5 deletions(-) diff --git a/pkg/applicationprofilemanager/v1/applicationprofile_manager.go b/pkg/applicationprofilemanager/v1/applicationprofile_manager.go index 3e10a001..83671e95 100644 --- a/pkg/applicationprofilemanager/v1/applicationprofile_manager.go +++ b/pkg/applicationprofilemanager/v1/applicationprofile_manager.go @@ -774,7 +774,6 @@ func (am *ApplicationProfileManager) ReportFileOpen(k8sContainerID string, event isSensitive := utils.IsSensitivePath(path, ruleengine.SensitiveFiles) if am.enricher != nil && isSensitive { - logger.L().Info("ApplicationProfileManager - sensitive file open event", helpers.String("path", path)) openIdentifier := utils.CalculateSHA256FileOpenHash(path) go am.enricher.EnrichEvent(k8sContainerID, &event, openIdentifier) } diff --git a/pkg/objectcache/applicationprofilecache/applicationprofilecache.go b/pkg/objectcache/applicationprofilecache/applicationprofilecache.go index 46688efb..b5ab0ea2 100644 --- a/pkg/objectcache/applicationprofilecache/applicationprofilecache.go +++ b/pkg/objectcache/applicationprofilecache/applicationprofilecache.go @@ -126,7 +126,6 @@ func (ap *ApplicationProfileCacheImpl) handleUserManagedProfile(appProfile *v1be // indexContainerCallStacks builds the search index for a container's call stacks and removes them from the profile func (ap *ApplicationProfileCacheImpl) indexContainerCallStacks(containerID, containerName string, appProfile *v1beta1.ApplicationProfile) { - logger.L().Info("ApplicationProfileCacheImpl - indexing call stacks for container", helpers.String("containerID", containerID), helpers.String("containerName", containerName)) // Initialize container index if needed if !ap.containerCallStacks.Has(containerID) { ap.containerCallStacks.Set(containerID, &ContainerCallStackIndex{ @@ -141,7 +140,6 @@ func (ap *ApplicationProfileCacheImpl) indexContainerCallStacks(containerID, con if appProfile.Spec.Containers[i].Name == containerName { // Index all call stacks for _, stack := range appProfile.Spec.Containers[i].IdentifiedCallStacks { - logger.L().Info("ApplicationProfileCacheImpl - indexing call stack", helpers.String("containerID", containerID), helpers.String("containerName", containerName), helpers.String("CallID", string(stack.CallID))) index.searchTree.AddCallStack(stack) } @@ -329,7 +327,6 @@ func (ap *ApplicationProfileCacheImpl) addPod(obj runtime.Object) { func (ap *ApplicationProfileCacheImpl) initContainerIdToName(pod *corev1.Pod) { for i, container := range pod.Spec.Containers { - logger.L().Info("ApplicationProfileCacheImpl - initContainerIdToName", helpers.String("containerID", utils.TrimRuntimePrefix(pod.Status.ContainerStatuses[i].ContainerID)), helpers.String("containerName", container.Name)) ap.containerToName.Set(utils.TrimRuntimePrefix(pod.Status.ContainerStatuses[i].ContainerID), container.Name) } for i, container := range pod.Spec.InitContainers { @@ -391,7 +388,6 @@ func (ap *ApplicationProfileCacheImpl) addFullApplicationProfile(appProfile *v1b ap.slugToAppProfile.Set(apName, fullAP) for _, i := range ap.slugToContainers.Get(apName).ToSlice() { ap.containerToSlug.Set(i, apName) - logger.L().Info("ApplicationProfileCacheImpl - indexing call stacks for container1", helpers.String("containerID", i)) ap.indexContainerCallStacks(i, ap.containerToName.Get(i), fullAP) } logger.L().Debug("ApplicationProfileCacheImpl - added pod to application profile cache", helpers.String("name", apName)) From a2846e91f4ccda812b673b52d08205b1a8001755 Mon Sep 17 00:00:00 2001 From: Amit Schendel Date: Thu, 6 Feb 2025 10:26:39 +0000 Subject: [PATCH 34/40] Bumping storage Signed-off-by: Amit Schendel --- go.mod | 4 +--- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index be950411..f4de87a4 100644 --- a/go.mod +++ b/go.mod @@ -31,7 +31,7 @@ require ( github.com/kubescape/backend v0.0.25 github.com/kubescape/go-logger v0.0.23 github.com/kubescape/k8s-interface v0.0.183 - github.com/kubescape/storage v0.0.141 + github.com/kubescape/storage v0.0.158 github.com/moby/sys/mountinfo v0.7.2 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.0 @@ -354,6 +354,4 @@ replace github.com/vishvananda/netns => github.com/inspektor-gadget/netns v0.0.5 replace github.com/mholt/archiver/v3 v3.5.1 => github.com/anchore/archiver/v3 v3.5.2 -replace github.com/kubescape/storage => github.com/kubescape/storage v0.0.154-0.20250128150729-f5cb1a341394 - replace github.com/inspektor-gadget/inspektor-gadget => github.com/afek854/inspektor-gadget v0.0.0-20250130122622-4a97666bae1c diff --git a/go.sum b/go.sum index d48c5306..a329031c 100644 --- a/go.sum +++ b/go.sum @@ -694,8 +694,8 @@ github.com/kubescape/go-logger v0.0.23 h1:5xh+Nm8eGImhFbtippRKLaFgsvlKE1ufvQhNM2 github.com/kubescape/go-logger v0.0.23/go.mod h1:Ayg7g769c7sXVB+P3fkJmbsJpoEmMmaUf9jeo+XuC3U= github.com/kubescape/k8s-interface v0.0.183 h1:eTuHlKJkBYYA03AR/YGr4KUC+xnbV6SG0/8+yrt9Yrs= github.com/kubescape/k8s-interface v0.0.183/go.mod h1:YjIAQtrK4nCy+XQ/6jwo+BqlLyJk7DN2Mx4pUcbzq10= -github.com/kubescape/storage v0.0.154-0.20250128150729-f5cb1a341394 h1:tZw2UA164uMf1ADUz1f0d3SrSp5l8EWWgVmvuXcMtJU= -github.com/kubescape/storage v0.0.154-0.20250128150729-f5cb1a341394/go.mod h1:7ai5ePqTXdSTCGjpEHVFXKggrbey/guM5e13w7Y3yMw= +github.com/kubescape/storage v0.0.158 h1:TbI1/rrRq+0gNbbPl8Z1weik0ShTWPJfy8lXjnzaDjw= +github.com/kubescape/storage v0.0.158/go.mod h1:K3QWf+zcXmXxfeQ2HD0dd0bF4FJ5gbxLTRZ7nx4dHXw= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= From 98fbd72f6af9efac5ced2e413b98afcd1df8c945 Mon Sep 17 00:00:00 2001 From: Amit Schendel Date: Thu, 6 Feb 2025 15:32:02 +0000 Subject: [PATCH 35/40] Skipping adding already added stacks Signed-off-by: Amit Schendel --- .../applicationprofilecache/callstackcache/callstackcache.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/objectcache/applicationprofilecache/callstackcache/callstackcache.go b/pkg/objectcache/applicationprofilecache/callstackcache/callstackcache.go index 8afcae3d..26d7c66b 100644 --- a/pkg/objectcache/applicationprofilecache/callstackcache/callstackcache.go +++ b/pkg/objectcache/applicationprofilecache/callstackcache/callstackcache.go @@ -34,6 +34,11 @@ func NewCallStackSearchTree() *CallStackSearchTree { // AddCallStack adds a new identified call stack to the search tree func (t *CallStackSearchTree) AddCallStack(stack v1beta1.IdentifiedCallStack) { + // If call stack already exists, skip + if _, ok := t.Roots[stack.CallID]; ok { + return + } + // Get all paths from the call stack paths := getCallStackPaths(stack.CallStack) if len(paths) == 0 { From c959d381e9ace92a9cc5a99640efd6f3f2349aeb Mon Sep 17 00:00:00 2001 From: Matthias Bertschy Date: Thu, 6 Feb 2025 17:06:17 +0100 Subject: [PATCH 36/40] replace duplicated conversion loops with a call to convertNodesToFrames Signed-off-by: Matthias Bertschy --- .../callstackcache/callstackcache.go | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/pkg/objectcache/applicationprofilecache/callstackcache/callstackcache.go b/pkg/objectcache/applicationprofilecache/callstackcache/callstackcache.go index 26d7c66b..c709281a 100644 --- a/pkg/objectcache/applicationprofilecache/callstackcache/callstackcache.go +++ b/pkg/objectcache/applicationprofilecache/callstackcache/callstackcache.go @@ -45,19 +45,15 @@ func (t *CallStackSearchTree) AddCallStack(stack v1beta1.IdentifiedCallStack) { return } - // First create root from first path + // First create root from first path using the helper firstPath := paths[0] - firstFrames := make([]v1beta1.StackFrame, len(firstPath)) - for i, node := range firstPath { - firstFrames[i] = node.Frame - } + firstFrames := convertNodesToFrames(firstPath) root := t.buildBidirectionalTree(firstFrames) t.Roots[stack.CallID] = root // Store paths for this CallID - t.PathsByCallID[stack.CallID] = make([][]v1beta1.StackFrame, 0) - t.PathsByCallID[stack.CallID] = append(t.PathsByCallID[stack.CallID], firstFrames) + t.PathsByCallID[stack.CallID] = [][]v1beta1.StackFrame{firstFrames} // Add first path to trie key := pathToKey(firstFrames) @@ -66,15 +62,10 @@ func (t *CallStackSearchTree) AddCallStack(stack v1beta1.IdentifiedCallStack) { // Process remaining paths for i := 1; i < len(paths); i++ { path := paths[i] - frames := make([]v1beta1.StackFrame, len(path)) - for j, node := range path { - frames[j] = node.Frame - } + frames := convertNodesToFrames(path) - // Add to paths by CallID + // Add to paths by CallID and trie t.PathsByCallID[stack.CallID] = append(t.PathsByCallID[stack.CallID], frames) - - // Add to trie key = pathToKey(frames) t.ForwardTrie.Put(key, frames) @@ -186,3 +177,12 @@ func frameKey(frame v1beta1.StackFrame) string { func framesEqual(a, b v1beta1.StackFrame) bool { return a.FileID == b.FileID && a.Lineno == b.Lineno } + +// Add after existing helper functions +func convertNodesToFrames(nodes []v1beta1.CallStackNode) []v1beta1.StackFrame { + frames := make([]v1beta1.StackFrame, len(nodes)) + for i, node := range nodes { + frames[i] = node.Frame + } + return frames +} From a96c2506389a1c931275456c3f610a6b49ae3007 Mon Sep 17 00:00:00 2001 From: Amit Schendel Date: Sun, 9 Feb 2025 08:13:24 +0000 Subject: [PATCH 37/40] Fixing syntax Signed-off-by: Amit Schendel --- .../applicationprofilecache/applicationprofilecache.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/objectcache/applicationprofilecache/applicationprofilecache.go b/pkg/objectcache/applicationprofilecache/applicationprofilecache.go index 9b8dfd7c..ee4d80ce 100644 --- a/pkg/objectcache/applicationprofilecache/applicationprofilecache.go +++ b/pkg/objectcache/applicationprofilecache/applicationprofilecache.go @@ -375,7 +375,7 @@ func (ap *ApplicationProfileCacheImpl) addFullApplicationProfile(appProfile *v1b ap.slugToAppProfile.Set(apName, appProfile) for _, i := range ap.slugToContainers.Get(apName).ToSlice() { ap.containerToSlug.Set(i, apName) - ap.indexContainerCallStacks(i, ap.containerToName.Get(i), fullAP) + ap.indexContainerCallStacks(i, ap.containerToName.Get(i), appProfile) } logger.L().Debug("ApplicationProfileCacheImpl - added pod to application profile cache", helpers.String("name", apName)) } From c909e3375a64840fa0cc9bf9dd62431fd679bf7a Mon Sep 17 00:00:00 2001 From: Amit Schendel <58078857+amitschendel@users.noreply.github.com> Date: Sun, 9 Feb 2025 10:17:49 +0200 Subject: [PATCH 38/40] Update pkg/utils/utils.go Co-authored-by: Matthias Bertschy Signed-off-by: Amit Schendel <58078857+amitschendel@users.noreply.github.com> --- pkg/utils/utils.go | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index c52fe388..35dd6e53 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -726,27 +726,17 @@ func DiskUsage(path string) int64 { // IsSensitivePath checks if a given path matches or is within any sensitive paths func IsSensitivePath(fullPath string, paths []string) bool { - // Clean the path to handle "..", "//", etc. fullPath = filepath.Clean(fullPath) - - for _, sensitivePath := range paths { - sensitivePath = filepath.Clean(sensitivePath) - - // Check if the path exactly matches - if fullPath == sensitivePath { - return true - } - - // Check if the path is a directory that contains sensitive files - if strings.HasPrefix(sensitivePath, fullPath+"/") { - return true + for _, p := range paths { + p = filepath.Clean(p) + rel, err := filepath.Rel(p, fullPath) + if err != nil { + continue } - - // Check if the path is within a sensitive directory - if strings.HasPrefix(fullPath, sensitivePath+"/") { + // fullPath is either equal to or inside the sensitive path p + if rel == "." || !strings.HasPrefix(rel, "..") { return true } } - return false } From 0225d86b9c1b1192604f7c7f1d316e28b9e808f5 Mon Sep 17 00:00:00 2001 From: Amit Schendel Date: Sun, 9 Feb 2025 08:47:16 +0000 Subject: [PATCH 39/40] Fixing sensitive path check Signed-off-by: Amit Schendel --- ...0_unexpected_sensitive_file_access_test.go | 53 +++++++++++++++++++ pkg/utils/utils.go | 37 ++++++++++--- 2 files changed, 83 insertions(+), 7 deletions(-) diff --git a/pkg/ruleengine/v1/r0010_unexpected_sensitive_file_access_test.go b/pkg/ruleengine/v1/r0010_unexpected_sensitive_file_access_test.go index d14361c2..943c3b3f 100644 --- a/pkg/ruleengine/v1/r0010_unexpected_sensitive_file_access_test.go +++ b/pkg/ruleengine/v1/r0010_unexpected_sensitive_file_access_test.go @@ -187,6 +187,59 @@ func TestR0010UnexpectedSensitiveFileAccess(t *testing.T) { expectAlert: false, description: "Should not alert when static segments match and dynamic segment matches timestamp", }, + // { + // name: "Double slashes in path", + // event: createTestEvent("/etc//shadow", []string{"O_RDONLY"}), + // profile: createTestProfile("test", []string{"/etc/shadow"}, []string{"O_RDONLY"}), + // expectAlert: false, + // description: "Should normalize paths with double slashes", + // }, + { + name: "Trailing slash differences", + event: createTestEvent("/etc/kubernetes/", []string{"O_RDONLY"}), + profile: createTestProfile("test", []string{"/etc/kubernetes"}, []string{"O_RDONLY"}), + expectAlert: false, + description: "Should handle trailing slash differences", + }, + { + name: "Partial path segment match", + event: createTestEvent("/etc/kubernetes-staging/secret", []string{"O_RDONLY"}), + profile: createTestProfile("test", []string{"/etc/kubernetes"}, []string{"O_RDONLY"}), + expectAlert: false, + description: "Should not alert when path merely starts with a sensitive path string", + }, + { + name: "Complex dynamic pattern combination", + event: createTestEvent("/var/log/2024/01/pod-123/container-456/app.log", []string{"O_RDONLY"}), + profile: createTestProfile("test", []string{ + "/var/log/" + dynamicpathdetector.DynamicIdentifier + "/" + + dynamicpathdetector.DynamicIdentifier + "/pod-" + + dynamicpathdetector.DynamicIdentifier + "/container-" + + dynamicpathdetector.DynamicIdentifier + "/app.log"}, []string{"O_RDONLY"}), + expectAlert: false, + description: "Should handle complex combinations of dynamic patterns", + }, + { + name: "Empty path handling", + event: createTestEvent("", []string{"O_RDONLY"}), + profile: createTestProfile("test", []string{"/test"}, []string{"O_RDONLY"}), + expectAlert: false, + description: "Should handle empty paths gracefully", + }, + { + name: "Special characters in path", + event: createTestEvent("/etc/conf!g#file", []string{"O_RDONLY"}), + profile: createTestProfile("test", []string{"/etc/conf!g#file"}, []string{"O_RDONLY"}), + expectAlert: false, + description: "Should handle special characters in paths", + }, + { + name: "Relative path with dots", + event: createTestEvent("./etc/shadow", []string{"O_RDONLY"}), + profile: createTestProfile("test", []string{"/etc/shadow"}, []string{"O_RDONLY"}), + expectAlert: true, + description: "Should handle relative paths correctly", + }, } for _, tt := range tests { diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 35dd6e53..6b388944 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -724,17 +724,40 @@ func DiskUsage(path string) int64 { return s } -// IsSensitivePath checks if a given path matches or is within any sensitive paths func IsSensitivePath(fullPath string, paths []string) bool { + if fullPath == "" { + return false + } + + // Clean and normalize the input path once fullPath = filepath.Clean(fullPath) - for _, p := range paths { - p = filepath.Clean(p) - rel, err := filepath.Rel(p, fullPath) - if err != nil { + if !filepath.IsAbs(fullPath) { + fullPath = filepath.Clean("/" + fullPath) + } + + // Pre-compute the directory of the full path since it's used in prefix checks + fullPathDir := filepath.Dir(fullPath) + + for _, sensitivePath := range paths { + if sensitivePath == "" { continue } - // fullPath is either equal to or inside the sensitive path p - if rel == "." || !strings.HasPrefix(rel, "..") { + + // Clean and normalize the sensitive path + sensitivePath = filepath.Clean(sensitivePath) + if !filepath.IsAbs(sensitivePath) { + sensitivePath = filepath.Clean("/" + sensitivePath) + } + + // Check exact match first (fast path) + if fullPath == sensitivePath { + return true + } + + // Check if the path is within the sensitive directory + // Note: This assumes sensitivePath is already verified as a directory + // through external means if needed + if strings.HasPrefix(fullPathDir, sensitivePath) { return true } } From e6aa81b03f140335d5229e5b74008fe0f3249225 Mon Sep 17 00:00:00 2001 From: Amit Schendel Date: Sun, 9 Feb 2025 08:53:20 +0000 Subject: [PATCH 40/40] Adding component test Signed-off-by: Amit Schendel --- .github/workflows/component-tests.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/component-tests.yaml b/.github/workflows/component-tests.yaml index 6af0fe15..14040329 100644 --- a/.github/workflows/component-tests.yaml +++ b/.github/workflows/component-tests.yaml @@ -53,6 +53,7 @@ jobs: Test_12_MergingProfilesTest, Test_13_MergingNetworkNeighborhoodTest, Test_14_RulePoliciesTest, + Test_15_CompletedApCannotBecomeReadyAgain ] steps: - name: Checkout code