Repository: nginx/nginx-gateway-fabric

Issue:
NGINX Plus: Allow for configuring `enforce_initial_report` field
As a potential NGINX Plus user,
I want to have a grace period for my initial licensing report,
So that I can validate NGF as a PoC without restriction.

NGF currently sets `enforce_initial_report off;` for NGINX Plus data planes when initially installing, but once the first configuration is sent from the control plane to the data plane, this directive is removed (defaulting to `on`). Users should be able to set this field themselves for other circumstances, like PoC testing or exemptions.

For reference: https://nginx.org/en/docs/ngx_mgmt_module.html#enforce_initial_report

## Acceptance
- add a CLI argument to the controller to enable or disable the initial report enforcement
- set the field in the nginx config based on the user input
- the existing bootstrap config that sets this field to `off` should be left as-is to ensure startup succeeds
- update the cli flag document
- add helm chart flag to the existing nginx plus usage section

## Dev Notes
- This cli option can fit alongside the existing nginx plus usage cli options we already have

Discussion:

Solution patch:
diff --git a/charts/nginx-gateway-fabric/README.md b/charts/nginx-gateway-fabric/README.md
index 4f254110..eb7f3ce1 100644
--- a/charts/nginx-gateway-fabric/README.md
+++ b/charts/nginx-gateway-fabric/README.md
@@ -207,7 +207,7 @@ The following table lists the configurable parameters of the NGINX Gateway Fabri
 | `certGenerator.ttlSecondsAfterFinished` | How long to wait after the cert generator job has finished before it is removed by the job controller. | int | `30` |
 | `clusterDomain` | The DNS cluster domain of your Kubernetes cluster. | string | `"cluster.local"` |
 | `gateways` | A list of Gateway objects. View https://gateway-api.sigs.k8s.io/reference/spec/#gateway for full Gateway reference. | list | `[]` |
-| `nginx` | The nginx section contains the configuration for all NGINX data plane deployments installed by the NGINX Gateway Fabric control plane. | object | `{"autoscaling":{"enable":false},"config":{},"container":{"hostPorts":[],"lifecycle":{},"readinessProbe":{},"resources":{},"volumeMounts":[]},"debug":false,"image":{"pullPolicy":"Always","repository":"ghcr.io/nginx/nginx-gateway-fabric/nginx","tag":"edge"},"imagePullSecret":"","imagePullSecrets":[],"kind":"deployment","nginxOneConsole":{"dataplaneKeySecretName":"","endpointHost":"agent.connect.nginx.com","endpointPort":443,"skipVerify":false},"patches":[],"plus":false,"pod":{},"replicas":1,"service":{"externalTrafficPolicy":"Local","loadBalancerClass":"","loadBalancerIP":"","loadBalancerSourceRanges":[],"nodePorts":[],"patches":[],"type":"LoadBalancer"},"usage":{"caSecretName":"","clientSSLSecretName":"","endpoint":"","resolver":"","secretName":"nplus-license","skipVerify":false}}` |
+| `nginx` | The nginx section contains the configuration for all NGINX data plane deployments installed by the NGINX Gateway Fabric control plane. | object | `{"autoscaling":{"enable":false},"config":{},"container":{"hostPorts":[],"lifecycle":{},"readinessProbe":{},"resources":{},"volumeMounts":[]},"debug":false,"image":{"pullPolicy":"Always","repository":"ghcr.io/nginx/nginx-gateway-fabric/nginx","tag":"edge"},"imagePullSecret":"","imagePullSecrets":[],"kind":"deployment","nginxOneConsole":{"dataplaneKeySecretName":"","endpointHost":"agent.connect.nginx.com","endpointPort":443,"skipVerify":false},"patches":[],"plus":false,"pod":{},"replicas":1,"service":{"externalTrafficPolicy":"Local","loadBalancerClass":"","loadBalancerIP":"","loadBalancerSourceRanges":[],"nodePorts":[],"patches":[],"type":"LoadBalancer"},"usage":{"caSecretName":"","clientSSLSecretName":"","endpoint":"","enforceInitialReport":true,"resolver":"","secretName":"nplus-license","skipVerify":false}}` |
 | `nginx.autoscaling` | Autoscaling configuration for the NGINX data plane. | object | `{"enable":false}` |
 | `nginx.autoscaling.enable` | Enable or disable Horizontal Pod Autoscaler for the NGINX data plane. | bool | `false` |
 | `nginx.config` | The configuration for the data plane that is contained in the NginxProxy resource. This is applied globally to all Gateways managed by this instance of NGINX Gateway Fabric. | object | `{}` |
@@ -241,6 +241,7 @@ The following table lists the configurable parameters of the NGINX Gateway Fabri
 | `nginx.usage.caSecretName` | The name of the Secret containing the NGINX Instance Manager CA certificate. Must exist in the same namespace that the NGINX Gateway Fabric control plane is running in (default namespace: nginx-gateway). | string | `""` |
 | `nginx.usage.clientSSLSecretName` | The name of the Secret containing the client certificate and key for authenticating with NGINX Instance Manager. Must exist in the same namespace that the NGINX Gateway Fabric control plane is running in (default namespace: nginx-gateway). | string | `""` |
 | `nginx.usage.endpoint` | The endpoint of the NGINX Plus usage reporting server. Default: product.connect.nginx.com | string | `""` |
+| `nginx.usage.enforceInitialReport` | Enable enforcement of the initial NGINX Plus licensing report. If set to false, the initial report is not enforced. | bool | `true` |
 | `nginx.usage.resolver` | The nameserver used to resolve the NGINX Plus usage reporting endpoint. Used with NGINX Instance Manager. | string | `""` |
 | `nginx.usage.secretName` | The name of the Secret containing the JWT for NGINX Plus usage reporting. Must exist in the same namespace that the NGINX Gateway Fabric control plane is running in (default namespace: nginx-gateway). | string | `"nplus-license"` |
 | `nginx.usage.skipVerify` | Disable client verification of the NGINX Plus usage reporting server certificate. | bool | `false` |
diff --git a/charts/nginx-gateway-fabric/templates/deployment.yaml b/charts/nginx-gateway-fabric/templates/deployment.yaml
index 9be1b13f..5bc292bd 100644
--- a/charts/nginx-gateway-fabric/templates/deployment.yaml
+++ b/charts/nginx-gateway-fabric/templates/deployment.yaml
@@ -72,6 +72,9 @@ spec:
           {{- if .Values.nginx.usage.clientSSLSecretName }}
         - --usage-report-client-ssl-secret={{ .Values.nginx.usage.clientSSLSecretName }}
           {{- end }}
+          {{- if hasKey .Values.nginx.usage "enforceInitialReport" }}
+        - --usage-report-enforce-initial-report={{ .Values.nginx.usage.enforceInitialReport }}
+          {{- end }}
         {{- end }}
         {{- if .Values.nginxGateway.metrics.enable }}
         - --metrics-port={{ .Values.nginxGateway.metrics.port }}
diff --git a/charts/nginx-gateway-fabric/values.schema.json b/charts/nginx-gateway-fabric/values.schema.json
index ca5d339d..9f44991d 100644
--- a/charts/nginx-gateway-fabric/values.schema.json
+++ b/charts/nginx-gateway-fabric/values.schema.json
@@ -692,6 +692,13 @@
               "title": "endpoint",
               "type": "string"
             },
+            "enforceInitialReport": {
+              "default": true,
+              "description": "Enable enforcement of the initial NGINX Plus licensing report. If set to false, the initial report is not enforced.",
+              "required": [],
+              "title": "enforceInitialReport",
+              "type": "boolean"
+            },
             "resolver": {
               "default": "",
               "description": "The nameserver used to resolve the NGINX Plus usage reporting endpoint. Used with NGINX Instance Manager.",
diff --git a/charts/nginx-gateway-fabric/values.yaml b/charts/nginx-gateway-fabric/values.yaml
index 044e0f2d..52f1e03e 100644
--- a/charts/nginx-gateway-fabric/values.yaml
+++ b/charts/nginx-gateway-fabric/values.yaml
@@ -337,6 +337,9 @@ nginx:
     # Must exist in the same namespace that the NGINX Gateway Fabric control plane is running in (default namespace: nginx-gateway).
     clientSSLSecretName: ""
 
+    # -- Enable enforcement of the initial NGINX Plus licensing report. If set to false, the initial report is not enforced.
+    enforceInitialReport: true
+
   # @schema
   # type: object
   # properties:
diff --git a/cmd/gateway/commands.go b/cmd/gateway/commands.go
index 9f83cbcf..f334d499 100644
--- a/cmd/gateway/commands.go
+++ b/cmd/gateway/commands.go
@@ -42,6 +42,17 @@ const (
 	nginxOneTelemetryEndpointHost = "agent.connect.nginx.com"
 )
 
+// usageReportParams holds the parameters for building the usage report configuration for PLUS.
+type usageReportParams struct {
+	SecretName           stringValidatingValue
+	ClientSSLSecretName  stringValidatingValue
+	CASecretName         stringValidatingValue
+	Endpoint             stringValidatingValue
+	Resolver             stringValidatingValue
+	SkipVerify           bool
+	EnforceInitialReport bool
+}
+
 func createRootCommand() *cobra.Command {
 	rootCmd := &cobra.Command{
 		Use:           "gateway",
@@ -58,31 +69,32 @@ func createRootCommand() *cobra.Command {
 func createControllerCommand() *cobra.Command {
 	// flag names
 	const (
-		configFlag                        = "config"
-		serviceFlag                       = "service"
-		agentTLSSecretFlag                = "agent-tls-secret"
-		nginxOneDataplaneKeySecretFlag    = "nginx-one-dataplane-key-secret" //nolint:gosec // not credentials
-		nginxOneTelemetryEndpointHostFlag = "nginx-one-telemetry-endpoint-host"
-		nginxOneTelemetryEndpointPortFlag = "nginx-one-telemetry-endpoint-port"
-		nginxOneTLSSkipVerifyFlag         = "nginx-one-tls-skip-verify"
-		metricsDisableFlag                = "metrics-disable"
-		metricsSecureFlag                 = "metrics-secure-serving"
-		metricsPortFlag                   = "metrics-port"
-		healthDisableFlag                 = "health-disable"
-		healthPortFlag                    = "health-port"
-		leaderElectionDisableFlag         = "leader-election-disable"
-		leaderElectionLockNameFlag        = "leader-election-lock-name"
-		productTelemetryDisableFlag       = "product-telemetry-disable"
-		gwAPIExperimentalFlag             = "gateway-api-experimental-features"
-		nginxDockerSecretFlag             = "nginx-docker-secret" //nolint:gosec // not credentials
-		usageReportSecretFlag             = "usage-report-secret"
-		usageReportEndpointFlag           = "usage-report-endpoint"
-		usageReportResolverFlag           = "usage-report-resolver"
-		usageReportSkipVerifyFlag         = "usage-report-skip-verify"
-		usageReportClientSSLSecretFlag    = "usage-report-client-ssl-secret" //nolint:gosec // not credentials
-		usageReportCASecretFlag           = "usage-report-ca-secret"         //nolint:gosec // not credentials
-		snippetsFiltersFlag               = "snippets-filters"
-		nginxSCCFlag                      = "nginx-scc"
+		configFlag                          = "config"
+		serviceFlag                         = "service"
+		agentTLSSecretFlag                  = "agent-tls-secret"
+		nginxOneDataplaneKeySecretFlag      = "nginx-one-dataplane-key-secret" //nolint:gosec // not credentials
+		nginxOneTelemetryEndpointHostFlag   = "nginx-one-telemetry-endpoint-host"
+		nginxOneTelemetryEndpointPortFlag   = "nginx-one-telemetry-endpoint-port"
+		nginxOneTLSSkipVerifyFlag           = "nginx-one-tls-skip-verify"
+		metricsDisableFlag                  = "metrics-disable"
+		metricsSecureFlag                   = "metrics-secure-serving"
+		metricsPortFlag                     = "metrics-port"
+		healthDisableFlag                   = "health-disable"
+		healthPortFlag                      = "health-port"
+		leaderElectionDisableFlag           = "leader-election-disable"
+		leaderElectionLockNameFlag          = "leader-election-lock-name"
+		productTelemetryDisableFlag         = "product-telemetry-disable"
+		gwAPIExperimentalFlag               = "gateway-api-experimental-features"
+		nginxDockerSecretFlag               = "nginx-docker-secret" //nolint:gosec // not credentials
+		usageReportSecretFlag               = "usage-report-secret"
+		usageReportEndpointFlag             = "usage-report-endpoint"
+		usageReportResolverFlag             = "usage-report-resolver"
+		usageReportSkipVerifyFlag           = "usage-report-skip-verify"
+		usageReportClientSSLSecretFlag      = "usage-report-client-ssl-secret" //nolint:gosec // not credentials
+		usageReportCASecretFlag             = "usage-report-ca-secret"         //nolint:gosec // not credentials
+		usageReportEnforceInitialReportFlag = "usage-report-enforce-initial-report"
+		snippetsFiltersFlag                 = "snippets-filters"
+		nginxSCCFlag                        = "nginx-scc"
 	)
 
 	// flag values
@@ -148,24 +160,26 @@ func createControllerCommand() *cobra.Command {
 		nginxDockerSecrets = stringSliceValidatingValue{
 			validator: validateResourceName,
 		}
-		usageReportSkipVerify bool
-		usageReportSecretName = stringValidatingValue{
+	)
+
+	usageReportParams := usageReportParams{
+		SecretName: stringValidatingValue{
 			validator: validateResourceName,
 			value:     "nplus-license",
-		}
-		usageReportEndpoint = stringValidatingValue{
+		},
+		Endpoint: stringValidatingValue{
 			validator: validateEndpointOptionalPort,
-		}
-		usageReportResolver = stringValidatingValue{
+		},
+		Resolver: stringValidatingValue{
 			validator: validateEndpointOptionalPort,
-		}
-		usageReportClientSSLSecretName = stringValidatingValue{
+		},
+		ClientSSLSecretName: stringValidatingValue{
 			validator: validateResourceName,
-		}
-		usageReportCASecretName = stringValidatingValue{
+		},
+		CASecretName: stringValidatingValue{
 			validator: validateResourceName,
-		}
-	)
+		},
+	}
 
 	cmd := &cobra.Command{
 		Use:   "controller",
@@ -212,18 +226,10 @@ func createControllerCommand() *cobra.Command {
 			}
 
 			var usageReportConfig config.UsageReportConfig
-			if plus && usageReportSecretName.value == "" {
-				return errors.New("usage-report-secret is required when using NGINX Plus")
-			}
-
 			if plus {
-				usageReportConfig = config.UsageReportConfig{
-					SecretName:          usageReportSecretName.value,
-					ClientSSLSecretName: usageReportClientSSLSecretName.value,
-					CASecretName:        usageReportCASecretName.value,
-					Endpoint:            usageReportEndpoint.value,
-					Resolver:            usageReportResolver.value,
-					SkipVerify:          usageReportSkipVerify,
+				usageReportConfig, err = buildUsageReportConfig(usageReportParams)
+				if err != nil {
+					return err
 				}
 			}
 
@@ -432,33 +438,33 @@ func createControllerCommand() *cobra.Command {
 	)
 
 	cmd.Flags().Var(
-		&usageReportSecretName,
+		&usageReportParams.SecretName,
 		usageReportSecretFlag,
 		"The name of the Secret containing the JWT for NGINX Plus usage reporting. Must exist in the same namespace "+
 			"that the NGINX Gateway Fabric control plane is running in (default namespace: nginx-gateway).",
 	)
 
 	cmd.Flags().Var(
-		&usageReportEndpoint,
+		&usageReportParams.Endpoint,
 		usageReportEndpointFlag,
 		"The endpoint of the NGINX Plus usage reporting server.",
 	)
 
 	cmd.Flags().Var(
-		&usageReportResolver,
+		&usageReportParams.Resolver,
 		usageReportResolverFlag,
 		"The nameserver used to resolve the NGINX Plus usage reporting endpoint. Used with NGINX Instance Manager.",
 	)
 
 	cmd.Flags().BoolVar(
-		&usageReportSkipVerify,
+		&usageReportParams.SkipVerify,
 		usageReportSkipVerifyFlag,
 		false,
 		"Disable client verification of the NGINX Plus usage reporting server certificate.",
 	)
 
 	cmd.Flags().Var(
-		&usageReportClientSSLSecretName,
+		&usageReportParams.ClientSSLSecretName,
 		usageReportClientSSLSecretFlag,
 		"The name of the Secret containing the client certificate and key for authenticating with NGINX Instance Manager. "+
 			"Must exist in the same namespace that the NGINX Gateway Fabric control plane is running in "+
@@ -466,13 +472,20 @@ func createControllerCommand() *cobra.Command {
 	)
 
 	cmd.Flags().Var(
-		&usageReportCASecretName,
+		&usageReportParams.CASecretName,
 		usageReportCASecretFlag,
 		"The name of the Secret containing the NGINX Instance Manager CA certificate. "+
 			"Must exist in the same namespace that the NGINX Gateway Fabric control plane is running in "+
 			"(default namespace: nginx-gateway).",
 	)
 
+	cmd.Flags().BoolVar(
+		&usageReportParams.EnforceInitialReport,
+		usageReportEnforceInitialReportFlag,
+		true,
+		"Enable enforcement of the initial NGINX Plus licensing report. If set to false, the initial report is not enforced.",
+	)
+
 	cmd.Flags().BoolVar(
 		&snippetsFilters,
 		snippetsFiltersFlag,
@@ -491,6 +504,22 @@ func createControllerCommand() *cobra.Command {
 	return cmd
 }
 
+func buildUsageReportConfig(params usageReportParams) (config.UsageReportConfig, error) {
+	if params.SecretName.value == "" {
+		return config.UsageReportConfig{}, errors.New("usage-report-secret is required when using NGINX Plus")
+	}
+
+	return config.UsageReportConfig{
+		SecretName:           params.SecretName.value,
+		ClientSSLSecretName:  params.ClientSSLSecretName.value,
+		CASecretName:         params.CASecretName.value,
+		Endpoint:             params.Endpoint.value,
+		Resolver:             params.Resolver.value,
+		SkipVerify:           params.SkipVerify,
+		EnforceInitialReport: params.EnforceInitialReport,
+	}, nil
+}
+
 func createGenerateCertsCommand() *cobra.Command {
 	// flag names
 	const (
diff --git a/deploy/experimental-nginx-plus/deploy.yaml b/deploy/experimental-nginx-plus/deploy.yaml
index 5b478afd..c11cbc66 100644
--- a/deploy/experimental-nginx-plus/deploy.yaml
+++ b/deploy/experimental-nginx-plus/deploy.yaml
@@ -271,6 +271,7 @@ spec:
         - --nginx-docker-secret=nginx-plus-registry-secret
         - --nginx-plus
         - --usage-report-secret=nplus-license
+        - --usage-report-enforce-initial-report=true
         - --metrics-port=9113
         - --health-port=8081
         - --leader-election-lock-name=nginx-gateway-leader-election
diff --git a/deploy/nginx-plus/deploy.yaml b/deploy/nginx-plus/deploy.yaml
index c82a24e0..70f8f34c 100644
--- a/deploy/nginx-plus/deploy.yaml
+++ b/deploy/nginx-plus/deploy.yaml
@@ -267,6 +267,7 @@ spec:
         - --nginx-docker-secret=nginx-plus-registry-secret
         - --nginx-plus
         - --usage-report-secret=nplus-license
+        - --usage-report-enforce-initial-report=true
         - --metrics-port=9113
         - --health-port=8081
         - --leader-election-lock-name=nginx-gateway-leader-election
diff --git a/deploy/snippets-filters-nginx-plus/deploy.yaml b/deploy/snippets-filters-nginx-plus/deploy.yaml
index 79b8a2bf..ed0dccb8 100644
--- a/deploy/snippets-filters-nginx-plus/deploy.yaml
+++ b/deploy/snippets-filters-nginx-plus/deploy.yaml
@@ -269,6 +269,7 @@ spec:
         - --nginx-docker-secret=nginx-plus-registry-secret
         - --nginx-plus
         - --usage-report-secret=nplus-license
+        - --usage-report-enforce-initial-report=true
         - --metrics-port=9113
         - --health-port=8081
         - --leader-election-lock-name=nginx-gateway-leader-election
diff --git a/internal/controller/config/config.go b/internal/controller/config/config.go
index ff6744b1..e23f73ca 100644
--- a/internal/controller/config/config.go
+++ b/internal/controller/config/config.go
@@ -125,6 +125,8 @@ type UsageReportConfig struct {
 	Resolver string
 	// SkipVerify controls whether the nginx verifies the server certificate.
 	SkipVerify bool
+	// EnforceInitialReport controls whether the initial NGINX Plus licensing report is enforced.
+	EnforceInitialReport bool
 }
 
 // Flags contains the NGF command-line flag names and values.
diff --git a/internal/controller/nginx/config/main_config.go b/internal/controller/nginx/config/main_config.go
index 1fb79912..bd58eabe 100644
--- a/internal/controller/nginx/config/main_config.go
+++ b/internal/controller/nginx/config/main_config.go
@@ -55,13 +55,14 @@ func executeEventsConfig(conf dataplane.Configuration) []executeResult {
 }
 
 type mgmtConf struct {
-	Endpoint          string
-	Resolver          string
-	LicenseTokenFile  string
-	CACertFile        string
-	ClientSSLCertFile string
-	ClientSSLKeyFile  string
-	SkipVerify        bool
+	Endpoint             string
+	Resolver             string
+	LicenseTokenFile     string
+	CACertFile           string
+	ClientSSLCertFile    string
+	ClientSSLKeyFile     string
+	SkipVerify           bool
+	EnforceInitialReport bool
 }
 
 // generateMgmtFiles generates the NGINX Plus configuration file for the mgmt block. As part of this,
@@ -88,10 +89,11 @@ func (g GeneratorImpl) generateMgmtFiles(conf dataplane.Configuration) []agent.F
 	files := []agent.File{tokenFile}
 
 	cfg := mgmtConf{
-		Endpoint:         g.usageReportConfig.Endpoint,
-		Resolver:         g.usageReportConfig.Resolver,
-		LicenseTokenFile: tokenFile.Meta.Name,
-		SkipVerify:       g.usageReportConfig.SkipVerify,
+		Endpoint:             g.usageReportConfig.Endpoint,
+		Resolver:             g.usageReportConfig.Resolver,
+		LicenseTokenFile:     tokenFile.Meta.Name,
+		SkipVerify:           g.usageReportConfig.SkipVerify,
+		EnforceInitialReport: g.usageReportConfig.EnforceInitialReport,
 	}
 
 	if content, ok := conf.AuxiliarySecrets[graph.PlusReportCACertificate]; ok {
diff --git a/internal/controller/nginx/config/main_config_template.go b/internal/controller/nginx/config/main_config_template.go
index ae668431..21f6a28a 100644
--- a/internal/controller/nginx/config/main_config_template.go
+++ b/internal/controller/nginx/config/main_config_template.go
@@ -37,5 +37,8 @@ mgmt {
 	ssl_certificate {{ .ClientSSLCertFile }};
 	ssl_certificate_key {{ .ClientSSLKeyFile }};
 	{{- end }}
+	{{- if not .EnforceInitialReport }}
+	enforce_initial_report off;
+	{{- end }}
 }
 `



Test patch:
diff --git a/cmd/gateway/commands_test.go b/cmd/gateway/commands_test.go
index 0ecd4111..4cb8f0d5 100644
--- a/cmd/gateway/commands_test.go
+++ b/cmd/gateway/commands_test.go
@@ -154,6 +154,7 @@ func TestControllerCmdFlagValidation(t *testing.T) {
 				"--usage-report-resolver=resolver.com",
 				"--usage-report-ca-secret=ca-secret",
 				"--usage-report-client-ssl-secret=client-secret",
+				"--usage-report-enforce-initial-report",
 				"--snippets-filters",
 				"--nginx-scc=nginx-sscc-name",
 				"--nginx-one-dataplane-key-secret=dataplane-key-secret",
@@ -854,3 +855,72 @@ func TestCreateGatewayPodConfig(t *testing.T) {
 	g.Expect(err).To(MatchError(errors.New("environment variable POD_UID not set")))
 	g.Expect(cfg).To(Equal(config.GatewayPodConfig{}))
 }
+
+func TestUsageReportConfig(t *testing.T) {
+	t.Parallel()
+
+	testCases := []struct {
+		name        string
+		params      usageReportParams
+		expected    config.UsageReportConfig
+		expectError bool
+	}{
+		{
+			name: "NGINX Plus enabled with all valid parameters",
+			params: usageReportParams{
+				SecretName:           stringValidatingValue{value: "test-secret"},
+				ClientSSLSecretName:  stringValidatingValue{value: "client-ssl-secret"},
+				CASecretName:         stringValidatingValue{value: "ca-secret"},
+				Endpoint:             stringValidatingValue{value: "example.com"},
+				Resolver:             stringValidatingValue{value: "resolver.com"},
+				SkipVerify:           true,
+				EnforceInitialReport: false,
+			},
+			expectError: false,
+			expected: config.UsageReportConfig{
+				SecretName:           "test-secret",
+				ClientSSLSecretName:  "client-ssl-secret",
+				CASecretName:         "ca-secret",
+				Endpoint:             "example.com",
+				Resolver:             "resolver.com",
+				SkipVerify:           true,
+				EnforceInitialReport: false,
+			},
+		},
+		{
+			name: "NGINX Plus enabled with missing secret",
+			params: usageReportParams{
+				SecretName:           stringValidatingValue{value: ""},
+				ClientSSLSecretName:  stringValidatingValue{value: "client-ssl-secret"},
+				CASecretName:         stringValidatingValue{value: "ca-secret"},
+				Endpoint:             stringValidatingValue{value: "example.com"},
+				Resolver:             stringValidatingValue{value: "resolver.com"},
+				SkipVerify:           true,
+				EnforceInitialReport: false,
+			},
+			expectError: true,
+			expected:    config.UsageReportConfig{},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			t.Parallel()
+			result, err := buildUsageReportConfig(tc.params)
+
+			if tc.expectError {
+				if err == nil {
+					t.Errorf("expected an error but got none")
+				}
+			} else {
+				if err != nil {
+					t.Errorf("did not expect an error but got: %v", err)
+				}
+
+				if result != tc.expected {
+					t.Errorf("expected result %+v, but got %+v", tc.expected, result)
+				}
+			}
+		})
+	}
+}
diff --git a/internal/controller/provisioner/objects_test.go b/internal/controller/provisioner/objects_test.go
index e72d1057..d4b580f5 100644
--- a/internal/controller/provisioner/objects_test.go
+++ b/internal/controller/provisioner/objects_test.go
@@ -587,6 +587,7 @@ func TestBuildNginxResourceObjects_Plus(t *testing.T) {
 	g.Expect(cm.Data["mgmt.conf"]).To(ContainSubstring("ssl_trusted_certificate"))
 	g.Expect(cm.Data["mgmt.conf"]).To(ContainSubstring("ssl_certificate"))
 	g.Expect(cm.Data["mgmt.conf"]).To(ContainSubstring("ssl_certificate_key"))
+	g.Expect(cm.Data["mgmt.conf"]).To(ContainSubstring("enforce_initial_report off"))
 
 	cmObj = objects[5]
 	cm, ok = cmObj.(*corev1.ConfigMap)