Skip to content

Commit

Permalink
Merge pull request #337 from kubescape/feature/container-enrichment
Browse files Browse the repository at this point in the history
Adding enrichment of WithContainerRuntimeEnrichment
  • Loading branch information
amitschendel authored Aug 11, 2024
2 parents 0411dd7 + b0bbd4f commit b25a871
Show file tree
Hide file tree
Showing 7 changed files with 139 additions and 12 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ binary:

docker-build:
docker buildx build --platform linux/amd64 -t $(IMAGE):$(TAG) -f $(DOCKERFILE_PATH) .

docker-push:
docker push $(IMAGE):$(TAG)
11 changes: 10 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,15 @@ func main() {
}

nodeName := os.Getenv(config.NodeNameEnvVar)

// Detect the container containerRuntime of the node
containerRuntime, err := utils.DetectContainerRuntimeViaK8sAPI(ctx, k8sClient, nodeName)
if err != nil {
logger.L().Ctx(ctx).Fatal("error detecting the container runtime", helpers.Error(err))
}

logger.L().Ctx(ctx).Info("Detected container runtime", helpers.String("containerRuntime", containerRuntime.Name.String()))

// Create watchers
dWatcher := dynamicwatcher.NewWatchHandler(k8sClient, cfg.SkipNamespace)
// create k8sObject cache
Expand Down Expand Up @@ -251,7 +260,7 @@ func main() {
}

// Create the container handler
mainHandler, err := containerwatcher.CreateIGContainerWatcher(cfg, applicationProfileManager, k8sClient, relevancyManager, networkManagerv1Client, networkManagerClient, dnsManagerClient, prometheusExporter, ruleManager, malwareManager, preRunningContainersIDs, &ruleBindingNotify)
mainHandler, err := containerwatcher.CreateIGContainerWatcher(cfg, applicationProfileManager, k8sClient, relevancyManager, networkManagerv1Client, networkManagerClient, dnsManagerClient, prometheusExporter, ruleManager, malwareManager, preRunningContainersIDs, &ruleBindingNotify, containerRuntime)
if err != nil {
logger.L().Ctx(ctx).Fatal("error creating the container watcher", helpers.Error(err))
}
Expand Down
8 changes: 7 additions & 1 deletion pkg/containerwatcher/v1/container_watcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

mapset "github.com/deckarep/golang-set/v2"
containercollection "github.com/inspektor-gadget/inspektor-gadget/pkg/container-collection"
containerutilsTypes "github.com/inspektor-gadget/inspektor-gadget/pkg/container-utils/types"
tracerseccomp "github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets/advise/seccomp/tracer"
tracercapabilities "github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets/trace/capabilities/tracer"
tracercapabilitiestype "github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets/trace/capabilities/types"
Expand Down Expand Up @@ -127,11 +128,14 @@ type IGContainerWatcher struct {

// cache
ruleBindingPodNotify *chan rulebinding.RuleBindingNotify

// container runtime
runtime *containerutilsTypes.RuntimeConfig
}

var _ containerwatcher.ContainerWatcher = (*IGContainerWatcher)(nil)

func CreateIGContainerWatcher(cfg config.Config, applicationProfileManager applicationprofilemanager.ApplicationProfileManagerClient, k8sClient *k8sinterface.KubernetesApi, relevancyManager relevancymanager.RelevancyManagerClient, networkManagerv1Client networkmanagerv1.NetworkManagerClient, networkManagerClient networkmanager.NetworkManagerClient, dnsManagerClient dnsmanager.DNSManagerClient, metrics metricsmanager.MetricsManager, ruleManager rulemanager.RuleManagerClient, malwareManager malwaremanager.MalwareManagerClient, preRunningContainers mapset.Set[string], ruleBindingPodNotify *chan rulebinding.RuleBindingNotify) (*IGContainerWatcher, error) {
func CreateIGContainerWatcher(cfg config.Config, applicationProfileManager applicationprofilemanager.ApplicationProfileManagerClient, k8sClient *k8sinterface.KubernetesApi, relevancyManager relevancymanager.RelevancyManagerClient, networkManagerv1Client networkmanagerv1.NetworkManagerClient, networkManagerClient networkmanager.NetworkManagerClient, dnsManagerClient dnsmanager.DNSManagerClient, metrics metricsmanager.MetricsManager, ruleManager rulemanager.RuleManagerClient, malwareManager malwaremanager.MalwareManagerClient, preRunningContainers mapset.Set[string], ruleBindingPodNotify *chan rulebinding.RuleBindingNotify, runtime *containerutilsTypes.RuntimeConfig) (*IGContainerWatcher, error) {
// Use container collection to get notified for new containers
containerCollection := &containercollection.ContainerCollection{}
// Create a tracer collection instance
Expand Down Expand Up @@ -337,6 +341,8 @@ func CreateIGContainerWatcher(cfg config.Config, applicationProfileManager appli

timeBasedContainers: mapset.NewSet[string](),
ruleManagedPods: mapset.NewSet[string](),

runtime: runtime,
}, nil
}

Expand Down
19 changes: 11 additions & 8 deletions pkg/containerwatcher/v1/container_watcher_private.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,25 +85,28 @@ func (ch *IGContainerWatcher) startContainerCollection(ctx context.Context) erro

// Define the different options for the container collection instance
opts := []containercollection.ContainerCollectionOption{
containercollection.WithTracerCollection(ch.tracerCollection),
// Get Notifications from the container collection
containercollection.WithPubSub(containerEventFuncs...),

// Enrich events with OCI config information
containercollection.WithOCIConfigEnrichment(),

// Get containers created with ebpf (works also if hostPid=false)
containercollection.WithContainerFanotifyEbpf(),

// Get containers created with docker
// Get containers enriched with cgroup information
containercollection.WithCgroupEnrichment(),

// Enrich events with Linux namespaces information, it is needed for per container filtering
containercollection.WithLinuxNamespaceEnrichment(),

// Get containers created with container runtimes
containercollection.WithContainerRuntimeEnrichment(ch.runtime),

// Get containers created with ebpf (works also if hostPid=false)
containercollection.WithContainerFanotifyEbpf(),

containercollection.WithTracerCollection(ch.tracerCollection),

// Enrich those containers with data from the Kubernetes API
containercollection.WithKubernetesEnrichment(ch.nodeName, ch.k8sClient.K8SConfig),

// Get Notifications from the container collection
containercollection.WithPubSub(containerEventFuncs...),
}

// Initialize the container collection
Expand Down
2 changes: 1 addition & 1 deletion pkg/containerwatcher/v1/open_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func BenchmarkIGContainerWatcher_openEventCallback(b *testing.B) {
assert.NoError(b, err)
mockExporter := metricsmanager.NewMetricsMock()

mainHandler, err := CreateIGContainerWatcher(cfg, nil, nil, relevancyManager, nil, nil, nil, mockExporter, nil, nil, nil, nil)
mainHandler, err := CreateIGContainerWatcher(cfg, nil, nil, relevancyManager, nil, nil, nil, mockExporter, nil, nil, nil, nil, nil)
assert.NoError(b, err)
event := &traceropentype.Event{
Event: types.Event{
Expand Down
2 changes: 1 addition & 1 deletion pkg/rulemanager/v1/rule_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ func (rm *RuleManager) monitorContainer(ctx context.Context, container *containe
select {
case <-syscallTicker.C:
if rm.syscallPeekFunc == nil {
logger.L().Error("RuleManager - syscallPeekFunc is not set", helpers.String("container ID", watchedContainer.ContainerID))
logger.L().Debug("RuleManager - syscallPeekFunc is not set", helpers.String("container ID", watchedContainer.ContainerID))
continue
}

Expand Down
108 changes: 108 additions & 0 deletions pkg/utils/utils.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package utils

import (
"context"
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
Expand All @@ -15,6 +16,7 @@ import (
"slices"
"strconv"
"strings"
"syscall"
"time"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand All @@ -28,15 +30,19 @@ import (
"github.com/kubescape/k8s-interface/instanceidhandler"
instanceidhandlerv1 "github.com/kubescape/k8s-interface/instanceidhandler/v1"
helpersv1 "github.com/kubescape/k8s-interface/instanceidhandler/v1/helpers"
"github.com/kubescape/k8s-interface/k8sinterface"
"github.com/kubescape/k8s-interface/workloadinterface"
"github.com/kubescape/storage/pkg/apis/softwarecomposition/v1beta1"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/validation"

"github.com/prometheus/procfs"

runtimeclient "github.com/inspektor-gadget/inspektor-gadget/pkg/container-utils/runtime-client"
containerutilsTypes "github.com/inspektor-gadget/inspektor-gadget/pkg/container-utils/types"
tracerexectype "github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets/trace/exec/types"
traceropentype "github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets/trace/open/types"
igtypes "github.com/inspektor-gadget/inspektor-gadget/pkg/types"

apitypes "github.com/armosec/armoapi-go/armotypes"
)
Expand Down Expand Up @@ -703,3 +709,105 @@ func ChunkBy[T any](items []T, chunkSize int) [][]T {
}
return append(chunks, items)
}

// isUnixSocket checks if the given path is a Unix socket.
func isUnixSocket(path string) (bool, error) {
fileInfo, err := os.Stat(path)
if err != nil {
return false, err // Could not obtain the file stats
}

stat, ok := fileInfo.Sys().(*syscall.Stat_t)
if !ok {
return false, fmt.Errorf("not a unix file")
}

// Check if the file is a socket
return (stat.Mode & syscall.S_IFMT) == syscall.S_IFSOCK, nil
}

func DetectContainerRuntimes(hostMount string) ([]*containerutilsTypes.RuntimeConfig, error) {
runtimes := map[igtypes.RuntimeName]string{
igtypes.RuntimeNameDocker: runtimeclient.DockerDefaultSocketPath,
igtypes.RuntimeNameCrio: runtimeclient.CrioDefaultSocketPath,
igtypes.RuntimeNameContainerd: runtimeclient.ContainerdDefaultSocketPath,
igtypes.RuntimeNamePodman: runtimeclient.PodmanDefaultSocketPath,
}

detectedRuntimes := make([]*containerutilsTypes.RuntimeConfig, 0)

for runtimeName, socketPath := range runtimes {
// Check if the socket is available on the host mount
socketPath = hostMount + socketPath
if isSocket, err := isUnixSocket(socketPath); err == nil && isSocket {
logger.L().Info("Detected container runtime", helpers.String("runtime", runtimeName.String()), helpers.String("socketPath", socketPath))
detectedRuntimes = append(detectedRuntimes, &containerutilsTypes.RuntimeConfig{
Name: runtimeName,
SocketPath: socketPath,
})
}
}

if len(detectedRuntimes) == 0 {
return nil, fmt.Errorf("no container runtimes detected at the following paths: %v", runtimes)
}

return detectedRuntimes, nil
}

func DetectContainerRuntimeViaK8sAPI(ctx context.Context, k8sClient *k8sinterface.KubernetesApi, nodeName string) (*containerutilsTypes.RuntimeConfig, error) {
// Get the current node
nodes, err := k8sClient.GetKubernetesClient().CoreV1().Nodes().List(ctx, metav1.ListOptions{
FieldSelector: fmt.Sprintf("metadata.name=%s", nodeName),
})
if err != nil {
return nil, fmt.Errorf("failed to list nodes: %v", err)
}

if len(nodes.Items) == 0 {
return nil, fmt.Errorf("no node found with name: %s", nodeName)
}

node := nodes.Items[0]
runtimeConfig := parseRuntimeInfo(node.Status.NodeInfo.ContainerRuntimeVersion)
if runtimeConfig.Name == igtypes.RuntimeNameUnknown {
return nil, fmt.Errorf("unknown container runtime: %s", node.Status.NodeInfo.ContainerRuntimeVersion)
}

return runtimeConfig, nil
}

func parseRuntimeInfo(version string) *containerutilsTypes.RuntimeConfig {
switch {
case strings.HasPrefix(version, "docker://"):
return &containerutilsTypes.RuntimeConfig{
Name: igtypes.RuntimeNameDocker,
SocketPath: runtimeclient.DockerDefaultSocketPath,
RuntimeProtocol: containerutilsTypes.RuntimeProtocolCRI,
}
case strings.HasPrefix(version, "containerd://"):
return &containerutilsTypes.RuntimeConfig{
Name: igtypes.RuntimeNameContainerd,
SocketPath: runtimeclient.ContainerdDefaultSocketPath,
RuntimeProtocol: containerutilsTypes.RuntimeProtocolCRI,
}
case strings.HasPrefix(version, "cri-o://"):
return &containerutilsTypes.RuntimeConfig{
Name: igtypes.RuntimeNameCrio,
SocketPath: runtimeclient.CrioDefaultSocketPath,
RuntimeProtocol: containerutilsTypes.RuntimeProtocolCRI,
}
case strings.HasPrefix(version, "podman://"):
return &containerutilsTypes.RuntimeConfig{
Name: igtypes.RuntimeNamePodman,
SocketPath: runtimeclient.PodmanDefaultSocketPath,
RuntimeProtocol: containerutilsTypes.RuntimeProtocolCRI,
}
default:
return &containerutilsTypes.RuntimeConfig{
Name: igtypes.RuntimeNameUnknown,
SocketPath: "",
RuntimeProtocol: containerutilsTypes.RuntimeProtocolCRI,
}
}
}

0 comments on commit b25a871

Please sign in to comment.