diff --git a/cmd/main.go b/cmd/main.go index 0545fda174..765f8a0234 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -29,6 +29,7 @@ import ( containerprofilemanagerv1 "github.com/kubescape/node-agent/pkg/containerprofilemanager/v1" "github.com/kubescape/node-agent/pkg/containerwatcher" containerwatcherv2 "github.com/kubescape/node-agent/pkg/containerwatcher/v2" + "github.com/kubescape/node-agent/pkg/contextdetection" "github.com/kubescape/node-agent/pkg/dnsmanager" "github.com/kubescape/node-agent/pkg/exporters" "github.com/kubescape/node-agent/pkg/fimmanager" @@ -290,8 +291,10 @@ func main() { logger.L().Ctx(ctx).Fatal("error creating CEL evaluator", helpers.Error(err)) } + mntnsRegistry := contextdetection.NewMntnsRegistry() + // create runtimeDetection managers - ruleManager, err = rulemanager.CreateRuleManager(ctx, cfg, k8sClient, ruleBindingCache, objCache, exporter, prometheusExporter, processTreeManager, dnsResolver, nil, ruleCooldown, adapterFactory, celEvaluator) + ruleManager, err = rulemanager.CreateRuleManager(ctx, cfg, k8sClient, ruleBindingCache, objCache, exporter, prometheusExporter, processTreeManager, dnsResolver, nil, ruleCooldown, adapterFactory, celEvaluator, mntnsRegistry) if err != nil { logger.L().Ctx(ctx).Fatal("error creating RuleManager", helpers.Error(err)) } diff --git a/configuration/config.json b/configuration/config.json index 1319d54fb4..8f00b5cbc6 100644 --- a/configuration/config.json +++ b/configuration/config.json @@ -15,6 +15,8 @@ "seccompServiceEnabled": "false", "enableEmbeddedSBOMs": "false", "fimEnabled": true, + "hostMonitoringEnabled": false, + "standaloneMonitoringEnabled": false, "exporters": { "syslogExporterURL": "http://syslog.kubescape.svc.cluster.local:514", "stdoutExporter": "false", diff --git a/docs/RULE_ENGINE_MULTI_CONTEXT_REDESIGN.md b/docs/RULE_ENGINE_MULTI_CONTEXT_REDESIGN.md new file mode 100644 index 0000000000..d027fc11b4 --- /dev/null +++ b/docs/RULE_ENGINE_MULTI_CONTEXT_REDESIGN.md @@ -0,0 +1,148 @@ +# Rule Engine Multi-Context Redesign + +## Overview + +This document describes the design and implementation of the multi-context rule engine in the Kubescape Node Agent. The system enables runtime security monitoring and alerting across three distinct execution contexts: + +1. **Kubernetes**: Containers running within a Kubernetes cluster (Pod-based). +2. **Host**: The underlying node itself, treated as a specialized context for monitoring host-level activities. +3. **Standalone**: Non-Kubernetes containers (e.g., Docker containers, standalone containerd instances) that are not managed by the Kubernetes orchestrator. + +## Goals + +- Provide a unified rule evaluation engine for all execution contexts. +- Use the mount namespace (mntns) ID as the primary key for identifying event contexts. +- Support multiple container runtimes through automated discovery (fanotify). +- Allow fine-grained control over where rules apply using context-specific tags. +- Maintain backward compatibility with existing Kubernetes-only monitoring and alert formats. + +## Architecture + +### 1. Event Source Context + +The system defines three primary context types in `pkg/contextdetection/types.go`: + +```go +type EventSourceContext string + +const ( + Kubernetes EventSourceContext = "kubernetes" + Host EventSourceContext = "host" + Standalone EventSourceContext = "standalone" +) +``` + +### 2. Context Detection and Registry + +The architecture relies on a discovery mechanism that identifies the nature of a process or container when it starts. + +#### Context Info and Detectors +Each detected context is represented by a `ContextInfo` object which provides the context type and a unique `WorkloadID`. + +```go +type ContextInfo interface { + Context() EventSourceContext + WorkloadID() string +} +``` + +The `DetectorManager` coordinates several `ContextDetector` implementations: +- **K8sDetector**: Identifies containers enriched with Kubernetes metadata (Namespace, Pod name). +- **HostDetector**: Identifies the host context based on PID 1 or the host's mount namespace. +- **StandaloneDetector**: Identifies containers that have runtime information but lack Kubernetes metadata. + +#### Mount Namespace Registry +The `MntnsRegistry` maintains a thread-safe mapping of mount namespace IDs to their corresponding `ContextInfo`. This registry is the "source of truth" used to enrich eBPF events as they arrive. + +```go +type Registry interface { + Register(mntns uint64, info ContextInfo) error + Lookup(mntns uint64) (ContextInfo, bool) + Unregister(mntns uint64) +} +``` + +### 3. Event Enrichment + +As eBPF events (exec, open, network, etc.) are captured, they are wrapped in an `EnrichedEvent`. The `RuleManager` enriches these events with context information by looking up the event's mount namespace ID in the registry. + +```go +func (rm *RuleManager) enrichEventWithContext(enrichedEvent *events.EnrichedEvent) { + mntnsID := enrichedEvent.Event.GetMountNsID() + enrichedEvent.MountNamespaceID = mntnsID + + if mntnsID != 0 { + if contextInfo, found := rm.mntnsRegistry.Lookup(mntnsID); found { + enrichedEvent.SourceContext = contextInfo + } + } +} +``` + +### 4. Rule Evaluation Logic + +#### Context-Aware Filtering +Rules can specify where they should execute using the `context:` tag prefix. The `RuleAppliesToContext` function determines if a rule is applicable: + +- If a rule has tags like `context:host`, it will only run for events detected as `Host`. +- If a rule has no `context:` tags, it defaults to `Kubernetes` only, ensuring backward compatibility for existing rule sets. + +```go +func RuleAppliesToContext(rule *typesv1.Rule, contextInfo contextdetection.ContextInfo) bool { + // ... logic to check "context:" tags ... + // Default: return currentContext == contextdetection.Kubernetes +} +``` + +#### Profile Dependencies +Kubernetes-specific features like Application Profiles and Network Neighborhoods are only enforced for the `Kubernetes` context. Rules requiring these profiles are skipped for `Host` and `Standalone` contexts. + +### 5. Alert Structure + +The `GenericRuleFailure` structure has been extended to include the `SourceContext`. To maintain compatibility with existing consumers (like the Kubescape Cloud or third-party SIEMs), context-specific metadata is mapped into the existing `RuntimeAlertK8sDetails` structure where appropriate: + +- **Host alerts**: The node's hostname is populated in the `NodeName` field. +- **Standalone alerts**: Container ID and Image information are populated, while K8s-specific fields (Namespace, Pod) remain empty. + +```go +type GenericRuleFailure struct { + // ... existing fields ... + SourceContext contextdetection.EventSourceContext +} +``` + +### 6. Multiple Runtime Discovery + +The Node Agent leverages `inspektor-gadget`'s `WithContainerFanotifyEbpf()` capability. This allows the agent to: +1. Use fanotify to watch for OCI runtime (runc, crun) executions. +2. Capture the container's bundle directory and PID. +3. Automatically detect and monitor containers regardless of whether they were started by `kubelet`, `docker`, or `containerd` directly. + +## Configuration + +Context monitoring is configurable via the Node Agent configuration: + +```yaml +# Enable/disable specific monitoring contexts +hostMonitoringEnabled: true +standaloneMonitoringEnabled: true + +# Note: Kubernetes monitoring is usually tied to enableRuntimeDetection +enableRuntimeDetection: true +``` + +## Implementation Status + +- [x] **Core Infrastructure**: Definition of context types and the `MntnsRegistry`. +- [x] **Detector Framework**: Implementation of K8s, Host, and Standalone detectors. +- [x] **Event Enrichment**: Integration into `RuleManager` to attach context to every event. +- [x] **Context-Aware Rules**: Support for `context:` tags in rule definitions. +- [x] **Unified Alerting**: Updated `RuleFailureCreator` to handle multi-context metadata. +- [x] **Multi-Runtime Support**: Integration with fanotify for standalone container discovery. +- [x] **Testing**: Unit and integration tests for context detection and rule application. + +## Future Considerations + +- **Standalone Profiles**: Extending Application Profile learning to standalone containers. +- **Host Policy**: Specific rule sets tailored for host-level hardening and monitoring. +- **Dynamic Context Tags**: Allowing users to define custom contexts based on container labels or environment variables. \ No newline at end of file diff --git a/pkg/cloudmetadata/metadata.go b/pkg/cloudmetadata/metadata.go index dd26d10d8a..c752632a95 100644 --- a/pkg/cloudmetadata/metadata.go +++ b/pkg/cloudmetadata/metadata.go @@ -3,6 +3,10 @@ package cloudmetadata import ( "context" "fmt" + "io" + "net/http" + "strings" + "time" apitypes "github.com/armosec/armoapi-go/armotypes" "github.com/kubescape/go-logger" @@ -12,6 +16,11 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +const ( + azureApiVersion = "2021-12-13" + metadataTimeout = 2 * time.Second +) + // GetCloudMetadata retrieves cloud metadata for a given node func GetCloudMetadata(ctx context.Context, client *k8sinterface.KubernetesApi, nodeName string) (*apitypes.CloudMetadata, error) { node, err := client.GetKubernetesClient().CoreV1().Nodes().Get(ctx, nodeName, metav1.GetOptions{}) @@ -53,9 +62,163 @@ func GetCloudMetadataWithIMDS(ctx context.Context) (*apitypes.CloudMetadata, err cMetadataClient := k8sInterfaceCloudMetadata.NewMetadataClient(true) cMetadata, err := cMetadataClient.GetMetadata(ctx) + if err == nil { + return cMetadata, nil + } + + logger.L().Info("failed to get cloud metadata from IMDS, trying fallbacks", helpers.Error(err)) + + // Fallback strategy: try different providers + fallbacks := []struct { + name string + fetch func(context.Context) (*apitypes.CloudMetadata, error) + }{ + {name: "DigitalOcean", fetch: fetchDigitalOceanMetadata}, + {name: "GCP", fetch: fetchGCPMetadata}, + {name: "Azure", fetch: fetchAzureMetadata}, + } + + for _, fb := range fallbacks { + if meta, ferr := fb.fetch(ctx); ferr == nil && meta != nil { + logger.L().Info(fmt.Sprintf("retrieved cloud metadata from %s metadata service", fb.name)) + return meta, nil + } + } + + // Wrap the underlying error with additional context so logs make it clearer why metadata is missing. + return nil, fmt.Errorf("failed to get cloud metadata from IMDS or fallbacks: %w", err) +} + +// fetchHTTPMetadata helper to fetch metadata from a URL with optional headers +func fetchHTTPMetadata(ctx context.Context, url string, headers map[string]string) (string, error) { + client := &http.Client{ + Timeout: metadataTimeout, + } + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + return "", err + } + for k, v := range headers { + req.Header.Set(k, v) + } + resp, err := client.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("metadata endpoint %s returned status: %d", url, resp.StatusCode) + } + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", err + } + return strings.TrimSpace(string(body)), nil +} + +func getLastPathPart(val string) string { + if val == "" { + return "" + } + parts := strings.Split(val, "/") + return parts[len(parts)-1] +} + +// fetchDigitalOceanMetadata attempts to fetch basic metadata from DigitalOcean's metadata service. +func fetchDigitalOceanMetadata(ctx context.Context) (*apitypes.CloudMetadata, error) { + base := "http://169.254.169.254/metadata/v1/" + + // Probe root to see whether the metadata endpoint responds and contains expected entries. + body, err := fetchHTTPMetadata(ctx, base, nil) if err != nil { return nil, err } - return cMetadata, nil + // Basic heuristic: the DO metadata root typically lists resources like 'id', 'hostname', 'region' etc. + if !strings.Contains(body, "id") && !strings.Contains(body, "region") && !strings.Contains(body, "hostname") { + return nil, fmt.Errorf("digitalocean metadata root missing expected entries") + } + + get := func(path string) string { + val, _ := fetchHTTPMetadata(ctx, base+path, nil) + return val + } + + id := get("id") + if id == "" { + id = get("droplet_id") + } + instanceType := get("size") + if instanceType == "" { + instanceType = get("type") + } + + meta := &apitypes.CloudMetadata{ + Provider: "digitalocean", + InstanceID: id, + InstanceType: instanceType, + Region: get("region"), + PrivateIP: get("interfaces/private/0/ipv4/address"), + PublicIP: get("interfaces/public/0/ipv4/address"), + Hostname: get("hostname"), + } + + // if nothing useful was obtained, return an error so callers can continue trying other fallbacks + if meta.InstanceID == "" && meta.Hostname == "" && meta.Region == "" && meta.PrivateIP == "" && meta.PublicIP == "" && meta.InstanceType == "" { + return nil, fmt.Errorf("digitalocean metadata endpoints returned no data") + } + + return meta, nil +} + +// fetchGCPMetadata attempts to fetch basic metadata from GCP's metadata service. +func fetchGCPMetadata(ctx context.Context) (*apitypes.CloudMetadata, error) { + base := "http://metadata.google.internal/computeMetadata/v1/" + headers := map[string]string{"Metadata-Flavor": "Google"} + + get := func(path string) string { + val, _ := fetchHTTPMetadata(ctx, base+path, headers) + return val + } + + machineType := get("instance/machine-type") + if machineType == "" { + return nil, fmt.Errorf("not a GCP instance") + } + + return &apitypes.CloudMetadata{ + Provider: "gcp", + AccountID: get("project/project-id"), + InstanceID: get("instance/id"), + InstanceType: getLastPathPart(machineType), + Zone: getLastPathPart(get("instance/zone")), + Hostname: get("instance/hostname"), + }, nil +} + +// fetchAzureMetadata attempts to fetch basic metadata from Azure's metadata service. +func fetchAzureMetadata(ctx context.Context) (*apitypes.CloudMetadata, error) { + base := "http://169.254.169.254/metadata/instance/compute/" + headers := map[string]string{"Metadata": "true"} + params := "?api-version=" + azureApiVersion + "&format=text" + + get := func(path string) string { + val, _ := fetchHTTPMetadata(ctx, base+path+params, headers) + return val + } + + vmSize := get("vmSize") + if vmSize == "" { + return nil, fmt.Errorf("not an Azure instance") + } + + return &apitypes.CloudMetadata{ + Provider: "azure", + AccountID: get("subscriptionId"), + InstanceID: get("vmId"), + InstanceType: vmSize, + Region: get("location"), + Zone: get("zone"), + Hostname: get("name"), + }, nil } diff --git a/pkg/config/config.go b/pkg/config/config.go index 948ae5f288..c49f458155 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -66,6 +66,8 @@ type Config struct { EnableRuntimeDetection bool `mapstructure:"runtimeDetectionEnabled"` EnableSbomGeneration bool `mapstructure:"sbomGenerationEnabled"` EnableSeccomp bool `mapstructure:"seccompServiceEnabled"` + HostMonitoringEnabled bool `mapstructure:"hostMonitoringEnabled"` + StandaloneMonitoringEnabled bool `mapstructure:"standaloneMonitoringEnabled"` SeccompProfileBackend string `mapstructure:"seccompProfileBackend"` EventBatchSize int `mapstructure:"eventBatchSize"` ExcludeJsonPaths []string `mapstructure:"excludeJsonPaths"` @@ -179,6 +181,8 @@ func LoadConfig(path string) (Config, error) { viper.SetDefault("dnsCacheSize", 50000) viper.SetDefault("seccompProfileBackend", "storage") // "storage" or "crd" viper.SetDefault("containerEolNotificationBuffer", 100) + viper.SetDefault("hostMonitoringEnabled", false) + viper.SetDefault("standaloneMonitoringEnabled", false) // HTTP Exporter Alert Bulking defaults viper.SetDefault("exporters::httpExporterConfig::bulkMaxAlerts", 50) viper.SetDefault("exporters::httpExporterConfig::bulkTimeoutSeconds", 10) diff --git a/pkg/containerwatcher/v2/container_watcher_collection.go b/pkg/containerwatcher/v2/container_watcher_collection.go index 59451d82b2..8065a3702e 100644 --- a/pkg/containerwatcher/v2/container_watcher_collection.go +++ b/pkg/containerwatcher/v2/container_watcher_collection.go @@ -6,8 +6,10 @@ import ( "time" containercollection "github.com/inspektor-gadget/inspektor-gadget/pkg/container-collection" + containerutils "github.com/inspektor-gadget/inspektor-gadget/pkg/container-utils" "github.com/inspektor-gadget/inspektor-gadget/pkg/operators/socketenricher" "github.com/inspektor-gadget/inspektor-gadget/pkg/params" + eventtypes "github.com/inspektor-gadget/inspektor-gadget/pkg/types" "github.com/inspektor-gadget/inspektor-gadget/pkg/utils/host" "github.com/kubescape/go-logger" "github.com/kubescape/go-logger/helpers" @@ -104,6 +106,27 @@ func (cw *ContainerWatcher) StartContainerCollection(ctx context.Context) error } logger.L().Info("ContainerManager - container collection initialized successfully") + // Create virtual host container if host monitoring enabled + if cw.cfg.HostMonitoringEnabled { + virtualHostContainer, err := GetHostAsContainer() + if err != nil { + logger.L().Warning("ContainerManager - failed to create virtual host container", + helpers.Error(err)) + } else { + cw.containerCollection.AddContainer(virtualHostContainer) + + // Manually trigger callbacks to ensure context detection runs + cw.containerCallback(containercollection.PubSubEvent{ + Type: containercollection.EventTypeAddContainer, + Container: virtualHostContainer, + }) + + logger.L().Info("ContainerManager - virtual host container created", + helpers.String("mntns", fmt.Sprintf("%d", virtualHostContainer.Mntns)), + helpers.String("pid", fmt.Sprintf("%d", virtualHostContainer.Runtime.ContainerPID))) + } + } + // Start monitoring for rule bindings notifications go cw.startRunningContainers() @@ -148,3 +171,24 @@ func (cw *ContainerWatcher) addRunningContainers(notf *rulebindingmanager.RuleBi cw.ruleManagedPods.Remove(k8sPodID) } } + +// GetHostAsContainer creates a synthetic container representing the host's mount namespace. +// This enables host-level events to be processed through the standard container infrastructure. +func GetHostAsContainer() (*containercollection.Container, error) { + hostInitPID := 1 + + // Get the mount namespace ID for PID 1 (init process) + mntns, err := containerutils.GetMntNs(hostInitPID) + if err != nil { + return nil, fmt.Errorf("getting mount namespace for host PID %d: %w", hostInitPID, err) + } + + return &containercollection.Container{ + Runtime: containercollection.RuntimeMetadata{ + BasicRuntimeMetadata: eventtypes.BasicRuntimeMetadata{ + ContainerPID: uint32(hostInitPID), + }, + }, + Mntns: mntns, + }, nil +} diff --git a/pkg/containerwatcher/v2/containercallback.go b/pkg/containerwatcher/v2/containercallback.go index 45aa3928ba..0fb5ce08cd 100644 --- a/pkg/containerwatcher/v2/containercallback.go +++ b/pkg/containerwatcher/v2/containercallback.go @@ -60,6 +60,12 @@ func (cw *ContainerWatcher) containerCallbackAsync(notif containercollection.Pub helpers.String("ContainerImageName", notif.Container.Runtime.ContainerImageName)) cw.metrics.ReportContainerStart() + // Skip shared data setup for virtual host container (identified by ContainerPID == 1) + if notif.Container.Runtime.ContainerPID == 1 { + logger.L().Debug("ContainerWatcher.containerCallback - skipping shared data setup for virtual host container") + return + } + // Set shared watched container data go cw.setSharedWatchedContainerData(notif.Container) case containercollection.EventTypeRemoveContainer: diff --git a/pkg/containerwatcher/v2/tracers/bpf.go b/pkg/containerwatcher/v2/tracers/bpf.go index 87f57b8a3f..d74c345035 100644 --- a/pkg/containerwatcher/v2/tracers/bpf.go +++ b/pkg/containerwatcher/v2/tracers/bpf.go @@ -68,7 +68,10 @@ func (bt *BpfTracer) Start(ctx context.Context) error { gadgetcontext.WithOrasReadonlyTarget(bt.ociStore), ) go func() { - err := bt.runtime.RunGadget(bt.gadgetCtx, nil, nil) + params := map[string]string{ + "operator.LocalManager.host": "true", // don't error if container-collection is nil when using local manager + } + err := bt.runtime.RunGadget(bt.gadgetCtx, nil, params) if err != nil { logger.L().Error("Error running gadget", helpers.String("gadget", bt.gadgetCtx.Name()), helpers.Error(err)) } diff --git a/pkg/containerwatcher/v2/tracers/capabilities.go b/pkg/containerwatcher/v2/tracers/capabilities.go index 74bd0b3d74..b00cc1dc5c 100644 --- a/pkg/containerwatcher/v2/tracers/capabilities.go +++ b/pkg/containerwatcher/v2/tracers/capabilities.go @@ -70,6 +70,7 @@ func (ct *CapabilitiesTracer) Start(ctx context.Context) error { params := map[string]string{ "operator.oci.ebpf.collect-kstack": "false", "operator.oci.ebpf.unique": "true", + "operator.LocalManager.host": "true", // don't error if container-collection is nil when using local manager } err := ct.runtime.RunGadget(ct.gadgetCtx, nil, params) if err != nil { diff --git a/pkg/containerwatcher/v2/tracers/dns.go b/pkg/containerwatcher/v2/tracers/dns.go index 0fd3dd0d9f..215e2df70f 100644 --- a/pkg/containerwatcher/v2/tracers/dns.go +++ b/pkg/containerwatcher/v2/tracers/dns.go @@ -74,7 +74,8 @@ func (dt *DNSTracer) Start(ctx context.Context) error { ) go func() { params := map[string]string{ - "operator.oci.ebpf.paths": "true", // CWD paths in events + "operator.oci.ebpf.paths": "true", // CWD paths in events + "operator.LocalManager.host": "true", // don't error if container-collection is nil when using local manager } err := dt.runtime.RunGadget(dt.gadgetCtx, nil, params) if err != nil { diff --git a/pkg/containerwatcher/v2/tracers/exec.go b/pkg/containerwatcher/v2/tracers/exec.go index 21a414a90c..6c25026ef1 100644 --- a/pkg/containerwatcher/v2/tracers/exec.go +++ b/pkg/containerwatcher/v2/tracers/exec.go @@ -69,7 +69,8 @@ func (et *ExecTracer) Start(ctx context.Context) error { ) go func() { params := map[string]string{ - "operator.oci.ebpf.paths": "true", // CWD paths in events + "operator.oci.ebpf.paths": "true", // CWD paths in events + "operator.LocalManager.host": "true", // don't error if container-collection is nil when using local manager } err := et.runtime.RunGadget(et.gadgetCtx, nil, params) if err != nil { diff --git a/pkg/containerwatcher/v2/tracers/exit.go b/pkg/containerwatcher/v2/tracers/exit.go index 03b0a3a72e..cdf9b2dd15 100644 --- a/pkg/containerwatcher/v2/tracers/exit.go +++ b/pkg/containerwatcher/v2/tracers/exit.go @@ -64,7 +64,10 @@ func (et *ExitTracer) Start(ctx context.Context) error { gadgetcontext.WithOrasReadonlyTarget(et.ociStore), ) go func() { - err := et.runtime.RunGadget(et.gadgetCtx, nil, nil) + params := map[string]string{ + "operator.LocalManager.host": "true", // don't error if container-collection is nil when using local manager + } + err := et.runtime.RunGadget(et.gadgetCtx, nil, params) if err != nil { logger.L().Error("Error running gadget", helpers.String("gadget", et.gadgetCtx.Name()), helpers.Error(err)) } diff --git a/pkg/containerwatcher/v2/tracers/fork.go b/pkg/containerwatcher/v2/tracers/fork.go index d31d133d7f..d06b159e5b 100644 --- a/pkg/containerwatcher/v2/tracers/fork.go +++ b/pkg/containerwatcher/v2/tracers/fork.go @@ -64,7 +64,10 @@ func (ft *ForkTracer) Start(ctx context.Context) error { gadgetcontext.WithOrasReadonlyTarget(ft.ociStore), ) go func() { - err := ft.runtime.RunGadget(ft.gadgetCtx, nil, nil) + params := map[string]string{ + "operator.LocalManager.host": "true", // don't error if container-collection is nil when using local manager + } + err := ft.runtime.RunGadget(ft.gadgetCtx, nil, params) if err != nil { logger.L().Error("Error running gadget", helpers.String("gadget", ft.gadgetCtx.Name()), helpers.Error(err)) } diff --git a/pkg/containerwatcher/v2/tracers/hardlink.go b/pkg/containerwatcher/v2/tracers/hardlink.go index be0558af7a..a3dfa85ecc 100644 --- a/pkg/containerwatcher/v2/tracers/hardlink.go +++ b/pkg/containerwatcher/v2/tracers/hardlink.go @@ -68,7 +68,10 @@ func (ht *HardlinkTracer) Start(ctx context.Context) error { gadgetcontext.WithOrasReadonlyTarget(ht.ociStore), ) go func() { - err := ht.runtime.RunGadget(ht.gadgetCtx, nil, nil) + params := map[string]string{ + "operator.LocalManager.host": "true", // don't error if container-collection is nil when using local manager + } + err := ht.runtime.RunGadget(ht.gadgetCtx, nil, params) if err != nil { logger.L().Error("Error running gadget", helpers.String("gadget", ht.gadgetCtx.Name()), helpers.Error(err)) } diff --git a/pkg/containerwatcher/v2/tracers/http.go b/pkg/containerwatcher/v2/tracers/http.go index 36f3e5b7e2..3577c7e7f9 100644 --- a/pkg/containerwatcher/v2/tracers/http.go +++ b/pkg/containerwatcher/v2/tracers/http.go @@ -82,7 +82,10 @@ func (ht *HTTPTracer) Start(ctx context.Context) error { gadgetcontext.WithOrasReadonlyTarget(ht.ociStore), ) go func() { - err := ht.runtime.RunGadget(ht.gadgetCtx, nil, nil) + params := map[string]string{ + "operator.LocalManager.host": "true", // don't error if container-collection is nil when using local manager + } + err := ht.runtime.RunGadget(ht.gadgetCtx, nil, params) if err != nil { logger.L().Error("Error running gadget", helpers.String("gadget", ht.gadgetCtx.Name()), helpers.Error(err)) } diff --git a/pkg/containerwatcher/v2/tracers/iouring.go b/pkg/containerwatcher/v2/tracers/iouring.go index 8977fc396c..2f23b27fd1 100644 --- a/pkg/containerwatcher/v2/tracers/iouring.go +++ b/pkg/containerwatcher/v2/tracers/iouring.go @@ -82,7 +82,10 @@ func (it *IoUringTracer) Start(ctx context.Context) error { gadgetcontext.WithOrasReadonlyTarget(it.ociStore), ) go func() { - err := it.runtime.RunGadget(it.gadgetCtx, nil, nil) + params := map[string]string{ + "operator.LocalManager.host": "true", // don't error if container-collection is nil when using local manager + } + err := it.runtime.RunGadget(it.gadgetCtx, nil, params) if err != nil { logger.L().Error("Error running gadget", helpers.String("gadget", it.gadgetCtx.Name()), helpers.Error(err)) } diff --git a/pkg/containerwatcher/v2/tracers/kmod.go b/pkg/containerwatcher/v2/tracers/kmod.go index 6d02d06143..6eaa373764 100644 --- a/pkg/containerwatcher/v2/tracers/kmod.go +++ b/pkg/containerwatcher/v2/tracers/kmod.go @@ -68,7 +68,10 @@ func (kt *KmodTracer) Start(ctx context.Context) error { gadgetcontext.WithOrasReadonlyTarget(kt.ociStore), ) go func() { - err := kt.runtime.RunGadget(kt.gadgetCtx, nil, nil) + params := map[string]string{ + "operator.LocalManager.host": "true", // don't error if container-collection is nil when using local manager + } + err := kt.runtime.RunGadget(kt.gadgetCtx, nil, params) if err != nil { logger.L().Error("Error running gadget", helpers.String("gadget", kt.gadgetCtx.Name()), helpers.Error(err)) } diff --git a/pkg/containerwatcher/v2/tracers/network.go b/pkg/containerwatcher/v2/tracers/network.go index c5c36537b1..e042203a55 100644 --- a/pkg/containerwatcher/v2/tracers/network.go +++ b/pkg/containerwatcher/v2/tracers/network.go @@ -83,7 +83,8 @@ func (nt *NetworkTracer) Start(ctx context.Context) error { ) go func() { params := map[string]string{ - "operator.oci.annotate": "network:kubenameresolver.enable=true", + "operator.oci.annotate": "network:kubenameresolver.enable=true", + "operator.LocalManager.host": "true", // don't error if container-collection is nil when using local manager } err := nt.runtime.RunGadget(nt.gadgetCtx, nil, params) if err != nil { diff --git a/pkg/containerwatcher/v2/tracers/open.go b/pkg/containerwatcher/v2/tracers/open.go index 4b5b6e2106..210dd5b7e5 100644 --- a/pkg/containerwatcher/v2/tracers/open.go +++ b/pkg/containerwatcher/v2/tracers/open.go @@ -70,7 +70,8 @@ func (ot *OpenTracer) Start(ctx context.Context) error { ) go func() { params := map[string]string{ - "operator.oci.ebpf.paths": strconv.FormatBool(ot.cfg.EnableFullPathTracing), + "operator.oci.ebpf.paths": strconv.FormatBool(ot.cfg.EnableFullPathTracing), + "operator.LocalManager.host": "true", // don't error if container-collection is nil when using local manager } err := ot.runtime.RunGadget(ot.gadgetCtx, nil, params) if err != nil { @@ -128,10 +129,6 @@ func (ot *OpenTracer) eventOperator() operators.DataOperator { // callback handles open events from the tracer func (ot *OpenTracer) callback(event utils.OpenEvent) { - if event.GetContainer() == "" { - return - } - errorRaw := event.GetError() if errorRaw > -1 { // Handle the event with syscall enrichment diff --git a/pkg/containerwatcher/v2/tracers/ptrace.go b/pkg/containerwatcher/v2/tracers/ptrace.go index d01625eb16..68201e686f 100644 --- a/pkg/containerwatcher/v2/tracers/ptrace.go +++ b/pkg/containerwatcher/v2/tracers/ptrace.go @@ -64,7 +64,10 @@ func (pt *PtraceTracer) Start(ctx context.Context) error { gadgetcontext.WithOrasReadonlyTarget(pt.ociStore), ) go func() { - err := pt.runtime.RunGadget(pt.gadgetCtx, nil, nil) + params := map[string]string{ + "operator.LocalManager.host": "true", // don't error if container-collection is nil when using local manager + } + err := pt.runtime.RunGadget(pt.gadgetCtx, nil, params) if err != nil { logger.L().Error("Error running gadget", helpers.String("gadget", pt.gadgetCtx.Name()), helpers.Error(err)) } diff --git a/pkg/containerwatcher/v2/tracers/randomx.go b/pkg/containerwatcher/v2/tracers/randomx.go index dd6b6bba36..eea8d9a9f4 100644 --- a/pkg/containerwatcher/v2/tracers/randomx.go +++ b/pkg/containerwatcher/v2/tracers/randomx.go @@ -65,7 +65,10 @@ func (rt *RandomXTracer) Start(ctx context.Context) error { gadgetcontext.WithOrasReadonlyTarget(rt.ociStore), ) go func() { - err := rt.runtime.RunGadget(rt.gadgetCtx, nil, nil) + params := map[string]string{ + "operator.LocalManager.host": "true", // don't error if container-collection is nil when using local manager + } + err := rt.runtime.RunGadget(rt.gadgetCtx, nil, params) if err != nil { logger.L().Error("Error running gadget", helpers.String("gadget", rt.gadgetCtx.Name()), helpers.Error(err)) } diff --git a/pkg/containerwatcher/v2/tracers/ssh.go b/pkg/containerwatcher/v2/tracers/ssh.go index c29157dc01..c6e9f899ea 100644 --- a/pkg/containerwatcher/v2/tracers/ssh.go +++ b/pkg/containerwatcher/v2/tracers/ssh.go @@ -69,7 +69,10 @@ func (st *SSHTracer) Start(ctx context.Context) error { gadgetcontext.WithOrasReadonlyTarget(st.ociStore), ) go func() { - err := st.runtime.RunGadget(st.gadgetCtx, nil, nil) + params := map[string]string{ + "operator.LocalManager.host": "true", // don't error if container-collection is nil when using local manager + } + err := st.runtime.RunGadget(st.gadgetCtx, nil, params) if err != nil { logger.L().Error("Error running gadget", helpers.String("gadget", st.gadgetCtx.Name()), helpers.Error(err)) } diff --git a/pkg/containerwatcher/v2/tracers/symlink.go b/pkg/containerwatcher/v2/tracers/symlink.go index 01aa8cc6e1..8e292aeacd 100644 --- a/pkg/containerwatcher/v2/tracers/symlink.go +++ b/pkg/containerwatcher/v2/tracers/symlink.go @@ -68,7 +68,10 @@ func (st *SymlinkTracer) Start(ctx context.Context) error { gadgetcontext.WithOrasReadonlyTarget(st.ociStore), ) go func() { - err := st.runtime.RunGadget(st.gadgetCtx, nil, nil) + params := map[string]string{ + "operator.LocalManager.host": "true", // don't error if container-collection is nil when using local manager + } + err := st.runtime.RunGadget(st.gadgetCtx, nil, params) if err != nil { logger.L().Error("Error running gadget", helpers.String("gadget", st.gadgetCtx.Name()), helpers.Error(err)) } diff --git a/pkg/containerwatcher/v2/tracers/syscall.go b/pkg/containerwatcher/v2/tracers/syscall.go index cc2e6d8f98..fbbcb13224 100644 --- a/pkg/containerwatcher/v2/tracers/syscall.go +++ b/pkg/containerwatcher/v2/tracers/syscall.go @@ -68,6 +68,7 @@ func (st *SyscallTracer) Start(ctx context.Context) error { params := map[string]string{ "operator.oci.ebpf.map-fetch-count": "0", "operator.oci.ebpf.map-fetch-interval": "30s", + "operator.LocalManager.host": "true", // don't error if container-collection is nil when using local manager } err := st.runtime.RunGadget(st.gadgetCtx, nil, params) if err != nil { diff --git a/pkg/containerwatcher/v2/tracers/unshare.go b/pkg/containerwatcher/v2/tracers/unshare.go index fdf96ac657..d6dd7e61bd 100644 --- a/pkg/containerwatcher/v2/tracers/unshare.go +++ b/pkg/containerwatcher/v2/tracers/unshare.go @@ -68,7 +68,10 @@ func (ut *UnshareTracer) Start(ctx context.Context) error { gadgetcontext.WithOrasReadonlyTarget(ut.ociStore), ) go func() { - err := ut.runtime.RunGadget(ut.gadgetCtx, nil, nil) + params := map[string]string{ + "operator.LocalManager.host": "true", // don't error if container-collection is nil when using local manager + } + err := ut.runtime.RunGadget(ut.gadgetCtx, nil, params) if err != nil { logger.L().Error("Error running gadget", helpers.String("gadget", ut.gadgetCtx.Name()), helpers.Error(err)) } diff --git a/pkg/contextdetection/detectors/host.go b/pkg/contextdetection/detectors/host.go new file mode 100644 index 0000000000..7cfc3e61bb --- /dev/null +++ b/pkg/contextdetection/detectors/host.go @@ -0,0 +1,57 @@ +package detectors + +import ( + "os" + + containercollection "github.com/inspektor-gadget/inspektor-gadget/pkg/container-collection" + "github.com/kubescape/node-agent/pkg/contextdetection" +) + +type HostContextInfo struct { + HostName string +} + +func (h *HostContextInfo) Context() contextdetection.EventSourceContext { + return contextdetection.Host +} + +func (h *HostContextInfo) WorkloadID() string { + if h.HostName != "" { + return h.HostName + } + return "host" +} + +type HostDetector struct { + name string + hostName string +} + +func NewHostDetector() *HostDetector { + hostName, _ := os.Hostname() + return &HostDetector{ + name: "HostDetector", + hostName: hostName, + } +} + +func (hd *HostDetector) Detect(container *containercollection.Container) (contextdetection.ContextInfo, bool) { + if container == nil { + return nil, false + } + + if container.K8s.PodName != "" || container.K8s.Namespace != "" { + return nil, false + } + + if container.ContainerPid() == 1 { + hostInfo := &HostContextInfo{HostName: hd.hostName} + return hostInfo, true + } + + return nil, false +} + +func (hd *HostDetector) Priority() int { + return 1 +} diff --git a/pkg/contextdetection/detectors/k8s.go b/pkg/contextdetection/detectors/k8s.go new file mode 100644 index 0000000000..ece4f69dfb --- /dev/null +++ b/pkg/contextdetection/detectors/k8s.go @@ -0,0 +1,52 @@ +package detectors + +import ( + "fmt" + + containercollection "github.com/inspektor-gadget/inspektor-gadget/pkg/container-collection" + "github.com/kubescape/node-agent/pkg/contextdetection" +) + +type K8sContextInfo struct { + Namespace string + PodName string + Workload string + WorkloadUID string +} + +func (k *K8sContextInfo) Context() contextdetection.EventSourceContext { + return contextdetection.Kubernetes +} + +func (k *K8sContextInfo) WorkloadID() string { + return fmt.Sprintf("%s/%s", k.Namespace, k.PodName) +} + +type K8sDetector struct { + name string +} + +func NewK8sDetector() *K8sDetector { + return &K8sDetector{name: "K8sDetector"} +} + +func (kd *K8sDetector) Detect(container *containercollection.Container) (contextdetection.ContextInfo, bool) { + if container == nil { + return nil, false + } + + if container.K8s.PodName == "" || container.K8s.Namespace == "" { + return nil, false + } + + k8sInfo := &K8sContextInfo{ + Namespace: container.K8s.Namespace, + PodName: container.K8s.PodName, + } + + return k8sInfo, true +} + +func (kd *K8sDetector) Priority() int { + return 0 +} diff --git a/pkg/contextdetection/detectors/manager.go b/pkg/contextdetection/detectors/manager.go new file mode 100644 index 0000000000..5c01f2f4b8 --- /dev/null +++ b/pkg/contextdetection/detectors/manager.go @@ -0,0 +1,67 @@ +package detectors + +import ( + "errors" + + containercollection "github.com/inspektor-gadget/inspektor-gadget/pkg/container-collection" + "github.com/kubescape/go-logger" + "github.com/kubescape/go-logger/helpers" + "github.com/kubescape/node-agent/pkg/contextdetection" +) + +var ( + ErrInvalidContainer = errors.New("invalid container: nil provided") +) + +type DetectorManager struct { + registry contextdetection.Registry + detectors []contextdetection.ContextDetector +} + +// NewDetectorManager creates a new DetectorManager with the given registry. +// Detectors are initialized in priority order: K8s (0), Host (1), Standalone (2). +func NewDetectorManager(registry contextdetection.Registry) *DetectorManager { + dm := &DetectorManager{ + registry: registry, + detectors: []contextdetection.ContextDetector{ + NewK8sDetector(), + NewHostDetector(), + NewStandaloneDetector(), + }, + } + + return dm +} + +// DetectContext detects the context of a container by running detectors in priority order. +// Returns the ContextInfo from the first detector that matches, or a default Standalone context +// if no detector matches. +func (dm *DetectorManager) DetectContext(container *containercollection.Container) (contextdetection.ContextInfo, error) { + if container == nil { + logger.L().Warning("DetectorManager - nil container provided") + return nil, ErrInvalidContainer + } + + containerID := container.Runtime.ContainerID + logger.L().Debug("DetectorManager - detecting context for container", + helpers.String("containerID", containerID)) + + for _, detector := range dm.detectors { + contextInfo, detected := detector.Detect(container) + if detected { + logger.L().Debug("DetectorManager - detected context", + helpers.String("containerID", containerID), + helpers.String("context", string(contextInfo.Context())), + helpers.String("workloadID", contextInfo.WorkloadID())) + return contextInfo, nil + } + } + + logger.L().Warning("DetectorManager - no detector matched, defaulting to Standalone", + helpers.String("containerID", containerID)) + + return &StandaloneContextInfo{ + ContainerID: container.Runtime.ContainerID, + ContainerName: container.Runtime.ContainerName, + }, nil +} diff --git a/pkg/contextdetection/detectors/standalone.go b/pkg/contextdetection/detectors/standalone.go new file mode 100644 index 0000000000..ef2f8bdd19 --- /dev/null +++ b/pkg/contextdetection/detectors/standalone.go @@ -0,0 +1,50 @@ +package detectors + +import ( + containercollection "github.com/inspektor-gadget/inspektor-gadget/pkg/container-collection" + "github.com/kubescape/node-agent/pkg/contextdetection" +) + +type StandaloneContextInfo struct { + ContainerID string + ContainerName string +} + +func (s *StandaloneContextInfo) Context() contextdetection.EventSourceContext { + return contextdetection.Standalone +} + +func (s *StandaloneContextInfo) WorkloadID() string { + if s.ContainerID == "" { + return s.ContainerName + } + if s.ContainerName != "" { + return s.ContainerName + ":" + s.ContainerID + } + return s.ContainerID +} + +type StandaloneDetector struct { + name string +} + +func NewStandaloneDetector() *StandaloneDetector { + return &StandaloneDetector{name: "StandaloneDetector"} +} + +func (sd *StandaloneDetector) Detect(container *containercollection.Container) (contextdetection.ContextInfo, bool) { + if container == nil { + return nil, false + } + + standaloneInfo := &StandaloneContextInfo{ + ContainerID: container.Runtime.ContainerID, + ContainerName: container.Runtime.ContainerName, + } + + return standaloneInfo, true +} + +func (sd *StandaloneDetector) Priority() int { + return 2 +} diff --git a/pkg/contextdetection/registry.go b/pkg/contextdetection/registry.go new file mode 100644 index 0000000000..55b8e7faa5 --- /dev/null +++ b/pkg/contextdetection/registry.go @@ -0,0 +1,80 @@ +package contextdetection + +import ( + "errors" + "fmt" + + "github.com/goradd/maps" + "github.com/kubescape/go-logger" + "github.com/kubescape/go-logger/helpers" +) + +var ( + ErrInvalidMntns = errors.New("invalid mount namespace ID: cannot be zero") +) + +// Registry defines the interface for managing mount namespace to context mappings. +type Registry interface { + // Register adds or updates a mount namespace entry in the registry. + // Returns an error if the mount namespace ID is invalid (zero). + Register(mntns uint64, info ContextInfo) error + + // Lookup retrieves the context information for a mount namespace. + // Returns the context info and true if found, false otherwise. + Lookup(mntns uint64) (ContextInfo, bool) + + // Unregister removes a mount namespace entry from the registry. + Unregister(mntns uint64) +} + +// MntnsRegistry maps mount namespace IDs to their context information. +type MntnsRegistry struct { + entries maps.SafeMap[uint64, ContextInfo] + hostMntns uint64 +} + +// Verify that MntnsRegistry implements the Registry interface. +var _ Registry = (*MntnsRegistry)(nil) + +// NewMntnsRegistry creates a new MntnsRegistry instance. +// The hostMntns should be set separately via SetHostMntns after initialization. +func NewMntnsRegistry() *MntnsRegistry { + return &MntnsRegistry{} +} + +// Register adds or updates a mount namespace entry in the registry. +func (r *MntnsRegistry) Register(mntns uint64, info ContextInfo) error { + if mntns == 0 { + return ErrInvalidMntns + } + + if r.entries.Has(mntns) { + logger.L().Warning("MntnsRegistry - mount namespace already registered, skipping", + helpers.String("mntns", fmt.Sprintf("%d", mntns)), + helpers.String("context", string(info.Context()))) + return nil + } + + r.entries.Set(mntns, info) + logger.L().Debug("MntnsRegistry - registered mount namespace", + helpers.String("mntns", fmt.Sprintf("%d", mntns)), + helpers.String("context", string(info.Context())), + helpers.String("workloadID", info.WorkloadID())) + + return nil +} + +// Lookup retrieves the context information for a mount namespace. +func (r *MntnsRegistry) Lookup(mntns uint64) (ContextInfo, bool) { + info, ok := r.entries.Load(mntns) + return info, ok +} + +// Unregister removes a mount namespace entry from the registry. +func (r *MntnsRegistry) Unregister(mntns uint64) { + if r.entries.Has(mntns) { + r.entries.Delete(mntns) + logger.L().Debug("MntnsRegistry - unregistered mount namespace", + helpers.String("mntns", fmt.Sprintf("%d", mntns))) + } +} diff --git a/pkg/contextdetection/types.go b/pkg/contextdetection/types.go new file mode 100644 index 0000000000..c6889a8cb2 --- /dev/null +++ b/pkg/contextdetection/types.go @@ -0,0 +1,23 @@ +package contextdetection + +import ( + containercollection "github.com/inspektor-gadget/inspektor-gadget/pkg/container-collection" +) + +type EventSourceContext string + +const ( + Kubernetes EventSourceContext = "kubernetes" + Host EventSourceContext = "host" + Standalone EventSourceContext = "standalone" +) + +type ContextInfo interface { + Context() EventSourceContext + WorkloadID() string +} + +type ContextDetector interface { + Detect(container *containercollection.Container) (ContextInfo, bool) + Priority() int +} diff --git a/pkg/ebpf/events/enriched_event.go b/pkg/ebpf/events/enriched_event.go index 5f04a8476b..9d869ef597 100644 --- a/pkg/ebpf/events/enriched_event.go +++ b/pkg/ebpf/events/enriched_event.go @@ -4,6 +4,7 @@ import ( "time" apitypes "github.com/armosec/armoapi-go/armotypes" + "github.com/kubescape/node-agent/pkg/contextdetection" "github.com/kubescape/node-agent/pkg/utils" ) @@ -18,10 +19,18 @@ func NewEnrichedEvent(event utils.K8sEvent, timestamp time.Time, containerID str } type EnrichedEvent struct { - Event utils.K8sEvent - Timestamp time.Time - ContainerID string - ProcessTree apitypes.Process - PID uint32 - PPID uint32 + Event utils.K8sEvent + Timestamp time.Time + ContainerID string + ProcessTree apitypes.Process + PID uint32 + PPID uint32 + // SourceContext holds the context information for this event (K8s, Host, or Standalone). + // This is populated during event enrichment if the feature is enabled. + // May be nil for legacy K8s-only events or when feature is disabled. + SourceContext contextdetection.ContextInfo + // MountNamespaceID is the mount namespace ID from the container. + // This uniquely identifies the container/host and is used for context lookup. + // May be 0 if unavailable. + MountNamespaceID uint64 } diff --git a/pkg/rulemanager/containercallbacks.go b/pkg/rulemanager/containercallbacks.go index d248dac1a3..f54899ef0f 100644 --- a/pkg/rulemanager/containercallbacks.go +++ b/pkg/rulemanager/containercallbacks.go @@ -10,6 +10,7 @@ import ( containercollection "github.com/inspektor-gadget/inspektor-gadget/pkg/container-collection" "github.com/kubescape/go-logger" "github.com/kubescape/go-logger/helpers" + "github.com/kubescape/node-agent/pkg/contextdetection/detectors" "github.com/kubescape/node-agent/pkg/objectcache" "github.com/kubescape/node-agent/pkg/utils" ) @@ -62,6 +63,26 @@ func (rm *RuleManager) ContainerCallback(notif containercollection.PubSubEvent) return } + // Detect context and add to the registry + contextInfo, err := rm.detectorManager.DetectContext(notif.Container) + if err != nil { + logger.L().Warning("RuleManager - failed to detect context, defaulting to standalone", + helpers.String("container ID", notif.Container.Runtime.ContainerID), + helpers.Error(err)) + contextInfo = &detectors.StandaloneContextInfo{ + ContainerID: notif.Container.Runtime.ContainerID, + ContainerName: notif.Container.Runtime.ContainerName, + } + } + + if contextInfo != nil && notif.Container.Mntns != 0 { + if err := rm.mntnsRegistry.Register(notif.Container.Mntns, contextInfo); err != nil { + logger.L().Warning("RuleManager - failed to register in mntns registry", + helpers.String("container ID", notif.Container.Runtime.ContainerID), + helpers.Error(err)) + } + } + rm.trackedContainers.Add(k8sContainerID) shim, err := utils.GetProcessStat(int(notif.Container.ContainerPid())) if err != nil { @@ -76,6 +97,10 @@ func (rm *RuleManager) ContainerCallback(notif containercollection.PubSubEvent) helpers.String("container ID", notif.Container.Runtime.ContainerID), helpers.String("k8s workload", k8sContainerID)) + if notif.Container.Mntns != 0 { + rm.mntnsRegistry.Unregister(notif.Container.Mntns) + } + rm.trackedContainers.Remove(k8sContainerID) namespace := notif.Container.K8s.Namespace podName := notif.Container.K8s.PodName diff --git a/pkg/rulemanager/rule_manager.go b/pkg/rulemanager/rule_manager.go index f39b072317..408631e5a5 100644 --- a/pkg/rulemanager/rule_manager.go +++ b/pkg/rulemanager/rule_manager.go @@ -14,6 +14,8 @@ import ( "github.com/kubescape/go-logger/helpers" helpersv1 "github.com/kubescape/k8s-interface/instanceidhandler/v1/helpers" "github.com/kubescape/node-agent/pkg/config" + "github.com/kubescape/node-agent/pkg/contextdetection" + "github.com/kubescape/node-agent/pkg/contextdetection/detectors" "github.com/kubescape/node-agent/pkg/dnsmanager" "github.com/kubescape/node-agent/pkg/ebpf/events" "github.com/kubescape/node-agent/pkg/exporters" @@ -57,6 +59,8 @@ type RuleManager struct { adapterFactory *ruleadapters.EventRuleAdapterFactory ruleFailureCreator ruleadapters.RuleFailureCreatorInterface rulePolicyValidator *RulePolicyValidator + mntnsRegistry contextdetection.Registry + detectorManager *detectors.DetectorManager } var _ RuleManagerClient = (*RuleManager)(nil) @@ -75,9 +79,11 @@ func CreateRuleManager( ruleCooldown *rulecooldown.RuleCooldown, adapterFactory *ruleadapters.EventRuleAdapterFactory, celEvaluator cel.CELRuleEvaluator, + mntnsRegistry contextdetection.Registry, ) (*RuleManager, error) { ruleFailureCreator := ruleadapters.NewRuleFailureCreator(enricher, dnsManager, adapterFactory) rulePolicyValidator := NewRulePolicyValidator(objectCache) + detectorManager := detectors.NewDetectorManager(mntnsRegistry) r := &RuleManager{ cfg: cfg, @@ -95,6 +101,8 @@ func CreateRuleManager( celEvaluator: celEvaluator, ruleFailureCreator: ruleFailureCreator, rulePolicyValidator: rulePolicyValidator, + mntnsRegistry: mntnsRegistry, + detectorManager: detectorManager, } return r, nil @@ -125,20 +133,46 @@ func (rm *RuleManager) startRuleManager(container *containercollection.Container } func (rm *RuleManager) ReportEnrichedEvent(enrichedEvent *events.EnrichedEvent) { + rm.enrichEventWithContext(enrichedEvent) + var profileExists bool + var details string namespace := enrichedEvent.Event.GetNamespace() pod := enrichedEvent.Event.GetPod() - podId := utils.CreateK8sPodID(namespace, pod) - details, ok := rm.podToWlid.Load(podId) - if !ok { - return + + // Determine workload ID based on the context type + isK8sContext := enrichedEvent.SourceContext == nil || enrichedEvent.SourceContext.Context() == contextdetection.Kubernetes + + if isK8sContext { + if pod == "" || namespace == "" { + return + } + podId := utils.CreateK8sPodID(namespace, pod) + var ok bool + details, ok = rm.podToWlid.Load(podId) + if !ok { + return + } + } else { + // Host or Standalone context: use SourceContext.WorkloadID() + if enrichedEvent.SourceContext == nil { + return + } + details = enrichedEvent.SourceContext.WorkloadID() + if details == "" { + return + } } - if pod == "" || namespace == "" { - return + // Retrieve rules based on context: K8s uses pod-based bindings + var rules []typesv1.Rule + if enrichedEvent.SourceContext == nil || enrichedEvent.SourceContext.Context() == contextdetection.Kubernetes { + rules = rm.ruleBindingCache.ListRulesForPod(namespace, pod) + } else { + // TODO: rule filtering based on context + rules = rm.ruleBindingCache.GetRuleCreator().CreateAllRules() } - rules := rm.ruleBindingCache.ListRulesForPod(namespace, pod) if len(rules) == 0 { return } @@ -150,12 +184,23 @@ func (rm *RuleManager) ReportEnrichedEvent(enrichedEvent *events.EnrichedEvent) _, apChecksum, err := profilehelper.GetContainerApplicationProfile(rm.objectCache, enrichedEvent.ContainerID) profileExists = err == nil + // Early exit if monitoring is disabled for this context - skip rule evaluation + if !rm.isMonitoringEnabledForContext(enrichedEvent.SourceContext) { + return + } + eventType := enrichedEvent.Event.GetEventType() for _, rule := range rules { if !rule.Enabled { continue } - if !profileExists && rule.ProfileDependency == armotypes.Required { + + if !RuleAppliesToContext(&rule, enrichedEvent.SourceContext) { + continue + } + // Skip profile dependency checks for non-K8s contexts (profiles are K8s-specific) + // Only K8s contexts should enforce profile dependencies + if isK8sContext && !profileExists && rule.ProfileDependency == armotypes.Required { continue } @@ -211,6 +256,45 @@ func (rm *RuleManager) ReportEnrichedEvent(enrichedEvent *events.EnrichedEvent) } } +func (rm *RuleManager) enrichEventWithContext(enrichedEvent *events.EnrichedEvent) { + // Extract mount namespace ID from the event + mntnsID := uint64(0) + if enrichEvent, ok := enrichedEvent.Event.(utils.EnrichEvent); ok { + mntnsID = enrichEvent.GetMountNsID() + } + enrichedEvent.MountNamespaceID = mntnsID + + if mntnsID == 0 { + return + } + if contextInfo, found := rm.mntnsRegistry.Lookup(mntnsID); found { + enrichedEvent.SourceContext = contextInfo + logger.L().Debug("RuleManager - enriched event with context", + helpers.String("mntns", fmt.Sprintf("%d", mntnsID)), + helpers.String("context", string(contextInfo.Context()))) + } +} + +func (rm *RuleManager) isMonitoringEnabledForContext(sourceContext contextdetection.ContextInfo) bool { + if sourceContext == nil { + // No context information, default to Kubernetes (backward compatible) + return true + } + + contextType := sourceContext.Context() + switch contextType { + case contextdetection.Host: + return rm.cfg.HostMonitoringEnabled + case contextdetection.Standalone: + return rm.cfg.StandaloneMonitoringEnabled + case contextdetection.Kubernetes: + // Kubernetes monitoring is always enabled (backward compatible) + return true + default: + return true + } +} + func (rm *RuleManager) HasApplicableRuleBindings(namespace, name string) bool { return len(rm.ruleBindingCache.ListRulesForPod(namespace, name)) > 0 } diff --git a/pkg/rulemanager/ruleadapters/creator.go b/pkg/rulemanager/ruleadapters/creator.go index 450ed65ba8..a9c3f904d0 100644 --- a/pkg/rulemanager/ruleadapters/creator.go +++ b/pkg/rulemanager/ruleadapters/creator.go @@ -3,6 +3,7 @@ package ruleadapters import ( "errors" "fmt" + "os" "path/filepath" "reflect" "time" @@ -15,6 +16,7 @@ import ( "github.com/kubescape/go-logger" "github.com/kubescape/go-logger/helpers" helpersv1 "github.com/kubescape/k8s-interface/instanceidhandler/v1/helpers" + "github.com/kubescape/node-agent/pkg/contextdetection" "github.com/kubescape/node-agent/pkg/dnsmanager" "github.com/kubescape/node-agent/pkg/ebpf/events" "github.com/kubescape/node-agent/pkg/objectcache" @@ -87,6 +89,7 @@ func (r *RuleFailureCreator) CreateRuleFailure(rule typesv1.Rule, enrichedEvent r.setRuntimeAlertK8sDetails(ruleFailure, objectCache) r.setCloudServices(ruleFailure) r.setProfileMetadata(rule, ruleFailure, objectCache) + r.setContextSpecificFields(ruleFailure, enrichedEvent) r.enrichRuleFailure(ruleFailure) if enrichedEvent.ProcessTree.PID != 0 { @@ -110,6 +113,11 @@ func (r *RuleFailureCreator) enrichRuleFailure(ruleFailure *types.GenericRuleFai } func (r *RuleFailureCreator) setProfileMetadata(rule typesv1.Rule, ruleFailure *types.GenericRuleFailure, objectCache objectcache.ObjectCache) { + triggerEvent := ruleFailure.GetTriggerEvent() + if triggerEvent == nil { + return + } + var profileType armotypes.ProfileType baseRuntimeAlert := ruleFailure.GetBaseRuntimeAlert() profileRequirment := rule.ProfileDependency @@ -128,7 +136,7 @@ func (r *RuleFailureCreator) setProfileMetadata(rule typesv1.Rule, ruleFailure * switch profileType { case armotypes.ApplicationProfile: - state := objectCache.ApplicationProfileCache().GetApplicationProfileState(ruleFailure.GetTriggerEvent().GetContainerID()) + state := objectCache.ApplicationProfileCache().GetApplicationProfileState(triggerEvent.GetContainerID()) if state != nil { profileMetadata := &armotypes.ProfileMetadata{ Status: state.Status, @@ -145,7 +153,7 @@ func (r *RuleFailureCreator) setProfileMetadata(rule typesv1.Rule, ruleFailure * } case armotypes.NetworkProfile: - state := objectCache.NetworkNeighborhoodCache().GetNetworkNeighborhoodState(ruleFailure.GetTriggerEvent().GetContainerID()) + state := objectCache.NetworkNeighborhoodCache().GetNetworkNeighborhoodState(triggerEvent.GetContainerID()) if state != nil { profileMetadata := &armotypes.ProfileMetadata{ Status: state.Status, @@ -172,7 +180,12 @@ func (r *RuleFailureCreator) setProfileMetadata(rule typesv1.Rule, ruleFailure * } func (r *RuleFailureCreator) setCloudServices(ruleFailure *types.GenericRuleFailure) { - if cloudServices := r.dnsManager.ResolveContainerProcessToCloudServices(ruleFailure.GetTriggerEvent().GetContainerID(), ruleFailure.GetBaseRuntimeAlert().InfectedPID); cloudServices != nil { + triggerEvent := ruleFailure.GetTriggerEvent() + if triggerEvent == nil { + return + } + + if cloudServices := r.dnsManager.ResolveContainerProcessToCloudServices(triggerEvent.GetContainerID(), ruleFailure.GetBaseRuntimeAlert().InfectedPID); cloudServices != nil { ruleFailure.SetCloudServices(cloudServices.ToSlice()) } @@ -183,6 +196,8 @@ func (r *RuleFailureCreator) setBaseRuntimeAlert(ruleFailure *types.GenericRuleF var err error var path string + triggerEvent := ruleFailure.GetTriggerEvent() + if ruleFailure.GetRuntimeProcessDetails().ProcessTree.Path == "" { path, err = utils.GetPathFromPid(ruleFailure.GetRuntimeProcessDetails().ProcessTree.PID) if err != nil { @@ -192,8 +207,8 @@ func (r *RuleFailureCreator) setBaseRuntimeAlert(ruleFailure *types.GenericRuleF } if err != nil { // FIXME WTF it's always nil here - if ruleFailure.GetRuntimeProcessDetails().ProcessTree.Path != "" { - hostPath = filepath.Join("/proc", fmt.Sprintf("/%d/root/%s", r.containerIdToPid.Get(ruleFailure.GetTriggerEvent().GetContainerID()), + if ruleFailure.GetRuntimeProcessDetails().ProcessTree.Path != "" && triggerEvent != nil { + hostPath = filepath.Join("/proc", fmt.Sprintf("/%d/root/%s", r.containerIdToPid.Get(triggerEvent.GetContainerID()), ruleFailure.GetRuntimeProcessDetails().ProcessTree.Path)) } } else { @@ -202,7 +217,9 @@ func (r *RuleFailureCreator) setBaseRuntimeAlert(ruleFailure *types.GenericRuleF baseRuntimeAlert := ruleFailure.GetBaseRuntimeAlert() - baseRuntimeAlert.Timestamp = time.Unix(0, int64(ruleFailure.GetTriggerEvent().GetTimestamp())) + if triggerEvent != nil { + baseRuntimeAlert.Timestamp = time.Unix(0, int64(triggerEvent.GetTimestamp())) + } var size int64 = 0 if hostPath != "" { size, err = utils.GetFileSize(hostPath) @@ -240,36 +257,41 @@ func (r *RuleFailureCreator) setBaseRuntimeAlert(ruleFailure *types.GenericRuleF func (r *RuleFailureCreator) setRuntimeAlertK8sDetails(ruleFailure *types.GenericRuleFailure, objectCache objectcache.ObjectCache) { runtimek8sdetails := ruleFailure.GetRuntimeAlertK8sDetails() + triggerEvent := ruleFailure.GetTriggerEvent() + if triggerEvent == nil { + return + } + if runtimek8sdetails.Image == "" { - runtimek8sdetails.Image = ruleFailure.GetTriggerEvent().GetContainerImage() + runtimek8sdetails.Image = triggerEvent.GetContainerImage() } if runtimek8sdetails.ImageDigest == "" { - runtimek8sdetails.ImageDigest = ruleFailure.GetTriggerEvent().GetContainerImageDigest() + runtimek8sdetails.ImageDigest = triggerEvent.GetContainerImageDigest() } if runtimek8sdetails.Namespace == "" { - runtimek8sdetails.Namespace = ruleFailure.GetTriggerEvent().GetNamespace() + runtimek8sdetails.Namespace = triggerEvent.GetNamespace() } if runtimek8sdetails.PodName == "" { - runtimek8sdetails.PodName = ruleFailure.GetTriggerEvent().GetPod() + runtimek8sdetails.PodName = triggerEvent.GetPod() } if runtimek8sdetails.PodNamespace == "" { - runtimek8sdetails.PodNamespace = ruleFailure.GetTriggerEvent().GetNamespace() + runtimek8sdetails.PodNamespace = triggerEvent.GetNamespace() } if runtimek8sdetails.ContainerName == "" { - runtimek8sdetails.ContainerName = ruleFailure.GetTriggerEvent().GetContainer() + runtimek8sdetails.ContainerName = triggerEvent.GetContainer() } if runtimek8sdetails.ContainerID == "" { - runtimek8sdetails.ContainerID = ruleFailure.GetTriggerEvent().GetContainerID() + runtimek8sdetails.ContainerID = triggerEvent.GetContainerID() } if runtimek8sdetails.HostNetwork == nil { - hostNetwork := ruleFailure.GetTriggerEvent().GetHostNetwork() + hostNetwork := triggerEvent.GetHostNetwork() runtimek8sdetails.HostNetwork = &hostNetwork } @@ -297,3 +319,48 @@ func (r *RuleFailureCreator) setRuntimeAlertK8sDetails(ruleFailure *types.Generi ruleFailure.SetRuntimeAlertK8sDetails(runtimek8sdetails) } + +func (r *RuleFailureCreator) setContextSpecificFields(ruleFailure *types.GenericRuleFailure, enrichedEvent *events.EnrichedEvent) { + // If no source context is available, default to Kubernetes (backward compatible) + if enrichedEvent.SourceContext == nil { + ruleFailure.SetSourceContext(contextdetection.Kubernetes) + return + } + + sourceContextType := enrichedEvent.SourceContext.Context() + k8sDetails := ruleFailure.GetRuntimeAlertK8sDetails() + + switch sourceContextType { + case contextdetection.Kubernetes: + ruleFailure.SetSourceContext(contextdetection.Kubernetes) + // K8s alerts use existing K8s details populated by setRuntimeAlertK8sDetails + + case contextdetection.Host: + ruleFailure.SetSourceContext(contextdetection.Host) + // For Host context, use NodeName to store the hostname + hostname, err := os.Hostname() + if err == nil { + k8sDetails.NodeName = hostname + } + + case contextdetection.Standalone: + ruleFailure.SetSourceContext(contextdetection.Standalone) + // For Standalone context, populate container-specific fields in RuntimeAlertK8sDetails + if k8sDetails.ContainerID == "" { + k8sDetails.ContainerID = enrichedEvent.ContainerID + } + + triggerEvent := ruleFailure.GetTriggerEvent() + if triggerEvent != nil { + if k8sDetails.Image == "" { + k8sDetails.Image = triggerEvent.GetContainerImage() + } + // Use container name from trigger event if available + if k8sDetails.ContainerName == "" { + k8sDetails.ContainerName = triggerEvent.GetContainer() + } + } + } + + ruleFailure.SetRuntimeAlertK8sDetails(k8sDetails) +} diff --git a/pkg/rulemanager/rulepolicy.go b/pkg/rulemanager/rulepolicy.go index b155d7a095..eef804a449 100644 --- a/pkg/rulemanager/rulepolicy.go +++ b/pkg/rulemanager/rulepolicy.go @@ -2,8 +2,11 @@ package rulemanager import ( "slices" + "strings" + "github.com/kubescape/node-agent/pkg/contextdetection" "github.com/kubescape/node-agent/pkg/objectcache" + typesv1 "github.com/kubescape/node-agent/pkg/rulemanager/types/v1" "github.com/kubescape/storage/pkg/apis/softwarecomposition/v1beta1" ) @@ -30,3 +33,27 @@ func (v *RulePolicyValidator) Validate(ruleId string, process string, ap *v1beta return false, nil } + +// RuleAppliesToContext checks if a rule should execute in the given context +func RuleAppliesToContext(rule *typesv1.Rule, contextInfo contextdetection.ContextInfo) bool { + var currentContext contextdetection.EventSourceContext + if contextInfo == nil { + currentContext = contextdetection.Kubernetes + } else { + currentContext = contextInfo.Context() + } + + var contextTags []string + for _, tag := range rule.Tags { + if ctx, found := strings.CutPrefix(tag, "context:"); found { + contextTags = append(contextTags, ctx) + } + } + + if len(contextTags) > 0 { + return slices.Contains(contextTags, string(currentContext)) + } + + // No context specified in tags: default to kubernetes only (backward compatible) + return currentContext == contextdetection.Kubernetes +} diff --git a/pkg/rulemanager/types/failure.go b/pkg/rulemanager/types/failure.go index 14380fea51..8bd5682de3 100644 --- a/pkg/rulemanager/types/failure.go +++ b/pkg/rulemanager/types/failure.go @@ -3,6 +3,7 @@ package types import ( apitypes "github.com/armosec/armoapi-go/armotypes" "github.com/armosec/utils-k8s-go/wlid" + "github.com/kubescape/node-agent/pkg/contextdetection" "github.com/kubescape/node-agent/pkg/utils" ) @@ -24,6 +25,7 @@ type GenericRuleFailure struct { HttpRuleAlert apitypes.HttpRuleAlert Extra interface{} IsTriggerAlert bool + SourceContext contextdetection.EventSourceContext } type RuleFailure interface { @@ -49,6 +51,8 @@ type RuleFailure interface { GetAlertPlatform() apitypes.AlertSourcePlatform // Get Extra GetExtra() interface{} + // Get Source Context + GetSourceContext() contextdetection.EventSourceContext // Set Workload Details SetWorkloadDetails(workloadDetails string) @@ -74,6 +78,8 @@ type RuleFailure interface { GetIsTriggerAlert() bool // Set IsTriggerAlert SetIsTriggerAlert(isTriggerAlert bool) + // Set Source Context + SetSourceContext(sourceContext contextdetection.EventSourceContext) } func (rule *GenericRuleFailure) GetBaseRuntimeAlert() apitypes.BaseRuntimeAlert { @@ -175,3 +181,11 @@ func (rule *GenericRuleFailure) GetIsTriggerAlert() bool { func (rule *GenericRuleFailure) SetIsTriggerAlert(isTriggerAlert bool) { rule.IsTriggerAlert = isTriggerAlert } + +func (rule *GenericRuleFailure) GetSourceContext() contextdetection.EventSourceContext { + return rule.SourceContext +} + +func (rule *GenericRuleFailure) SetSourceContext(sourceContext contextdetection.EventSourceContext) { + rule.SourceContext = sourceContext +} diff --git a/pkg/utils/datasource_event.go b/pkg/utils/datasource_event.go index 143d90ad30..499298b36d 100644 --- a/pkg/utils/datasource_event.go +++ b/pkg/utils/datasource_event.go @@ -1,6 +1,7 @@ package utils import ( + "errors" "net" "net/http" "os" @@ -10,6 +11,7 @@ import ( igconsts "github.com/inspektor-gadget/inspektor-gadget/gadgets/trace_exec/consts" "github.com/inspektor-gadget/inspektor-gadget/pkg/datasource" + "github.com/inspektor-gadget/inspektor-gadget/pkg/gadget-service/api" "github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets" "github.com/inspektor-gadget/inspektor-gadget/pkg/types" "github.com/inspektor-gadget/inspektor-gadget/pkg/utils/syscalls" @@ -81,6 +83,93 @@ var _ SshEvent = (*DatasourceEvent)(nil) var _ SyscallEvent = (*DatasourceEvent)(nil) var _ UnshareEvent = (*DatasourceEvent)(nil) +var errFieldNotFound = errors.New("field not found") + +type nullFieldAccessor struct{} + +func (n *nullFieldAccessor) Name() string { return "" } +func (n *nullFieldAccessor) FullName() string { return "" } +func (n *nullFieldAccessor) Size() uint32 { return 0 } +func (n *nullFieldAccessor) Get(data datasource.Data) []byte { return nil } +func (n *nullFieldAccessor) Set(data datasource.Data, value []byte) error { return nil } +func (n *nullFieldAccessor) IsRequested() bool { return false } +func (n *nullFieldAccessor) AddSubField(name string, kind api.Kind, opts ...datasource.FieldOption) (datasource.FieldAccessor, error) { + return nil, errFieldNotFound +} +func (n *nullFieldAccessor) GetSubFieldsWithTag(tag ...string) []datasource.FieldAccessor { return nil } +func (n *nullFieldAccessor) Parent() datasource.FieldAccessor { return nil } +func (n *nullFieldAccessor) SubFields() []datasource.FieldAccessor { return nil } +func (n *nullFieldAccessor) SetHidden(hidden bool, recurse bool) {} +func (n *nullFieldAccessor) Type() api.Kind { return api.Kind_Invalid } +func (n *nullFieldAccessor) Flags() uint32 { return 0 } +func (n *nullFieldAccessor) Tags() []string { return nil } +func (n *nullFieldAccessor) AddTags(tags ...string) {} +func (n *nullFieldAccessor) HasAllTagsOf(tags ...string) bool { return false } +func (n *nullFieldAccessor) HasAnyTagsOf(tags ...string) bool { return false } +func (n *nullFieldAccessor) Annotations() map[string]string { return nil } +func (n *nullFieldAccessor) AddAnnotation(key, value string) {} +func (n *nullFieldAccessor) RemoveReference(recurse bool) {} +func (n *nullFieldAccessor) Rename(string) error { return errFieldNotFound } + +func (n *nullFieldAccessor) Uint8(datasource.Data) (uint8, error) { return 0, errFieldNotFound } +func (n *nullFieldAccessor) Uint16(datasource.Data) (uint16, error) { return 0, errFieldNotFound } +func (n *nullFieldAccessor) Uint32(datasource.Data) (uint32, error) { return 0, errFieldNotFound } +func (n *nullFieldAccessor) Uint64(datasource.Data) (uint64, error) { return 0, errFieldNotFound } +func (n *nullFieldAccessor) Int8(datasource.Data) (int8, error) { return 0, errFieldNotFound } +func (n *nullFieldAccessor) Int16(datasource.Data) (int16, error) { return 0, errFieldNotFound } +func (n *nullFieldAccessor) Int32(datasource.Data) (int32, error) { return 0, errFieldNotFound } +func (n *nullFieldAccessor) Int64(datasource.Data) (int64, error) { return 0, errFieldNotFound } +func (n *nullFieldAccessor) Float32(datasource.Data) (float32, error) { return 0, errFieldNotFound } +func (n *nullFieldAccessor) Float64(datasource.Data) (float64, error) { return 0, errFieldNotFound } +func (n *nullFieldAccessor) String(datasource.Data) (string, error) { return "", errFieldNotFound } +func (n *nullFieldAccessor) Bytes(datasource.Data) ([]byte, error) { return nil, errFieldNotFound } +func (n *nullFieldAccessor) Bool(datasource.Data) (bool, error) { return false, errFieldNotFound } + +func (n *nullFieldAccessor) Uint8Array(datasource.Data) ([]uint8, error) { + return nil, errFieldNotFound +} +func (n *nullFieldAccessor) Uint16Array(datasource.Data) ([]uint16, error) { + return nil, errFieldNotFound +} +func (n *nullFieldAccessor) Uint32Array(datasource.Data) ([]uint32, error) { + return nil, errFieldNotFound +} +func (n *nullFieldAccessor) Uint64Array(datasource.Data) ([]uint64, error) { + return nil, errFieldNotFound +} +func (n *nullFieldAccessor) Int8Array(datasource.Data) ([]int8, error) { return nil, errFieldNotFound } +func (n *nullFieldAccessor) Int16Array(datasource.Data) ([]int16, error) { + return nil, errFieldNotFound +} +func (n *nullFieldAccessor) Int32Array(datasource.Data) ([]int32, error) { + return nil, errFieldNotFound +} +func (n *nullFieldAccessor) Int64Array(datasource.Data) ([]int64, error) { + return nil, errFieldNotFound +} +func (n *nullFieldAccessor) Float32Array(datasource.Data) ([]float32, error) { + return nil, errFieldNotFound +} +func (n *nullFieldAccessor) Float64Array(datasource.Data) ([]float64, error) { + return nil, errFieldNotFound +} + +func (n *nullFieldAccessor) PutUint8(datasource.Data, uint8) error { return errFieldNotFound } +func (n *nullFieldAccessor) PutUint16(datasource.Data, uint16) error { return errFieldNotFound } +func (n *nullFieldAccessor) PutUint32(datasource.Data, uint32) error { return errFieldNotFound } +func (n *nullFieldAccessor) PutUint64(datasource.Data, uint64) error { return errFieldNotFound } +func (n *nullFieldAccessor) PutInt8(datasource.Data, int8) error { return errFieldNotFound } +func (n *nullFieldAccessor) PutInt16(datasource.Data, int16) error { return errFieldNotFound } +func (n *nullFieldAccessor) PutInt32(datasource.Data, int32) error { return errFieldNotFound } +func (n *nullFieldAccessor) PutInt64(datasource.Data, int64) error { return errFieldNotFound } +func (n *nullFieldAccessor) PutFloat32(datasource.Data, float32) error { return errFieldNotFound } +func (n *nullFieldAccessor) PutFloat64(datasource.Data, float64) error { return errFieldNotFound } +func (n *nullFieldAccessor) PutString(datasource.Data, string) error { return errFieldNotFound } +func (n *nullFieldAccessor) PutBytes(datasource.Data, []byte) error { return errFieldNotFound } +func (n *nullFieldAccessor) PutBool(datasource.Data, bool) error { return errFieldNotFound } + +var missingFieldAccessor datasource.FieldAccessor = &nullFieldAccessor{} + func (e *DatasourceEvent) getFieldAccessor(fieldName string) datasource.FieldAccessor { cache, loaded := fieldCaches.Load(e.EventType) if !loaded { @@ -88,7 +177,12 @@ func (e *DatasourceEvent) getFieldAccessor(fieldName string) datasource.FieldAcc } accessor, loaded := cache.(*sync.Map).Load(fieldName) if !loaded { - accessor, _ = cache.(*sync.Map).LoadOrStore(fieldName, e.Datasource.GetField(fieldName)) + field := e.Datasource.GetField(fieldName) + accessor, _ = cache.(*sync.Map).LoadOrStore(fieldName, field) + } + // Handle case where field doesn't exist + if accessor == nil { + return missingFieldAccessor } return accessor.(datasource.FieldAccessor) } @@ -166,16 +260,7 @@ func (e *DatasourceEvent) GetComm() string { container := e.GetContainer() return container default: - comm := e.getFieldAccessor("proc.comm") - if comm == nil { - logger.L().Warning("GetComm - proc.comm field not found in event type", helpers.String("eventType", string(e.EventType))) - return "" - } - commValue, err := comm.String(e.Data) - if err != nil { - logger.L().Warning("GetComm - cannot read proc.comm field in event", helpers.String("eventType", string(e.EventType))) - return "" - } + commValue, _ := e.getFieldAccessor("proc.comm").String(e.Data) return commValue } } @@ -346,6 +431,9 @@ func (e *DatasourceEvent) GetFullPath() string { switch e.EventType { case OpenEventType: path, _ := e.getFieldAccessor("fpath").String(e.Data) + if path == "" { + path, _ = e.getFieldAccessor("fname").String(e.Data) + } return path default: logger.L().Warning("GetFullPath not implemented for event type", helpers.String("eventType", string(e.EventType))) @@ -498,16 +586,7 @@ func (e *DatasourceEvent) GetPID() uint32 { containerPid, _ := e.getFieldAccessor("runtime.containerPid").Uint32(e.Data) return containerPid default: - pid := e.getFieldAccessor("proc.pid") - if pid == nil { - logger.L().Warning("GetPID - proc.pid field not found in event type", helpers.String("eventType", string(e.EventType))) - return 0 - } - pidValue, err := pid.Uint32(e.Data) - if err != nil { - logger.L().Warning("GetPID cannot read proc.pid field in event", helpers.String("eventType", string(e.EventType))) - return 0 - } + pidValue, _ := e.getFieldAccessor("proc.pid").Uint32(e.Data) return pidValue } }