mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-08 09:46:29 +00:00
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
This commit is contained in:
parent
9a8e52d1fc
commit
d254937590
@ -1,7 +1,6 @@
|
|||||||
"""Representation of a backup file."""
|
"""Representation of a backup file."""
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from base64 import b64decode, b64encode
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from collections.abc import AsyncGenerator, Awaitable
|
from collections.abc import AsyncGenerator, Awaitable
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
@ -20,7 +19,6 @@ from typing import Any, Self
|
|||||||
|
|
||||||
from awesomeversion import AwesomeVersion, AwesomeVersionCompareException
|
from awesomeversion import AwesomeVersion, AwesomeVersionCompareException
|
||||||
from cryptography.hazmat.backends import default_backend
|
from cryptography.hazmat.backends import default_backend
|
||||||
from cryptography.hazmat.primitives import padding
|
|
||||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||||
from securetar import SecureTarFile, atomic_contents_add, secure_path
|
from securetar import SecureTarFile, atomic_contents_add, secure_path
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
@ -38,16 +36,13 @@ from ..const import (
|
|||||||
ATTR_FOLDERS,
|
ATTR_FOLDERS,
|
||||||
ATTR_HOMEASSISTANT,
|
ATTR_HOMEASSISTANT,
|
||||||
ATTR_NAME,
|
ATTR_NAME,
|
||||||
ATTR_PASSWORD,
|
|
||||||
ATTR_PATH,
|
ATTR_PATH,
|
||||||
ATTR_PROTECTED,
|
ATTR_PROTECTED,
|
||||||
ATTR_REGISTRIES,
|
|
||||||
ATTR_REPOSITORIES,
|
ATTR_REPOSITORIES,
|
||||||
ATTR_SIZE,
|
ATTR_SIZE,
|
||||||
ATTR_SLUG,
|
ATTR_SLUG,
|
||||||
ATTR_SUPERVISOR_VERSION,
|
ATTR_SUPERVISOR_VERSION,
|
||||||
ATTR_TYPE,
|
ATTR_TYPE,
|
||||||
ATTR_USERNAME,
|
|
||||||
ATTR_VERSION,
|
ATTR_VERSION,
|
||||||
CRYPTO_AES128,
|
CRYPTO_AES128,
|
||||||
)
|
)
|
||||||
@ -371,28 +366,6 @@ class Backup(JobGroup):
|
|||||||
backend=default_backend(),
|
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:
|
async def validate_password(self, location: str | None) -> bool:
|
||||||
"""Validate backup password.
|
"""Validate backup password.
|
||||||
|
|
||||||
@ -899,32 +872,3 @@ class Backup(JobGroup):
|
|||||||
return self.sys_store.update_repositories(
|
return self.sys_store.update_repositories(
|
||||||
self.repositories, add_with_errors=True, replace=replace
|
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()
|
|
||||||
|
@ -24,7 +24,6 @@ class BackupJobStage(StrEnum):
|
|||||||
|
|
||||||
ADDON_REPOSITORIES = "addon_repositories"
|
ADDON_REPOSITORIES = "addon_repositories"
|
||||||
ADDONS = "addons"
|
ADDONS = "addons"
|
||||||
DOCKER_CONFIG = "docker_config"
|
|
||||||
FINISHING_FILE = "finishing_file"
|
FINISHING_FILE = "finishing_file"
|
||||||
FOLDERS = "folders"
|
FOLDERS = "folders"
|
||||||
HOME_ASSISTANT = "home_assistant"
|
HOME_ASSISTANT = "home_assistant"
|
||||||
@ -39,7 +38,6 @@ class RestoreJobStage(StrEnum):
|
|||||||
ADDONS = "addons"
|
ADDONS = "addons"
|
||||||
AWAIT_ADDON_RESTARTS = "await_addon_restarts"
|
AWAIT_ADDON_RESTARTS = "await_addon_restarts"
|
||||||
AWAIT_HOME_ASSISTANT_RESTART = "await_home_assistant_restart"
|
AWAIT_HOME_ASSISTANT_RESTART = "await_home_assistant_restart"
|
||||||
DOCKER_CONFIG = "docker_config"
|
|
||||||
FOLDERS = "folders"
|
FOLDERS = "folders"
|
||||||
HOME_ASSISTANT = "home_assistant"
|
HOME_ASSISTANT = "home_assistant"
|
||||||
REMOVE_DELTA_ADDONS = "remove_delta_addons"
|
REMOVE_DELTA_ADDONS = "remove_delta_addons"
|
||||||
|
@ -215,8 +215,6 @@ class BackupManager(FileConfiguration, JobGroup):
|
|||||||
|
|
||||||
self._change_stage(BackupJobStage.ADDON_REPOSITORIES, backup)
|
self._change_stage(BackupJobStage.ADDON_REPOSITORIES, backup)
|
||||||
backup.store_repositories()
|
backup.store_repositories()
|
||||||
self._change_stage(BackupJobStage.DOCKER_CONFIG, backup)
|
|
||||||
backup.store_dockerconfig()
|
|
||||||
|
|
||||||
return backup
|
return backup
|
||||||
|
|
||||||
@ -655,10 +653,6 @@ class BackupManager(FileConfiguration, JobGroup):
|
|||||||
try:
|
try:
|
||||||
task_hass: asyncio.Task | None = None
|
task_hass: asyncio.Task | None = None
|
||||||
async with backup.open(location):
|
async with backup.open(location):
|
||||||
# Restore docker config
|
|
||||||
self._change_stage(RestoreJobStage.DOCKER_CONFIG, backup)
|
|
||||||
backup.restore_dockerconfig(replace)
|
|
||||||
|
|
||||||
# Process folders
|
# Process folders
|
||||||
if folder_list:
|
if folder_list:
|
||||||
self._change_stage(RestoreJobStage.FOLDERS, backup)
|
self._change_stage(RestoreJobStage.FOLDERS, backup)
|
||||||
|
@ -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_homeassistant.assert_called_once()
|
||||||
backup_instance.store_repositories.assert_called_once()
|
backup_instance.store_repositories.assert_called_once()
|
||||||
backup_instance.store_dockerconfig.assert_called_once()
|
|
||||||
|
|
||||||
backup_instance.store_addons.assert_called_once()
|
backup_instance.store_addons.assert_called_once()
|
||||||
assert install_addon_ssh in backup_instance.store_addons.call_args[0][0]
|
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_homeassistant.assert_called_once()
|
||||||
backup_instance.store_repositories.assert_called_once()
|
backup_instance.store_repositories.assert_called_once()
|
||||||
backup_instance.store_dockerconfig.assert_called_once()
|
|
||||||
|
|
||||||
backup_instance.store_addons.assert_called_once()
|
backup_instance.store_addons.assert_called_once()
|
||||||
assert install_addon_ssh in backup_instance.store_addons.call_args[0][0]
|
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_homeassistant.assert_not_called()
|
||||||
backup_instance.store_repositories.assert_called_once()
|
backup_instance.store_repositories.assert_called_once()
|
||||||
backup_instance.store_dockerconfig.assert_called_once()
|
|
||||||
|
|
||||||
backup_instance.store_addons.assert_not_called()
|
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_homeassistant.assert_not_called()
|
||||||
backup_instance.store_repositories.assert_called_once()
|
backup_instance.store_repositories.assert_called_once()
|
||||||
backup_instance.store_dockerconfig.assert_called_once()
|
|
||||||
|
|
||||||
backup_instance.store_addons.assert_not_called()
|
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_homeassistant.assert_called_once()
|
||||||
backup_instance.store_repositories.assert_called_once()
|
backup_instance.store_repositories.assert_called_once()
|
||||||
backup_instance.store_dockerconfig.assert_called_once()
|
|
||||||
|
|
||||||
backup_instance.store_addons.assert_called_once()
|
backup_instance.store_addons.assert_called_once()
|
||||||
assert install_addon_ssh in backup_instance.store_addons.call_args[0][0]
|
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_homeassistant.assert_called_once()
|
||||||
backup_instance.restore_repositories.assert_called_once()
|
backup_instance.restore_repositories.assert_called_once()
|
||||||
backup_instance.restore_dockerconfig.assert_called_once()
|
|
||||||
|
|
||||||
backup_instance.restore_addons.assert_called_once()
|
backup_instance.restore_addons.assert_called_once()
|
||||||
install_addon_ssh.uninstall.assert_not_called()
|
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_homeassistant.assert_called_once()
|
||||||
backup_instance.restore_repositories.assert_called_once()
|
backup_instance.restore_repositories.assert_called_once()
|
||||||
backup_instance.restore_dockerconfig.assert_called_once()
|
|
||||||
|
|
||||||
backup_instance.restore_addons.assert_called_once()
|
backup_instance.restore_addons.assert_called_once()
|
||||||
install_addon_ssh.uninstall.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_homeassistant.assert_not_called()
|
||||||
backup_instance.restore_repositories.assert_not_called()
|
backup_instance.restore_repositories.assert_not_called()
|
||||||
backup_instance.restore_dockerconfig.assert_called_once()
|
|
||||||
|
|
||||||
backup_instance.restore_addons.assert_not_called()
|
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_homeassistant.assert_called_once()
|
||||||
backup_instance.restore_repositories.assert_called_once()
|
backup_instance.restore_repositories.assert_called_once()
|
||||||
backup_instance.restore_dockerconfig.assert_called_once()
|
|
||||||
|
|
||||||
backup_instance.restore_addons.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 = full_backup_mock.return_value
|
||||||
backup_instance.protected = False
|
backup_instance.protected = False
|
||||||
backup_instance.restore_dockerconfig.side_effect = BackupError()
|
backup_instance.restore_homeassistant.side_effect = BackupError()
|
||||||
with pytest.raises(BackupError):
|
with pytest.raises(BackupError):
|
||||||
await coresys.backups.do_restore_full(backup_instance)
|
await coresys.backups.do_restore_full(backup_instance)
|
||||||
capture_exception.assert_not_called()
|
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):
|
with pytest.raises(BackupError):
|
||||||
await coresys.backups.do_restore_full(backup_instance)
|
await coresys.backups.do_restore_full(backup_instance)
|
||||||
capture_exception.assert_called_once_with(err)
|
capture_exception.assert_called_once_with(err)
|
||||||
@ -1129,9 +1120,6 @@ async def test_backup_progress(
|
|||||||
_make_backup_message_for_assert(
|
_make_backup_message_for_assert(
|
||||||
reference=full_backup.slug, stage="addon_repositories"
|
reference=full_backup.slug, stage="addon_repositories"
|
||||||
),
|
),
|
||||||
_make_backup_message_for_assert(
|
|
||||||
reference=full_backup.slug, stage="docker_config"
|
|
||||||
),
|
|
||||||
_make_backup_message_for_assert(
|
_make_backup_message_for_assert(
|
||||||
reference=full_backup.slug, stage="home_assistant"
|
reference=full_backup.slug, stage="home_assistant"
|
||||||
),
|
),
|
||||||
@ -1173,11 +1161,6 @@ async def test_backup_progress(
|
|||||||
reference=partial_backup.slug,
|
reference=partial_backup.slug,
|
||||||
stage="addon_repositories",
|
stage="addon_repositories",
|
||||||
),
|
),
|
||||||
_make_backup_message_for_assert(
|
|
||||||
action="partial_backup",
|
|
||||||
reference=partial_backup.slug,
|
|
||||||
stage="docker_config",
|
|
||||||
),
|
|
||||||
_make_backup_message_for_assert(
|
_make_backup_message_for_assert(
|
||||||
action="partial_backup", reference=partial_backup.slug, stage="addons"
|
action="partial_backup", reference=partial_backup.slug, stage="addons"
|
||||||
),
|
),
|
||||||
@ -1244,9 +1227,6 @@ async def test_restore_progress(
|
|||||||
_make_backup_message_for_assert(
|
_make_backup_message_for_assert(
|
||||||
action="full_restore", reference=full_backup.slug, stage=None
|
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(
|
_make_backup_message_for_assert(
|
||||||
action="full_restore", reference=full_backup.slug, stage="folders"
|
action="full_restore", reference=full_backup.slug, stage="folders"
|
||||||
),
|
),
|
||||||
@ -1311,11 +1291,6 @@ async def test_restore_progress(
|
|||||||
reference=folders_backup.slug,
|
reference=folders_backup.slug,
|
||||||
stage=None,
|
stage=None,
|
||||||
),
|
),
|
||||||
_make_backup_message_for_assert(
|
|
||||||
action="partial_restore",
|
|
||||||
reference=folders_backup.slug,
|
|
||||||
stage="docker_config",
|
|
||||||
),
|
|
||||||
_make_backup_message_for_assert(
|
_make_backup_message_for_assert(
|
||||||
action="partial_restore",
|
action="partial_restore",
|
||||||
reference=folders_backup.slug,
|
reference=folders_backup.slug,
|
||||||
@ -1357,11 +1332,6 @@ async def test_restore_progress(
|
|||||||
reference=addon_backup.slug,
|
reference=addon_backup.slug,
|
||||||
stage=None,
|
stage=None,
|
||||||
),
|
),
|
||||||
_make_backup_message_for_assert(
|
|
||||||
action="partial_restore",
|
|
||||||
reference=addon_backup.slug,
|
|
||||||
stage="docker_config",
|
|
||||||
),
|
|
||||||
_make_backup_message_for_assert(
|
_make_backup_message_for_assert(
|
||||||
action="partial_restore",
|
action="partial_restore",
|
||||||
reference=addon_backup.slug,
|
reference=addon_backup.slug,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user