From 15042a8cb6cce5ad65f90bc7ca0dd89d9c48d5f8 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Mon, 14 Sep 2026 20:04:25 +0200 Subject: [PATCH] Encrypt and decrypt supervisor.tar in backup rewrites (#182220) Co-authored-by: Claude Fable 5.1 --- homeassistant/components/backup/util.py | 3 +- .../c0cb53bd_supervisor.tar.decrypted | Bin 0 -> 10240 bytes .../c0cb53bd_supervisor.tar.encrypted_v3 | Bin 0 -> 10240 bytes tests/components/backup/test_util.py | 36 +++++++++++++++--- 4 files changed, 32 insertions(+), 7 deletions(-) create mode 100644 tests/components/backup/fixtures/test_backups/c0cb53bd_supervisor.tar.decrypted create mode 100644 tests/components/backup/fixtures/test_backups/c0cb53bd_supervisor.tar.encrypted_v3 diff --git a/homeassistant/components/backup/util.py b/homeassistant/components/backup/util.py index 2cb423b07b48..f803b7854c06 100644 --- a/homeassistant/components/backup/util.py +++ b/homeassistant/components/backup/util.py @@ -213,7 +213,8 @@ def validate_password_stream( def _get_expected_archives(backup: AgentBackup) -> set[str]: """Get the expected archives in the backup.""" - expected_archives = set() + # Supervisor specific config, not in the metadata (since Supervisor 2026.03.3) + expected_archives = {"supervisor"} if backup.homeassistant_included: expected_archives.add("homeassistant") for addon in backup.addons: diff --git a/tests/components/backup/fixtures/test_backups/c0cb53bd_supervisor.tar.decrypted b/tests/components/backup/fixtures/test_backups/c0cb53bd_supervisor.tar.decrypted new file mode 100644 index 0000000000000000000000000000000000000000..05dec3d9b0e03cdcf36cc0a8e52db406d62a2e6a GIT binary patch literal 10240 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 literal 0 HcmV?d00001 diff --git a/tests/components/backup/test_util.py b/tests/components/backup/test_util.py index aca3c937b9e6..7dad63b2f48e 100644 --- a/tests/components/backup/test_util.py +++ b/tests/components/backup/test_util.py @@ -307,7 +307,7 @@ def test_validate_password_no_homeassistant(caplog: pytest.LogCaptureFixture) -> @pytest.mark.parametrize( - ("addons", "padding_size", "decrypted_backup"), + ("addons", "padding_size", "encrypted_backup", "decrypted_backup"), [ ( [ @@ -315,6 +315,7 @@ def test_validate_password_no_homeassistant(caplog: pytest.LogCaptureFixture) -> AddonInfo(name="Core 2", slug="core2", version="1.0.0"), ], 51200, # 5 x 10240 byte of padding + "test_backups/c0cb53bd.tar", "test_backups/c0cb53bd.tar.decrypted", ), ( @@ -322,19 +323,31 @@ def test_validate_password_no_homeassistant(caplog: pytest.LogCaptureFixture) -> AddonInfo(name="Core 1", slug="core1", version="1.0.0"), ], 40960, # 4 x 10240 byte of padding + "test_backups/c0cb53bd.tar", "test_backups/c0cb53bd.tar.decrypted_skip_core2", ), + # supervisor.tar.gz is not in the backup metadata but must be decrypted + ( + [ + AddonInfo(name="Core 1", slug="core1", version="1.0.0"), + AddonInfo(name="Core 2", slug="core2", version="1.0.0"), + ], + 51200, # 5 x 10240 byte of padding + "test_backups/c0cb53bd_supervisor.tar.encrypted_v3", + "test_backups/c0cb53bd_supervisor.tar.decrypted", + ), ], ) async def test_decrypted_backup_streamer( hass: HomeAssistant, addons: list[AddonInfo], padding_size: int, + encrypted_backup: str, decrypted_backup: str, ) -> None: """Test the decrypted backup streamer.""" decrypted_backup_path = get_fixture_path(decrypted_backup, DOMAIN) - encrypted_backup_path = get_fixture_path("test_backups/c0cb53bd.tar", DOMAIN) + encrypted_backup_path = get_fixture_path(encrypted_backup, DOMAIN) backup = AgentBackup( addons=addons, backup_id="1234", @@ -485,7 +498,7 @@ async def test_decrypted_backup_streamer_wrong_password(hass: HomeAssistant) -> @pytest.mark.parametrize( - ("addons", "padding_size", "encrypted_backup"), + ("addons", "padding_size", "decrypted_backup", "encrypted_backup"), [ ( [ @@ -493,6 +506,7 @@ async def test_decrypted_backup_streamer_wrong_password(hass: HomeAssistant) -> AddonInfo(name="Core 2", slug="core2", version="1.0.0"), ], 51200, # 5 x 10240 byte of padding + "test_backups/c0cb53bd.tar.decrypted", "test_backups/c0cb53bd.tar.encrypted_v3", ), ( @@ -500,20 +514,30 @@ async def test_decrypted_backup_streamer_wrong_password(hass: HomeAssistant) -> AddonInfo(name="Core 1", slug="core1", version="1.0.0"), ], 40960, # 4 x 10240 byte of padding + "test_backups/c0cb53bd.tar.decrypted", "test_backups/c0cb53bd.tar.encrypted_v3_skip_core2", ), + # supervisor.tar.gz is not in the backup metadata but must be encrypted + ( + [ + AddonInfo(name="Core 1", slug="core1", version="1.0.0"), + AddonInfo(name="Core 2", slug="core2", version="1.0.0"), + ], + 51200, # 5 x 10240 byte of padding + "test_backups/c0cb53bd_supervisor.tar.decrypted", + "test_backups/c0cb53bd_supervisor.tar.encrypted_v3", + ), ], ) async def test_encrypted_backup_streamer( hass: HomeAssistant, addons: list[AddonInfo], padding_size: int, + decrypted_backup: str, encrypted_backup: str, ) -> None: """Test the encrypted backup streamer.""" - decrypted_backup_path = get_fixture_path( - "test_backups/c0cb53bd.tar.decrypted", DOMAIN - ) + decrypted_backup_path = get_fixture_path(decrypted_backup, DOMAIN) encrypted_backup_path = get_fixture_path(encrypted_backup, DOMAIN) backup = AgentBackup( addons=addons,