Skip to content

Commit 4b7ada4

Browse files
authored
Check allowed dir (#1007)
1 parent 7bc297c commit 4b7ada4

File tree

9 files changed

+276
-121
lines changed

9 files changed

+276
-121
lines changed

internal/config/config.go

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const (
2929
EnvPrefix = "NGINX_AGENT"
3030
KeyDelimiter = "_"
3131
KeyValueNumber = 2
32+
AgentDirName = "/etc/nginx-agent/"
3233
)
3334

3435
var viperInstance = viper.NewWithOptions(viper.KeyDelimiter(KeyDelimiter))
@@ -73,15 +74,20 @@ func RegisterConfigFile() error {
7374
func ResolveConfig() (*Config, error) {
7475
// Collect allowed directories, so that paths in the config can be validated.
7576
directories := viperInstance.GetStringSlice(AllowedDirectoriesKey)
76-
allowedDirs := make([]string, 0)
77+
allowedDirs := []string{AgentDirName}
7778

7879
// Check directories in allowed_directories are valid
7980
for _, dir := range directories {
80-
if dir != "" && filepath.IsAbs(dir) {
81-
allowedDirs = append(allowedDirs, dir)
82-
} else {
81+
if dir == "" || !filepath.IsAbs(dir) {
8382
slog.Warn("Invalid directory: ", "dir", dir)
83+
continue
8484
}
85+
86+
if !strings.HasSuffix(dir, "/") {
87+
dir += "/"
88+
}
89+
allowedDirs = append(allowedDirs, dir)
90+
slog.Info("Configured allowed directories", "allowed_directories", allowedDirs)
8591
}
8692

8793
// Collect all parsing errors before returning the error, so the user sees all issues with config
@@ -161,7 +167,8 @@ func registerFlags() {
161167

162168
fs.StringSlice(AllowedDirectoriesKey,
163169
DefaultAllowedDirectories(),
164-
"A comma-separated list of paths that you want to grant NGINX Agent read/write access to")
170+
"A comma-separated list of paths that you want to grant NGINX Agent read/write access to. Allowed "+
171+
"directories are case sensitive")
165172

166173
fs.Duration(
167174
InstanceWatcherMonitoringFrequencyKey,

internal/config/config_test.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -625,7 +625,8 @@ func getAgentConfig() *Config {
625625
},
626626
},
627627
AllowedDirectories: []string{
628-
"/etc/nginx", "/usr/local/etc/nginx", "/var/run/nginx", "/var/log/nginx", "/usr/share/nginx/modules",
628+
"/etc/nginx/", "/etc/nginx-agent/", "/usr/local/etc/nginx/", "/var/run/nginx/", "/var/log/nginx/",
629+
"/usr/share/nginx/modules/",
629630
},
630631
Collector: &Collector{
631632
ConfigPath: "/etc/nginx-agent/nginx-agent-otelcol.yaml",
@@ -755,7 +756,8 @@ func createConfig() *Config {
755756
},
756757
},
757758
AllowedDirectories: []string{
758-
"/etc/nginx", "/usr/local/etc/nginx", "/var/run/nginx", "/usr/share/nginx/modules", "/var/log/nginx",
759+
"/etc/nginx-agent/", "/etc/nginx/", "/usr/local/etc/nginx/", "/var/run/nginx/",
760+
"/usr/share/nginx/modules/", "/var/log/nginx/",
759761
},
760762
DataPlaneConfig: &DataPlaneConfig{
761763
Nginx: &NginxDataPlaneConfig{

internal/config/testdata/nginx-agent.conf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ allowed_directories:
4747
- /usr/local/etc/nginx
4848
- /var/run/nginx
4949
- /usr/share/nginx/modules
50-
- /var/log/nginx
50+
- /var/log/nginx
5151

5252
command:
5353
server:

internal/config/types_test.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// Copyright (c) F5, Inc.
2+
//
3+
// This source code is licensed under the Apache License, Version 2.0 license found in the
4+
// LICENSE file in the root directory of this source tree.
5+
6+
package config
7+
8+
import (
9+
"testing"
10+
11+
"github.com/stretchr/testify/assert"
12+
)
13+
14+
func TestTypes_IsDirectoryAllowed(t *testing.T) {
15+
config := getAgentConfig()
16+
17+
tests := []struct {
18+
name string
19+
fileDir string
20+
allowedDirs []string
21+
allowed bool
22+
}{
23+
{
24+
name: "Test 1: directory allowed",
25+
allowed: true,
26+
allowedDirs: []string{
27+
AgentDirName,
28+
"/etc/nginx",
29+
"/var/log/nginx/",
30+
},
31+
fileDir: "/etc/nginx/nginx.conf",
32+
},
33+
{
34+
name: "Test 2: directory not allowed",
35+
allowed: false,
36+
allowedDirs: []string{
37+
AgentDirName,
38+
"/etc/nginx/",
39+
"/var/log/nginx/",
40+
},
41+
fileDir: "/etc/nginx-test/nginx-agent.conf",
42+
},
43+
{
44+
name: "Test 3: directory allowed",
45+
allowed: true,
46+
allowedDirs: []string{
47+
AgentDirName,
48+
"/etc/nginx/",
49+
"/var/log/nginx/",
50+
},
51+
fileDir: "/etc/nginx/conf.d/nginx-agent.conf",
52+
},
53+
{
54+
name: "Test 4: directory not allowed",
55+
allowed: false,
56+
allowedDirs: []string{
57+
AgentDirName,
58+
"/etc/nginx",
59+
"/var/log/nginx",
60+
},
61+
fileDir: "~/test.conf",
62+
},
63+
{
64+
name: "Test 5: directory not allowed",
65+
allowed: false,
66+
allowedDirs: []string{
67+
AgentDirName,
68+
"/etc/nginx/",
69+
"/var/log/nginx/",
70+
},
71+
fileDir: "//test.conf",
72+
},
73+
{
74+
name: "Test 6: directory allowed",
75+
allowed: true,
76+
allowedDirs: []string{
77+
AgentDirName,
78+
"/etc/nginx/",
79+
"/var/log/nginx/",
80+
"/",
81+
},
82+
fileDir: "/test.conf",
83+
},
84+
}
85+
86+
for _, test := range tests {
87+
t.Run(test.name, func(t *testing.T) {
88+
config.AllowedDirectories = test.allowedDirs
89+
result := config.IsDirectoryAllowed(test.fileDir)
90+
assert.Equal(t, test.allowed, result)
91+
})
92+
}
93+
}

internal/watcher/instance/nginx_config_parser.go

Lines changed: 13 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"encoding/json"
1111
"fmt"
1212
"io"
13-
"io/fs"
1413
"log/slog"
1514
"net"
1615
"net/http"
@@ -117,6 +116,14 @@ func (ncp *NginxConfigParser) createNginxConfigContext(
117116
rootDir := filepath.Dir(instance.GetInstanceRuntime().GetConfigPath())
118117

119118
for _, conf := range payload.Config {
119+
if !ncp.agentConfig.IsDirectoryAllowed(conf.File) {
120+
slog.WarnContext(ctx, "File included in NGINX config is outside of allowed directories, "+
121+
"excluding from config",
122+
"file", conf.File)
123+
124+
continue
125+
}
126+
120127
formatMap := make(map[string]string)
121128
err := ncp.crossplaneConfigTraverse(ctx, &conf,
122129
func(ctx context.Context, parent, directive *crossplane.Directive) error {
@@ -138,10 +145,8 @@ func (ncp *NginxConfigParser) createNginxConfigContext(
138145
"is disabled while applying a config; "+"log errors to file to enable error monitoring",
139146
directive.Args[0]), "error_log", directive.Args[0])
140147
}
141-
case "root":
142-
rootFiles := ncp.rootFiles(ctx, directive.Args[0])
143-
nginxConfigContext.Files = append(nginxConfigContext.Files, rootFiles...)
144-
case "ssl_certificate", "proxy_ssl_certificate", "ssl_client_certificate", "ssl_trusted_certificate":
148+
case "ssl_certificate", "proxy_ssl_certificate", "ssl_client_certificate",
149+
"ssl_trusted_certificate":
145150
sslCertFile := ncp.sslCert(ctx, directive.Args[0], rootDir)
146151
if !ncp.isDuplicateFile(nginxConfigContext.Files, sslCertFile) {
147152
nginxConfigContext.Files = append(nginxConfigContext.Files, sslCertFile)
@@ -195,18 +200,17 @@ func (ncp *NginxConfigParser) createNginxConfigContext(
195200
}
196201

197202
func (ncp *NginxConfigParser) ignoreLog(logPath string) bool {
198-
logLower := strings.ToLower(logPath)
199203
ignoreLogs := []string{"off", "/dev/stderr", "/dev/stdout", "/dev/null", "stderr", "stdout"}
200204

201-
if strings.HasPrefix(logLower, "syslog:") || slices.Contains(ignoreLogs, logLower) {
205+
if strings.HasPrefix(logPath, "syslog:") || slices.Contains(ignoreLogs, logPath) {
202206
return true
203207
}
204208

205-
if ncp.isExcludeLog(logLower) {
209+
if ncp.isExcludeLog(logPath) {
206210
return true
207211
}
208212

209-
if !ncp.agentConfig.IsDirectoryAllowed(logLower) {
213+
if !ncp.agentConfig.IsDirectoryAllowed(logPath) {
210214
slog.Warn("Log being read is outside of allowed directories", "log_path", logPath)
211215
}
212216

@@ -315,39 +319,6 @@ func (ncp *NginxConfigParser) errorLogDirectiveLevel(directive *crossplane.Direc
315319
return ""
316320
}
317321

318-
func (ncp *NginxConfigParser) rootFiles(ctx context.Context, rootDir string) (rootFiles []*mpi.File) {
319-
if !ncp.agentConfig.IsDirectoryAllowed(rootDir) {
320-
slog.DebugContext(ctx, "Root directory not in allowed directories", "root_directory", rootDir)
321-
return rootFiles
322-
}
323-
324-
err := filepath.WalkDir(rootDir,
325-
func(path string, d fs.DirEntry, err error) error {
326-
if err != nil {
327-
return err
328-
}
329-
330-
if d.IsDir() {
331-
return nil
332-
}
333-
334-
rootFileMeta, fileMetaErr := files.FileMeta(path)
335-
if fileMetaErr != nil {
336-
return fileMetaErr
337-
}
338-
339-
rootFiles = append(rootFiles, &mpi.File{FileMeta: rootFileMeta})
340-
341-
return nil
342-
},
343-
)
344-
if err != nil {
345-
slog.WarnContext(ctx, "Unable to walk root directory", "root_directory", rootDir)
346-
}
347-
348-
return rootFiles
349-
}
350-
351322
func (ncp *NginxConfigParser) sslCert(ctx context.Context, file, rootDir string) (sslCertFile *mpi.File) {
352323
if strings.Contains(file, "$") {
353324
// cannot process any filepath with variables

0 commit comments

Comments
 (0)