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

[CWS] sysctl_snapshot event #34599

Open
wants to merge 2 commits into
base: will/sysctl-event
Choose a base branch
from
Open
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
9 changes: 9 additions & 0 deletions docs/cloud-workload-security/backend_linux.md
Original file line number Diff line number Diff line change
Expand Up @@ -1622,6 +1622,10 @@ CSM Threats event for Linux systems have the following JSON schema:
},
"SysCtlEvent": {
"properties": {
"proc": {
"type": "object",
"description": "Proc contains the /proc system control parameters and their values"
},
"action": {
"type": "string",
"description": "action performed on the system control parameter"
Expand Down Expand Up @@ -4399,6 +4403,10 @@ CSM Threats event for Linux systems have the following JSON schema:
{{< code-block lang="json" collapsible="true" >}}
{
"properties": {
"proc": {
"type": "object",
"description": "Proc contains the /proc system control parameters and their values"
},
"action": {
"type": "string",
"description": "action performed on the system control parameter"
Expand Down Expand Up @@ -4441,6 +4449,7 @@ CSM Threats event for Linux systems have the following JSON schema:

| Field | Description |
| ----- | ----------- |
| `proc` | Proc contains the /proc system control parameters and their values |
| `action` | action performed on the system control parameter |
| `file_position` | file_position is the position in the sysctl control parameter file at which the action occurred |
| `name` | name is the name of the system control parameter |
Expand Down
4 changes: 4 additions & 0 deletions docs/cloud-workload-security/backend_linux.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1611,6 +1611,10 @@
},
"SysCtlEvent": {
"properties": {
"proc": {
"type": "object",
"description": "Proc contains the /proc system control parameters and their values"
},
"action": {
"type": "string",
"description": "action performed on the system control parameter"
Expand Down
4 changes: 4 additions & 0 deletions pkg/config/setup/system_probe_cws.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@ func initCWSSystemProbeConfig(cfg pkgconfigmodel.Config) {
cfg.BindEnvAndSetDefault("runtime_security_config.hash_resolver.cache_size", 500)
cfg.BindEnvAndSetDefault("runtime_security_config.hash_resolver.replace", map[string]string{})

// CWS - SysCtl snapshot
cfg.BindEnvAndSetDefault("runtime_security_config.sysctl_snapshot.enabled", true)
cfg.BindEnvAndSetDefault("runtime_security_config.sysctl_snapshot.period", "1h")

// CWS - UserSessions
cfg.BindEnvAndSetDefault("runtime_security_config.user_sessions.cache_size", 1024)

Expand Down
9 changes: 9 additions & 0 deletions pkg/security/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,11 @@ type RuntimeSecurityConfig struct {
// HashResolverReplace is used to apply specific hash to specific file path
HashResolverReplace map[string]string

// SysCtlSnapshotEnabled defines if the sysctl snapshot feature should be enabled
SysCtlSnapshotEnabled bool
// SysCtlSnapshotPeriod defines at which time interval a new snapshot of sysctl parameters should be sent
SysCtlSnapshotPeriod time.Duration

// UserSessionsCacheSize defines the size of the User Sessions cache size
UserSessionsCacheSize int

Expand Down Expand Up @@ -413,6 +418,10 @@ func NewRuntimeSecurityConfig() (*RuntimeSecurityConfig, error) {
HashResolverCacheSize: pkgconfigsetup.SystemProbe().GetInt("runtime_security_config.hash_resolver.cache_size"),
HashResolverReplace: pkgconfigsetup.SystemProbe().GetStringMapString("runtime_security_config.hash_resolver.replace"),

// SysCtl config parameter
SysCtlSnapshotEnabled: pkgconfigsetup.SystemProbe().GetBool("runtime_security_config.sysctl_snapshot.enabled"),
SysCtlSnapshotPeriod: pkgconfigsetup.SystemProbe().GetDuration("runtime_security_config.sysctl_snapshot.period"),

// security profiles
SecurityProfileEnabled: pkgconfigsetup.SystemProbe().GetBool("runtime_security_config.security_profile.enabled"),
SecurityProfileMaxImageTags: pkgconfigsetup.SystemProbe().GetInt("runtime_security_config.security_profile.max_image_tags"),
Expand Down
6 changes: 6 additions & 0 deletions pkg/security/events/custom.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ const (
InternalCoreDumpRuleID = "internal_core_dump"
// InternalCoreDumpRuleDesc internal core dump
InternalCoreDumpRuleDesc = "Internal Core Dump"

// SysCtlSnapshotRuleID is the rule ID used when sending a sysctl snapshot event
SysCtlSnapshotRuleID = "sysctl_snapshot"
// SysCtlSnapshotRuleDesc is the description of the sysctl snapshot rule
SysCtlSnapshotRuleDesc = "A new sysctl snapshot was generated"
)

// AgentContainerContext is like model.ContainerContext, but without event based resolvers
Expand Down Expand Up @@ -105,6 +110,7 @@ func AllCustomRuleIDs() []string {
NoProcessContextErrorRuleID,
BrokenProcessLineageErrorRuleID,
InternalCoreDumpRuleID,
SysCtlSnapshotRuleID,
}
}

Expand Down
29 changes: 29 additions & 0 deletions pkg/security/probe/probe_ebpf.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import (
"github.com/DataDog/datadog-agent/pkg/security/probe/eventstream/ringbuffer"
"github.com/DataDog/datadog-agent/pkg/security/probe/kfilters"
"github.com/DataDog/datadog-agent/pkg/security/probe/managerhelper"
"github.com/DataDog/datadog-agent/pkg/security/probe/sysctl"
"github.com/DataDog/datadog-agent/pkg/security/resolvers"
"github.com/DataDog/datadog-agent/pkg/security/resolvers/mount"
"github.com/DataDog/datadog-agent/pkg/security/resolvers/netns"
Expand Down Expand Up @@ -597,6 +598,9 @@ func (p *EBPFProbe) Start() error {
// start new tc classifier loop
go p.startSetupNewTCClassifierLoop()

// start sysctl snapshot loop
go p.startSysCtlSnapshotLoop()

return p.eventStream.Start(&p.wg)
}

Expand Down Expand Up @@ -1749,6 +1753,31 @@ func (p *EBPFProbe) Close() error {
return p.Resolvers.Close()
}

func (p *EBPFProbe) startSysCtlSnapshotLoop() {
ticker := time.NewTicker(p.config.RuntimeSecurity.SysCtlSnapshotPeriod)
defer ticker.Stop()
for {
select {
case <-p.ctx.Done():
return
case <-ticker.C:
// create the sysctl snapshot
event, err := sysctl.NewSnapshotEvent()
if err != nil {
seclog.Errorf("sysctl snapshot failed: %v", err)
continue
}

// send a sysctl snapshot event
rule := events.NewCustomRule(events.SysCtlSnapshotRuleID, events.SysCtlSnapshotRuleDesc)
customEvent := events.NewCustomEvent(model.CustomEventType, event)

p.probe.DispatchCustomEvent(rule, customEvent)
seclog.Tracef("sysctl snapshot sent !")
}
}
}

// QueuedNetworkDeviceError is used to indicate that the new network device was queued until its namespace handle is
// resolved.
type QueuedNetworkDeviceError struct {
Expand Down
167 changes: 167 additions & 0 deletions pkg/security/probe/sysctl/snapshot.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016-present Datadog, Inc.

//go:build linux

// Package sysctl is used to process and analyze sysctl data
package sysctl

import (
"encoding/json"
"fmt"
"github.com/DataDog/datadog-agent/pkg/util/kernel"
"io/fs"
"os"
"path"
"path/filepath"
"slices"
"strings"
)

var (
ignoredBaseNames = []string{
"netdev_rss_key",
"stable_secret",
}
redactedContent = "******"
)

// readFileContent reads a file and processes its content based on the given rules.
func readFileContent(file string) (string, error) {
if slices.Contains(ignoredBaseNames, path.Base(file)) {
return redactedContent, nil
}

data, err := os.ReadFile(file)
if err != nil {
return "", err
}

return string(data), nil
}

// SnapshotEvent is a wrapper used for serialization
type SnapshotEvent struct {
Sysctl Snapshot `json:"sysctl"`
}

// NewSnapshotEvent returns a new sysctl snapshot event
func NewSnapshotEvent() (*SnapshotEvent, error) {
se := &SnapshotEvent{
Sysctl: NewSnapshot(),
}
if err := se.Sysctl.Snapshot(); err != nil {
return nil, err
}
return se, nil
}

// ToJSON serializes the current SnapshotEvent object to JSON
func (s *SnapshotEvent) ToJSON() ([]byte, error) {
return json.Marshal(s)
}

// Snapshot defines an internal core dump
type Snapshot struct {
// Proc contains the /proc system control parameters and their values
Proc map[string]interface{} `json:"proc,omitempty"`
// Sys contains the /sys system control parameters and their values
Sys map[string]interface{} `json:"sys,omitempty"`
}

// NewSnapshot returns a new sysctl snapshot
func NewSnapshot() Snapshot {
return Snapshot{
Proc: make(map[string]interface{}),
Sys: make(map[string]interface{}),
}
}

// Snapshot runs the snapshot by going through the filesystem
func (s *Snapshot) Snapshot() error {
if err := s.snapshotProcSys(); err != nil {
return fmt.Errorf("couldn't snapshot /proc/sys: %w", err)
}

if err := s.snapshotSys(); err != nil {
return fmt.Errorf("coudln't snapshot /sys: %w", err)
}
return nil
}

// snapshotProcSys recursively reads files in /proc/sys and builds a nested JSON structure.
func (s *Snapshot) snapshotProcSys() error {
return filepath.Walk(kernel.HostProc("/sys"), func(file string, info fs.FileInfo, err error) error {
if err != nil {
return err
}

// Skip directories
if info.IsDir() {
return nil
}

// Skip if mode doesn't allow reading
mode := info.Mode()
if mode&0444 == 0 {
return nil
}

relPath, err := filepath.Rel(kernel.ProcFSRoot(), file)
if err != nil {
return err
}

value, err := readFileContent(file)
if err != nil {
return nil // Skip files that can't be read
}

s.InsertSnapshotEntry(s.Proc, relPath, value)
return nil
})
}

// snapshotSys reads security relevant files from the /sys filesystem
func (s *Snapshot) snapshotSys() error {
lockdownValue, err := readFileContent(kernel.HostSys("/kernel/security/lockdown"))
if err != nil {
return err
}
s.InsertSnapshotEntry(s.Sys, "kernel/security/lockdown", lockdownValue)

lsmValue, err := readFileContent(kernel.HostSys("/kernel/security/lsm"))
if err != nil {
return err
}
s.InsertSnapshotEntry(s.Sys, "kernel/security/lsm", lsmValue)
return nil
}

// InsertSnapshotEntry inserts the provided file and its value in the input data
func (s *Snapshot) InsertSnapshotEntry(data map[string]interface{}, file string, value string) {
if len(value) == 0 {
// ignore
return
}

parts := strings.Split(strings.TrimPrefix(file, "/"), string(os.PathSeparator))
current := data
for i, part := range parts {
if i == len(parts)-1 {
current[part] = strings.TrimSpace(value)
} else {
if _, exists := current[part]; !exists {
current[part] = make(map[string]interface{})
}
current = current[part].(map[string]interface{})
}
}
}

// ToJSON serializes the current Snapshot object to JSON
func (s *Snapshot) ToJSON() ([]byte, error) {
return json.Marshal(s)
}
Loading