diff --git a/pkg/cloudmetadata/metadata.go b/pkg/cloudmetadata/metadata.go index dd26d10d8..522c7b37e 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" @@ -54,8 +58,179 @@ func GetCloudMetadataWithIMDS(ctx context.Context) (*apitypes.CloudMetadata, err cMetadata, err := cMetadataClient.GetMetadata(ctx) if err != nil { - return nil, err + // Try DigitalOcean metadata endpoints as a fallback (e.g., droplets) if IMDS didn't work. + if doMeta, derr := fetchDigitalOceanMetadata(ctx); derr == nil && doMeta != nil { + logger.L().Info("retrieved cloud metadata from DigitalOcean metadata service as fallback") + return doMeta, nil + } + + // Try GCP metadata endpoints as a fallback + if gcpMeta, gerr := fetchGCPMetadata(ctx); gerr == nil && gcpMeta != nil { + logger.L().Info("retrieved cloud metadata from GCP metadata service as fallback") + return gcpMeta, nil + } + + // Try Azure metadata endpoints as a fallback + if azureMeta, aerr := fetchAzureMetadata(ctx); aerr == nil && azureMeta != nil { + logger.L().Info("retrieved cloud metadata from Azure metadata service as fallback") + return azureMeta, nil + } + + // Wrap the underlying error with additional context so logs make it clearer why metadata is missing. + // This helps surface issues like IMDS token endpoint failures (e.g. IMDSv2 token 404), unreachable metadata endpoints, + // or provider-specific metadata problems. + return nil, fmt.Errorf("failed to get cloud metadata from IMDS: %w", err) } return cMetadata, nil } + +// fetchDigitalOceanMetadata attempts to fetch basic metadata from DigitalOcean's metadata service. +// +// It probes the metadata root and queries a few commonly available endpoints. +// It returns a non-nil error if it does not look like DigitalOcean's metadata service or no useful values were found. +func fetchDigitalOceanMetadata(ctx context.Context) (*apitypes.CloudMetadata, error) { + client := &http.Client{ + Timeout: 2 * time.Second, + } + base := "http://169.254.169.254/metadata/v1/" + + // Probe root to see whether the metadata endpoint responds and contains expected entries. + req, _ := http.NewRequestWithContext(ctx, http.MethodGet, base, nil) + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + if resp.StatusCode != 200 { + return nil, fmt.Errorf("digitalocean metadata root returned status: %d", resp.StatusCode) + } + body, _ := io.ReadAll(resp.Body) + bstr := string(body) + // Basic heuristic: the DO metadata root typically lists resources like 'id', 'hostname', 'region' etc. + if !strings.Contains(bstr, "id") && !strings.Contains(bstr, "region") && !strings.Contains(bstr, "hostname") { + return nil, fmt.Errorf("digitalocean metadata root missing expected entries") + } + + // helper to fetch a single textual endpoint and return trimmed result or empty string + get := func(path string) string { + url := base + path + r, _ := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + resp2, err2 := client.Do(r) + if err2 != nil || resp2.StatusCode != 200 { + if resp2 != nil { + resp2.Body.Close() + } + return "" + } + defer resp2.Body.Close() + b, _ := io.ReadAll(resp2.Body) + return strings.TrimSpace(string(b)) + } + + id := get("id") + if id == "" { + id = get("droplet_id") + } + hostname := get("hostname") + region := get("region") + instanceType := get("size") + if instanceType == "" { + instanceType = get("type") + } + privateIP := get("interfaces/private/0/ipv4/address") + publicIP := get("interfaces/public/0/ipv4/address") + + // if nothing useful was obtained, return an error so callers can continue trying other fallbacks + if id == "" && hostname == "" && region == "" && privateIP == "" && publicIP == "" && instanceType == "" { + return nil, fmt.Errorf("digitalocean metadata endpoints returned no data") + } + + return &apitypes.CloudMetadata{ + Provider: "digitalocean", + InstanceID: id, + InstanceType: instanceType, + Region: region, + PrivateIP: privateIP, + PublicIP: publicIP, + Hostname: hostname, + }, nil +} + +// fetchGCPMetadata attempts to fetch basic metadata from GCP's metadata service. +func fetchGCPMetadata(ctx context.Context) (*apitypes.CloudMetadata, error) { + client := &http.Client{ + Timeout: 2 * time.Second, + } + base := "http://metadata.google.internal/computeMetadata/v1/instance/" + + get := func(path string) string { + req, _ := http.NewRequestWithContext(ctx, http.MethodGet, base+path, nil) + req.Header.Set("Metadata-Flavor", "Google") + resp, err := client.Do(req) + if err != nil || resp.StatusCode != 200 { + if resp != nil { + resp.Body.Close() + } + return "" + } + defer resp.Body.Close() + b, _ := io.ReadAll(resp.Body) + return strings.TrimSpace(string(b)) + } + + machineType := get("machine-type") + if machineType == "" { + return nil, fmt.Errorf("not a GCP instance") + } + + // GCP returns full path like "projects/12345/machineTypes/n1-standard-1" + parts := strings.Split(machineType, "/") + instanceType := parts[len(parts)-1] + + return &apitypes.CloudMetadata{ + Provider: "gcp", + InstanceID: get("id"), + InstanceType: instanceType, + Zone: get("zone"), + Hostname: get("hostname"), + }, nil +} + +// fetchAzureMetadata attempts to fetch basic metadata from Azure's metadata service. +func fetchAzureMetadata(ctx context.Context) (*apitypes.CloudMetadata, error) { + client := &http.Client{ + Timeout: 2 * time.Second, + } + base := "http://169.254.169.254/metadata/instance/compute/" + + get := func(path string) string { + url := base + path + "?api-version=2021-02-01&format=text" + req, _ := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + req.Header.Set("Metadata", "true") + resp, err := client.Do(req) + if err != nil || resp.StatusCode != 200 { + if resp != nil { + resp.Body.Close() + } + return "" + } + defer resp.Body.Close() + b, _ := io.ReadAll(resp.Body) + return strings.TrimSpace(string(b)) + } + + vmSize := get("vmSize") + if vmSize == "" { + return nil, fmt.Errorf("not an Azure instance") + } + + return &apitypes.CloudMetadata{ + Provider: "azure", + InstanceID: get("vmId"), + InstanceType: vmSize, + Region: get("location"), + Zone: get("zone"), + Hostname: get("name"), + }, nil +} diff --git a/pkg/containerwatcher/v2/tracers/bpf.go b/pkg/containerwatcher/v2/tracers/bpf.go index 87f57b8a3..d74c34503 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 74bd0b3d7..b00cc1dc5 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 0fd3dd0d9..215e2df70 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 21a414a90..6c25026ef 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 03b0a3a72..cdf9b2dd1 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 d31d133d7..d06b159e5 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 be0558af7..a3dfa85ec 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 36f3e5b7e..3577c7e7f 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 8977fc396..2f23b27fd 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 6d02d0614..6eaa37376 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 c5c36537b..e042203a5 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 4b5b6e210..210dd5b7e 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 d01625eb1..68201e686 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 dd6b6bba3..eea8d9a9f 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 c29157dc0..c6e9f899e 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 01aa8cc6e..8e292aeac 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 cc2e6d8f9..fbbcb1322 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 fdf96ac65..d6dd7e61b 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/utils/datasource_event.go b/pkg/utils/datasource_event.go index 143d90ad3..bee5cc037 100644 --- a/pkg/utils/datasource_event.go +++ b/pkg/utils/datasource_event.go @@ -346,6 +346,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)))