Skip to content

Commit

Permalink
[CWS] Allow suppressing all non-drift rule-based events (#25913)
Browse files Browse the repository at this point in the history
Co-authored-by: spikat <[email protected]>
  • Loading branch information
YoannGh and spikat authored May 31, 2024
1 parent fe122d8 commit 4e1b59c
Show file tree
Hide file tree
Showing 15 changed files with 425 additions and 298 deletions.
15 changes: 13 additions & 2 deletions docs/cloud-workload-security/backend.md
Original file line number Diff line number Diff line change
Expand Up @@ -1384,6 +1384,10 @@ CSM Threats logs have the following JSON schema:
"event_in_profile": {
"type": "boolean",
"description": "True if the corresponding event is part of this profile"
},
"event_type_state": {
"type": "string",
"description": "State of the event type in this profile"
}
},
"additionalProperties": false,
Expand All @@ -1392,7 +1396,8 @@ CSM Threats logs have the following JSON schema:
"name",
"version",
"tags",
"event_in_profile"
"event_in_profile",
"event_type_state"
],
"description": "SecurityProfileContextSerializer serializes the security profile context in an event"
},
Expand Down Expand Up @@ -3693,6 +3698,10 @@ CSM Threats logs have the following JSON schema:
"event_in_profile": {
"type": "boolean",
"description": "True if the corresponding event is part of this profile"
},
"event_type_state": {
"type": "string",
"description": "State of the event type in this profile"
}
},
"additionalProperties": false,
Expand All @@ -3701,7 +3710,8 @@ CSM Threats logs have the following JSON schema:
"name",
"version",
"tags",
"event_in_profile"
"event_in_profile",
"event_type_state"
],
"description": "SecurityProfileContextSerializer serializes the security profile context in an event"
}
Expand All @@ -3714,6 +3724,7 @@ CSM Threats logs have the following JSON schema:
| `version` | Version of the profile in use |
| `tags` | List of tags associated to this profile |
| `event_in_profile` | True if the corresponding event is part of this profile |
| `event_type_state` | State of the event type in this profile |


## `SignalEvent`
Expand Down
7 changes: 6 additions & 1 deletion docs/cloud-workload-security/backend.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1368,6 +1368,10 @@
"event_in_profile": {
"type": "boolean",
"description": "True if the corresponding event is part of this profile"
},
"event_type_state": {
"type": "string",
"description": "State of the event type in this profile"
}
},
"additionalProperties": false,
Expand All @@ -1376,7 +1380,8 @@
"name",
"version",
"tags",
"event_in_profile"
"event_in_profile",
"event_type_state"
],
"description": "SecurityProfileContextSerializer serializes the security profile context in an event"
},
Expand Down
2 changes: 1 addition & 1 deletion pkg/security/module/cws.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ func (c *CWSConsumer) sendStats() {
if counter > 0 {
tags := []string{
fmt.Sprintf("rule_id:%s", statsTags.RuleID),
fmt.Sprintf("tree_type:%s", statsTags.TreeType),
fmt.Sprintf("suppression_type:%s", statsTags.SuppressionType),
}
_ = c.statsdClient.Count(metrics.MetricRulesSuppressed, counter, tags, 1.0)
}
Expand Down
83 changes: 47 additions & 36 deletions pkg/security/rules/autosuppression/autosuppression.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,23 @@ import (
"github.com/DataDog/datadog-agent/pkg/security/secl/rules"
)

// isAllowAutosuppressionRule returns true if the given rule allows auto suppression
func isAllowAutosuppressionRule(rule *rules.Rule) bool {
if val, ok := rule.Definition.GetTag("allow_autosuppression"); ok {
// booleanTagEquals returns true if the given rule has the given tag set to a boolean and its value matches the given value
func booleanTagEquals(rule *rules.Rule, tag string, value bool) bool {
if val, ok := rule.Definition.GetTag(tag); ok {
b, err := strconv.ParseBool(val)
return err == nil && b
return err == nil && b == value
}
return false
}

func isAllowAutosuppressionRule(rule *rules.Rule) bool {
return booleanTagEquals(rule, "allow_autosuppression", true)
}

const (
securityProfileTreeType = "security_profile"
activityDumpTreeType = "activity_dump"
securityProfileSuppressionType = "security_profile"
activityDumpSuppressionType = "activity_dump"
noWorkloadDriftSuppressionType = "no_workload_drift"
)

// Opts holds options for auto suppression
Expand All @@ -40,60 +45,66 @@ type Opts struct {

// StatsTags holds tags for auto suppression stats
type StatsTags struct {
RuleID string
TreeType string
RuleID string
SuppressionType string
}

// AutoSuppression is a struct that encapsulates the auto suppression logic
type AutoSuppression struct {
once sync.Once
opts Opts
statsLock sync.RWMutex
stats map[StatsTags]*atomic.Int64
enabledTreeTypes []string
once sync.Once
opts Opts
statsLock sync.RWMutex
stats map[StatsTags]*atomic.Int64
}

// Init initializes the auto suppression with the given options
func (as *AutoSuppression) Init(opts Opts) {
as.once.Do(func() {
as.opts = opts
as.stats = make(map[StatsTags]*atomic.Int64)
if opts.SecurityProfileAutoSuppressionEnabled {
as.enabledTreeTypes = append(as.enabledTreeTypes, securityProfileTreeType)
}
if opts.ActivityDumpAutoSuppressionEnabled {
as.enabledTreeTypes = append(as.enabledTreeTypes, activityDumpTreeType)
}
})
}

// Suppresses returns true if the event should be suppressed for the given rule, false otherwise. It also counts statistics depending on this result
func (as *AutoSuppression) Suppresses(rule *rules.Rule, event *model.Event) bool {
if as.opts.SecurityProfileAutoSuppressionEnabled &&
event.IsInProfile() &&
slices.Contains(as.opts.EventTypes, event.GetEventType()) &&
isAllowAutosuppressionRule(rule) {
as.count(rule.ID, securityProfileTreeType)
return true
} else if as.opts.ActivityDumpAutoSuppressionEnabled &&
event.HasActiveActivityDump() &&
slices.Contains(as.opts.EventTypes, event.GetEventType()) &&
isAllowAutosuppressionRule(rule) {
as.count(rule.ID, activityDumpTreeType)
return true
if isAllowAutosuppressionRule(rule) && event.ContainerContext.ID != "" && slices.Contains(as.opts.EventTypes, event.GetEventType()) {
if as.opts.ActivityDumpAutoSuppressionEnabled {
if event.HasActiveActivityDump() {
as.count(rule.ID, activityDumpSuppressionType)
return true
} else if event.SecurityProfileContext.EventTypeState == model.NoProfile {
as.count(rule.ID, noWorkloadDriftSuppressionType)
return true
}
}
if as.opts.SecurityProfileAutoSuppressionEnabled {
if event.IsInProfile() {
as.count(rule.ID, securityProfileSuppressionType)
return true
}
}
}
return false
}

// Apply resets the auto suppression stats based on the given ruleset
func (as *AutoSuppression) Apply(ruleSet *rules.RuleSet) {
var enabledSuppressionTypes []string
if as.opts.SecurityProfileAutoSuppressionEnabled {
enabledSuppressionTypes = append(enabledSuppressionTypes, securityProfileSuppressionType)
}
if as.opts.ActivityDumpAutoSuppressionEnabled {
enabledSuppressionTypes = append(enabledSuppressionTypes, activityDumpSuppressionType)
enabledSuppressionTypes = append(enabledSuppressionTypes, noWorkloadDriftSuppressionType)
}

tags := StatsTags{}
newStats := make(map[StatsTags]*atomic.Int64)
for _, rule := range ruleSet.GetRules() {
if isAllowAutosuppressionRule(rule) {
tags.RuleID = rule.ID
for _, treeType := range as.enabledTreeTypes {
tags.TreeType = treeType
for _, suppressionType := range enabledSuppressionTypes {
tags.SuppressionType = suppressionType
newStats[tags] = atomic.NewInt64(0)
}
}
Expand All @@ -104,13 +115,13 @@ func (as *AutoSuppression) Apply(ruleSet *rules.RuleSet) {
as.statsLock.Unlock()
}

func (as *AutoSuppression) count(ruleID string, treeType string) {
func (as *AutoSuppression) count(ruleID string, suppressionType string) {
as.statsLock.RLock()
defer as.statsLock.RUnlock()

tags := StatsTags{
RuleID: ruleID,
TreeType: treeType,
RuleID: ruleID,
SuppressionType: suppressionType,
}

if stat, ok := as.stats[tags]; ok {
Expand Down
9 changes: 5 additions & 4 deletions pkg/security/secl/model/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,11 @@ type ContainerContext struct {

// SecurityProfileContext holds the security context of the profile
type SecurityProfileContext struct {
Name string `field:"name"` // SECLDoc[name] Definition:`Name of the security profile`
Version string `field:"version"` // SECLDoc[version] Definition:`Version of the security profile`
Tags []string `field:"tags"` // SECLDoc[tags] Definition:`Tags of the security profile`
EventTypes []EventType `field:"event_types"` // SECLDoc[event_types] Definition:`Event types enabled for the security profile`
Name string `field:"name"` // SECLDoc[name] Definition:`Name of the security profile`
Version string `field:"version"` // SECLDoc[version] Definition:`Version of the security profile`
Tags []string `field:"tags"` // SECLDoc[tags] Definition:`Tags of the security profile`
EventTypes []EventType `field:"event_types"` // SECLDoc[event_types] Definition:`Event types enabled for the security profile`
EventTypeState EventFilteringProfileState `field:"-"` // State of the event type in this profile
}

// IPPortContext is used to hold an IP and Port
Expand Down
54 changes: 54 additions & 0 deletions pkg/security/secl/model/security_profile.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// 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.

// Package model holds the security profile data model
package model

// EventFilteringProfileState is used to compute metrics for the event filtering feature
type EventFilteringProfileState uint8

const (
// NoProfile is used to count the events for which we didn't have a profile
NoProfile EventFilteringProfileState = iota
// ProfileAtMaxSize is used to count the events that didn't make it into a profile because their matching profile
// reached the max size threshold
ProfileAtMaxSize
// UnstableEventType is used to count the events that didn't make it into a profile because their matching profile was
// unstable for their event type
UnstableEventType
// StableEventType is used to count the events linked to a stable profile for their event type
StableEventType
// AutoLearning is used to count the event during the auto learning phase
AutoLearning
// WorkloadWarmup is used to count the learned events due to workload warm up time
WorkloadWarmup
)

// AllEventFilteringProfileState is the list of all EventFilteringProfileState
var AllEventFilteringProfileState = []EventFilteringProfileState{NoProfile, ProfileAtMaxSize, UnstableEventType, StableEventType, AutoLearning, WorkloadWarmup}

// String returns the string representation of the EventFilteringProfileState
func (efr EventFilteringProfileState) String() string {
switch efr {
case NoProfile:
return "no_profile"
case ProfileAtMaxSize:
return "profile_at_max_size"
case UnstableEventType:
return "unstable_event_type"
case StableEventType:
return "stable_event_type"
case AutoLearning:
return "auto_learning"
case WorkloadWarmup:
return "workload_warmup"
}
return ""
}

// ToTag returns the tag representation of the EventFilteringProfileState
func (efr EventFilteringProfileState) ToTag() string {
return "profile_state:" + efr.String()
}
Loading

0 comments on commit 4e1b59c

Please sign in to comment.