Skip to content

fix: tighten recovery target validation - #10565

Merged
mnencia merged 1 commit into
mainfrom
dev/recovery-target-validation
May 7, 2026
Merged

mnencia merged 1 commit into
mainfrom
dev/recovery-target-validation

Conversation

@mnencia

@mnencia mnencia commented Apr 28, 2026 •

Copy link
Copy Markdown
Member

TargetXID was previously unchecked and a malformed value surfaced only at PostgreSQL startup. Validate it as a non-negative 32-bit integer at admission, matching PG's TransactionId width so values that would silently truncate to a different XID are rejected up front.

TargetName accepts arbitrary text in pg_create_restore_point, but ASCII control characters (NUL, newline, DEL) are never legitimate and would either break the line-oriented config file or signal a hostile spec. Reject them. Also enforce the 64-byte MAXFNAMELEN limit at admission rather than letting PG fail at recovery.

Related #10515

Suggested-by: Koda Reef [email protected]

@mnencia
mnencia requested a review from a team April 28, 2026 14:18
@dosubot dosubot Bot added size:M This PR changes 30-99 lines, ignoring generated files. bug 🐛 Something isn't working labels Apr 28, 2026
@cnpg-bot cnpg-bot added backport-requested ◀️ This pull request should be backported to all supported releases release-1.25 release-1.28 labels Apr 28, 2026
@github-actions

Copy link
Copy Markdown
Contributor

❗ By default, the pull request is configured to backport to all release branches.

  • To stop backporting this pr, remove the label: backport-requested ◀️ or add the label 'do not backport'
  • To stop backporting this pr to a certain release branch, remove the specific branch label: release-x.y

@mnencia

mnencia commented Apr 28, 2026

Copy link
Copy Markdown
Member Author

/test

@github-actions

Copy link
Copy Markdown
Contributor

@mnencia, here's the link to the E2E on CNPG workflow run: https://github.com/cloudnative-pg/cloudnative-pg/actions/runs/25059531741

@dosubot dosubot Bot added the lgtm This PR has been approved by a maintainer label Apr 30, 2026
@mnencia
mnencia force-pushed the dev/recovery-target-validation branch from fc47025 to dc524dd Compare April 30, 2026 17:27
@mnencia

mnencia commented Apr 30, 2026

Copy link
Copy Markdown
Member Author

/test

@github-actions

Copy link
Copy Markdown
Contributor

@mnencia, here's the link to the E2E on CNPG workflow run: https://github.com/cloudnative-pg/cloudnative-pg/actions/runs/25179673809

@cnpg-bot cnpg-bot added the ok to merge 👌 This PR can be merged label May 1, 2026
@sxd

sxd commented May 4, 2026

Copy link
Copy Markdown
Contributor

@mnencia why not doing the verification also in the API? This because the webhook can be skipped but not the API

@mnencia

mnencia commented May 4, 2026

Copy link
Copy Markdown
Member Author

Fair point about API-level validation. The other recovery target fields are already validated in the webhook, so this PR follows the existing pattern. Moving these checks to CEL would be a good improvement, but I'd do it as a separate patch that also wires up envtest in this package so the rules get test coverage.

@mnencia
mnencia force-pushed the dev/recovery-target-validation branch from dc524dd to 3018b1f Compare May 5, 2026 11:58
@sxd

sxd commented May 5, 2026

Copy link
Copy Markdown
Contributor

@mnencia I'm creating the issue for it then

@sxd

sxd commented May 5, 2026

Copy link
Copy Markdown
Contributor

Follow up issue created here #10621

@mnencia
mnencia force-pushed the dev/recovery-target-validation branch 2 times, most recently from 6a7e0a3 to c580c2e Compare May 5, 2026 17:15
@gbartolini

Copy link
Copy Markdown
Contributor

Follow up issue created here #10621

Isn't this a broader issue, unrelated to this specific ticket? My understanding is that we have normally relied on the webhook for this kind of validation.

@sxd

sxd commented May 6, 2026

Copy link
Copy Markdown
Contributor

@gbartolini @mnencia I don't think we should merge this before handling the situation described in #10621 since it will be a partial fix. Probably the PR for #10621 can be merged into this PR to have a full fix?

@gbartolini yes, normally we relied on it, but webhook can be skipped and there has been discussions about having these webhook removed in some configurations, so we should also start adding these verification on the API. On the other hand, having the validation on the API helps any GUI to validated objects before sinding it to the kubernetes clsuter.

I think it's not necessary to say that if webhook can be skipped, all the validations there are useless and will arrive to the PostgreSQL configuration.

@leonardoce

Copy link
Copy Markdown
Contributor

I believe we should merge this and we should merge #10621 too.
I don't see so much difference between merging a single PR or two PRs in a row it they both reach the release.

You can skip the webhook but, doing that:

[you] permit unsafe or destructive operations. Use this setting with caution and at your own risk.

About webhooks being not installed... I worked on a patch a while ago to prevent the operator and the instance manager from receiving invalid CR definitions: 09c0ade

It is a separate topic, but would something like this help?

@gbartolini

Copy link
Copy Markdown
Contributor

Thanks for the follow-up, @sxd. I want to address the premise here, because I think it affects the conclusion.

My understanding and position is that currently webhooks are not optional in a supported CloudNativePG installation.

The operator itself treats their absence as an anomaly: in setDefaults, the controller detects when the mutating webhook did not run, logs "Admission controllers (webhooks) appear to have been disabled. Please enable them for this object/namespace", and patches the object itself to apply the missing defaults. This is a remediation path for a broken configuration, not a supported deployment variant.

The cnpg.io/validation annotation exists as a per-object escape hatch, but it is documented with an explicit warning that disabling validation may permit unsafe or destructive operations. Similarly, the webhook disabled warning in the admission path states that all changes are accepted without validation and urges extreme caution. The installation documentation does not describe any supported configuration without webhooks — the GKE note instructs users to open port 9443, not to remove the webhook.

So, in my opinion, the validation in this PR is not partial. It is complete relative to the architecture we support and the existing pattern across all other recovery target fields.

@mnencia's response on 4 May already addressed this correctly: CEL is a worthwhile future improvement but belongs in a separate patch with proper envtest coverage, not folded into this fix. Coupling this PR to #10621 would block a well-scoped, tested fix on work that is substantially larger in scope and not yet designed.

Unless there is a specific technical concern about the validation logic itself, I think this is ready to merge.

@mnencia

mnencia commented May 6, 2026

Copy link
Copy Markdown
Member Author

@sxd I think the framing is off. The real fix for malformed recovery target values is #10515, which is already merged. That PR makes the operator escape whatever the user provides, so it cannot corrupt postgresql.conf or get silently mangled by PostgreSQL's config lexer. Safety is in place regardless of any validation layer above it.

This PR is a UX improvement on top: reject obviously-bad input at admission, so users get a clear error instead of seeing PostgreSQL fail to start later. #10621 would be another UX layer (CEL on the API for clients that bypass the webhook). Neither is essential for safety; both just improve the UX at different surfaces.

So this is not a partial fix. The real fix already shipped. Let's land this UX improvement now and tackle #10621 separately when we plan CEL coverage and envtest setup for the package.

TargetXID was previously unchecked and a malformed value surfaced
only at PostgreSQL startup. Validate it as a non-negative 32-bit
integer at admission, matching PG's TransactionId width so values
that would silently truncate to a different XID are rejected up
front.

TargetName accepts arbitrary text in pg_create_restore_point, but
ASCII control characters (NUL, newline, DEL) are never legitimate
and would either break the line-oriented config file or signal a
hostile spec. Reject them. Also enforce the 64-byte MAXFNAMELEN
limit at admission rather than letting PG fail at recovery.

The escape layers introduced in #10515 already neutralise these
values, so this is admission-time defense-in-depth: it surfaces
the same errors earlier and with a clearer message.

Signed-off-by: Marco Nenciarini <[email protected]>
@mnencia
mnencia force-pushed the dev/recovery-target-validation branch from c580c2e to 5668bb9 Compare May 6, 2026 09:44
@mnencia
mnencia merged commit caf2077 into main May 7, 2026
42 checks passed
@mnencia
mnencia deleted the dev/recovery-target-validation branch May 7, 2026 11:48
cnpg-bot pushed a commit that referenced this pull request May 7, 2026
TargetXID was previously unchecked and a malformed value surfaced only
at PostgreSQL startup. Validate it as a non-negative 32-bit integer at
admission, matching PG's TransactionId width so values that would
silently truncate to a different XID are rejected up front.

TargetName accepts arbitrary text in pg_create_restore_point, but ASCII
control characters (NUL, newline, DEL) are never legitimate and would
either break the line-oriented config file or signal a hostile spec.
Reject them. Also enforce the 64-byte MAXFNAMELEN limit at admission
rather than letting PG fail at recovery.

Related #10515

Suggested-by: Koda Reef <[email protected]>
Signed-off-by: Marco Nenciarini <[email protected]>
(cherry picked from commit caf2077)
cnpg-bot pushed a commit that referenced this pull request May 7, 2026
TargetXID was previously unchecked and a malformed value surfaced only
at PostgreSQL startup. Validate it as a non-negative 32-bit integer at
admission, matching PG's TransactionId width so values that would
silently truncate to a different XID are rejected up front.

TargetName accepts arbitrary text in pg_create_restore_point, but ASCII
control characters (NUL, newline, DEL) are never legitimate and would
either break the line-oriented config file or signal a hostile spec.
Reject them. Also enforce the 64-byte MAXFNAMELEN limit at admission
rather than letting PG fail at recovery.

Related #10515

Suggested-by: Koda Reef <[email protected]>
Signed-off-by: Marco Nenciarini <[email protected]>
(cherry picked from commit caf2077)
cnpg-bot pushed a commit that referenced this pull request May 7, 2026
TargetXID was previously unchecked and a malformed value surfaced only
at PostgreSQL startup. Validate it as a non-negative 32-bit integer at
admission, matching PG's TransactionId width so values that would
silently truncate to a different XID are rejected up front.

TargetName accepts arbitrary text in pg_create_restore_point, but ASCII
control characters (NUL, newline, DEL) are never legitimate and would
either break the line-oriented config file or signal a hostile spec.
Reject them. Also enforce the 64-byte MAXFNAMELEN limit at admission
rather than letting PG fail at recovery.

Related #10515

Suggested-by: Koda Reef <[email protected]>
Signed-off-by: Marco Nenciarini <[email protected]>
(cherry picked from commit caf2077)
sdwilsh pushed a commit to sdwilsh/ansible-playbooks that referenced this pull request May 11, 2026
##### [\`v1.29.1\`](https://github.com/cloudnative-pg/cloudnative-pg/releases/tag/v1.29.1)

**Release date:** May 8, 2026

##### Security and Supply Chain

- **`CVE-2026-44477` / `GHSA-423p-g724-fr39`: metrics exporter privilege escalation**: the metrics exporter no longer authenticates as the `postgres` superuser. It now uses a dedicated `cnpg_metrics_exporter` role with `pg_monitor` privileges only, closing a chain that let a low-privilege database user gain PostgreSQL superuser. ([`GHSA-423p-g724-fr39`](GHSA-423p-g724-fr39)) <!-- 1.29 1.28 1.25 -->

  Upgrade impact: custom monitoring queries that read user-owned tables, or use `target_databases: '*'` against databases where `PUBLIC CONNECT` has been revoked, need explicit `GRANT` statements to `cnpg_metrics_exporter`. See ["Custom query privileges and safety"](../monitoring.md#custom-query-privileges-and-safety) and ["Manually creating the metrics exporter role"](../monitoring.md#manually-creating-the-metrics-exporter-role) in the monitoring documentation.

  For replica clusters, upgrade the source primary cluster before any replica clusters that consume from it. The `cnpg_metrics_exporter` role is created on the source primary and replicates downstream; a replica cluster upgraded first will scrape against a missing role until the source primary upgrades. The manual-recovery section linked above also covers replica clusters.

- **Schema-qualified catalog references in default monitoring queries**: hardened the shipped monitoring configuration and documentation samples by qualifying every `pg_catalog` object explicitly. Unqualified references resolve through `search_path`, which a database user can manipulate to shadow built-in objects. ([#10576](cloudnative-pg/cloudnative-pg#10576)) <!-- 1.29 1.28 1.25 -->

- **Discoverable SBOM and provenance attestations**: SBOM and SLSA provenance attached to operator container images now follow the OCI 1.1 Referrers spec, so standard registry tooling and supply-chain scanners can discover them automatically. ([#10601](cloudnative-pg/cloudnative-pg#10601)) <!-- 1.29 1.28 1.25 -->

- **CVE remediation in `github.com/jackc/pgx/v5`**: bumped to v5.9.2 to pick up upstream fixes for `CVE-2026-33816` (memory-safety in `pgproto3`) and `GHSA-j88v-2chj-qfwx` (SQL injection via simple-protocol dollar-quoted string handling). ([#10437](cloudnative-pg/cloudnative-pg#10437), [#10499](cloudnative-pg/cloudnative-pg#10499))

- **CVE remediation in the Go runtime**: built with Go 1.26.3 to pick up upstream fixes in `crypto/x509`, `crypto/tls`, `net/http`, and `net` (CVE-2026-32280, CVE-2026-32281, CVE-2026-33810, CVE-2026-33814, CVE-2026-33811, CVE-2026-39825). ([#10463](cloudnative-pg/cloudnative-pg#10463), [#10647](cloudnative-pg/cloudnative-pg#10647)) <!-- 1.29 1.28 1.25 -->

- **Build pipeline hardening**: the Go 1.26.3 bump also addresses CVE-2026-42501 (`cmd/go` module-checksum validation), reducing supply-chain exposure during release builds. The affected code paths are not reachable from the running operator. ([#10647](cloudnative-pg/cloudnative-pg#10647)) <!-- 1.29 1.28 1.25 -->

##### Changes

- Switched TLS peer verification from `VerifyPeerCertificate` to `VerifyConnection`, which runs on every completed handshake (the former is skipped on resumed TLS 1.3 sessions). Session resumption is not enabled in CloudNativePG today, so this has no observable effect, but it future-proofs verification if session caching is introduced later. ([#10478](cloudnative-pg/cloudnative-pg#10478)) <!-- 1.29 1.28 1.25 -->

##### Fixes

- Fixed a failover window where the former primary kept its primary label. If it returned during failover (for example, after a transient network partition), the `-rw` service kept routing to it, replicas could reconnect, and committed writes were lost to `pg_rewind`. The old primary is now labeled `unhealthy` to isolate it from service traffic during failover. ([#10409](cloudnative-pg/cloudnative-pg#10409)) <!-- 1.29 1.28 1.25 -->

- Fixed failover not being triggered when the node hosting the primary becomes unreachable. The operator now reads the pod's `Ready` condition (flipped to `False` by the node controller when the kubelet stops reporting) instead of `ContainersReady`, which stays stale as `True` in that scenario. Combined with the spurious-failover guard ([#10445](cloudnative-pg/cloudnative-pg#10445)), failover triggers only when Kubernetes itself marks the pod not Ready. ([#10448](cloudnative-pg/cloudnative-pg#10448)) <!-- 1.29 1.28 1.25 -->

- Fixed spurious failovers caused by transient failures on the primary's HTTP status endpoint. ([#10445](cloudnative-pg/cloudnative-pg#10445)) <!-- 1.29 1.28 1.25 -->

- Fixed escaping of backslashes and control characters in PostgreSQL configuration values. Previously, such characters in parameters like `log_line_prefix` could corrupt the configuration file or be silently stripped at runtime. ([#10515](cloudnative-pg/cloudnative-pg#10515)) <!-- 1.29 1.28 1.25 -->

- Fixed `restore_command` construction to shell-quote each argument. Values such as a `destinationPath` containing whitespace (for example, `s3://my bucket/wal`) were word-split by the POSIX shell and passed to the WAL restore tool as separate arguments. ([#10518](cloudnative-pg/cloudnative-pg#10518)) <!-- 1.29 1.28 1.25 -->

- Tightened `recoveryTarget` validation in the admission webhook: `targetXID` must now be a non-negative 32-bit integer, and `targetName` must be shorter than 64 bytes and free of ASCII control characters. Malformed values are rejected at admission instead of failing later during PostgreSQL recovery. ([#10565](cloudnative-pg/cloudnative-pg#10565)) <!-- 1.29 1.28 1.25 -->

- Fixed snapshot restores failing when leftover `pgsql_tmp*` directories were present in the data directory. ([#10447](cloudnative-pg/cloudnative-pg#10447)) <!-- 1.29 1.28 1.25 -->

- Fixed a deadlock occurring when PVC storage size and resource requests are changed simultaneously. ([#10427](cloudnative-pg/cloudnative-pg#10427)) <!-- 1.29 1.28 1.25 -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport-requested ◀️ This pull request should be backported to all supported releases bug 🐛 Something isn't working lgtm This PR has been approved by a maintainer no-issue ok to merge 👌 This PR can be merged release-1.28 size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants