Skip to content
58 changes: 58 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ func ResolveConfig() (*Config, error) {
Features: viperInstance.GetStringSlice(FeaturesKey),
Labels: resolveLabels(),
LibDir: viperInstance.GetString(LibDirPathKey),
ExternalDataSource: resolveExternalDataSource(),
SyslogServer: resolveSyslogServer(),
}

Expand Down Expand Up @@ -475,6 +476,7 @@ func registerFlags() {
registerCollectorFlags(fs)
registerClientFlags(fs)
registerDataPlaneFlags(fs)
registerExternalDataSourceFlags(fs)

fs.SetNormalizeFunc(normalizeFunc)

Expand All @@ -489,6 +491,24 @@ func registerFlags() {
})
}

func registerExternalDataSourceFlags(fs *flag.FlagSet) {
fs.String(
ExternalDataSourceProxyUrlKey,
DefExternalDataSourceProxyUrl,
"Url to the proxy service to fetch the external file.",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"Url to the proxy service to fetch the external file.",
"Url to the proxy service for fetching external files.",

)
fs.StringSlice(
ExternalDataSourceAllowDomainsKey,
[]string{},
"List of allowed domains for external data sources.",
)
fs.Int64(
ExternalDataSourceMaxBytesKey,
DefExternalDataSourceMaxBytes,
"Maximum size in bytes for external data sources.",
)
}

func registerDataPlaneFlags(fs *flag.FlagSet) {
fs.Duration(
NginxReloadMonitoringPeriodKey,
Expand Down Expand Up @@ -628,6 +648,11 @@ func registerClientFlags(fs *flag.FlagSet) {
DefMaxFileSize,
"Max file size in bytes.",
)
fs.Duration(
ClientFileDownloadTimeoutKey,
DefClientFileDownloadTimeout,
"Timeout value in seconds, to downloading file for config apply.",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"Timeout value in seconds, to downloading file for config apply.",
"Timeout value in seconds, to download a file during a config apply request.",

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"Timeout value in seconds, to downloading file for config apply.",
"Timeout value in seconds, for downloading a file during a config apply.",

)

fs.Int(
ClientGRPCMaxParallelFileOperationsKey,
Expand Down Expand Up @@ -1120,6 +1145,7 @@ func resolveClient() *Client {
RandomizationFactor: viperInstance.GetFloat64(ClientBackoffRandomizationFactorKey),
Multiplier: viperInstance.GetFloat64(ClientBackoffMultiplierKey),
},
FileDownloadTimeout: viperInstance.GetDuration(ClientFileDownloadTimeoutKey),
}
}

Expand Down Expand Up @@ -1560,3 +1586,35 @@ func areCommandServerProxyTLSSettingsSet() bool {
viperInstance.IsSet(CommandServerProxyTLSSkipVerifyKey) ||
viperInstance.IsSet(CommandServerProxyTLSServerNameKey)
}

func resolveExternalDataSource() *ExternalDataSource {
proxyURLStruct := ProxyURL{
URL: viperInstance.GetString(ExternalDataSourceProxyUrlKey),
}
externalDataSource := &ExternalDataSource{
ProxyURL: proxyURLStruct,
AllowedDomains: viperInstance.GetStringSlice(ExternalDataSourceAllowDomainsKey),
MaxBytes: viperInstance.GetInt64(ExternalDataSourceMaxBytesKey),
}

if err := validateAllowedDomains(externalDataSource.AllowedDomains); err != nil {
return nil
}

return externalDataSource
}

func validateAllowedDomains(domains []string) error {
if len(domains) == 0 {
return nil
}

for _, domain := range domains {
if strings.ContainsAny(domain, "/\\ ") || domain == "" {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a better way to validate if the string is a valid domain name or not? Maybe there is a regex we could use?

slog.Error("domain is not specified in allowed_domains")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need to log error here if you are returning the error anyways

return errors.New("invalid domain found in allowed_domains")
}
}

return nil
}
77 changes: 77 additions & 0 deletions internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1425,6 +1425,13 @@ func createConfig() *Config {
config.FeatureCertificates, config.FeatureFileWatcher, config.FeatureMetrics,
config.FeatureAPIAction, config.FeatureLogsNap,
},
ExternalDataSource: &ExternalDataSource{
ProxyURL: ProxyURL{
URL: "",
},
AllowedDomains: nil,
MaxBytes: 0,
},
}
}

Expand Down Expand Up @@ -1569,3 +1576,73 @@ func TestValidateLabel(t *testing.T) {
})
}
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a test for external Data source and add the external data source to TestResolveConfig() which checks all the values that can be set in the agent config

func TestValidateAllowedDomains(t *testing.T) {
tests := []struct {
name string
domains []string
wantErr bool
}{
{
name: "Test 1: Success: Empty slice",
domains: []string{},
wantErr: false,
},
{
name: "Test 2: Success: Nil slice",
domains: nil,
wantErr: false,
},
{
name: "Test 3: Success: Valid domains",
domains: []string{"example.com", "api.nginx.com", "sub.domain.io"},
wantErr: false,
},
{
name: "Test 4: Failure: Domain contains space",
domains: []string{"valid.com", "bad domain.com"},
wantErr: true,
},
{
name: "Test 5: Failure: Empty string domain",
domains: []string{"valid.com", ""},
wantErr: true,
},
{
name: "Test 6: Failure: Domain contains forward slash /",
domains: []string{"domain.com/path"},
wantErr: true,
},
{
name: "Test 7: Failure: Domain contains backward slash \\",
domains: []string{"domain.com\\path"},
wantErr: true,
},
{
name: "Test 8: Failure: Mixed valid and invalid (first is invalid)",
domains: []string{" only.com", "good.com"},
wantErr: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var logBuffer bytes.Buffer
logHandler := slog.NewTextHandler(&logBuffer, &slog.HandlerOptions{Level: slog.LevelError})

originalLogger := slog.Default()
slog.SetDefault(slog.New(logHandler))
defer slog.SetDefault(originalLogger)

actualErr := validateAllowedDomains(tt.domains)

if tt.wantErr {
require.Error(t, actualErr, "Expected an error but got nil.")
assert.Contains(t, logBuffer.String(), "domain is not specified in allowed_domains",
"Expected the error log message to be present in the output.")
} else {
assert.NoError(t, actualErr, "Did not expect an error but got one: %v", actualErr)
}
})
}
}
5 changes: 5 additions & 0 deletions internal/config/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ const (
DefBackoffMaxInterval = 20 * time.Second
DefBackoffMaxElapsedTime = 1 * time.Minute

DefClientFileDownloadTimeout = 60 * time.Second

// Watcher defaults
DefInstanceWatcherMonitoringFrequency = 5 * time.Second
DefInstanceHealthWatcherMonitoringFrequency = 5 * time.Second
Expand Down Expand Up @@ -114,6 +116,9 @@ const (

// File defaults
DefLibDir = "/var/lib/nginx-agent"

DefExternalDataSourceProxyUrl = ""
DefExternalDataSourceMaxBytes = 100 * 1024 * 1024
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add a comment to say what the default size is in MB

)

func DefaultFeatures() []string {
Expand Down
7 changes: 7 additions & 0 deletions internal/config/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const (
InstanceHealthWatcherMonitoringFrequencyKey = "watchers_instance_health_watcher_monitoring_frequency"
FileWatcherKey = "watchers_file_watcher"
LibDirPathKey = "lib_dir"
ExternalDataSourceRootKey = "external_data_source"
)

var (
Expand All @@ -47,6 +48,7 @@ var (
ClientBackoffMaxElapsedTimeKey = pre(ClientRootKey) + "backoff_max_elapsed_time"
ClientBackoffRandomizationFactorKey = pre(ClientRootKey) + "backoff_randomization_factor"
ClientBackoffMultiplierKey = pre(ClientRootKey) + "backoff_multiplier"
ClientFileDownloadTimeoutKey = pre(ClientRootKey) + "file_download_timeout"

CollectorConfigPathKey = pre(CollectorRootKey) + "config_path"
CollectorAdditionalConfigPathsKey = pre(CollectorRootKey) + "additional_config_paths"
Expand Down Expand Up @@ -141,6 +143,11 @@ var (

FileWatcherMonitoringFrequencyKey = pre(FileWatcherKey) + "monitoring_frequency"
NginxExcludeFilesKey = pre(FileWatcherKey) + "exclude_files"

ExternalDataSourceProxyKey = pre(ExternalDataSourceRootKey) + "proxy"
ExternalDataSourceProxyUrlKey = pre(ExternalDataSourceProxyKey) + "url"
ExternalDataSourceMaxBytesKey = pre(ExternalDataSourceRootKey) + "max_bytes"
ExternalDataSourceAllowDomainsKey = pre(ExternalDataSourceRootKey) + "allowed_domains"
)

func pre(prefixes ...string) string {
Expand Down
48 changes: 30 additions & 18 deletions internal/config/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,21 +36,22 @@ func parseServerType(str string) (ServerType, bool) {

type (
Config struct {
Command *Command `yaml:"command" mapstructure:"command"`
AuxiliaryCommand *Command `yaml:"auxiliary_command" mapstructure:"auxiliary_command"`
Log *Log `yaml:"log" mapstructure:"log"`
DataPlaneConfig *DataPlaneConfig `yaml:"data_plane_config" mapstructure:"data_plane_config"`
Client *Client `yaml:"client" mapstructure:"client"`
Collector *Collector `yaml:"collector" mapstructure:"collector"`
Watchers *Watchers `yaml:"watchers" mapstructure:"watchers"`
SyslogServer *SyslogServer `yaml:"syslog_server" mapstructure:"syslog_server"`
Labels map[string]any `yaml:"labels" mapstructure:"labels"`
Version string `yaml:"-"`
Path string `yaml:"-"`
UUID string `yaml:"-"`
LibDir string `yaml:"-"`
AllowedDirectories []string `yaml:"allowed_directories" mapstructure:"allowed_directories"`
Features []string `yaml:"features" mapstructure:"features"`
Command *Command `yaml:"command" mapstructure:"command"`
AuxiliaryCommand *Command `yaml:"auxiliary_command" mapstructure:"auxiliary_command"`
Log *Log `yaml:"log" mapstructure:"log"`
DataPlaneConfig *DataPlaneConfig `yaml:"data_plane_config" mapstructure:"data_plane_config"`
Client *Client `yaml:"client" mapstructure:"client"`
Collector *Collector `yaml:"collector" mapstructure:"collector"`
Watchers *Watchers `yaml:"watchers" mapstructure:"watchers"`
SyslogServer *SyslogServer `yaml:"syslog_server" mapstructure:"syslog_server"`
ExternalDataSource *ExternalDataSource `yaml:"external_data_source" mapstructure:"external_data_source"`
Labels map[string]any `yaml:"labels" mapstructure:"labels"`
Version string `yaml:"-"`
Path string `yaml:"-"`
UUID string `yaml:"-"`
LibDir string `yaml:"-"`
AllowedDirectories []string `yaml:"allowed_directories" mapstructure:"allowed_directories"`
Features []string `yaml:"features" mapstructure:"features"`
}

Log struct {
Expand All @@ -74,9 +75,10 @@ type (
}

Client struct {
HTTP *HTTP `yaml:"http" mapstructure:"http"`
Grpc *GRPC `yaml:"grpc" mapstructure:"grpc"`
Backoff *BackOff `yaml:"backoff" mapstructure:"backoff"`
HTTP *HTTP `yaml:"http" mapstructure:"http"`
Grpc *GRPC `yaml:"grpc" mapstructure:"grpc"`
Backoff *BackOff `yaml:"backoff" mapstructure:"backoff"`
FileDownloadTimeout time.Duration `yaml:"file_download_timeout" mapstructure:"file_download_timeout"`
}

HTTP struct {
Expand Down Expand Up @@ -358,6 +360,16 @@ type (
Token string `yaml:"token,omitempty" mapstructure:"token"`
Timeout time.Duration `yaml:"timeout" mapstructure:"timeout"`
}

ProxyURL struct {
URL string `yaml:"url" mapstructure:"url"`
}

ExternalDataSource struct {
ProxyURL ProxyURL `yaml:"proxy" mapstructure:"proxy"`
AllowedDomains []string `yaml:"allowed_domains" mapstructure:"allowed_domains"`
MaxBytes int64 `yaml:"max_bytes" mapstructure:"max_bytes"`
}
)

func (col *Collector) Validate(allowedDirectories []string) error {
Expand Down
Loading
Loading