SEP-1672: Reject an encrypted base backup before a fast_restore incremental - #1384
Open
marcuscruz-percona wants to merge 5 commits into
Open
SEP-1672: Reject an encrypted base backup before a fast_restore incremental#1384marcuscruz-percona wants to merge 5 commits into
marcuscruz-percona wants to merge 5 commits into
Conversation
…mental `_is_good_base_backup` promised to reject a base that is "fully prepared or encrypted", but only read `xtrabackup_checkpoints` -- a file kept in plaintext so the base's LSN stays readable. It could not see encryption at all. `_is_compressed_backup`'s existence fallback then caught the base anyway, because encryption renames `ibdata1`, so the run fell back to a full backup while the log blamed compression. Detection now runs against both encryption formats, independent of whether the job still has encryption enabled: a base encrypted by an earlier run stays encrypted after the setting is turned off. `less_space` is deliberately exempt. It takes its LSN from the plaintext checkpoints and never reads the base directory, and restore decrypts the whole chain before preparing it, so rejecting the base would turn every scheduled run into a full backup. The guard is funded by removing dead code from the same payload, which sat 6 bytes under the 16 KiB Nomad dispatch limit: the unused `strict_mode` parameter, the unreachable encrypted-suffix names in `_is_compressed_backup`, and a dead local in `DirectoryEncryptor._load_config`.
marcuscruz-percona
requested review from
a team,
peter-o-addo and
yyyyyyyan
as code owners
August 20, 2026 20:27
Contributor
There was a problem hiding this comment.
Pull request overview
Adds encrypted-base detection for fast_restore, with broad guard coverage and synchronized payload variants.
Changes:
- Detects xbcrypt/GPG-encrypted bases before incremental merging.
- Preserves
less_spacebehavior and improves fallback logging. - Adds guard tests and a shared GPG test harness.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
app/sep/apps/mysql_backups/xtrabackup_payload |
Implements canonical encrypted-base guard. |
app/sep/apps/mysql_backups/xtrabackup_s3_payload |
Updates generated S3 variant. |
app/sep/apps/mysql_backups/xtrabackup_gsutil_payload |
Updates generated gsutil variant. |
app/sep/apps/mysql_backups/xtrabackup_rsync_payload |
Updates generated rsync variant. |
app/sep/apps/mysql_backups/xtrabackup_noupload_payload |
Updates generated no-upload variant. |
app/sep/apps/mysql_backups/xtrabackup_s3_gsutil_payload |
Updates generated S3/gsutil variant. |
app/sep/apps/mysql_backups/xtrabackup_rsync_s3_payload |
Updates generated rsync/S3 variant. |
app/sep/apps/mysql_backups/xtrabackup_rsync_gsutil_payload |
Updates generated rsync/gsutil variant. |
tests/app/sep/apps/mysql_backups/test_xtrabackup_incremental_base_guard.py |
Adds base-state and fallback tests. |
tests/app/sep/apps/mysql_backups/test_xtrabackup_aes256_encrypt.py |
Reuses shared GPG probe. |
tests/app/sep/apps/mysql_backups/payload_harness.py |
Adds reusable GPG process harness. |
changelog.d/SEP-1672.fixed.md |
Documents corrected fallback reporting. |
Suppressed comments (2)
app/sep/apps/mysql_backups/xtrabackup_payload:2933
- The return documentation is narrower than the implementation. The final fallback also returns
Truefor encrypted or malformed/metadata-only bases with noibdata1, none of which “would have to be decompressed.” Describe this as detecting a base that cannot be merged as uncompressed plaintext, or narrow the fallback to actual compression.
The closing fallback still catches an encrypted base the guard could not
see, since a renamed ``ibdata1`` cannot be merged either way.
:param backup_path: Base backup directory.
:return: True if the base would have to be decompressed before a merge.
app/sep/apps/mysql_backups/xtrabackup_payload:2869
- Add
path: strand-> boolto this new method. Without them, the newly introduced encryption decision is outside the payload’s typed interface and static checks cannot validate its callers.
def _is_encrypted_base(self, path):
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Address review on the encrypted-base guard. `is_encrypted_dir` answers "is every file encrypted", returning False on the first plaintext file. An encryption pass that dies part-way leaves a mixed base behind after the backup has already been moved into the retained directory, and a plaintext `ibdata1` also defeats the compression guard's existence fallback -- so such a base started an incremental whose merge could not work. Detection now asks whether *any* file carries an encrypted rename. Reading the renames instead of the file contents drops the gpg subprocess, its OSError fallback and the dependency on the binary being installed, and frees 21 payload bytes (16,346 / 16,384). The real-gpg coverage moves to the tests for `is_encrypted_dir` itself, which still probes contents. Also corrects two docstrings: `less_space` does read the base's plaintext checkpoints, it just never feeds the InnoDB files to `--prepare`; and `_is_compressed_backup`'s fallback is wider than compression by design. Both guards now carry annotations, which the minifier strips before dispatch.
Coverage reportClick to see where and how coverage changed
This report was generated by python-coverage-comment-action |
||||||||||||||||||||||||||||||||||||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
_is_good_base_backuppromised to reject a base that is "fully prepared or encrypted" but only readxtrabackup_checkpoints, a file kept in plaintext so the base's LSN stays readable — so it could not see encryption at all._is_compressed_backup's existence fallback caught the base anyway (encryption renamesibdata1), so afast_restorejob did fall back to a full backup, but the log blamed compression. The guard now detects both encryption formats and reports the real reason.xbcryptis checked by rename (stat-only, so nogpgis spawned for such a base); gpg is verified with the real per-file probe, falling back to the.gpgrename the encryptor leaves behind on hosts wheregpgis not installed — otherwise a missing binary would either crash the run or permanently reject good plaintext bases.less_spaceis deliberately exempt. It takes its LSN from the plaintext checkpoints, never reads the base directory, and restore decrypts the whole chain before preparing it, so rejecting an encrypted base there would turn every scheduled run into a full backup.strict_modeparameter and its unreachableraise, the encrypted-suffix names in_is_compressed_backupthat the new guard makes unreachable, and a dead local inDirectoryEncryptor._load_config. Net 16,367 / 16,384 bytes (17 bytes headroom), down from 16,378; all 7 generated variants improved.payload_harness.gpg_probeis now shared, replacing the duplicatedPopenfake intest_xtrabackup_aes256_encrypt.py.Verified locally beyond the unit suite:
OSErrorfallbacks, droppedaes256probe, top-level-only scan, guard ignoringincremental_method). None missed.gpg— a base encrypted withDirectoryEncryptor's own argv (recipient-keyed) is rejected underfast_restoreand accepted underless_space. Confirmedgpg --list-only --decryptexits 0 on a host holding no private key, which is how backup hosts are set up; had it needed the secret key, detection would have failed in production while passing every faked test.make test(10,143 passed),make lint,make run-pre-commit,make smoke-xtrabackup-variants(8 variant imports + 4 real rsync uploads) all pass.Tested
XTRABACKUP_AES256_KEYFILEset,XTRABACKUP_INCREMENTAL_METHOD=fast_restore: run twice — the second run logs the "fully prepared or encrypted base" line, not the compressed one, and produces a full backupPOST_RUN_ENCRYPTset with gpg,fast_restore: same expectationPOST_RUN_ENCRYPTsince removed,fast_restore: still names the encrypted basefast_restore: the second run still produces an incremental and merges itibdata1.qp),fast_restore: the compressed-base line still firesXTRABACKUP_AES256_KEYFILEset,less_space: the incremental chain still builds against the encrypted base, and a restore of that chain succeedsChecklist
make test)make run-pre-commit)make makemigrations) — N/A, no models touchedchangelog.d/if the change is user-facing (make changelog-add), or confirmed N/A