From e3d1f6226e27a144578939274401a861512a92bd Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 10 Apr 2026 17:21:18 +0000 Subject: [PATCH 1/4] feat: implement CEL validations for OperatorConfig Adds kubebuilder CEL validations to `OperatorConfig` fields based on the migration away from webhook validation, per the provided design doc requirements. - Validates prometheus label keys in `externalLabels` - Validates `queryProjectID` constraints (length and regex pattern) - Adds `isURL` checks for `generatorUrl`, `exports.url`, `externalURL` - Implements RFC 1123 label constraints for AlertmanagerEndpoints namespace and name - Validates TLSConfig KeySecret name - Enforces HTTP/HTTPS scheme for AlertmanagerEndpoints Co-authored-by: bernot-dev <34751694+bernot-dev@users.noreply.github.com> --- ...toring.googleapis.com_operatorconfigs.yaml | 25 +++++++++++++++++++ manifests/setup.yaml | 24 ++++++++++++++++++ .../apis/monitoring/v1/operator_types.go | 14 +++++++++++ 3 files changed, 63 insertions(+) diff --git a/charts/operator/crds/monitoring.googleapis.com_operatorconfigs.yaml b/charts/operator/crds/monitoring.googleapis.com_operatorconfigs.yaml index dc435aa295..3bbf9da58e 100644 --- a/charts/operator/crds/monitoring.googleapis.com_operatorconfigs.yaml +++ b/charts/operator/crds/monitoring.googleapis.com_operatorconfigs.yaml @@ -86,6 +86,9 @@ spec: data before being written to Google Cloud Monitoring or any other additional exports specified in the OperatorConfig. The precedence behavior matches that of Prometheus. type: object + x-kubernetes-validations: + - message: Invalid label key + rule: self.all(key, key.matches('^[a-zA-Z_][a-zA-Z0-9_]*$')) filter: description: Filter limits which metric data is sent to Cloud Monitoring (it doesn't apply to additional exports). @@ -157,6 +160,8 @@ spec: description: The URL of the endpoint that supports Prometheus Remote Write to export samples to. type: string + x-kubernetes-validations: + - rule: self == '' || isURL(self) required: - url type: object @@ -237,6 +242,8 @@ spec: If no URL is provided, Alertmanager will point to the Google Cloud Metric Explorer page. type: string + x-kubernetes-validations: + - rule: self == '' || isURL(self) type: object metadata: type: object @@ -296,9 +303,13 @@ spec: type: object name: description: Name of Endpoints object in Namespace. + maxLength: 63 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ type: string namespace: description: Namespace of Endpoints object. + maxLength: 63 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ type: string pathPrefix: description: Prefix for the HTTP path alerts are pushed @@ -312,6 +323,9 @@ spec: x-kubernetes-int-or-string: true scheme: description: Scheme to use when firing alerts. + enum: + - http + - https type: string timeout: description: Timeout is a per-target Alertmanager timeout @@ -456,6 +470,9 @@ spec: - key type: object x-kubernetes-map-type: atomic + x-kubernetes-validations: + - rule: has(self.name) && self.name.matches('^[a-z0-9]([-a-z0-9]*[a-z0-9])?$') + && size(self.name) <= 63 maxVersion: description: |- Maximum TLS version. Accepted values: TLS10 (TLS 1.0), TLS11 (TLS 1.1), TLS12 (TLS 1.2), TLS13 (TLS 1.3). @@ -516,16 +533,24 @@ spec: results and alerts produced by rules. The precedence behavior matches that of Prometheus. type: object + x-kubernetes-validations: + - message: Invalid label key + rule: self.all(key, key.matches('^[a-zA-Z_][a-zA-Z0-9_]*$')) generatorUrl: description: |- The base URL used for the generator URL in the alert notification payload. Should point to an instance of a query frontend that gives access to queryProjectID. type: string + x-kubernetes-validations: + - rule: self == '' || isURL(self) queryProjectID: description: |- QueryProjectID is the GCP project ID to evaluate rules against. If left blank, the rule-evaluator will try attempt to infer the Project ID from the environment. + maxLength: 30 + minLength: 6 + pattern: ^[a-z][a-z0-9-]*[a-z0-9]$ type: string type: object scaling: diff --git a/manifests/setup.yaml b/manifests/setup.yaml index 498588e261..e20ec41726 100644 --- a/manifests/setup.yaml +++ b/manifests/setup.yaml @@ -1921,6 +1921,9 @@ spec: data before being written to Google Cloud Monitoring or any other additional exports specified in the OperatorConfig. The precedence behavior matches that of Prometheus. type: object + x-kubernetes-validations: + - message: Invalid label key + rule: self.all(key, key.matches('^[a-zA-Z_][a-zA-Z0-9_]*$')) filter: description: Filter limits which metric data is sent to Cloud Monitoring (it doesn't apply to additional exports). properties: @@ -1990,6 +1993,8 @@ spec: url: description: The URL of the endpoint that supports Prometheus Remote Write to export samples to. type: string + x-kubernetes-validations: + - rule: self == '' || isURL(self) required: - url type: object @@ -2067,6 +2072,8 @@ spec: If no URL is provided, Alertmanager will point to the Google Cloud Metric Explorer page. type: string + x-kubernetes-validations: + - rule: self == '' || isURL(self) type: object metadata: type: object @@ -2121,9 +2128,13 @@ spec: type: object name: description: Name of Endpoints object in Namespace. + maxLength: 63 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ type: string namespace: description: Namespace of Endpoints object. + maxLength: 63 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ type: string pathPrefix: description: Prefix for the HTTP path alerts are pushed to. @@ -2136,6 +2147,9 @@ spec: x-kubernetes-int-or-string: true scheme: description: Scheme to use when firing alerts. + enum: + - http + - https type: string timeout: description: Timeout is a per-target Alertmanager timeout when pushing alerts. @@ -2264,6 +2278,8 @@ spec: - key type: object x-kubernetes-map-type: atomic + x-kubernetes-validations: + - rule: has(self.name) && self.name.matches('^[a-z0-9]([-a-z0-9]*[a-z0-9])?$') && size(self.name) <= 63 maxVersion: description: |- Maximum TLS version. Accepted values: TLS10 (TLS 1.0), TLS11 (TLS 1.1), TLS12 (TLS 1.2), TLS13 (TLS 1.3). @@ -2323,16 +2339,24 @@ spec: results and alerts produced by rules. The precedence behavior matches that of Prometheus. type: object + x-kubernetes-validations: + - message: Invalid label key + rule: self.all(key, key.matches('^[a-zA-Z_][a-zA-Z0-9_]*$')) generatorUrl: description: |- The base URL used for the generator URL in the alert notification payload. Should point to an instance of a query frontend that gives access to queryProjectID. type: string + x-kubernetes-validations: + - rule: self == '' || isURL(self) queryProjectID: description: |- QueryProjectID is the GCP project ID to evaluate rules against. If left blank, the rule-evaluator will try attempt to infer the Project ID from the environment. + maxLength: 30 + minLength: 6 + pattern: ^[a-z][a-z0-9-]*[a-z0-9]$ type: string type: object scaling: diff --git a/pkg/operator/apis/monitoring/v1/operator_types.go b/pkg/operator/apis/monitoring/v1/operator_types.go index a3fb792188..f2da8526a7 100644 --- a/pkg/operator/apis/monitoring/v1/operator_types.go +++ b/pkg/operator/apis/monitoring/v1/operator_types.go @@ -146,13 +146,18 @@ type RuleEvaluatorSpec struct { // ExternalLabels specifies external labels that are attached to any rule // results and alerts produced by rules. The precedence behavior matches that // of Prometheus. + // +kubebuilder:validation:XValidation:rule="self.all(key, key.matches('^[a-zA-Z_][a-zA-Z0-9_]*$'))",message="Invalid label key" ExternalLabels map[string]string `json:"externalLabels,omitempty"` // QueryProjectID is the GCP project ID to evaluate rules against. // If left blank, the rule-evaluator will try attempt to infer the Project ID // from the environment. + // +kubebuilder:validation:MinLength=6 + // +kubebuilder:validation:MaxLength=30 + // +kubebuilder:validation:Pattern=^[a-z][a-z0-9-]*[a-z0-9]$ QueryProjectID string `json:"queryProjectID,omitempty"` // The base URL used for the generator URL in the alert notification payload. // Should point to an instance of a query frontend that gives access to queryProjectID. + // +kubebuilder:validation:XValidation:rule="self == '' || isURL(self)" GeneratorURL string `json:"generatorUrl,omitempty"` // Alerting contains how the rule-evaluator configures alerting. Alerting AlertingSpec `json:"alerting,omitempty"` @@ -170,6 +175,7 @@ type CollectionSpec struct { // ExternalLabels specifies external labels that are attached to all scraped // data before being written to Google Cloud Monitoring or any other additional exports // specified in the OperatorConfig. The precedence behavior matches that of Prometheus. + // +kubebuilder:validation:XValidation:rule="self.all(key, key.matches('^[a-zA-Z_][a-zA-Z0-9_]*$'))",message="Invalid label key" ExternalLabels map[string]string `json:"externalLabels,omitempty"` // Filter limits which metric data is sent to Cloud Monitoring (it doesn't apply to additional exports). Filter ExportFilters `json:"filter,omitempty"` @@ -187,6 +193,7 @@ type CollectionSpec struct { type ExportSpec struct { // The URL of the endpoint that supports Prometheus Remote Write to export samples to. + // +kubebuilder:validation:XValidation:rule="self == '' || isURL(self)" URL string `json:"url"` } @@ -288,6 +295,7 @@ type ManagedAlertmanagerSpec struct { // be derived automatically. // // If no URL is provided, Alertmanager will point to the Google Cloud Metric Explorer page. + // +kubebuilder:validation:XValidation:rule="self == '' || isURL(self)" ExternalURL string `json:"externalURL,omitempty"` } @@ -295,12 +303,17 @@ type ManagedAlertmanagerSpec struct { // containing alertmanager IPs to fire alerts against. type AlertmanagerEndpoints struct { // Namespace of Endpoints object. + // +kubebuilder:validation:Pattern=^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + // +kubebuilder:validation:MaxLength=63 Namespace string `json:"namespace"` // Name of Endpoints object in Namespace. + // +kubebuilder:validation:Pattern=^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + // +kubebuilder:validation:MaxLength=63 Name string `json:"name"` // Port the Alertmanager API is exposed on. Port intstr.IntOrString `json:"port"` // Scheme to use when firing alerts. + // +kubebuilder:validation:Enum=http;https Scheme string `json:"scheme,omitempty"` // Prefix for the HTTP path alerts are pushed to. PathPrefix string `json:"pathPrefix,omitempty"` @@ -332,6 +345,7 @@ type TLSConfig struct { // Struct containing the client cert file for the targets. Cert *SecretOrConfigMap `json:"cert,omitempty"` // Secret containing the client key file for the targets. + // +kubebuilder:validation:XValidation:rule="has(self.name) && self.name.matches('^[a-z0-9]([-a-z0-9]*[a-z0-9])?$') && size(self.name) <= 63" KeySecret *corev1.SecretKeySelector `json:"keySecret,omitempty"` // Used to verify the hostname for the targets. ServerName string `json:"serverName,omitempty"` From b81131ba2b2be608996ca2ae8e32ac64461fdde9 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 10 Apr 2026 19:24:32 +0000 Subject: [PATCH 2/4] fix: properly escape RFC 1123 dot in CEL matcher Addresses code review feedback to properly escape the `.` character in the CEL regex validation for the KeySecret name, so it correctly matches literal dots instead of "any character". Also correctly implemented the full RFC1123 subdomain rules `^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$` Co-authored-by: bernot-dev <34751694+bernot-dev@users.noreply.github.com> --- .../crds/monitoring.googleapis.com_operatorconfigs.yaml | 2 +- manifests/setup.yaml | 2 +- pkg/operator/apis/monitoring/v1/operator_types.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/charts/operator/crds/monitoring.googleapis.com_operatorconfigs.yaml b/charts/operator/crds/monitoring.googleapis.com_operatorconfigs.yaml index 3bbf9da58e..dcadadca05 100644 --- a/charts/operator/crds/monitoring.googleapis.com_operatorconfigs.yaml +++ b/charts/operator/crds/monitoring.googleapis.com_operatorconfigs.yaml @@ -471,7 +471,7 @@ spec: type: object x-kubernetes-map-type: atomic x-kubernetes-validations: - - rule: has(self.name) && self.name.matches('^[a-z0-9]([-a-z0-9]*[a-z0-9])?$') + - rule: has(self.name) && self.name.matches('^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$') && size(self.name) <= 63 maxVersion: description: |- diff --git a/manifests/setup.yaml b/manifests/setup.yaml index e20ec41726..c8190dd5bd 100644 --- a/manifests/setup.yaml +++ b/manifests/setup.yaml @@ -2279,7 +2279,7 @@ spec: type: object x-kubernetes-map-type: atomic x-kubernetes-validations: - - rule: has(self.name) && self.name.matches('^[a-z0-9]([-a-z0-9]*[a-z0-9])?$') && size(self.name) <= 63 + - rule: has(self.name) && self.name.matches('^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$') && size(self.name) <= 63 maxVersion: description: |- Maximum TLS version. Accepted values: TLS10 (TLS 1.0), TLS11 (TLS 1.1), TLS12 (TLS 1.2), TLS13 (TLS 1.3). diff --git a/pkg/operator/apis/monitoring/v1/operator_types.go b/pkg/operator/apis/monitoring/v1/operator_types.go index f2da8526a7..3c67888605 100644 --- a/pkg/operator/apis/monitoring/v1/operator_types.go +++ b/pkg/operator/apis/monitoring/v1/operator_types.go @@ -345,7 +345,7 @@ type TLSConfig struct { // Struct containing the client cert file for the targets. Cert *SecretOrConfigMap `json:"cert,omitempty"` // Secret containing the client key file for the targets. - // +kubebuilder:validation:XValidation:rule="has(self.name) && self.name.matches('^[a-z0-9]([-a-z0-9]*[a-z0-9])?$') && size(self.name) <= 63" + // +kubebuilder:validation:XValidation:rule="has(self.name) && self.name.matches('^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$') && size(self.name) <= 63" KeySecret *corev1.SecretKeySelector `json:"keySecret,omitempty"` // Used to verify the hostname for the targets. ServerName string `json:"serverName,omitempty"` From d946be1c3c0c0db9df2890c02dfe0bd46acb15ba Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 10 Apr 2026 19:29:05 +0000 Subject: [PATCH 3/4] refactor(operator): properly escape rfc 1123 dot in cel matcher Addresses code review feedback to properly escape the `.` character in the CEL regex validation for the KeySecret name, so it correctly matches literal dots instead of "any character". Also correctly implemented the full RFC1123 subdomain rules `^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$` Co-authored-by: bernot-dev <34751694+bernot-dev@users.noreply.github.com> From 64f16ce7b0911834184dc44025881a3832401289 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 10 Apr 2026 19:42:31 +0000 Subject: [PATCH 4/4] refactor(operator): update commit messages Updates commit messages to conform to conventional commits standards. Co-authored-by: bernot-dev <34751694+bernot-dev@users.noreply.github.com>