Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
28 changes: 25 additions & 3 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,19 @@ func registerDataPlaneFlags(fs *flag.FlagSet) {
)

fs.String(
NginxApiTlsCa,
NginxApiURLKey,
"",
"The NGINX Plus API URL.",
)

fs.String(
NginxApiSocketKey,
"",
"The NGINX Plus API Unix socket path.",
)

fs.String(
NginxApiTlsCaKey,
DefNginxApiTlsCa,
"The NGINX Plus CA certificate file location needed to call the NGINX Plus API if SSL is enabled.",
)
Expand Down Expand Up @@ -1090,12 +1102,16 @@ func parseJSON(value string) interface{} {
}

func resolveDataPlaneConfig() *DataPlaneConfig {
return &DataPlaneConfig{
dataPlaneConfig := &DataPlaneConfig{
Nginx: &NginxDataPlaneConfig{
ReloadMonitoringPeriod: viperInstance.GetDuration(NginxReloadMonitoringPeriodKey),
TreatWarningsAsErrors: viperInstance.GetBool(NginxTreatWarningsAsErrorsKey),
ExcludeLogs: viperInstance.GetStringSlice(NginxExcludeLogsKey),
APITls: TLSConfig{Ca: viperInstance.GetString(NginxApiTlsCa)},
API: &NginxAPI{
URL: viperInstance.GetString(NginxApiURLKey),
Socket: viperInstance.GetString(NginxApiSocketKey),
TLS: TLSConfig{Ca: viperInstance.GetString(NginxApiTlsCaKey)},
},
ReloadBackoff: &BackOff{
InitialInterval: viperInstance.GetDuration(NginxReloadBackoffInitialIntervalKey),
MaxInterval: viperInstance.GetDuration(NginxReloadBackoffMaxIntervalKey),
Expand All @@ -1105,6 +1121,12 @@ func resolveDataPlaneConfig() *DataPlaneConfig {
},
},
}

if dataPlaneConfig.Nginx.API.Socket != "" && !strings.HasPrefix(dataPlaneConfig.Nginx.API.Socket, "unix:") {
dataPlaneConfig.Nginx.API.Socket = "unix:" + dataPlaneConfig.Nginx.API.Socket
}

return dataPlaneConfig
}

func resolveClient() *Client {
Expand Down
3 changes: 3 additions & 0 deletions internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1212,6 +1212,9 @@ func createConfig() *Config {
RandomizationFactor: 1.5,
Multiplier: 1.5,
},
API: &NginxAPI{
URL: "http://127.0.0.1:80/api",
},
},
},
Collector: &Collector{
Expand Down
4 changes: 3 additions & 1 deletion internal/config/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,9 @@ var (
NginxReloadBackoffRandomizationFactorKey = pre(NginxReloadBackoffKey) + "randomization_factor"
NginxReloadBackoffMultiplierKey = pre(NginxReloadBackoffKey) + "multiplier"
NginxExcludeLogsKey = pre(DataPlaneConfigRootKey, "nginx") + "exclude_logs"
NginxApiTlsCa = pre(DataPlaneConfigRootKey, "nginx") + "api_tls_ca"
NginxApiTlsCaKey = pre(DataPlaneConfigRootKey, "nginx") + "api_tls_ca"
NginxApiURLKey = pre(DataPlaneConfigRootKey, "nginx") + "api_url"
NginxApiSocketKey = pre(DataPlaneConfigRootKey, "nginx") + "api_socket"

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

Expand Down
38 changes: 20 additions & 18 deletions internal/config/testdata/nginx-agent.conf
Original file line number Diff line number Diff line change
Expand Up @@ -18,29 +18,31 @@ labels:
label3: 123

features:
- certificates
- file-watcher
- metrics
- api-action
- logs-nap
- certificates
- file-watcher
- metrics
- api-action
- logs-nap


syslog_server:
port: 1512
port: 1512

data_plane_config:
nginx:
reload_monitoring_period: 30s
treat_warnings_as_errors: true
exclude_logs:
- /var/log/nginx/error.log
- ^/var/log/nginx/.*.log$
reload_backoff:
initial_interval: 100ms
max_interval: 20s
max_elapsed_time: 15s
randomization_factor: 1.5
multiplier: 1.5
nginx:
api:
url: "http://127.0.0.1:80/api"
reload_monitoring_period: 30s
treat_warnings_as_errors: true
exclude_logs:
- /var/log/nginx/error.log
- ^/var/log/nginx/.*.log$
reload_backoff:
initial_interval: 100ms
max_interval: 20s
max_elapsed_time: 15s
randomization_factor: 1.5
multiplier: 1.5
client:
http:
timeout: 15s
Expand Down
32 changes: 31 additions & 1 deletion internal/config/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,18 @@ type (
}
NginxDataPlaneConfig struct {
ReloadBackoff *BackOff `yaml:"reload_backoff" mapstructure:"reload_backoff"`
APITls TLSConfig `yaml:"api_tls" mapstructure:"api_tls"`
API *NginxAPI `yaml:"api" mapstructure:"api"`
ExcludeLogs []string `yaml:"exclude_logs" mapstructure:"exclude_logs"`
ReloadMonitoringPeriod time.Duration `yaml:"reload_monitoring_period" mapstructure:"reload_monitoring_period"`
TreatWarningsAsErrors bool `yaml:"treat_warnings_as_errors" mapstructure:"treat_warnings_as_errors"`
}

NginxAPI struct {
URL string `yaml:"url" mapstructure:"url"`
Socket string `yaml:"socket" mapstructure:"socket"`
TLS TLSConfig `yaml:"tls" mapstructure:"tls"`
}

Client struct {
HTTP *HTTP `yaml:"http" mapstructure:"http"`
Grpc *GRPC `yaml:"grpc" mapstructure:"grpc"`
Expand Down Expand Up @@ -496,3 +502,27 @@ func checkDirIsAllowed(path string, allowedDirs []string) bool {

return checkDirIsAllowed(filepath.Dir(path), allowedDirs)
}

func (c *Config) IsNginxApiUrlConfigured() bool {
if !c.IsNginxApiConfigured() {
return false
}

return c.DataPlaneConfig.Nginx.API.URL != ""
}

func (c *Config) IsNginxApiSocketConfigured() bool {
if !c.IsNginxApiConfigured() {
return false
}

return c.DataPlaneConfig.Nginx.API.Socket != ""
}

func (c *Config) IsNginxApiConfigured() bool {
if c.DataPlaneConfig == nil || c.DataPlaneConfig.Nginx == nil || c.DataPlaneConfig.Nginx.API == nil {
return false
}

return true
}
135 changes: 98 additions & 37 deletions internal/datasource/config/nginx_config_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"log/slog"
"net"
"net/http"
"net/url"
"os"
"path/filepath"
"regexp"
Expand Down Expand Up @@ -269,18 +270,20 @@ func (ncp *NginxConfigParser) createNginxConfigContext(
return nginxConfigContext, fmt.Errorf("traverse nginx config: %w", err)
}

stubStatuses := ncp.crossplaneConfigTraverseAPIDetails(
ctx, &conf, ncp.apiCallback, stubStatusAPIDirective,
)
if stubStatuses != nil {
nginxConfigContext.StubStatuses = append(nginxConfigContext.StubStatuses, stubStatuses...)
}
if !ncp.agentConfig.IsNginxApiUrlConfigured() {
stubStatuses := ncp.crossplaneConfigTraverseAPIDetails(
ctx, &conf, ncp.apiCallback, stubStatusAPIDirective,
)
if stubStatuses != nil {
nginxConfigContext.StubStatuses = append(nginxConfigContext.StubStatuses, stubStatuses...)
}

plusAPIs := ncp.crossplaneConfigTraverseAPIDetails(
ctx, &conf, ncp.apiCallback, plusAPIDirective,
)
if plusAPIs != nil {
nginxConfigContext.PlusAPIs = append(nginxConfigContext.PlusAPIs, plusAPIs...)
plusAPIs := ncp.crossplaneConfigTraverseAPIDetails(
ctx, &conf, ncp.apiCallback, plusAPIDirective,
)
if plusAPIs != nil {
nginxConfigContext.PlusAPIs = append(nginxConfigContext.PlusAPIs, plusAPIs...)
}
}

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

nginxConfigContext.PlusAPIs = ncp.sortPlusAPIs(ctx, nginxConfigContext.PlusAPIs)
nginxConfigContext.StubStatus = ncp.FindStubStatusAPI(ctx, nginxConfigContext)
nginxConfigContext.PlusAPI = ncp.FindPlusAPI(ctx, nginxConfigContext)
if !ncp.agentConfig.IsNginxApiUrlConfigured() {
nginxConfigContext.PlusAPIs = ncp.sortPlusAPIs(ctx, nginxConfigContext.PlusAPIs)
nginxConfigContext.StubStatus = ncp.FindStubStatusAPI(ctx, nginxConfigContext)
nginxConfigContext.PlusAPI = ncp.FindPlusAPI(ctx, nginxConfigContext)
} else {
nginxConfigContext = ncp.addApiToNginxConfigContext(ctx, nginxConfigContext)
}

return nginxConfigContext, nil
}

func (ncp *NginxConfigParser) addApiToNginxConfigContext(
ctx context.Context,
nginxConfigContext *model.NginxConfigContext,
) *model.NginxConfigContext {
apiDetails, err := parseURL(ncp.agentConfig.DataPlaneConfig.Nginx.API.URL)
if err != nil {
slog.ErrorContext(
ctx,
"Configured NGINX API URL is invalid",
"url", ncp.agentConfig.DataPlaneConfig.Nginx.API.URL,
"error", err,
)

return nginxConfigContext
}

if ncp.agentConfig.IsNginxApiSocketConfigured() {
apiDetails.Listen = ncp.agentConfig.DataPlaneConfig.Nginx.API.Socket
}

if ncp.pingAPIEndpoint(ctx, apiDetails, stubStatusAPIDirective) {
nginxConfigContext.StubStatus = apiDetails
} else if ncp.pingAPIEndpoint(ctx, apiDetails, plusAPIDirective) {
nginxConfigContext.PlusAPI = apiDetails
} else {
slog.WarnContext(
ctx,
"Configured NGINX API URL is not reachable",
"url", ncp.agentConfig.DataPlaneConfig.Nginx.API.URL,
)
}

return nginxConfigContext
}

func (ncp *NginxConfigParser) findLocalSysLogServers(sysLogServer string) string {
re := regexp.MustCompile(`syslog:server=([\S]+)`)
matches := re.FindStringSubmatch(sysLogServer)
Expand Down Expand Up @@ -886,24 +928,26 @@ func (ncp *NginxConfigParser) socketClient(socketPath string) *http.Client {
// prepareHTTPClient handles TLS config
func (ncp *NginxConfigParser) prepareHTTPClient(ctx context.Context) (*http.Client, error) {
httpClient := http.DefaultClient
caCertLocation := ncp.agentConfig.DataPlaneConfig.Nginx.APITls.Ca

if caCertLocation != "" && ncp.agentConfig.IsDirectoryAllowed(caCertLocation) {
slog.DebugContext(ctx, "Reading CA certificate", "file_path", caCertLocation)
caCert, err := os.ReadFile(caCertLocation)
if err != nil {
return nil, err
}
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)

httpClient = &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: caCertPool,
MinVersion: tls.VersionTLS13,
if ncp.agentConfig.IsNginxApiConfigured() {
caCertLocation := ncp.agentConfig.DataPlaneConfig.Nginx.API.TLS.Ca

if caCertLocation != "" && ncp.agentConfig.IsDirectoryAllowed(caCertLocation) {
slog.DebugContext(ctx, "Reading CA certificate", "file_path", caCertLocation)
caCert, err := os.ReadFile(caCertLocation)
if err != nil {
return nil, err
}
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)

httpClient = &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: caCertPool,
MinVersion: tls.VersionTLS13,
},
},
},
}
}
}

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

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

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

return caCertLocation
}

return caCertLocation
return ""
}

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

return apis
}

func parseURL(unparsedUrl string) (*model.APIDetails, error) {
parsedURL, err := url.Parse(unparsedUrl)
if err != nil {
return nil, err
}

return &model.APIDetails{
URL: unparsedUrl,
Listen: parsedURL.Host,
Location: parsedURL.Path,
}, nil
}
Loading
Loading