From 8203d5c6b3a88e87485550848f99425fc84a19e6 Mon Sep 17 00:00:00 2001 From: deajan Date: Mon, 6 May 2024 18:44:46 +0200 Subject: [PATCH] Implement alternate AES key loading as in #56 --- npbackup/__main__.py | 6 ++++-- npbackup/configuration.py | 16 ++++++++++------ npbackup/gui/__main__.py | 9 ++++++--- npbackup/gui/config.py | 4 +++- npbackup/key_management.py | 35 +++++++++++++++++++++++++++++++++++ 5 files changed, 58 insertions(+), 12 deletions(-) create mode 100644 npbackup/key_management.py diff --git a/npbackup/__main__.py b/npbackup/__main__.py index f0220d5..a3470a5 100644 --- a/npbackup/__main__.py +++ b/npbackup/__main__.py @@ -26,7 +26,7 @@ from npbackup.__debug__ import _DEBUG from npbackup.common import execution_logs from npbackup.core import upgrade_runner from npbackup.core.i18n_helper import _t - +from npbackup import key_management if os.name == "nt": from npbackup.windows.task import create_scheduled_task @@ -304,7 +304,9 @@ This is free software, and you are welcome to redistribute it under certain cond json_error_logging(False, msg, "critical") sys.exit(70) - full_config = npbackup.configuration.load_config(CONFIG_FILE) + aes_key = key_management.get_aes_key() + + full_config = npbackup.configuration.load_config(CONFIG_FILE, aes_key) if not full_config: msg = "Cannot obtain repo config" json_error_logging(False, msg, "critical") diff --git a/npbackup/configuration.py b/npbackup/configuration.py index 5965b1a..c291596 100644 --- a/npbackup/configuration.py +++ b/npbackup/configuration.py @@ -7,7 +7,7 @@ __intname__ = "npbackup.configuration" __author__ = "Orsiris de Jong" __copyright__ = "Copyright (C) 2022-2024 NetInvent" __license__ = "GPL-3.0-only" -__build__ = "2024042301" +__build__ = "2024050301" __version__ = "npbackup 3.0.0+" MIN_CONF_VERSION = 3.0 @@ -690,7 +690,9 @@ def _load_config_file(config_file: Path) -> Union[bool, dict]: return False -def load_config(config_file: Path) -> Optional[dict]: +def load_config(config_file: Path, aes_key: bytes = None) -> Optional[dict]: + if not aes_key: + aes_key = AES_KEY logger.info(f"Loading configuration file {config_file}") full_config = _load_config_file(config_file) @@ -726,7 +728,7 @@ def load_config(config_file: Path) -> Optional[dict]: config_file_is_updated = True # Decrypt variables full_config = crypt_config( - full_config, AES_KEY, ENCRYPTED_OPTIONS, operation="decrypt" + full_config, aes_key, ENCRYPTED_OPTIONS, operation="decrypt" ) if full_config == False: if EARLIER_AES_KEY: @@ -760,20 +762,22 @@ def load_config(config_file: Path) -> Optional[dict]: return full_config -def save_config(config_file: Path, full_config: dict) -> bool: +def save_config(config_file: Path, full_config: dict, aes_key: bytes = None) -> bool: + if not aes_key: + aes_key = AES_KEY try: with open(config_file, "w", encoding="utf-8") as file_handle: full_config = inject_permissions_into_full_config(full_config) if not is_encrypted(full_config): full_config = crypt_config( - full_config, AES_KEY, ENCRYPTED_OPTIONS, operation="encrypt" + full_config, aes_key, ENCRYPTED_OPTIONS, operation="encrypt" ) yaml = YAML(typ="rt") yaml.dump(full_config, file_handle) # Since yaml is a "pointer object", we need to decrypt after saving full_config = crypt_config( - full_config, AES_KEY, ENCRYPTED_OPTIONS, operation="decrypt" + full_config, aes_key, ENCRYPTED_OPTIONS, operation="decrypt" ) # We also need to extract permissions again full_config = extract_permissions_from_full_config(full_config) diff --git a/npbackup/gui/__main__.py b/npbackup/gui/__main__.py index 0714baa..34589c7 100644 --- a/npbackup/gui/__main__.py +++ b/npbackup/gui/__main__.py @@ -55,10 +55,13 @@ from npbackup.path_helper import CURRENT_DIR from npbackup.__version__ import version_string from npbackup.__debug__ import _DEBUG from npbackup.restic_wrapper import ResticRunner +from npbackup import key_management logger = getLogger() backend_binary = None +aes_key = key_management.get_aes_key() + sg.theme(PYSIMPLEGUI_THEME) sg.SetOptions(icon=OEM_ICON) @@ -451,7 +454,7 @@ def _main_gui(viewer_mode: bool): if not values["-config_file-"] or not config_file.exists(): sg.PopupError(_t("generic.file_does_not_exist")) continue - full_config = npbackup.configuration.load_config(config_file) + full_config = npbackup.configuration.load_config(config_file, aes_key) if not full_config: sg.PopupError(_t("generic.bad_file")) continue @@ -549,7 +552,7 @@ def _main_gui(viewer_mode: bool): Load config file until we got something """ if config_file: - full_config = npbackup.configuration.load_config(config_file) + full_config = npbackup.configuration.load_config(config_file, aes_key) if not config_file.exists(): config_file = None if not full_config: @@ -569,7 +572,7 @@ def _main_gui(viewer_mode: bool): ) if config_file: logger.info(f"Using configuration file {config_file}") - full_config = npbackup.configuration.load_config(config_file) + full_config = npbackup.configuration.load_config(config_file, aes_key) if not full_config: sg.PopupError(f"{_t('main_gui.config_error')} {config_file}") config_file = None diff --git a/npbackup/gui/config.py b/npbackup/gui/config.py index 10fe5a8..181693f 100644 --- a/npbackup/gui/config.py +++ b/npbackup/gui/config.py @@ -31,11 +31,13 @@ from npbackup.customization import ( TREE_ICON, INHERITED_TREE_ICON, ) +from npbackup import key_management if os.name == "nt": from npbackup.windows.task import create_scheduled_task logger = getLogger() +aes_key = key_management.get_aes_key() # Monkeypatching PySimpleGUI @@ -1922,7 +1924,7 @@ def config_gui(full_config: dict, config_file: str): full_config = update_config_dict( full_config, current_object_type, current_object_name, values ) - result = configuration.save_config(config_file, full_config) + result = configuration.save_config(config_file, full_config, aes_key) if result: sg.Popup(_t("config_gui.configuration_saved"), keep_on_top=True) logger.info("Configuration saved successfully.") diff --git a/npbackup/key_management.py b/npbackup/key_management.py new file mode 100644 index 0000000..2635bc4 --- /dev/null +++ b/npbackup/key_management.py @@ -0,0 +1,35 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +# +# This file is part of npbackup + +__intname__ = "npbackup.get_key" + + +import os +from command_runner import command_runner + + +def get_aes_key(): + """ + Get encryption key from environment variable or file + """ + key = None + + key_location = os.environ.get("NPBACKUP_KEY_LOCATION", None) + if key_location and os.path.isfile(key_location): + try: + with open(key_location, "rb") as key_file: + key = key_file.read() + except OSError as exc: + msg = f"Cannot read encryption key file: {exc}" + return False, msg + else: + key_command = os.environ.get("NPBACKUP_KEY_COMMAND", None) + if key_command: + exit_code, output = command_runner(key_command, shell=True) + if exit_code != 0: + msg = f"Cannot run encryption key command: {output}" + return False, msg + key = output + return key \ No newline at end of file