Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tetragon: Add debug interface to track cgroups to workload/ns mappings #2540

Merged
merged 3 commits into from
Jul 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions bpf/process/bpf_execve_event.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
#include "bpf_helpers.h"
#include "bpf_rate.h"

#include "policy_filter.h"

char _license[] __attribute__((section("license"), used)) = "Dual BSD/GPL";

struct {
Expand Down
10 changes: 9 additions & 1 deletion bpf/process/policy_filter.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,15 @@

#include "bpf_tracing.h"

#define POLICY_FILTER_MAX_POLICIES 128
#define POLICY_FILTER_MAX_POLICIES 128
#define POLICY_FILTER_MAX_NAMESPACES 1024

struct {
__uint(type, BPF_MAP_TYPE_LRU_HASH);
__uint(max_entries, POLICY_FILTER_MAX_NAMESPACES);
__uint(key_size, sizeof(u64));
__uint(value_size, sizeof(u64));
} tg_cgroup_namespace_map SEC(".maps");

struct {
__uint(type, BPF_MAP_TYPE_HASH_OF_MAPS);
Expand Down
25 changes: 25 additions & 0 deletions cmd/tetra/dump/dump.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,28 @@ func PolicyfilterState(fname string) {
fmt.Printf("%d: %s\n", polId, strings.Join(ids, ","))
}
}

func NamespaceState(fname string) error {
m, err := ebpf.LoadPinnedMap(fname, &ebpf.LoadPinOptions{
ReadOnly: true,
})
if err != nil {
logger.GetLogger().WithError(err).WithField("file", fname).Warn("Could not open process tree map")
return err
}

defer m.Close()

var (
key uint64
val uint64
)

fmt.Printf("cgroupId: stableId\n")
iter := m.Iterate()
for iter.Next(&key, &val) {
fmt.Printf("%d: %d\n", key, val)
}

return nil
}
17 changes: 17 additions & 0 deletions cmd/tetra/policyfilter/policyfilter.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,28 @@ func New() *cobra.Command {
dumpCmd(),
addCommand(),
cgroupGetIDCommand(),
dumpDebugCmd(),
)

return ret
}

func dumpDebugCmd() *cobra.Command {
mapFname := filepath.Join(defaults.DefaultMapRoot, defaults.DefaultMapPrefix, policyfilter.CgrpNsMapName)
ret := &cobra.Command{
Use: "dumpcgrp",
Short: "dump cgroup ID to namespace state",
Args: cobra.ExactArgs(0),
Run: func(_ *cobra.Command, _ []string) {
dump.NamespaceState(mapFname)
},
}

flags := ret.Flags()
flags.StringVar(&mapFname, "map-fname", mapFname, "policyfilter map filename")
return ret
}

func cgroupGetIDCommand() *cobra.Command {
mapFname := filepath.Join(defaults.DefaultMapRoot, defaults.DefaultMapPrefix, policyfilter.MapName)
ret := &cobra.Command{
Expand Down
8 changes: 6 additions & 2 deletions pkg/policyfilter/disabled.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ func (s *disabled) DelPolicy(polID PolicyID) error {
return fmt.Errorf("policyfilter is disabled")
}

func (s *disabled) AddPodContainer(podID PodID, namespace string, podLabels labels.Labels,
func (s *disabled) AddPodContainer(podID PodID, namespace, workload, kind string, podLabels labels.Labels,
containerID string, cgID CgroupID, containerName string) error {
return nil
}

func (s *disabled) UpdatePod(podID PodID, namespace string, podLabels labels.Labels,
func (s *disabled) UpdatePod(podID PodID, namespace, workload, kind string, podLabels labels.Labels,
containerIDs []string, containerNames []string) error {
return nil
}
Expand All @@ -55,3 +55,7 @@ func (s *disabled) RegisterPodHandlers(podInformer cache.SharedIndexInformer) {
func (s *disabled) Close() error {
return nil
}

func (s *disabled) GetNsId(stateID StateID) (*NSID, bool) {
return nil, false
}
83 changes: 83 additions & 0 deletions pkg/policyfilter/namespace.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of Tetragon

package policyfilter

import (
"fmt"
"os"
"path"
"path/filepath"

"github.com/cilium/ebpf"
"github.com/cilium/tetragon/pkg/bpf"
"github.com/cilium/tetragon/pkg/option"
"github.com/cilium/tetragon/pkg/sensors/exec/config"
lru "github.com/hashicorp/golang-lru/v2"
)

const (
CgrpNsMapName = "tg_cgroup_namespace_map"
namespaceCacheSize = 1024
)

type NSID struct {
Namespace string
Workload string
Kind string
}

// NamespaceMap is a simple wrapper for ebpf.Map so that we can write methods for it
type NamespaceMap struct {
cgroupIdMap *ebpf.Map
nsIdMap *lru.Cache[StateID, NSID]
nsNameMap *lru.Cache[NSID, StateID]
id StateID
}

// newNamespaceMap returns a new namespace mapping. The namespace map consists of
// two pieces. First a cgroup to ID map. The ID is useful for BPF so we can avoid
// strings in BPF side. Then a stable ID to namespace mapping.
func newNamespaceMap() (*NamespaceMap, error) {
idCache, err := lru.New[StateID, NSID](namespaceCacheSize)
if err != nil {
return nil, fmt.Errorf("create namespace ID cache failed")
}
nameCache, err := lru.New[NSID, StateID](namespaceCacheSize)
if err != nil {
return nil, fmt.Errorf("create namespace name cache failed")
}

objName := config.ExecObj()
objPath := path.Join(option.Config.HubbleLib, objName)
spec, err := ebpf.LoadCollectionSpec(objPath)
if err != nil {
return nil, fmt.Errorf("loading spec for %s failed: %w", objPath, err)
}
nsMapSpec, ok := spec.Maps[CgrpNsMapName]
if !ok {
return nil, fmt.Errorf("%s not found in %s", CgrpNsMapName, objPath)
}

ret, err := ebpf.NewMap(nsMapSpec)
if err != nil {
return nil, err
}

mapDir := bpf.MapPrefixPath()
pinPath := filepath.Join(mapDir, CgrpNsMapName)
os.Remove(pinPath)
os.Mkdir(mapDir, os.ModeDir)
err = ret.Pin(pinPath)
if err != nil {
ret.Close()
return nil, fmt.Errorf("failed to pin Namespace map in %s: %w", pinPath, err)
}

return &NamespaceMap{
cgroupIdMap: ret,
nsIdMap: idCache,
nsNameMap: nameCache,
id: 1,
}, err
}
8 changes: 6 additions & 2 deletions pkg/policyfilter/policyfilter.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,19 +98,23 @@ type State interface {
// AddPodContainer informs policyfilter about a new container and its cgroup id in a pod.
// The pod might or might not have been encountered before.
// This method is intended to update policyfilter state from container hooks
AddPodContainer(podID PodID, namespace string, podLabels labels.Labels,
AddPodContainer(podID PodID, namespace, workload, kind string, podLabels labels.Labels,
containerID string, cgID CgroupID, containerName string) error

// UpdatePod updates the pod state for a pod, where containerIDs contains all the container ids for the given pod.
// This method is intended to be used from k8s watchers (where no cgroup information is available)
UpdatePod(podID PodID, namespace string, podLabels labels.Labels,
UpdatePod(podID PodID, namespace, workload, kind string, podLabels labels.Labels,
containerIDs []string, containerNames []string) error

// DelPodContainer informs policyfilter that a container was deleted from a pod
DelPodContainer(podID PodID, containerID string) error
// DelPod informs policyfilter that a pod has been deleted
DelPod(podID PodID) error

// Report opaque cgroup ID to nsId mapping. This method is intended to allow inspecting
// and reporting the state of the system to subsystems and tooling.
GetNsId(stateID StateID) (*NSID, bool)

// RegisterPodHandlers can be used to register appropriate pod handlers to a pod informer
// that for keeping the policy filter state up-to-date.
RegisterPodHandlers(podInformer cache.SharedIndexInformer)
Expand Down
8 changes: 7 additions & 1 deletion pkg/policyfilter/rthooks/rthooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/cilium/tetragon/pkg/logger"
"github.com/cilium/tetragon/pkg/metrics/policyfiltermetrics"
"github.com/cilium/tetragon/pkg/policyfilter"
"github.com/cilium/tetragon/pkg/process"
"github.com/cilium/tetragon/pkg/rthooks"
"github.com/google/uuid"
"github.com/sirupsen/logrus"
Expand Down Expand Up @@ -93,6 +94,9 @@ func createContainerHook(_ context.Context, arg *rthooks.CreateContainerArg) err
}

namespace := pod.ObjectMeta.Namespace
workloadMeta, workloadKind := process.GetWorkloadMetaFromPod(pod)
workload := workloadMeta.Name
kind := workloadKind.Kind

containerName := arg.Req.ContainerName
if containerName == "" {
Expand All @@ -103,12 +107,14 @@ func createContainerHook(_ context.Context, arg *rthooks.CreateContainerArg) err
log.WithFields(logrus.Fields{
"pod-id": podID,
"namespace": namespace,
"workload": workload,
"workload-kind": kind,
"container-id": containerID,
"cgroup-id": cgID,
"container-name": containerName,
}).Trace("policyfilter: add pod container")
cgid := policyfilter.CgroupID(cgID)
err = pfState.AddPodContainer(policyfilter.PodID(podID), namespace, pod.Labels, containerID, cgid, containerName)
err = pfState.AddPodContainer(policyfilter.PodID(podID), namespace, workload, kind, pod.Labels, containerID, cgid, containerName)
policyfiltermetrics.OpInc(policyfiltermetrics.RTHooksSubsys, policyfiltermetrics.AddContainerOperation, policyfilter.ErrorLabel(err))

if err != nil {
Expand Down
Loading
Loading