Skip to content

Commit 34e8a62

Browse files
authored
fix(clusterchecksrunner): preserve packaged conf.d assets (e.g. SNMP … (#3374)
fix(clusterchecksrunner): preserve packaged conf.d assets (e.g. SNMP profiles) on CLC runners (CONS-8516) test(common): cover remove-corechecks volume helpers The patch-coverage gate computes per-package coverage (go test ./... without -coverpkg), so GetVolumeMountForRmCorechecksInit and the edited remove-corechecks volume helpers in the common package registered as 0% even though a clusterchecksrunner test exercises them. Add a common-package test that covers these helpers directly. fix(clusterchecksrunner): copy check assets without preserving ownership cp -a (= --preserve=all) tries to preserve the root ownership of the image's packaged conf.d. When the runner is forced to run as a non-root UID (OpenShift restricted SCC, or a securityContext.runAsUser override), that fails with "Operation not permitted" and, under set -e, blocks the pod from starting. Use cp -RL to copy recursively without preserving privileged attributes so the assets are owned by the runtime user. test(golden): regenerate golden manifests for cp -RL asset copy Merge branch 'main' into fspano/CONS-8516-clc-preserve-check-assets Co-authored-by: frank.spano <frank.spano@datadoghq.com>
1 parent ae9eacd commit 34e8a62

10 files changed

Lines changed: 258 additions & 6 deletions

File tree

internal/controller/datadogagent/common/const.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,15 @@ const (
8383
ChecksdVolumeName = "checksd"
8484
ChecksdVolumePath = "/checks.d"
8585

86+
// RmCorechecksVolumeName is the volume that overlays the agent's conf.d
87+
// directory on the cluster checks runner to prevent default core checks
88+
// from running there.
89+
RmCorechecksVolumeName = "remove-corechecks"
90+
// RmCorechecksConfdInitPath is the scratch mount path used by the init
91+
// container that seeds the remove-corechecks overlay with the packaged
92+
// conf.d assets (e.g. SNMP profiles) from the agent image.
93+
RmCorechecksConfdInitPath = "/opt/conf.d-overlay"
94+
8695
HostRootVolumeName = "hostroot"
8796
HostRootHostPath = "/"
8897
HostRootMountPath = "/host/root"

internal/controller/datadogagent/common/volumes.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ func GetVolumeForChecksd() corev1.Volume {
4949
// GetVolumeForRmCorechecks return the volume that overwrites the corecheck directory
5050
func GetVolumeForRmCorechecks() corev1.Volume {
5151
return corev1.Volume{
52-
Name: "remove-corechecks",
52+
Name: RmCorechecksVolumeName,
5353
VolumeSource: corev1.VolumeSource{
5454
EmptyDir: &corev1.EmptyDirVolumeSource{},
5555
},
@@ -171,11 +171,22 @@ func GetVolumeMountForChecksd() corev1.VolumeMount {
171171
// GetVolumeMountForRmCorechecks return the VolumeMount that overwrites the corechecks directory
172172
func GetVolumeMountForRmCorechecks() corev1.VolumeMount {
173173
return corev1.VolumeMount{
174-
Name: "remove-corechecks",
174+
Name: RmCorechecksVolumeName,
175175
MountPath: fmt.Sprintf("%s/%s", ConfigVolumePath, "conf.d"),
176176
}
177177
}
178178

179+
// GetVolumeMountForRmCorechecksInit return the VolumeMount used by the init
180+
// container that seeds the remove-corechecks overlay. It is mounted at a scratch
181+
// path (not over the agent conf.d) so the init container can read the packaged
182+
// conf.d assets from the agent image and copy them into the overlay.
183+
func GetVolumeMountForRmCorechecksInit() corev1.VolumeMount {
184+
return corev1.VolumeMount{
185+
Name: RmCorechecksVolumeName,
186+
MountPath: RmCorechecksConfdInitPath,
187+
}
188+
}
189+
179190
// GetVolumeMountForAuth returns the VolumeMount that contains the authentication information
180191
func GetVolumeMountForAuth(readOnly bool) corev1.VolumeMount {
181192
return corev1.VolumeMount{
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Unless explicitly stated otherwise all files in this repository are licensed
2+
// under the Apache License Version 2.0.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
// Copyright 2016-present Datadog, Inc.
5+
6+
package common
7+
8+
import (
9+
"fmt"
10+
"testing"
11+
12+
"github.com/stretchr/testify/assert"
13+
corev1 "k8s.io/api/core/v1"
14+
)
15+
16+
func TestGetVolumeForRmCorechecks(t *testing.T) {
17+
vol := GetVolumeForRmCorechecks()
18+
19+
assert.Equal(t, RmCorechecksVolumeName, vol.Name)
20+
assert.NotNil(t, vol.EmptyDir, "remove-corechecks must be backed by an emptyDir so the init container can seed it")
21+
}
22+
23+
func TestGetVolumeMountForRmCorechecks(t *testing.T) {
24+
vm := GetVolumeMountForRmCorechecks()
25+
26+
assert.Equal(t, RmCorechecksVolumeName, vm.Name)
27+
// This mount overlays the agent conf.d so default core checks do not run.
28+
assert.Equal(t, fmt.Sprintf("%s/%s", ConfigVolumePath, "conf.d"), vm.MountPath)
29+
}
30+
31+
func TestGetVolumeMountForRmCorechecksInit(t *testing.T) {
32+
vm := GetVolumeMountForRmCorechecksInit()
33+
34+
// Shares the remove-corechecks volume ...
35+
assert.Equal(t, RmCorechecksVolumeName, vm.Name)
36+
// ... but mounts it at a scratch path, NOT over the agent conf.d, so the
37+
// init container can still read the image's packaged conf.d assets.
38+
assert.Equal(t, RmCorechecksConfdInitPath, vm.MountPath)
39+
assert.NotEqual(t, fmt.Sprintf("%s/%s", ConfigVolumePath, "conf.d"), vm.MountPath)
40+
}
41+
42+
// TestRmCorechecksInitAndOverlayShareVolume documents the contract the init
43+
// container relies on: the seeding mount and the runtime overlay mount refer to
44+
// the same volume at different paths, so assets copied by the init container are
45+
// visible under the agent conf.d overlay at runtime.
46+
func TestRmCorechecksInitAndOverlayShareVolume(t *testing.T) {
47+
initMount := GetVolumeMountForRmCorechecksInit()
48+
overlayMount := GetVolumeMountForRmCorechecks()
49+
50+
assert.Equal(t, initMount.Name, overlayMount.Name)
51+
assert.NotEqual(t, initMount.MountPath, overlayMount.MountPath)
52+
53+
// Both mounts must reference the volume produced by GetVolumeForRmCorechecks.
54+
vol := GetVolumeForRmCorechecks()
55+
for _, vm := range []corev1.VolumeMount{initMount, overlayMount} {
56+
assert.Equal(t, vol.Name, vm.Name)
57+
}
58+
}

internal/controller/datadogagent/component/clusterchecksrunner/const.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,35 @@
55

66
package clusterchecksrunner
77

8+
import (
9+
"github.com/DataDog/datadog-operator/internal/controller/datadogagent/common"
10+
)
11+
812
const (
913
pdbMaxUnavailableInstances = 1
14+
15+
// initCopyCheckAssetsContainerName is the name of the init container that
16+
// seeds the conf.d overlay with packaged check assets from the agent image.
17+
initCopyCheckAssetsContainerName = "init-copy-check-assets"
18+
19+
// copyCheckAssetsCmd copies the packaged conf.d assets from the agent image
20+
// into the remove-corechecks overlay, then drops the default check
21+
// configurations ("*.yaml.default") so default core checks still do not run
22+
// on the cluster checks runner. This preserves packaged assets that live
23+
// under "conf.d/<check>.d/" subdirectories (for example SNMP profiles under
24+
// "snmp.d/default_profiles"), mirroring the Helm chart behaviour and letting
25+
// cluster checks such as the SNMP check autodetect profiles.
26+
//
27+
// The copy uses "cp -RL" rather than "cp -a": the packaged conf.d is owned by
28+
// root in the image, and "cp -a" (= --preserve=all) would try to preserve
29+
// that ownership. When the runner is forced to run as a non-root UID (e.g. an
30+
// OpenShift restricted SCC or a securityContext.runAsUser override) preserving
31+
// ownership fails with "Operation not permitted", and with "set -e" that would
32+
// block the pod from starting. Copying recursively without preserving
33+
// privileged attributes lets the assets be owned by the runtime user instead.
34+
copyCheckAssetsCmd = `set -euo pipefail
35+
if [ -d /etc/datadog-agent/conf.d ]; then
36+
cp -RL /etc/datadog-agent/conf.d/. ` + common.RmCorechecksConfdInitPath + `/
37+
find ` + common.RmCorechecksConfdInitPath + ` -type f -name '*.yaml.default' -delete
38+
fi`
1039
)

internal/controller/datadogagent/component/clusterchecksrunner/default.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,28 @@ func defaultPodSpec(dda metav1.Object, volumes []corev1.Volume, volumeMounts []c
161161
},
162162
VolumeMounts: volumeMountsForInitConfig(),
163163
},
164+
{
165+
// Seed the conf.d overlay with the packaged check assets from
166+
// the agent image (e.g. SNMP profiles) while dropping default
167+
// check configs, so cluster checks that rely on those assets
168+
// (such as SNMP profile autodetection) work on the runner. This
169+
// container intentionally does not mount the config or
170+
// remove-corechecks volumes over /etc/datadog-agent/conf.d so it
171+
// can read the image's packaged conf.d.
172+
Name: initCopyCheckAssetsContainerName,
173+
Image: clusterChecksRunnerImage(),
174+
Command: []string{"bash", "-c"},
175+
Args: []string{
176+
copyCheckAssetsCmd,
177+
},
178+
VolumeMounts: []corev1.VolumeMount{
179+
common.GetVolumeMountForRmCorechecksInit(),
180+
},
181+
SecurityContext: &corev1.SecurityContext{
182+
ReadOnlyRootFilesystem: new(true),
183+
AllowPrivilegeEscalation: new(false),
184+
},
185+
},
164186
},
165187
Containers: []corev1.Container{
166188
{

internal/controller/datadogagent/component/clusterchecksrunner/default_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"testing"
1010

1111
"github.com/stretchr/testify/assert"
12+
"github.com/stretchr/testify/require"
1213
corev1 "k8s.io/api/core/v1"
1314
policyv1 "k8s.io/api/policy/v1"
1415
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -42,6 +43,52 @@ func Test_getPodDisruptionBudget(t *testing.T) {
4243
assert.Nil(t, testpdb.Spec.MinAvailable)
4344
}
4445

46+
func Test_copyCheckAssetsInitContainer(t *testing.T) {
47+
dda := v2alpha1.DatadogAgent{
48+
ObjectMeta: metav1.ObjectMeta{
49+
Name: "my-datadog-agent",
50+
Namespace: "some-namespace",
51+
},
52+
}
53+
54+
podSpec := NewDefaultClusterChecksRunnerPodTemplateSpec(dda.GetObjectMeta(), &dda.Spec).Spec
55+
56+
// Locate the init container that seeds the conf.d overlay with packaged
57+
// check assets (SNMP profiles, etc.) so cluster checks can use them.
58+
var initContainer *corev1.Container
59+
for i := range podSpec.InitContainers {
60+
if podSpec.InitContainers[i].Name == initCopyCheckAssetsContainerName {
61+
initContainer = &podSpec.InitContainers[i]
62+
break
63+
}
64+
}
65+
66+
if !assert.NotNil(t, initContainer, "expected a %q init container", initCopyCheckAssetsContainerName) {
67+
return
68+
}
69+
70+
// It must mount remove-corechecks at the scratch path and must NOT overlay
71+
// /etc/datadog-agent or its conf.d, otherwise it could not read the image's
72+
// packaged conf.d assets.
73+
assert.Len(t, initContainer.VolumeMounts, 1)
74+
assert.Equal(t, common.RmCorechecksVolumeName, initContainer.VolumeMounts[0].Name)
75+
assert.Equal(t, common.RmCorechecksConfdInitPath, initContainer.VolumeMounts[0].MountPath)
76+
for _, vm := range initContainer.VolumeMounts {
77+
assert.NotEqual(t, common.ConfigVolumePath, vm.MountPath)
78+
assert.NotEqual(t, common.ConfigVolumePath+"/conf.d", vm.MountPath)
79+
}
80+
81+
// The command copies the image conf.d into the overlay and drops default
82+
// check configs while preserving packaged sub-directory assets. It must copy
83+
// without preserving ownership (no "cp -a") so it does not fail when the
84+
// runner is forced to run as a non-root UID.
85+
require.Len(t, initContainer.Args, 1)
86+
assert.Contains(t, initContainer.Args[0], "cp -RL /etc/datadog-agent/conf.d/.")
87+
assert.NotContains(t, initContainer.Args[0], "cp -a", "cp -a preserves root ownership and breaks non-root runners")
88+
assert.Contains(t, initContainer.Args[0], common.RmCorechecksConfdInitPath)
89+
assert.Contains(t, initContainer.Args[0], "*.yaml.default")
90+
}
91+
4592
func TestDefaultEnvVarsJMXUseContainerSupport(t *testing.T) {
4693
tests := []struct {
4794
name string

internal/controller/testutils/renderer/testdata/golden/comprehensive-aks.golden.yaml

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2168,7 +2168,7 @@ apiVersion: apps/v1
21682168
kind: Deployment
21692169
metadata:
21702170
annotations:
2171-
agent.datadoghq.com/agentspechash: 9f9cc18615463016b51e37676cc4b72a
2171+
agent.datadoghq.com/agentspechash: e5182e5df9f9aa5754b67d69dde750fe
21722172
labels:
21732173
agent.datadoghq.com/component: cluster-checks-runner
21742174
agent.datadoghq.com/name: datadog-agent
@@ -2362,6 +2362,25 @@ spec:
23622362
- mountPath: /checks.d
23632363
name: checksd
23642364
readOnly: true
2365+
- args:
2366+
- |-
2367+
set -euo pipefail
2368+
if [ -d /etc/datadog-agent/conf.d ]; then
2369+
cp -RL /etc/datadog-agent/conf.d/. /opt/conf.d-overlay/
2370+
find /opt/conf.d-overlay -type f -name '*.yaml.default' -delete
2371+
fi
2372+
command:
2373+
- bash
2374+
- -c
2375+
image: registry.datadoghq.com/agent:7.81.1
2376+
name: init-copy-check-assets
2377+
resources: {}
2378+
securityContext:
2379+
allowPrivilegeEscalation: false
2380+
readOnlyRootFilesystem: true
2381+
volumeMounts:
2382+
- mountPath: /opt/conf.d-overlay
2383+
name: remove-corechecks
23652384
serviceAccountName: datadog-agent-cluster-checks-runner
23662385
volumes:
23672386
- configMap:

internal/controller/testutils/renderer/testdata/golden/comprehensive-autopilot.golden.yaml

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2133,7 +2133,7 @@ apiVersion: apps/v1
21332133
kind: Deployment
21342134
metadata:
21352135
annotations:
2136-
agent.datadoghq.com/agentspechash: 9bb3d4e42d99fde0ef482717a4d139d7
2136+
agent.datadoghq.com/agentspechash: 977561eccc4bc79683f1bbbbf100d060
21372137
labels:
21382138
agent.datadoghq.com/component: cluster-checks-runner
21392139
agent.datadoghq.com/name: datadog-agent
@@ -2331,6 +2331,25 @@ spec:
23312331
- mountPath: /checks.d
23322332
name: checksd
23332333
readOnly: true
2334+
- args:
2335+
- |-
2336+
set -euo pipefail
2337+
if [ -d /etc/datadog-agent/conf.d ]; then
2338+
cp -RL /etc/datadog-agent/conf.d/. /opt/conf.d-overlay/
2339+
find /opt/conf.d-overlay -type f -name '*.yaml.default' -delete
2340+
fi
2341+
command:
2342+
- bash
2343+
- -c
2344+
image: gcr.io/datadoghq/agent:7.81.1
2345+
name: init-copy-check-assets
2346+
resources: {}
2347+
securityContext:
2348+
allowPrivilegeEscalation: false
2349+
readOnlyRootFilesystem: true
2350+
volumeMounts:
2351+
- mountPath: /opt/conf.d-overlay
2352+
name: remove-corechecks
23342353
serviceAccountName: datadog-agent-cluster-checks-runner
23352354
volumes:
23362355
- configMap:

internal/controller/testutils/renderer/testdata/golden/comprehensive-baseline.golden.yaml

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2165,7 +2165,7 @@ apiVersion: apps/v1
21652165
kind: Deployment
21662166
metadata:
21672167
annotations:
2168-
agent.datadoghq.com/agentspechash: 9f9cc18615463016b51e37676cc4b72a
2168+
agent.datadoghq.com/agentspechash: e5182e5df9f9aa5754b67d69dde750fe
21692169
labels:
21702170
agent.datadoghq.com/component: cluster-checks-runner
21712171
agent.datadoghq.com/name: datadog-agent
@@ -2359,6 +2359,25 @@ spec:
23592359
- mountPath: /checks.d
23602360
name: checksd
23612361
readOnly: true
2362+
- args:
2363+
- |-
2364+
set -euo pipefail
2365+
if [ -d /etc/datadog-agent/conf.d ]; then
2366+
cp -RL /etc/datadog-agent/conf.d/. /opt/conf.d-overlay/
2367+
find /opt/conf.d-overlay -type f -name '*.yaml.default' -delete
2368+
fi
2369+
command:
2370+
- bash
2371+
- -c
2372+
image: registry.datadoghq.com/agent:7.81.1
2373+
name: init-copy-check-assets
2374+
resources: {}
2375+
securityContext:
2376+
allowPrivilegeEscalation: false
2377+
readOnlyRootFilesystem: true
2378+
volumeMounts:
2379+
- mountPath: /opt/conf.d-overlay
2380+
name: remove-corechecks
23622381
serviceAccountName: datadog-agent-cluster-checks-runner
23632382
volumes:
23642383
- configMap:

internal/controller/testutils/renderer/testdata/golden/comprehensive-eks-hostname-from-file.golden.yaml

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2187,7 +2187,7 @@ apiVersion: apps/v1
21872187
kind: Deployment
21882188
metadata:
21892189
annotations:
2190-
agent.datadoghq.com/agentspechash: 9f9cc18615463016b51e37676cc4b72a
2190+
agent.datadoghq.com/agentspechash: e5182e5df9f9aa5754b67d69dde750fe
21912191
labels:
21922192
agent.datadoghq.com/component: cluster-checks-runner
21932193
agent.datadoghq.com/name: datadog-agent
@@ -2381,6 +2381,25 @@ spec:
23812381
- mountPath: /checks.d
23822382
name: checksd
23832383
readOnly: true
2384+
- args:
2385+
- |-
2386+
set -euo pipefail
2387+
if [ -d /etc/datadog-agent/conf.d ]; then
2388+
cp -RL /etc/datadog-agent/conf.d/. /opt/conf.d-overlay/
2389+
find /opt/conf.d-overlay -type f -name '*.yaml.default' -delete
2390+
fi
2391+
command:
2392+
- bash
2393+
- -c
2394+
image: registry.datadoghq.com/agent:7.81.1
2395+
name: init-copy-check-assets
2396+
resources: {}
2397+
securityContext:
2398+
allowPrivilegeEscalation: false
2399+
readOnlyRootFilesystem: true
2400+
volumeMounts:
2401+
- mountPath: /opt/conf.d-overlay
2402+
name: remove-corechecks
23842403
serviceAccountName: datadog-agent-cluster-checks-runner
23852404
volumes:
23862405
- configMap:

0 commit comments

Comments
 (0)