Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
e055610
third party config support
Akshay2191 Nov 12, 2025
daf36e3
lint fix
Akshay2191 Nov 12, 2025
2b878a4
Added a generic file timeout
Akshay2191 Nov 13, 2025
c357355
added generic timeout to create connection
Akshay2191 Nov 13, 2025
d1e8a9d
added uT
Akshay2191 Nov 19, 2025
7e616fc
Added UT
Akshay2191 Nov 19, 2025
1fd2cc6
fix lint issues
Akshay2191 Nov 19, 2025
ac5bd5e
Added UT for Proxy URL
Akshay2191 Nov 19, 2025
280ed4f
increasing UT coverage
Akshay2191 Nov 19, 2025
e494337
updated Mock management to test external file config apply
Akshay2191 Nov 20, 2025
28328a4
PR review comments addressed
Akshay2191 Nov 24, 2025
d77faea
worked on PR comments
Akshay2191 Nov 24, 2025
7b59e69
worked on PR comments
Akshay2191 Nov 28, 2025
2281b14
removed duplicate rename function
Akshay2191 Dec 9, 2025
3843ee2
worked on review comment
Akshay2191 Dec 18, 2025
480e402
Ensure only files are in file overview in config apply request (#1395)
dhurley Nov 19, 2025
0171367
Retry request if no response is sent by management plane (#1381)
aphralG Nov 27, 2025
16839e8
third party config support
Akshay2191 Nov 12, 2025
433da00
Added a generic file timeout
Akshay2191 Nov 13, 2025
cba25ca
removed duplicate rename function
Akshay2191 Dec 9, 2025
4837339
linter fix
Akshay2191 Dec 18, 2025
3225ff3
code fix for rebase
Akshay2191 Dec 18, 2025
2e981e0
fixing rebase errors
Akshay2191 Dec 19, 2025
c28e679
refactored new code andded file validation
Akshay2191 Jan 6, 2026
e4cfe0c
fixed push error
Akshay2191 Jan 6, 2026
5b8b5ab
Fixed UT test failing in git pipeline
Akshay2191 Jan 7, 2026
52e6aa3
returning error message with code
Jan 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
github.com/cenkalti/backoff/v4 v4.3.0
github.com/docker/docker v28.5.2+incompatible
github.com/fsnotify/fsnotify v1.9.0
github.com/gabriel-vasile/mimetype v1.4.8
github.com/go-resty/resty/v2 v2.16.5
github.com/goccy/go-yaml v1.18.0
github.com/google/go-cmp v0.7.0
Expand Down Expand Up @@ -151,7 +152,6 @@ require (
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/foxboron/go-tpm-keyfiles v0.0.0-20250903184740-5d135037bd4d // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
Expand Down
69 changes: 69 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ const (
regexLabelPattern = "^[a-zA-Z0-9]([a-zA-Z0-9-_.]{0,254}[a-zA-Z0-9])?$"
)

var domainRegex = regexp.MustCompile(
`^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$`,
)

var viperInstance = viper.NewWithOptions(viper.KeyDelimiter(KeyDelimiter))

func RegisterRunner(r func(cmd *cobra.Command, args []string)) {
Expand Down Expand Up @@ -158,6 +162,7 @@ func ResolveConfig() (*Config, error) {
Labels: resolveLabels(),
LibDir: viperInstance.GetString(LibDirPathKey),
SyslogServer: resolveSyslogServer(),
ExternalDataSource: resolveExternalDataSource(),
}

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

fs.SetNormalizeFunc(normalizeFunc)

Expand All @@ -489,6 +495,29 @@ func registerFlags() {
})
}

func registerExternalDataSourceFlags(fs *flag.FlagSet) {
fs.String(
ExternalDataSourceProxyUrlKey,
DefExternalDataSourceProxyUrl,
"Url to the proxy service for fetching external files.",
)
fs.StringSlice(
ExternalDataSourceAllowDomainsKey,
[]string{},
"List of allowed domains for external data sources.",
)
fs.StringSlice(
ExternalDataSourceAllowedFileTypesKey,
[]string{},
"List of allowed file types 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 @@ -646,6 +675,11 @@ func registerClientFlags(fs *flag.FlagSet) {
DefMaxParallelFileOperations,
"Maximum number of file downloads or uploads performed in parallel",
)
fs.Duration(
ClientFileDownloadTimeoutKey,
DefClientFileDownloadTimeout,
"Timeout value in seconds, for downloading a file during a config apply.",
)
}

func registerCommandFlags(fs *flag.FlagSet) {
Expand Down Expand Up @@ -1134,6 +1168,7 @@ func resolveClient() *Client {
RandomizationFactor: viperInstance.GetFloat64(ClientBackoffRandomizationFactorKey),
Multiplier: viperInstance.GetFloat64(ClientBackoffMultiplierKey),
},
FileDownloadTimeout: viperInstance.GetDuration(ClientFileDownloadTimeoutKey),
}
}

Expand Down Expand Up @@ -1574,3 +1609,37 @@ 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),
AllowedFileTypes: viperInstance.GetStringSlice(ExternalDataSourceAllowedFileTypesKey),
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 {
// Validating syntax using the RFC-compliant regex
if !domainRegex.MatchString(domain) || domain == "" {
slog.Error("domain specified in allowed_domains is invalid", "domain", domain)
return errors.New("invalid domain found in allowed_domains")
}
}

return nil
}
78 changes: 78 additions & 0 deletions internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1161,6 +1161,7 @@ func agentConfig() *Config {
}
}

//nolint:maintidx // createConfig creates a sample Config object for testing purposes.
func createConfig() *Config {
return &Config{
Log: &Log{
Expand Down Expand Up @@ -1428,6 +1429,13 @@ func createConfig() *Config {
config.FeatureCertificates, config.FeatureFileWatcher, config.FeatureMetrics,
config.FeatureAPIAction, config.FeatureLogsNap,
},
ExternalDataSource: &ExternalDataSource{
ProxyURL: ProxyURL{
URL: "http://proxy.example.com",
},
AllowedDomains: []string{"example.com", "api.example.com"},
MaxBytes: 1048576,
},
}
}

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

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 specified in allowed_domains is invalid",
"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 @@ -84,6 +84,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 @@ -116,6 +118,9 @@ const (

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

DefExternalDataSourceProxyUrl = ""
DefExternalDataSourceMaxBytes = 100 * 1024 * 1024 // default 100MB
)

func DefaultFeatures() []string {
Expand Down
8 changes: 8 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 @@ -49,6 +50,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 @@ -143,6 +145,12 @@ 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"
ExternalDataSourceAllowedFileTypesKey = pre(ExternalDataSourceRootKey) + "allowed_file_types"
)

func pre(prefixes ...string) string {
Expand Down
8 changes: 8 additions & 0 deletions internal/config/testdata/nginx-agent.conf
Original file line number Diff line number Diff line change
Expand Up @@ -184,3 +184,11 @@ collector:
log:
level: "INFO"
path: "/var/log/nginx-agent/opentelemetry-collector-agent.log"

external_data_source:
proxy:
url: "http://proxy.example.com"
allowed_domains:
- example.com
- api.example.com
max_bytes: 1048576
49 changes: 31 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"`
ExternalDataSource *ExternalDataSource `yaml:"external_data_source" mapstructure:"external_data_source"`
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"`
}

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 @@ -360,6 +362,17 @@ 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"`
AllowedFileTypes []string `yaml:"allowed_file_types" mapstructure:"allowed_file_types"`
MaxBytes int64 `yaml:"max_bytes" mapstructure:"max_bytes"`
}
)

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