Skip to content

Commit 77170c9

Browse files
authored
Add agent config option to set NGINX API URL and socket path (#1485)
1 parent 9e67328 commit 77170c9

File tree

9 files changed

+349
-60
lines changed

9 files changed

+349
-60
lines changed

internal/config/config.go

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -531,7 +531,19 @@ func registerDataPlaneFlags(fs *flag.FlagSet) {
531531
)
532532

533533
fs.String(
534-
NginxApiTlsCa,
534+
NginxApiURLKey,
535+
"",
536+
"The NGINX Plus API URL.",
537+
)
538+
539+
fs.String(
540+
NginxApiSocketKey,
541+
"",
542+
"The NGINX Plus API Unix socket path.",
543+
)
544+
545+
fs.String(
546+
NginxApiTlsCaKey,
535547
DefNginxApiTlsCa,
536548
"The NGINX Plus CA certificate file location needed to call the NGINX Plus API if SSL is enabled.",
537549
)
@@ -1124,12 +1136,16 @@ func parseJSON(value string) interface{} {
11241136
}
11251137

11261138
func resolveDataPlaneConfig() *DataPlaneConfig {
1127-
return &DataPlaneConfig{
1139+
dataPlaneConfig := &DataPlaneConfig{
11281140
Nginx: &NginxDataPlaneConfig{
11291141
ReloadMonitoringPeriod: viperInstance.GetDuration(NginxReloadMonitoringPeriodKey),
11301142
TreatWarningsAsErrors: viperInstance.GetBool(NginxTreatWarningsAsErrorsKey),
11311143
ExcludeLogs: viperInstance.GetStringSlice(NginxExcludeLogsKey),
1132-
APITls: TLSConfig{Ca: viperInstance.GetString(NginxApiTlsCa)},
1144+
API: &NginxAPI{
1145+
URL: viperInstance.GetString(NginxApiURLKey),
1146+
Socket: viperInstance.GetString(NginxApiSocketKey),
1147+
TLS: TLSConfig{Ca: viperInstance.GetString(NginxApiTlsCaKey)},
1148+
},
11331149
ReloadBackoff: &BackOff{
11341150
InitialInterval: viperInstance.GetDuration(NginxReloadBackoffInitialIntervalKey),
11351151
MaxInterval: viperInstance.GetDuration(NginxReloadBackoffMaxIntervalKey),
@@ -1139,6 +1155,12 @@ func resolveDataPlaneConfig() *DataPlaneConfig {
11391155
},
11401156
},
11411157
}
1158+
1159+
if dataPlaneConfig.Nginx.API.Socket != "" && !strings.HasPrefix(dataPlaneConfig.Nginx.API.Socket, "unix:") {
1160+
dataPlaneConfig.Nginx.API.Socket = "unix:" + dataPlaneConfig.Nginx.API.Socket
1161+
}
1162+
1163+
return dataPlaneConfig
11421164
}
11431165

11441166
func resolveClient() *Client {

internal/config/config_test.go

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

internal/config/flags.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,9 @@ var (
139139
NginxReloadBackoffRandomizationFactorKey = pre(NginxReloadBackoffKey) + "randomization_factor"
140140
NginxReloadBackoffMultiplierKey = pre(NginxReloadBackoffKey) + "multiplier"
141141
NginxExcludeLogsKey = pre(DataPlaneConfigRootKey, "nginx") + "exclude_logs"
142-
NginxApiTlsCa = pre(DataPlaneConfigRootKey, "nginx") + "api_tls_ca"
142+
NginxApiTlsCaKey = pre(DataPlaneConfigRootKey, "nginx") + "api_tls_ca"
143+
NginxApiURLKey = pre(DataPlaneConfigRootKey, "nginx") + "api_url"
144+
NginxApiSocketKey = pre(DataPlaneConfigRootKey, "nginx") + "api_socket"
143145

144146
SyslogServerPort = pre("syslog_server") + "port"
145147

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: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,18 @@ type (
6868
}
6969
NginxDataPlaneConfig struct {
7070
ReloadBackoff *BackOff `yaml:"reload_backoff" mapstructure:"reload_backoff"`
71-
APITls TLSConfig `yaml:"api_tls" mapstructure:"api_tls"`
71+
API *NginxAPI `yaml:"api" mapstructure:"api"`
7272
ExcludeLogs []string `yaml:"exclude_logs" mapstructure:"exclude_logs"`
7373
ReloadMonitoringPeriod time.Duration `yaml:"reload_monitoring_period" mapstructure:"reload_monitoring_period"`
7474
TreatWarningsAsErrors bool `yaml:"treat_warnings_as_errors" mapstructure:"treat_warnings_as_errors"`
7575
}
7676

77+
NginxAPI struct {
78+
URL string `yaml:"url" mapstructure:"url"`
79+
Socket string `yaml:"socket" mapstructure:"socket"`
80+
TLS TLSConfig `yaml:"tls" mapstructure:"tls"`
81+
}
82+
7783
Client struct {
7884
HTTP *HTTP `yaml:"http" mapstructure:"http"`
7985
Grpc *GRPC `yaml:"grpc" mapstructure:"grpc"`
@@ -509,3 +515,27 @@ func checkDirIsAllowed(path string, allowedDirs []string) bool {
509515

510516
return checkDirIsAllowed(filepath.Dir(path), allowedDirs)
511517
}
518+
519+
func (c *Config) IsNginxApiUrlConfigured() bool {
520+
if !c.IsNginxApiConfigured() {
521+
return false
522+
}
523+
524+
return c.DataPlaneConfig.Nginx.API.URL != ""
525+
}
526+
527+
func (c *Config) IsNginxApiSocketConfigured() bool {
528+
if !c.IsNginxApiConfigured() {
529+
return false
530+
}
531+
532+
return c.DataPlaneConfig.Nginx.API.Socket != ""
533+
}
534+
535+
func (c *Config) IsNginxApiConfigured() bool {
536+
if c.DataPlaneConfig == nil || c.DataPlaneConfig.Nginx == nil || c.DataPlaneConfig.Nginx.API == nil {
537+
return false
538+
}
539+
540+
return true
541+
}

internal/datasource/config/nginx_config_parser.go

Lines changed: 98 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,52 @@ 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.agentConfig.IsNginxApiSocketConfigured() {
334+
apiDetails.Listen = ncp.agentConfig.DataPlaneConfig.Nginx.API.Socket
335+
}
336+
337+
if ncp.pingAPIEndpoint(ctx, apiDetails, stubStatusAPIDirective) {
338+
nginxConfigContext.StubStatus = apiDetails
339+
} else if ncp.pingAPIEndpoint(ctx, apiDetails, plusAPIDirective) {
340+
nginxConfigContext.PlusAPI = apiDetails
341+
} else {
342+
slog.WarnContext(
343+
ctx,
344+
"Configured NGINX API URL is not reachable",
345+
"url", ncp.agentConfig.DataPlaneConfig.Nginx.API.URL,
346+
)
347+
}
348+
349+
return nginxConfigContext
350+
}
351+
310352
func (ncp *NginxConfigParser) findLocalSysLogServers(sysLogServer string) string {
311353
re := regexp.MustCompile(`syslog:server=([\S]+)`)
312354
matches := re.FindStringSubmatch(sysLogServer)
@@ -886,24 +928,26 @@ func (ncp *NginxConfigParser) socketClient(socketPath string) *http.Client {
886928
// prepareHTTPClient handles TLS config
887929
func (ncp *NginxConfigParser) prepareHTTPClient(ctx context.Context) (*http.Client, error) {
888930
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,
931+
if ncp.agentConfig.IsNginxApiConfigured() {
932+
caCertLocation := ncp.agentConfig.DataPlaneConfig.Nginx.API.TLS.Ca
933+
934+
if caCertLocation != "" && ncp.agentConfig.IsDirectoryAllowed(caCertLocation) {
935+
slog.DebugContext(ctx, "Reading CA certificate", "file_path", caCertLocation)
936+
caCert, err := os.ReadFile(caCertLocation)
937+
if err != nil {
938+
return nil, err
939+
}
940+
caCertPool := x509.NewCertPool()
941+
caCertPool.AppendCertsFromPEM(caCert)
942+
943+
httpClient = &http.Client{
944+
Transport: &http.Transport{
945+
TLSClientConfig: &tls.Config{
946+
RootCAs: caCertPool,
947+
MinVersion: tls.VersionTLS13,
948+
},
905949
},
906-
},
950+
}
907951
}
908952
}
909953

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

913957
// Populate the CA cert location based ondirectory allowance.
914958
func (ncp *NginxConfigParser) selfSignedCACertLocation(ctx context.Context) string {
915-
caCertLocation := ncp.agentConfig.DataPlaneConfig.Nginx.APITls.Ca
959+
if ncp.agentConfig.IsNginxApiConfigured() {
960+
caCertLocation := ncp.agentConfig.DataPlaneConfig.Nginx.API.TLS.Ca
916961

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 ""
962+
if caCertLocation != "" && !ncp.agentConfig.IsDirectoryAllowed(caCertLocation) {
963+
// If SSL is enabled but CA cert is provided and not allowed, treat it as if no CA cert
964+
slog.WarnContext(ctx, "CA certificate location is not allowed, treating as if no CA cert provided.")
965+
return ""
966+
}
967+
968+
return caCertLocation
921969
}
922970

923-
return caCertLocation
971+
return ""
924972
}
925973

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

9771025
return apis
9781026
}
1027+
1028+
func parseURL(unparsedUrl string) (*model.APIDetails, error) {
1029+
parsedURL, err := url.Parse(unparsedUrl)
1030+
if err != nil {
1031+
return nil, err
1032+
}
1033+
1034+
return &model.APIDetails{
1035+
URL: unparsedUrl,
1036+
Listen: parsedURL.Host,
1037+
Location: parsedURL.Path,
1038+
}, nil
1039+
}

0 commit comments

Comments
 (0)