Skip to content

Commit

Permalink
Drop Docker config from Supervisor backup (#5605)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
agners authored Feb 6, 2025
1 parent 9a8e52d commit d254937
Show file tree
Hide file tree
Showing 4 changed files with 2 additions and 96 deletions.
56 changes: 0 additions & 56 deletions supervisor/backups/backup.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand All @@ -38,16 +36,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,
)
Expand Down Expand Up @@ -371,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.
Expand Down Expand Up @@ -899,32 +872,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()
2 changes: 0 additions & 2 deletions supervisor/backups/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
6 changes: 0 additions & 6 deletions supervisor/backups/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)
Expand Down
34 changes: 2 additions & 32 deletions tests/backups/test_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand All @@ -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()

Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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"
),
Expand Down Expand Up @@ -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"
),
Expand Down Expand Up @@ -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"
),
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down

0 comments on commit d254937

Please sign in to comment.