Skip to content

Commit 19a6b24

Browse files
authored
fix(installer): Set proper permissions for files written for config (#44320)
### What does this PR do? Sets proper permissions for files written for config: - 0644 for application_monitoring.yaml - root:root & 0640 for system-probe.yaml & security-agent.yaml - dd-agent:dd-agent with 0640 for the other files ### Motivation ### Describe how you validated your changes E2E test ### Additional Notes Co-authored-by: baptiste.foy <baptiste.foy@datadoghq.com>
1 parent 270dada commit 19a6b24

File tree

8 files changed

+320
-40
lines changed

8 files changed

+320
-40
lines changed

pkg/fleet/installer/config/config.go

Lines changed: 82 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
package config
88

99
import (
10+
"context"
1011
"encoding/json"
1112
"fmt"
1213
"io"
@@ -56,15 +57,15 @@ type Operations struct {
5657
}
5758

5859
// Apply applies the operations to the root.
59-
func (o *Operations) Apply(rootPath string) error {
60+
func (o *Operations) Apply(ctx context.Context, rootPath string) error {
6061
root, err := os.OpenRoot(rootPath)
6162
if err != nil {
6263
return err
6364
}
6465
defer root.Close()
6566
for _, operation := range o.FileOperations {
6667
// TODO (go.1.25): we won't need rootPath in 1.25
67-
err := operation.apply(root, rootPath)
68+
err := operation.apply(ctx, root, rootPath)
6869
if err != nil {
6970
return err
7071
}
@@ -80,8 +81,9 @@ type FileOperation struct {
8081
Patch json.RawMessage `json:"patch,omitempty"`
8182
}
8283

83-
func (a *FileOperation) apply(root *os.Root, rootPath string) error {
84-
if !configNameAllowed(a.FilePath) {
84+
func (a *FileOperation) apply(ctx context.Context, root *os.Root, rootPath string) error {
85+
spec := getConfigFileSpec(a.FilePath)
86+
if spec == nil {
8587
return fmt.Errorf("modifying config file %s is not allowed", a.FilePath)
8688
}
8789
path := strings.TrimPrefix(a.FilePath, "/")
@@ -149,9 +151,19 @@ func (a *FileOperation) apply(root *os.Root, rootPath string) error {
149151
if err != nil {
150152
return err
151153
}
152-
return err
154+
// Set proper ownership and permissions for the file
155+
fullPath := filepath.Join(rootPath, path)
156+
if err := setFileOwnershipAndPermissions(ctx, fullPath, spec); err != nil {
157+
return err
158+
}
159+
return nil
153160
case FileOperationCopy:
154161
// TODO(go.1.25): os.Root.MkdirAll and os.Root.WriteFile are only available starting go 1.25
162+
destSpec := getConfigFileSpec(a.DestinationPath)
163+
if destSpec == nil {
164+
return fmt.Errorf("modifying config file %s is not allowed", a.DestinationPath)
165+
}
166+
155167
err := ensureDir(root, destinationPath)
156168
if err != nil {
157169
return err
@@ -179,9 +191,20 @@ func (a *FileOperation) apply(root *os.Root, rootPath string) error {
179191
if err != nil {
180192
return err
181193
}
194+
195+
// Set proper ownership and permissions for the destination file
196+
fullDestPath := filepath.Join(rootPath, destinationPath)
197+
if err := setFileOwnershipAndPermissions(ctx, fullDestPath, destSpec); err != nil {
198+
return err
199+
}
182200
return nil
183201
case FileOperationMove:
184202
// TODO(go.1.25): os.Root.Rename is only available starting go 1.25 so we'll use it instead
203+
destSpec := getConfigFileSpec(a.DestinationPath)
204+
if destSpec == nil {
205+
return fmt.Errorf("modifying config file %s is not allowed", a.DestinationPath)
206+
}
207+
185208
err := ensureDir(root, destinationPath)
186209
if err != nil {
187210
return err
@@ -214,6 +237,12 @@ func (a *FileOperation) apply(root *os.Root, rootPath string) error {
214237
if err != nil {
215238
return err
216239
}
240+
241+
// Set proper ownership and permissions for the destination file
242+
fullDestPath := filepath.Join(rootPath, destinationPath)
243+
if err := setFileOwnershipAndPermissions(ctx, fullDestPath, destSpec); err != nil {
244+
return err
245+
}
217246
return nil
218247
case FileOperationDelete:
219248
err := root.Remove(path)
@@ -275,39 +304,70 @@ func ensureDir(root *os.Root, filePath string) error {
275304
return nil
276305
}
277306

307+
// configFileSpec specifies a config file pattern, its ownership, and permissions.
308+
type configFileSpec struct {
309+
pattern string
310+
owner string
311+
group string
312+
mode os.FileMode
313+
}
314+
278315
var (
279-
allowedConfigFiles = []string{
280-
"/datadog.yaml",
281-
"/otel-config.yaml",
282-
"/security-agent.yaml",
283-
"/system-probe.yaml",
284-
"/application_monitoring.yaml",
285-
"/conf.d/*.yaml",
286-
"/conf.d/*.d/*.yaml",
316+
allowedConfigFiles = []configFileSpec{
317+
{pattern: "/datadog.yaml", owner: "dd-agent", group: "dd-agent", mode: 0640},
318+
{pattern: "/otel-config.yaml", owner: "dd-agent", group: "dd-agent", mode: 0640},
319+
{pattern: "/security-agent.yaml", owner: "root", group: "root", mode: 0640},
320+
{pattern: "/system-probe.yaml", owner: "root", group: "root", mode: 0640},
321+
{pattern: "/application_monitoring.yaml", owner: "root", group: "root", mode: 0644},
322+
{pattern: "/conf.d/*.yaml", owner: "dd-agent", group: "dd-agent", mode: 0640},
323+
{pattern: "/conf.d/*.d/*.yaml", owner: "dd-agent", group: "dd-agent", mode: 0640},
287324
}
288325

289326
legacyPathPrefix = filepath.Join("managed", "datadog-agent", "stable")
290327
)
291328

292-
func configNameAllowed(file string) bool {
293-
// Normalize path to use forward slashes for consistent matching on all platforms
329+
func getConfigFileSpec(file string) *configFileSpec {
294330
normalizedFile := filepath.ToSlash(file)
295331

296-
// Matching everything under the legacy /managed directory
332+
// Fallback for legacy files under the /managed directory
297333
if strings.HasPrefix(normalizedFile, "/managed") {
298-
return true
334+
filename := filepath.Base(normalizedFile)
335+
336+
for _, spec := range allowedConfigFiles {
337+
// Skip patterns with nested paths (e.g., /conf.d/*.yaml)
338+
if strings.Count(spec.pattern, "/") > 1 {
339+
continue
340+
}
341+
342+
// Extract just the filename from the pattern
343+
patternFilename := filepath.Base(spec.pattern)
344+
match, err := filepath.Match(patternFilename, filename)
345+
if err != nil {
346+
continue
347+
}
348+
if match {
349+
// Return a copy with the original pattern set to the full managed path
350+
return &configFileSpec{
351+
pattern: normalizedFile,
352+
owner: spec.owner,
353+
group: spec.group,
354+
mode: spec.mode,
355+
}
356+
}
357+
}
358+
return &configFileSpec{pattern: normalizedFile, owner: "dd-agent", group: "dd-agent", mode: 0640}
299359
}
300360

301-
for _, allowedFile := range allowedConfigFiles {
302-
match, err := filepath.Match(allowedFile, normalizedFile)
361+
for _, spec := range allowedConfigFiles {
362+
match, err := filepath.Match(spec.pattern, normalizedFile)
303363
if err != nil {
304-
return false
364+
continue
305365
}
306366
if match {
307-
return true
367+
return &spec
308368
}
309369
}
310-
return false
370+
return nil
311371
}
312372

313373
func buildOperationsFromLegacyInstaller(rootPath string) []FileOperation {

pkg/fleet/installer/config/config_nix.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@ import (
1313
"os"
1414
"path/filepath"
1515

16+
"github.com/DataDog/datadog-agent/pkg/fleet/installer/packages/file"
1617
"github.com/DataDog/datadog-agent/pkg/fleet/installer/symlink"
1718
"github.com/DataDog/datadog-agent/pkg/fleet/installer/telemetry"
19+
"github.com/DataDog/datadog-agent/pkg/util/log"
1820
)
1921

2022
const (
@@ -58,7 +60,7 @@ func (d *Directories) WriteExperiment(ctx context.Context, operations Operations
5860

5961
operations.FileOperations = append(buildOperationsFromLegacyInstaller(d.StablePath), operations.FileOperations...)
6062

61-
err = operations.Apply(d.ExperimentPath)
63+
err = operations.Apply(ctx, d.ExperimentPath)
6264
if err != nil {
6365
return err
6466
}
@@ -140,3 +142,23 @@ func replaceConfigDirectory(oldDir, newDir string) (err error) {
140142
}
141143
return nil
142144
}
145+
146+
// setFileOwnershipAndPermissions sets the ownership and permissions for a file based on its configFileSpec.
147+
// If the user doesn't exist (e.g., in tests) or if we don't have permission
148+
// to change ownership, the function logs a warning and continues without failing.
149+
func setFileOwnershipAndPermissions(ctx context.Context, filePath string, spec *configFileSpec) error {
150+
// Set file permissions
151+
if spec.mode != 0 {
152+
if err := os.Chmod(filePath, spec.mode); err != nil {
153+
return fmt.Errorf("error setting file permissions for %s: %w", filePath, err)
154+
}
155+
}
156+
157+
// Set file ownership
158+
err := file.Chown(ctx, filePath, spec.owner, spec.group)
159+
if err != nil {
160+
log.Warnf("error setting file ownership for %s: %v", filePath, err)
161+
}
162+
163+
return nil
164+
}

0 commit comments

Comments
 (0)