Skip to content

Commit 0746682

Browse files
committed
Add agent config option to set NGINX API URL
1 parent 5df1f54 commit 0746682

File tree

9 files changed

+280
-59
lines changed

9 files changed

+280
-59
lines changed

internal/config/config.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -502,7 +502,7 @@ func registerDataPlaneFlags(fs *flag.FlagSet) {
502502
)
503503

504504
fs.String(
505-
NginxApiTlsCa,
505+
NginxApiTlsCaKey,
506506
DefNginxApiTlsCa,
507507
"The NGINX Plus CA certificate file location needed to call the NGINX Plus API if SSL is enabled.",
508508
)
@@ -1095,7 +1095,10 @@ func resolveDataPlaneConfig() *DataPlaneConfig {
10951095
ReloadMonitoringPeriod: viperInstance.GetDuration(NginxReloadMonitoringPeriodKey),
10961096
TreatWarningsAsErrors: viperInstance.GetBool(NginxTreatWarningsAsErrorsKey),
10971097
ExcludeLogs: viperInstance.GetStringSlice(NginxExcludeLogsKey),
1098-
APITls: TLSConfig{Ca: viperInstance.GetString(NginxApiTlsCa)},
1098+
API: &NginxAPI{
1099+
URL: viperInstance.GetString(NginxApiURLKey),
1100+
TLS: TLSConfig{Ca: viperInstance.GetString(NginxApiTlsCaKey)},
1101+
},
10991102
ReloadBackoff: &BackOff{
11001103
InitialInterval: viperInstance.GetDuration(NginxReloadBackoffInitialIntervalKey),
11011104
MaxInterval: viperInstance.GetDuration(NginxReloadBackoffMaxIntervalKey),

internal/config/config_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1212,6 +1212,9 @@ func createConfig() *Config {
12121212
RandomizationFactor: 1.5,
12131213
Multiplier: 1.5,
12141214
},
1215+
API: &NginxAPI{
1216+
URL: "http://127.0.0.1:80/api",
1217+
},
12151218
},
12161219
},
12171220
Collector: &Collector{

internal/config/flags.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,8 @@ var (
137137
NginxReloadBackoffRandomizationFactorKey = pre(NginxReloadBackoffKey) + "randomization_factor"
138138
NginxReloadBackoffMultiplierKey = pre(NginxReloadBackoffKey) + "multiplier"
139139
NginxExcludeLogsKey = pre(DataPlaneConfigRootKey, "nginx") + "exclude_logs"
140-
NginxApiTlsCa = pre(DataPlaneConfigRootKey, "nginx") + "api_tls_ca"
140+
NginxApiTlsCaKey = pre(DataPlaneConfigRootKey, "nginx") + "api_tls_ca"
141+
NginxApiURLKey = pre(DataPlaneConfigRootKey, "nginx") + "api_url"
141142

142143
SyslogServerPort = pre("syslog_server") + "port"
143144

internal/config/testdata/nginx-agent.conf

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,29 +18,31 @@ labels:
1818
label3: 123
1919

2020
features:
21-
- certificates
22-
- file-watcher
23-
- metrics
24-
- api-action
25-
- logs-nap
21+
- certificates
22+
- file-watcher
23+
- metrics
24+
- api-action
25+
- logs-nap
2626

2727

2828
syslog_server:
29-
port: 1512
29+
port: 1512
3030

3131
data_plane_config:
32-
nginx:
33-
reload_monitoring_period: 30s
34-
treat_warnings_as_errors: true
35-
exclude_logs:
36-
- /var/log/nginx/error.log
37-
- ^/var/log/nginx/.*.log$
38-
reload_backoff:
39-
initial_interval: 100ms
40-
max_interval: 20s
41-
max_elapsed_time: 15s
42-
randomization_factor: 1.5
43-
multiplier: 1.5
32+
nginx:
33+
api:
34+
url: "http://127.0.0.1:80/api"
35+
reload_monitoring_period: 30s
36+
treat_warnings_as_errors: true
37+
exclude_logs:
38+
- /var/log/nginx/error.log
39+
- ^/var/log/nginx/.*.log$
40+
reload_backoff:
41+
initial_interval: 100ms
42+
max_interval: 20s
43+
max_elapsed_time: 15s
44+
randomization_factor: 1.5
45+
multiplier: 1.5
4446
client:
4547
http:
4648
timeout: 15s

internal/config/types.go

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,17 @@ type (
6767
}
6868
NginxDataPlaneConfig struct {
6969
ReloadBackoff *BackOff `yaml:"reload_backoff" mapstructure:"reload_backoff"`
70-
APITls TLSConfig `yaml:"api_tls" mapstructure:"api_tls"`
70+
API *NginxAPI `yaml:"api" mapstructure:"api"`
7171
ExcludeLogs []string `yaml:"exclude_logs" mapstructure:"exclude_logs"`
7272
ReloadMonitoringPeriod time.Duration `yaml:"reload_monitoring_period" mapstructure:"reload_monitoring_period"`
7373
TreatWarningsAsErrors bool `yaml:"treat_warnings_as_errors" mapstructure:"treat_warnings_as_errors"`
7474
}
7575

76+
NginxAPI struct {
77+
URL string `yaml:"url" mapstructure:"url"`
78+
TLS TLSConfig `yaml:"tls" mapstructure:"tls"`
79+
}
80+
7681
Client struct {
7782
HTTP *HTTP `yaml:"http" mapstructure:"http"`
7883
Grpc *GRPC `yaml:"grpc" mapstructure:"grpc"`
@@ -496,3 +501,19 @@ func checkDirIsAllowed(path string, allowedDirs []string) bool {
496501

497502
return checkDirIsAllowed(filepath.Dir(path), allowedDirs)
498503
}
504+
505+
func (c *Config) IsNginxApiUrlConfigured() bool {
506+
if !c.IsNginxApiConfigured() {
507+
return false
508+
}
509+
510+
return c.DataPlaneConfig.Nginx.API.URL != ""
511+
}
512+
513+
func (c *Config) IsNginxApiConfigured() bool {
514+
if c.DataPlaneConfig == nil || c.DataPlaneConfig.Nginx == nil || c.DataPlaneConfig.Nginx.API == nil {
515+
return false
516+
}
517+
518+
return true
519+
}

internal/datasource/config/nginx_config_parser.go

Lines changed: 94 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"log/slog"
1717
"net"
1818
"net/http"
19+
"net/url"
1920
"os"
2021
"path/filepath"
2122
"regexp"
@@ -269,18 +270,20 @@ func (ncp *NginxConfigParser) createNginxConfigContext(
269270
return nginxConfigContext, fmt.Errorf("traverse nginx config: %w", err)
270271
}
271272

272-
stubStatuses := ncp.crossplaneConfigTraverseAPIDetails(
273-
ctx, &conf, ncp.apiCallback, stubStatusAPIDirective,
274-
)
275-
if stubStatuses != nil {
276-
nginxConfigContext.StubStatuses = append(nginxConfigContext.StubStatuses, stubStatuses...)
277-
}
273+
if !ncp.agentConfig.IsNginxApiUrlConfigured() {
274+
stubStatuses := ncp.crossplaneConfigTraverseAPIDetails(
275+
ctx, &conf, ncp.apiCallback, stubStatusAPIDirective,
276+
)
277+
if stubStatuses != nil {
278+
nginxConfigContext.StubStatuses = append(nginxConfigContext.StubStatuses, stubStatuses...)
279+
}
278280

279-
plusAPIs := ncp.crossplaneConfigTraverseAPIDetails(
280-
ctx, &conf, ncp.apiCallback, plusAPIDirective,
281-
)
282-
if plusAPIs != nil {
283-
nginxConfigContext.PlusAPIs = append(nginxConfigContext.PlusAPIs, plusAPIs...)
281+
plusAPIs := ncp.crossplaneConfigTraverseAPIDetails(
282+
ctx, &conf, ncp.apiCallback, plusAPIDirective,
283+
)
284+
if plusAPIs != nil {
285+
nginxConfigContext.PlusAPIs = append(nginxConfigContext.PlusAPIs, plusAPIs...)
286+
}
284287
}
285288

286289
fileMeta, err := files.FileMeta(conf.File)
@@ -300,13 +303,48 @@ func (ncp *NginxConfigParser) createNginxConfigContext(
300303
"server configured on port %s", ncp.agentConfig.SyslogServer.Port))
301304
}
302305

303-
nginxConfigContext.PlusAPIs = ncp.sortPlusAPIs(ctx, nginxConfigContext.PlusAPIs)
304-
nginxConfigContext.StubStatus = ncp.FindStubStatusAPI(ctx, nginxConfigContext)
305-
nginxConfigContext.PlusAPI = ncp.FindPlusAPI(ctx, nginxConfigContext)
306+
if !ncp.agentConfig.IsNginxApiUrlConfigured() {
307+
nginxConfigContext.PlusAPIs = ncp.sortPlusAPIs(ctx, nginxConfigContext.PlusAPIs)
308+
nginxConfigContext.StubStatus = ncp.FindStubStatusAPI(ctx, nginxConfigContext)
309+
nginxConfigContext.PlusAPI = ncp.FindPlusAPI(ctx, nginxConfigContext)
310+
} else {
311+
nginxConfigContext = ncp.addApiToNginxConfigContext(ctx, nginxConfigContext)
312+
}
306313

307314
return nginxConfigContext, nil
308315
}
309316

317+
func (ncp *NginxConfigParser) addApiToNginxConfigContext(
318+
ctx context.Context,
319+
nginxConfigContext *model.NginxConfigContext,
320+
) *model.NginxConfigContext {
321+
apiDetails, err := parseURL(ncp.agentConfig.DataPlaneConfig.Nginx.API.URL)
322+
if err != nil {
323+
slog.ErrorContext(
324+
ctx,
325+
"Configured NGINX API URL is invalid",
326+
"url", ncp.agentConfig.DataPlaneConfig.Nginx.API.URL,
327+
"error", err,
328+
)
329+
330+
return nginxConfigContext
331+
}
332+
333+
if ncp.pingAPIEndpoint(ctx, apiDetails, stubStatusAPIDirective) {
334+
nginxConfigContext.StubStatus = apiDetails
335+
} else if ncp.pingAPIEndpoint(ctx, apiDetails, plusAPIDirective) {
336+
nginxConfigContext.PlusAPI = apiDetails
337+
} else {
338+
slog.WarnContext(
339+
ctx,
340+
"Configured NGINX API URL is not reachable",
341+
"url", ncp.agentConfig.DataPlaneConfig.Nginx.API.URL,
342+
)
343+
}
344+
345+
return nginxConfigContext
346+
}
347+
310348
func (ncp *NginxConfigParser) findLocalSysLogServers(sysLogServer string) string {
311349
re := regexp.MustCompile(`syslog:server=([\S]+)`)
312350
matches := re.FindStringSubmatch(sysLogServer)
@@ -886,24 +924,26 @@ func (ncp *NginxConfigParser) socketClient(socketPath string) *http.Client {
886924
// prepareHTTPClient handles TLS config
887925
func (ncp *NginxConfigParser) prepareHTTPClient(ctx context.Context) (*http.Client, error) {
888926
httpClient := http.DefaultClient
889-
caCertLocation := ncp.agentConfig.DataPlaneConfig.Nginx.APITls.Ca
890-
891-
if caCertLocation != "" && ncp.agentConfig.IsDirectoryAllowed(caCertLocation) {
892-
slog.DebugContext(ctx, "Reading CA certificate", "file_path", caCertLocation)
893-
caCert, err := os.ReadFile(caCertLocation)
894-
if err != nil {
895-
return nil, err
896-
}
897-
caCertPool := x509.NewCertPool()
898-
caCertPool.AppendCertsFromPEM(caCert)
899-
900-
httpClient = &http.Client{
901-
Transport: &http.Transport{
902-
TLSClientConfig: &tls.Config{
903-
RootCAs: caCertPool,
904-
MinVersion: tls.VersionTLS13,
927+
if ncp.agentConfig.IsNginxApiConfigured() {
928+
caCertLocation := ncp.agentConfig.DataPlaneConfig.Nginx.API.TLS.Ca
929+
930+
if caCertLocation != "" && ncp.agentConfig.IsDirectoryAllowed(caCertLocation) {
931+
slog.DebugContext(ctx, "Reading CA certificate", "file_path", caCertLocation)
932+
caCert, err := os.ReadFile(caCertLocation)
933+
if err != nil {
934+
return nil, err
935+
}
936+
caCertPool := x509.NewCertPool()
937+
caCertPool.AppendCertsFromPEM(caCert)
938+
939+
httpClient = &http.Client{
940+
Transport: &http.Transport{
941+
TLSClientConfig: &tls.Config{
942+
RootCAs: caCertPool,
943+
MinVersion: tls.VersionTLS13,
944+
},
905945
},
906-
},
946+
}
907947
}
908948
}
909949

@@ -912,15 +952,19 @@ func (ncp *NginxConfigParser) prepareHTTPClient(ctx context.Context) (*http.Clie
912952

913953
// Populate the CA cert location based ondirectory allowance.
914954
func (ncp *NginxConfigParser) selfSignedCACertLocation(ctx context.Context) string {
915-
caCertLocation := ncp.agentConfig.DataPlaneConfig.Nginx.APITls.Ca
955+
if ncp.agentConfig.IsNginxApiConfigured() {
956+
caCertLocation := ncp.agentConfig.DataPlaneConfig.Nginx.API.TLS.Ca
957+
958+
if caCertLocation != "" && !ncp.agentConfig.IsDirectoryAllowed(caCertLocation) {
959+
// If SSL is enabled but CA cert is provided and not allowed, treat it as if no CA cert
960+
slog.WarnContext(ctx, "CA certificate location is not allowed, treating as if no CA cert provided.")
961+
return ""
962+
}
916963

917-
if caCertLocation != "" && !ncp.agentConfig.IsDirectoryAllowed(caCertLocation) {
918-
// If SSL is enabled but CA cert is provided and not allowed, treat it as if no CA cert
919-
slog.WarnContext(ctx, "CA certificate location is not allowed, treating as if no CA cert provided.")
920-
return ""
964+
return caCertLocation
921965
}
922966

923-
return caCertLocation
967+
return ""
924968
}
925969

926970
func (ncp *NginxConfigParser) isDuplicateFile(nginxConfigContextFiles []*mpi.File, newFile *mpi.File) bool {
@@ -976,3 +1020,16 @@ func (ncp *NginxConfigParser) sortPlusAPIs(ctx context.Context, apis []*model.AP
9761020

9771021
return apis
9781022
}
1023+
1024+
func parseURL(unparsedUrl string) (*model.APIDetails, error) {
1025+
parsedURL, err := url.Parse(unparsedUrl)
1026+
if err != nil {
1027+
return nil, err
1028+
}
1029+
1030+
return &model.APIDetails{
1031+
URL: unparsedUrl,
1032+
Listen: parsedURL.Host,
1033+
Location: parsedURL.Path,
1034+
}, nil
1035+
}

0 commit comments

Comments
 (0)