diff --git a/api/datadoghq/v2alpha1/datadogagent_types.go b/api/datadoghq/v2alpha1/datadogagent_types.go index 9606de3b56..a7f2444d5c 100644 --- a/api/datadoghq/v2alpha1/datadogagent_types.go +++ b/api/datadoghq/v2alpha1/datadogagent_types.go @@ -567,6 +567,13 @@ type CWSSecurityProfilesConfig struct { // Default: true // +optional Enabled *bool `json:"enabled,omitempty"` + + // V2 enables Activity Dump v2 Security Profiles collection for Cloud Workload Security. + // Requires Agent version >= 7.81.0. On older or unidentifiable Agent images (e.g. digest-pinned + // or custom tags) this is disabled to avoid unbounded memory usage. + // Default: true + // +optional + V2 *bool `json:"v2,omitempty"` } type CWSRemoteConfigurationConfig struct { diff --git a/api/datadoghq/v2alpha1/zz_generated.deepcopy.go b/api/datadoghq/v2alpha1/zz_generated.deepcopy.go index 3ce3956403..72686f0ae8 100644 --- a/api/datadoghq/v2alpha1/zz_generated.deepcopy.go +++ b/api/datadoghq/v2alpha1/zz_generated.deepcopy.go @@ -695,6 +695,11 @@ func (in *CWSSecurityProfilesConfig) DeepCopyInto(out *CWSSecurityProfilesConfig *out = new(bool) **out = **in } + if in.V2 != nil { + in, out := &in.V2, &out.V2 + *out = new(bool) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CWSSecurityProfilesConfig. diff --git a/config/crd/bases/v1/datadoghq.com_datadogagentinternals.yaml b/config/crd/bases/v1/datadoghq.com_datadogagentinternals.yaml index 0fd41618bc..f776e61852 100644 --- a/config/crd/bases/v1/datadoghq.com_datadogagentinternals.yaml +++ b/config/crd/bases/v1/datadoghq.com_datadogagentinternals.yaml @@ -1407,6 +1407,13 @@ spec: Enabled enables Security Profiles collection for Cloud Workload Security. Default: true type: boolean + v2: + description: |- + V2 enables Activity Dump v2 Security Profiles collection for Cloud Workload Security. + Requires Agent version >= 7.81.0. On older or unidentifiable Agent images (e.g. digest-pinned + or custom tags) this is disabled to avoid unbounded memory usage. + Default: true + type: boolean type: object syscallMonitorEnabled: description: |- @@ -10114,6 +10121,13 @@ spec: Enabled enables Security Profiles collection for Cloud Workload Security. Default: true type: boolean + v2: + description: |- + V2 enables Activity Dump v2 Security Profiles collection for Cloud Workload Security. + Requires Agent version >= 7.81.0. On older or unidentifiable Agent images (e.g. digest-pinned + or custom tags) this is disabled to avoid unbounded memory usage. + Default: true + type: boolean type: object syscallMonitorEnabled: description: |- diff --git a/config/crd/bases/v1/datadoghq.com_datadogagentinternals_v1alpha1.json b/config/crd/bases/v1/datadoghq.com_datadogagentinternals_v1alpha1.json index 3c61a4a54a..a6edaf80c8 100644 --- a/config/crd/bases/v1/datadoghq.com_datadogagentinternals_v1alpha1.json +++ b/config/crd/bases/v1/datadoghq.com_datadogagentinternals_v1alpha1.json @@ -1397,6 +1397,10 @@ "enabled": { "description": "Enabled enables Security Profiles collection for Cloud Workload Security.\nDefault: true", "type": "boolean" + }, + "v2": { + "description": "V2 enables Activity Dump v2 Security Profiles collection for Cloud Workload Security.\nRequires Agent version \u003e= 7.81.0. On older or unidentifiable Agent images (e.g. digest-pinned\nor custom tags) this is disabled to avoid unbounded memory usage.\nDefault: true", + "type": "boolean" } }, "type": "object" @@ -9823,6 +9827,10 @@ "enabled": { "description": "Enabled enables Security Profiles collection for Cloud Workload Security.\nDefault: true", "type": "boolean" + }, + "v2": { + "description": "V2 enables Activity Dump v2 Security Profiles collection for Cloud Workload Security.\nRequires Agent version \u003e= 7.81.0. On older or unidentifiable Agent images (e.g. digest-pinned\nor custom tags) this is disabled to avoid unbounded memory usage.\nDefault: true", + "type": "boolean" } }, "type": "object" diff --git a/config/crd/bases/v1/datadoghq.com_datadogagentprofiles.yaml b/config/crd/bases/v1/datadoghq.com_datadogagentprofiles.yaml index 5bc20fa1e9..a34e0c0ca5 100644 --- a/config/crd/bases/v1/datadoghq.com_datadogagentprofiles.yaml +++ b/config/crd/bases/v1/datadoghq.com_datadogagentprofiles.yaml @@ -1407,6 +1407,13 @@ spec: Enabled enables Security Profiles collection for Cloud Workload Security. Default: true type: boolean + v2: + description: |- + V2 enables Activity Dump v2 Security Profiles collection for Cloud Workload Security. + Requires Agent version >= 7.81.0. On older or unidentifiable Agent images (e.g. digest-pinned + or custom tags) this is disabled to avoid unbounded memory usage. + Default: true + type: boolean type: object syscallMonitorEnabled: description: |- diff --git a/config/crd/bases/v1/datadoghq.com_datadogagentprofiles_v1alpha1.json b/config/crd/bases/v1/datadoghq.com_datadogagentprofiles_v1alpha1.json index a7488a18ca..26fa280c78 100644 --- a/config/crd/bases/v1/datadoghq.com_datadogagentprofiles_v1alpha1.json +++ b/config/crd/bases/v1/datadoghq.com_datadogagentprofiles_v1alpha1.json @@ -1401,6 +1401,10 @@ "enabled": { "description": "Enabled enables Security Profiles collection for Cloud Workload Security.\nDefault: true", "type": "boolean" + }, + "v2": { + "description": "V2 enables Activity Dump v2 Security Profiles collection for Cloud Workload Security.\nRequires Agent version \u003e= 7.81.0. On older or unidentifiable Agent images (e.g. digest-pinned\nor custom tags) this is disabled to avoid unbounded memory usage.\nDefault: true", + "type": "boolean" } }, "type": "object" diff --git a/config/crd/bases/v1/datadoghq.com_datadogagents.yaml b/config/crd/bases/v1/datadoghq.com_datadogagents.yaml index 61b96259f8..b50f1692bd 100644 --- a/config/crd/bases/v1/datadoghq.com_datadogagents.yaml +++ b/config/crd/bases/v1/datadoghq.com_datadogagents.yaml @@ -1411,6 +1411,13 @@ spec: Enabled enables Security Profiles collection for Cloud Workload Security. Default: true type: boolean + v2: + description: |- + V2 enables Activity Dump v2 Security Profiles collection for Cloud Workload Security. + Requires Agent version >= 7.81.0. On older or unidentifiable Agent images (e.g. digest-pinned + or custom tags) this is disabled to avoid unbounded memory usage. + Default: true + type: boolean type: object syscallMonitorEnabled: description: |- @@ -10207,6 +10214,13 @@ spec: Enabled enables Security Profiles collection for Cloud Workload Security. Default: true type: boolean + v2: + description: |- + V2 enables Activity Dump v2 Security Profiles collection for Cloud Workload Security. + Requires Agent version >= 7.81.0. On older or unidentifiable Agent images (e.g. digest-pinned + or custom tags) this is disabled to avoid unbounded memory usage. + Default: true + type: boolean type: object syscallMonitorEnabled: description: |- diff --git a/config/crd/bases/v1/datadoghq.com_datadogagents_v2alpha1.json b/config/crd/bases/v1/datadoghq.com_datadogagents_v2alpha1.json index 0f72aabd93..ad9f0167b9 100644 --- a/config/crd/bases/v1/datadoghq.com_datadogagents_v2alpha1.json +++ b/config/crd/bases/v1/datadoghq.com_datadogagents_v2alpha1.json @@ -1397,6 +1397,10 @@ "enabled": { "description": "Enabled enables Security Profiles collection for Cloud Workload Security.\nDefault: true", "type": "boolean" + }, + "v2": { + "description": "V2 enables Activity Dump v2 Security Profiles collection for Cloud Workload Security.\nRequires Agent version \u003e= 7.81.0. On older or unidentifiable Agent images (e.g. digest-pinned\nor custom tags) this is disabled to avoid unbounded memory usage.\nDefault: true", + "type": "boolean" } }, "type": "object" @@ -9922,6 +9926,10 @@ "enabled": { "description": "Enabled enables Security Profiles collection for Cloud Workload Security.\nDefault: true", "type": "boolean" + }, + "v2": { + "description": "V2 enables Activity Dump v2 Security Profiles collection for Cloud Workload Security.\nRequires Agent version \u003e= 7.81.0. On older or unidentifiable Agent images (e.g. digest-pinned\nor custom tags) this is disabled to avoid unbounded memory usage.\nDefault: true", + "type": "boolean" } }, "type": "object" diff --git a/docs/configuration.v2alpha1.md b/docs/configuration.v2alpha1.md index 2bc0e20346..99c727256c 100644 --- a/docs/configuration.v2alpha1.md +++ b/docs/configuration.v2alpha1.md @@ -101,6 +101,7 @@ spec: | features.cws.network.enabled | Enables Cloud Workload Security Network detections. Default: true | | features.cws.remoteConfiguration.enabled | Enables Remote Configuration for Cloud Workload Security. Default: true | | features.cws.securityProfiles.enabled | Enables Security Profiles collection for Cloud Workload Security. Default: true | +| features.cws.securityProfiles.v2 | Enables Activity Dump v2 Security Profiles collection for Cloud Workload Security. Requires Agent version >= 7.81.0. On older or unidentifiable Agent images (e.g. digest-pinned or custom tags) this is disabled to avoid unbounded memory usage. Default: true | | features.cws.syscallMonitorEnabled | SyscallMonitorEnabled enables Syscall Monitoring (recommended for troubleshooting only). Default: false | | features.dataPlane.dogstatsd.enabled | Configures the Data Plane to handle DogStatsD traffic. When set to false, DogStatsD is handled by the Core Agent instead. Default: true | | features.dataPlane.enabled | Enables the Data Plane. Default: false | diff --git a/docs/configuration_public.md b/docs/configuration_public.md index c141925584..079ddd8346 100644 --- a/docs/configuration_public.md +++ b/docs/configuration_public.md @@ -168,6 +168,9 @@ spec: `features.cws.securityProfiles.enabled` : Enables Security Profiles collection for Cloud Workload Security. Default: true +`features.cws.securityProfiles.v2` +: Enables Activity Dump v2 Security Profiles collection for Cloud Workload Security. Requires Agent version >= 7.81.0. On older or unidentifiable Agent images (e.g. digest-pinned or custom tags) this is disabled to avoid unbounded memory usage. Default: true + `features.cws.syscallMonitorEnabled` : SyscallMonitorEnabled enables Syscall Monitoring (recommended for troubleshooting only). Default: false diff --git a/internal/controller/datadogagent/feature/cws/envvar.go b/internal/controller/datadogagent/feature/cws/envvar.go index f2d1a2204d..09e865f0b9 100644 --- a/internal/controller/datadogagent/feature/cws/envvar.go +++ b/internal/controller/datadogagent/feature/cws/envvar.go @@ -16,4 +16,5 @@ const ( DDRuntimeSecurityConfigRemoteConfigurationEnabled = "DD_RUNTIME_SECURITY_CONFIG_REMOTE_CONFIGURATION_ENABLED" DDRuntimeSecurityConfigDirectSendFromSystemProbe = "DD_RUNTIME_SECURITY_CONFIG_DIRECT_SEND_FROM_SYSTEM_PROBE" DDRuntimeSecurityConfigEventGRPCServer = "DD_RUNTIME_SECURITY_CONFIG_EVENT_GRPC_SERVER" + DDRuntimeConfigActivityDumpV2 = "DD_RUNTIME_SECURITY_CONFIG_ACTIVITY_DUMP_V2" ) diff --git a/internal/controller/datadogagent/feature/cws/feature.go b/internal/controller/datadogagent/feature/cws/feature.go index be61445ab6..2daca83077 100644 --- a/internal/controller/datadogagent/feature/cws/feature.go +++ b/internal/controller/datadogagent/feature/cws/feature.go @@ -24,9 +24,13 @@ import ( "github.com/DataDog/datadog-operator/internal/controller/datadogagent/object/volume" "github.com/DataDog/datadog-operator/pkg/constants" "github.com/DataDog/datadog-operator/pkg/controller/utils/comparison" + "github.com/DataDog/datadog-operator/pkg/images" "github.com/DataDog/datadog-operator/pkg/kubernetes" + "github.com/DataDog/datadog-operator/pkg/utils" ) +const ActivityDumpV2MinVersion = "7.81.0-0" + func init() { err := feature.Register(feature.CWSIDType, buildCWSFeature) if err != nil { @@ -52,6 +56,7 @@ type cwsFeature struct { directSendFromSystemProbe bool enforcementEnabled bool useVSock bool + activityDumpV2 bool owner metav1.Object logger logr.Logger @@ -62,6 +67,19 @@ type cwsFeature struct { customConfigAnnotationValue string } +// activityDumpV2Supported reports whether the configured Agent image is known to be >= +// ActivityDumpV2MinVersion. The default value passed to IsAboveMinVersion is intentionally false: +// Activity Dump v2 lacks guards against unbounded memory allocation on older Agents, so when the +// version cannot be parsed (e.g. custom or digest-pinned tags) we must fail closed rather than risk +// an OOM. Do not flip this to true without adding such guards. +func activityDumpV2Supported(ddaSpec *v2alpha1.DatadogAgentSpec) bool { + defaultValue := false + if nodeAgent, ok := ddaSpec.Override[v2alpha1.NodeAgentComponentName]; ok && nodeAgent.Image != nil { + return utils.IsAboveMinVersion(common.GetAgentVersionFromImage(*nodeAgent.Image), ActivityDumpV2MinVersion, &defaultValue) + } + return utils.IsAboveMinVersion(images.AgentLatestVersion, ActivityDumpV2MinVersion, &defaultValue) +} + // ID returns the ID of the Feature func (f *cwsFeature) ID() feature.IDType { return feature.CWSIDType @@ -104,6 +122,19 @@ func (f *cwsFeature) Configure(dda metav1.Object, ddaSpec *v2alpha1.DatadogAgent } if cwsConfig.SecurityProfiles != nil { f.activityDumpEnabled = apiutils.BoolValue(cwsConfig.SecurityProfiles.Enabled) + + if apiutils.BoolValue(cwsConfig.SecurityProfiles.V2) { + if activityDumpV2Supported(ddaSpec) { + f.activityDumpV2 = true + } else { + // Surface the silent gate: the user explicitly opted into v2 but we are disabling + // it because the Agent version can't be confirmed to support it. Logged at the + // default Info level so it is visible without raising log verbosity. + f.logger.Info("CWS Activity Dump v2 was requested but is disabled: the Agent image version could not be confirmed to be >= "+ + ActivityDumpV2MinVersion+" (custom or digest-pinned tags parse as unknown). Enabling it on older Agents risks unbounded memory usage.", + "minVersion", ActivityDumpV2MinVersion) + } + } } if ddaSpec.Features != nil && ddaSpec.Features.RemoteConfiguration != nil { diff --git a/internal/controller/datadogagent/feature/cws/feature_test.go b/internal/controller/datadogagent/feature/cws/feature_test.go index d42cbb7dcf..14567e2695 100644 --- a/internal/controller/datadogagent/feature/cws/feature_test.go +++ b/internal/controller/datadogagent/feature/cws/feature_test.go @@ -128,6 +128,127 @@ func Test_cwsFeature_Configure(t *testing.T) { tests.Run(t, buildCWSFeature) } +func Test_activityDumpV2Supported(t *testing.T) { + nodeAgentWithTag := func(tag string) map[v2alpha1.ComponentName]*v2alpha1.DatadogAgentComponentOverride { + return map[v2alpha1.ComponentName]*v2alpha1.DatadogAgentComponentOverride{ + v2alpha1.NodeAgentComponentName: { + Image: &v2alpha1.AgentImageConfig{Tag: tag}, + }, + } + } + + tests := []struct { + name string + override map[v2alpha1.ComponentName]*v2alpha1.DatadogAgentComponentOverride + want bool + }{ + { + name: "agent tag at minimum version", + override: nodeAgentWithTag("7.81.0"), + want: true, + }, + { + name: "agent tag above minimum version", + override: nodeAgentWithTag("7.82.0"), + want: true, + }, + { + name: "agent tag below minimum version", + override: nodeAgentWithTag("7.80.0"), + want: false, + }, + { + // Fails closed: an unparseable tag (custom build, digest-pinned, etc.) must not enable v2 + // because older Agents have no guard against unbounded memory allocation. + name: "unparseable custom tag fails closed", + override: nodeAgentWithTag("custom-internal-build"), + want: false, + }, + { + // No image override resolves to images.AgentLatestVersion, which is currently below the + // minimum, so v2 stays off until the operator default Agent version is bumped. + name: "no image override uses latest default", + override: nil, + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ddaSpec := &v2alpha1.DatadogAgentSpec{Override: tt.override} + assert.Equal(t, tt.want, activityDumpV2Supported(ddaSpec)) + }) + } +} + +func Test_cwsFeature_ConfigureActivityDumpV2(t *testing.T) { + newDDASpec := func(v2 *bool, tag string) *v2alpha1.DatadogAgentSpec { + spec := &v2alpha1.DatadogAgentSpec{ + Features: &v2alpha1.DatadogFeatures{ + CWS: &v2alpha1.CWSFeatureConfig{ + Enabled: ptr.To(true), + SecurityProfiles: &v2alpha1.CWSSecurityProfilesConfig{ + Enabled: ptr.To(true), + V2: v2, + }, + }, + }, + } + if tag != "" { + spec.Override = map[v2alpha1.ComponentName]*v2alpha1.DatadogAgentComponentOverride{ + v2alpha1.NodeAgentComponentName: {Image: &v2alpha1.AgentImageConfig{Tag: tag}}, + } + } + return spec + } + + tests := []struct { + name string + v2 *bool + tag string + want bool + }{ + { + name: "v2 requested on supported agent is enabled", + v2: ptr.To(true), + tag: "7.81.0", + want: true, + }, + { + name: "v2 requested on old agent is gated off", + v2: ptr.To(true), + tag: "7.80.0", + want: false, + }, + { + name: "v2 requested on unparseable tag is gated off", + v2: ptr.To(true), + tag: "custom-internal-build", + want: false, + }, + { + name: "v2 not requested stays off even on supported agent", + v2: ptr.To(false), + tag: "7.81.0", + want: false, + }, + { + name: "v2 unset stays off", + v2: nil, + tag: "7.81.0", + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f := &cwsFeature{} + f.Configure(&v2alpha1.DatadogAgent{}, newDDASpec(tt.v2, tt.tag), nil) + assert.Equal(t, tt.want, f.activityDumpV2) + }) + } +} + func cwsAgentNodeWantFunc(withSubFeatures bool, directSendFromSysProbe bool, enforcementEnabled bool) *test.ComponentTest { return test.NewDefaultComponentTest().WithWantFunc( func(t testing.TB, mgrInterface feature.PodTemplateManagers) {