From 0534d67c9f3243df079035f7b5467ce65fb07c74 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Tue, 4 Feb 2025 16:10:01 +0000 Subject: [PATCH 1/3] Drop Docker config from Supervisor backup The Docker config is part of the main backup metadata. Because we consolidate encrypted and unencrypted backups today, this leads to potential bugs when restoring a backup. --- supervisor/backups/backup.py | 32 -------------------------------- supervisor/backups/manager.py | 6 ------ tests/backups/test_manager.py | 34 ++-------------------------------- 3 files changed, 2 insertions(+), 70 deletions(-) diff --git a/supervisor/backups/backup.py b/supervisor/backups/backup.py index fdf35dfdf8e..28b4a5c975d 100644 --- a/supervisor/backups/backup.py +++ b/supervisor/backups/backup.py @@ -38,16 +38,13 @@ ATTR_FOLDERS, ATTR_HOMEASSISTANT, ATTR_NAME, - ATTR_PASSWORD, ATTR_PATH, ATTR_PROTECTED, - ATTR_REGISTRIES, ATTR_REPOSITORIES, ATTR_SIZE, ATTR_SLUG, ATTR_SUPERVISOR_VERSION, ATTR_TYPE, - ATTR_USERNAME, ATTR_VERSION, CRYPTO_AES128, ) @@ -899,32 +896,3 @@ def restore_repositories(self, replace: bool = False) -> Awaitable[None]: return self.sys_store.update_repositories( self.repositories, add_with_errors=True, replace=replace ) - - def store_dockerconfig(self): - """Store the configuration for Docker.""" - self.docker = { - ATTR_REGISTRIES: { - registry: { - ATTR_USERNAME: credentials[ATTR_USERNAME], - ATTR_PASSWORD: self._encrypt_data(credentials[ATTR_PASSWORD]), - } - for registry, credentials in self.sys_docker.config.registries.items() - } - } - - def restore_dockerconfig(self, replace: bool = False): - """Restore the configuration for Docker.""" - if replace: - self.sys_docker.config.registries.clear() - - if ATTR_REGISTRIES in self.docker: - self.sys_docker.config.registries.update( - { - registry: { - ATTR_USERNAME: credentials[ATTR_USERNAME], - ATTR_PASSWORD: self._decrypt_data(credentials[ATTR_PASSWORD]), - } - for registry, credentials in self.docker[ATTR_REGISTRIES].items() - } - ) - self.sys_docker.config.save_data() diff --git a/supervisor/backups/manager.py b/supervisor/backups/manager.py index af236695c96..87e7b2de58a 100644 --- a/supervisor/backups/manager.py +++ b/supervisor/backups/manager.py @@ -215,8 +215,6 @@ def _create_backup( self._change_stage(BackupJobStage.ADDON_REPOSITORIES, backup) backup.store_repositories() - self._change_stage(BackupJobStage.DOCKER_CONFIG, backup) - backup.store_dockerconfig() return backup @@ -655,10 +653,6 @@ async def _do_restore( try: task_hass: asyncio.Task | None = None async with backup.open(location): - # Restore docker config - self._change_stage(RestoreJobStage.DOCKER_CONFIG, backup) - backup.restore_dockerconfig(replace) - # Process folders if folder_list: self._change_stage(RestoreJobStage.FOLDERS, backup) diff --git a/tests/backups/test_manager.py b/tests/backups/test_manager.py index 09eb9be075d..9be702d7f4b 100644 --- a/tests/backups/test_manager.py +++ b/tests/backups/test_manager.py @@ -63,7 +63,6 @@ async def test_do_backup_full(coresys: CoreSys, backup_mock, install_addon_ssh): backup_instance.store_homeassistant.assert_called_once() backup_instance.store_repositories.assert_called_once() - backup_instance.store_dockerconfig.assert_called_once() backup_instance.store_addons.assert_called_once() assert install_addon_ssh in backup_instance.store_addons.call_args[0][0] @@ -115,7 +114,6 @@ async def test_do_backup_full_uncompressed( backup_instance.store_homeassistant.assert_called_once() backup_instance.store_repositories.assert_called_once() - backup_instance.store_dockerconfig.assert_called_once() backup_instance.store_addons.assert_called_once() assert install_addon_ssh in backup_instance.store_addons.call_args[0][0] @@ -146,7 +144,6 @@ async def test_do_backup_partial_minimal( backup_instance.store_homeassistant.assert_not_called() backup_instance.store_repositories.assert_called_once() - backup_instance.store_dockerconfig.assert_called_once() backup_instance.store_addons.assert_not_called() @@ -176,7 +173,6 @@ async def test_do_backup_partial_minimal_uncompressed( backup_instance.store_homeassistant.assert_not_called() backup_instance.store_repositories.assert_called_once() - backup_instance.store_dockerconfig.assert_called_once() backup_instance.store_addons.assert_not_called() @@ -208,7 +204,6 @@ async def test_do_backup_partial_maximal( backup_instance.store_homeassistant.assert_called_once() backup_instance.store_repositories.assert_called_once() - backup_instance.store_dockerconfig.assert_called_once() backup_instance.store_addons.assert_called_once() assert install_addon_ssh in backup_instance.store_addons.call_args[0][0] @@ -240,7 +235,6 @@ async def test_do_restore_full(coresys: CoreSys, full_backup_mock, install_addon backup_instance.restore_homeassistant.assert_called_once() backup_instance.restore_repositories.assert_called_once() - backup_instance.restore_dockerconfig.assert_called_once() backup_instance.restore_addons.assert_called_once() install_addon_ssh.uninstall.assert_not_called() @@ -273,7 +267,6 @@ async def test_do_restore_full_different_addon( backup_instance.restore_homeassistant.assert_called_once() backup_instance.restore_repositories.assert_called_once() - backup_instance.restore_dockerconfig.assert_called_once() backup_instance.restore_addons.assert_called_once() install_addon_ssh.uninstall.assert_called_once() @@ -300,7 +293,6 @@ async def test_do_restore_partial_minimal( backup_instance.restore_homeassistant.assert_not_called() backup_instance.restore_repositories.assert_not_called() - backup_instance.restore_dockerconfig.assert_called_once() backup_instance.restore_addons.assert_not_called() @@ -329,7 +321,6 @@ async def test_do_restore_partial_maximal(coresys: CoreSys, partial_backup_mock) backup_instance.restore_homeassistant.assert_called_once() backup_instance.restore_repositories.assert_called_once() - backup_instance.restore_dockerconfig.assert_called_once() backup_instance.restore_addons.assert_called_once() @@ -431,12 +422,12 @@ async def test_restore_error( backup_instance = full_backup_mock.return_value backup_instance.protected = False - backup_instance.restore_dockerconfig.side_effect = BackupError() + backup_instance.restore_homeassistant.side_effect = BackupError() with pytest.raises(BackupError): await coresys.backups.do_restore_full(backup_instance) capture_exception.assert_not_called() - backup_instance.restore_dockerconfig.side_effect = (err := DockerError()) + backup_instance.restore_homeassistant.side_effect = (err := DockerError()) with pytest.raises(BackupError): await coresys.backups.do_restore_full(backup_instance) capture_exception.assert_called_once_with(err) @@ -1129,9 +1120,6 @@ async def test_backup_progress( _make_backup_message_for_assert( reference=full_backup.slug, stage="addon_repositories" ), - _make_backup_message_for_assert( - reference=full_backup.slug, stage="docker_config" - ), _make_backup_message_for_assert( reference=full_backup.slug, stage="home_assistant" ), @@ -1173,11 +1161,6 @@ async def test_backup_progress( reference=partial_backup.slug, stage="addon_repositories", ), - _make_backup_message_for_assert( - action="partial_backup", - reference=partial_backup.slug, - stage="docker_config", - ), _make_backup_message_for_assert( action="partial_backup", reference=partial_backup.slug, stage="addons" ), @@ -1244,9 +1227,6 @@ async def test_restore_progress( _make_backup_message_for_assert( action="full_restore", reference=full_backup.slug, stage=None ), - _make_backup_message_for_assert( - action="full_restore", reference=full_backup.slug, stage="docker_config" - ), _make_backup_message_for_assert( action="full_restore", reference=full_backup.slug, stage="folders" ), @@ -1311,11 +1291,6 @@ async def test_restore_progress( reference=folders_backup.slug, stage=None, ), - _make_backup_message_for_assert( - action="partial_restore", - reference=folders_backup.slug, - stage="docker_config", - ), _make_backup_message_for_assert( action="partial_restore", reference=folders_backup.slug, @@ -1357,11 +1332,6 @@ async def test_restore_progress( reference=addon_backup.slug, stage=None, ), - _make_backup_message_for_assert( - action="partial_restore", - reference=addon_backup.slug, - stage="docker_config", - ), _make_backup_message_for_assert( action="partial_restore", reference=addon_backup.slug, From 75575f3b858203f03b0575600738c25f7889f260 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Wed, 5 Feb 2025 13:54:31 +0000 Subject: [PATCH 2/3] Drop obsolete encrypt/decrypt functions --- supervisor/backups/backup.py | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/supervisor/backups/backup.py b/supervisor/backups/backup.py index 28b4a5c975d..2e379df6e43 100644 --- a/supervisor/backups/backup.py +++ b/supervisor/backups/backup.py @@ -1,7 +1,6 @@ """Representation of a backup file.""" import asyncio -from base64 import b64decode, b64encode from collections import defaultdict from collections.abc import AsyncGenerator, Awaitable from contextlib import asynccontextmanager @@ -20,7 +19,6 @@ from awesomeversion import AwesomeVersion, AwesomeVersionCompareException from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives import padding from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from securetar import SecureTarFile, atomic_contents_add, secure_path import voluptuous as vol @@ -368,28 +366,6 @@ def _init_password(self, password: str) -> None: backend=default_backend(), ) - def _encrypt_data(self, data: str) -> str: - """Make data secure.""" - if not self._key or data is None: - return data - - encrypt = self._aes.encryptor() - padder = padding.PKCS7(128).padder() - - data = padder.update(data.encode()) + padder.finalize() - return b64encode(encrypt.update(data)).decode() - - def _decrypt_data(self, data: str) -> str: - """Make data readable.""" - if not self._key or data is None: - return data - - decrypt = self._aes.decryptor() - padder = padding.PKCS7(128).unpadder() - - data = padder.update(decrypt.update(b64decode(data))) + padder.finalize() - return data.decode() - async def validate_password(self, location: str | None) -> bool: """Validate backup password. From d3aa46580dd42454eb285264dfe7c4a7f1f4d627 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Thu, 6 Feb 2025 10:56:19 +0100 Subject: [PATCH 3/3] Drop unused Backup Job stage --- supervisor/backups/const.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/supervisor/backups/const.py b/supervisor/backups/const.py index ea40c1f0318..b0cd396a114 100644 --- a/supervisor/backups/const.py +++ b/supervisor/backups/const.py @@ -24,7 +24,6 @@ class BackupJobStage(StrEnum): ADDON_REPOSITORIES = "addon_repositories" ADDONS = "addons" - DOCKER_CONFIG = "docker_config" FINISHING_FILE = "finishing_file" FOLDERS = "folders" HOME_ASSISTANT = "home_assistant" @@ -39,7 +38,6 @@ class RestoreJobStage(StrEnum): ADDONS = "addons" AWAIT_ADDON_RESTARTS = "await_addon_restarts" AWAIT_HOME_ASSISTANT_RESTART = "await_home_assistant_restart" - DOCKER_CONFIG = "docker_config" FOLDERS = "folders" HOME_ASSISTANT = "home_assistant" REMOVE_DELTA_ADDONS = "remove_delta_addons"