Support Python 3.11 isolated restore
Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a02994-7685-7940-bf34-3555b8256018
This commit is contained in:
parent
295bf43d54
commit
2c92b97f37
3 changed files with 21 additions and 3 deletions
|
|
@ -210,7 +210,13 @@ def extract_bundle(
|
||||||
expected = validated["manifest"]["os_config_members"]
|
expected = validated["manifest"]["os_config_members"]
|
||||||
if observed != expected:
|
if observed != expected:
|
||||||
raise RestoreError("decrypted archive membership or modes differ from manifest")
|
raise RestoreError("decrypted archive membership or modes differ from manifest")
|
||||||
archive.extractall(path=stage, filter="data")
|
# Python 3.12 added the extraction filter argument. On older
|
||||||
|
# supported runtimes the complete member/link/type validation
|
||||||
|
# above is the equivalent fail-closed gate before extraction.
|
||||||
|
if sys.version_info >= (3, 12):
|
||||||
|
archive.extractall(path=stage, filter="data")
|
||||||
|
else:
|
||||||
|
archive.extractall(path=stage)
|
||||||
plain_archive.unlink()
|
plain_archive.unlink()
|
||||||
packages = stage / "packages.txt"
|
packages = stage / "packages.txt"
|
||||||
_decrypt(executable, identity, artifacts["packages"], packages)
|
_decrypt(executable, identity, artifacts["packages"], packages)
|
||||||
|
|
|
||||||
|
|
@ -163,6 +163,18 @@ class S1BackupRecoveryTests(unittest.TestCase):
|
||||||
with self.assertRaisesRegex(RestoreError, "digest mismatch"):
|
with self.assertRaisesRegex(RestoreError, "digest mismatch"):
|
||||||
validate_bundle(bundle, self.spec)
|
validate_bundle(bundle, self.spec)
|
||||||
|
|
||||||
|
def test_isolated_restore_supports_python_311_tar_api(self) -> None:
|
||||||
|
bundle = self._create()
|
||||||
|
with mock.patch("s1_restore.sys.version_info", (3, 11, 0)):
|
||||||
|
restored = extract_bundle(
|
||||||
|
bundle=bundle,
|
||||||
|
spec_path=self.spec,
|
||||||
|
destination=self.root / "restore-python311",
|
||||||
|
identity=self.identity,
|
||||||
|
age_binary=shutil.which("age"),
|
||||||
|
)
|
||||||
|
self.assertEqual("fixture-host\n", (restored / "etc/hostname").read_text())
|
||||||
|
|
||||||
def test_archive_traversal_and_escaping_link_fail(self) -> None:
|
def test_archive_traversal_and_escaping_link_fail(self) -> None:
|
||||||
member = tarfile.TarInfo("../../etc/shadow")
|
member = tarfile.TarInfo("../../etc/shadow")
|
||||||
with self.assertRaisesRegex(RestoreError, "unsafe archive member"):
|
with self.assertRaisesRegex(RestoreError, "unsafe archive member"):
|
||||||
|
|
|
||||||
|
|
@ -178,9 +178,9 @@ decrypted configuration.
|
||||||
|
|
||||||
## Source delivery record — 2026-08-23
|
## Source delivery record — 2026-08-23
|
||||||
|
|
||||||
- `make validate-s1-backup` passes the declaration and 15 fixture recovery
|
- `make validate-s1-backup` passes the declaration and 16 fixture recovery
|
||||||
tests using disposable age identities and storage.
|
tests using disposable age identities and storage.
|
||||||
- The full repository suite passes 45 tests. The timer calendar is accepted by
|
- The full repository suite passes 46 tests. The timer calendar is accepted by
|
||||||
`systemd-analyze`; deployment YAML and separation from bootstrap are checked
|
`systemd-analyze`; deployment YAML and separation from bootstrap are checked
|
||||||
as source contracts.
|
as source contracts.
|
||||||
- Native `ansible-playbook --syntax-check` was unavailable on the development
|
- Native `ansible-playbook --syntax-check` was unavailable on the development
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue