From d254937590deb8097d7a220d80eac452651a8379 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Thu, 6 Feb 2025 11:15:56 +0100 Subject: [PATCH] Drop Docker config from Supervisor backup (#5605) * 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. * Drop obsolete encrypt/decrypt functions * Drop unused Backup Job stage --- supervisor/backups/backup.py | 56 ----------------------------------- supervisor/backups/const.py | 2 -- supervisor/backups/manager.py | 6 ---- tests/backups/test_manager.py | 34 ++------------------- 4 files changed, 2 insertions(+), 96 deletions(-) diff --git a/supervisor/backups/backup.py b/supervisor/backups/backup.py index fdf35dfdf..2e379df6e 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 typing import Any, Self 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 @@ -38,16 +36,13 @@ from ..const import ( 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, ) @@ -371,28 +366,6 @@ class Backup(JobGroup): 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. @@ -899,32 +872,3 @@ class Backup(JobGroup): 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/const.py b/supervisor/backups/const.py index ea40c1f03..b0cd396a1 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" diff --git a/supervisor/backups/manager.py b/supervisor/backups/manager.py index af236695c..87e7b2de5 100644 --- a/supervisor/backups/manager.py +++ b/supervisor/backups/manager.py @@ -215,8 +215,6 @@ class BackupManager(FileConfiguration, JobGroup): 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 @@ class BackupManager(FileConfiguration, JobGroup): 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 09eb9be07..9be702d7f 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,