From ea26182ad15e94cb32a4fc068a691aa347d8f9cc Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 3 Jun 2020 20:17:49 +0200 Subject: [PATCH] Fix regen-conf mechanism for SSH --- locales/en.json | 1 + src/yunohost/regenconf.py | 22 +++++++++++++-- src/yunohost/tests/test_regenconf.py | 40 +++++++++++++++++++++++++++- src/yunohost/tools.py | 5 +--- 4 files changed, 61 insertions(+), 7 deletions(-) diff --git a/locales/en.json b/locales/en.json index 05f097345..428cbd2ac 100644 --- a/locales/en.json +++ b/locales/en.json @@ -535,6 +535,7 @@ "regenconf_dry_pending_applying": "Checking pending configuration which would have been applied for category '{category}'…", "regenconf_failed": "Could not regenerate the configuration for category(s): {categories}", "regenconf_pending_applying": "Applying pending configuration for category '{category}'…", + "regenconf_need_to_explicitly_specify_ssh": "The ssh configuration has been manually modified, but you need to explicitly specify category 'ssh' with --force to actually apply the changes.", "restore_already_installed_app": "An app with the ID '{app:s}' is already installed", "restore_already_installed_apps": "The following apps can't be restored because they are already installed: {apps}", "restore_app_failed": "Could not restore the app '{app:s}'", diff --git a/src/yunohost/regenconf.py b/src/yunohost/regenconf.py index 53deb7073..5bfc5df77 100644 --- a/src/yunohost/regenconf.py +++ b/src/yunohost/regenconf.py @@ -125,11 +125,12 @@ def regen_conf(operation_logger, names=[], with_diff=False, force=False, dry_run # return the arguments to pass to the script return pre_args + [category_pending_path, ] - # Don't regen SSH if not specifically specified + ssh_explicitly_specified = isinstance(names, list) and "ssh" in names + + # By default, we regen everything if not names: names = hook_list('conf_regen', list_by='name', show_info=False)['hooks'] - names.remove('ssh') # Dirty hack for legacy code : avoid attempting to regen the conf for # glances because it got removed ... This is only needed *once* @@ -185,6 +186,20 @@ def regen_conf(operation_logger, names=[], with_diff=False, force=False, dry_run succeed_regen = {} failed_regen = {} + # Here we are doing some weird legacy shit + # The thing is, on some very old or specific setup, the sshd_config file + # was absolutely not managed by the regenconf ... + # But we now want to make sure that this file is managed. + # However, we don't want to overwrite a specific custom sshd_config + # which may make the admin unhappy ... + # So : if the hash for this file does not exists, we set the hash as the + # hash of the pending configuration ... + # That way, the file will later appear as manually modified. + sshd_config = "/etc/ssh/sshd_config" + if category == "ssh" and sshd_config not in conf_hashes and sshd_config in conf_files: + conf_hashes[sshd_config] = _calculate_hash(conf_files[sshd_config]) + _update_conf_hashes(category, conf_hashes) + for system_path, pending_path in conf_files.items(): logger.debug("processing pending conf '%s' to system conf '%s'", pending_path, system_path) @@ -267,6 +282,9 @@ def regen_conf(operation_logger, names=[], with_diff=False, force=False, dry_run logger.debug("> new conf is as current system conf") conf_status = 'managed' regenerated = True + elif force and system_path == sshd_config and not ssh_explicitly_specified: + logger.warning(m18n.n('regenconf_need_to_explicitly_specify_ssh')) + conf_status = 'modified' elif force: regenerated = _regen(system_path, pending_path) conf_status = 'force-updated' diff --git a/src/yunohost/tests/test_regenconf.py b/src/yunohost/tests/test_regenconf.py index 357f96c88..f618278f3 100644 --- a/src/yunohost/tests/test_regenconf.py +++ b/src/yunohost/tests/test_regenconf.py @@ -11,10 +11,11 @@ from moulinette.utils.filesystem import mkdir from yunohost.domain import _get_maindomain, domain_add, domain_remove, domain_list from yunohost.utils.error import YunohostError -from yunohost.regenconf import manually_modified_files, _get_conf_hashes, _force_clear_hashes +from yunohost.regenconf import manually_modified_files, regen_conf, _get_conf_hashes, _force_clear_hashes TEST_DOMAIN = "secondarydomain.test" TEST_DOMAIN_NGINX_CONFIG = "/etc/nginx/conf.d/secondarydomain.test.conf" +SSHD_CONFIG = "/etc/ssh/sshd_config" def setup_function(function): @@ -43,6 +44,8 @@ def clean(): assert TEST_DOMAIN_NGINX_CONFIG not in _get_conf_hashes("nginx") assert TEST_DOMAIN_NGINX_CONFIG not in manually_modified_files() + regen_conf(['ssh'], force=True) + def test_add_domain(): @@ -78,3 +81,38 @@ def test_add_domain_conf_already_exists(): assert os.path.exists(TEST_DOMAIN_NGINX_CONFIG) assert TEST_DOMAIN_NGINX_CONFIG in _get_conf_hashes("nginx") assert TEST_DOMAIN_NGINX_CONFIG not in manually_modified_files() + + +def test_ssh_conf_unmanaged(): + + _force_clear_hashes([SSHD_CONFIG]) + + assert SSHD_CONFIG not in _get_conf_hashes("ssh") + + regen_conf() + + assert SSHD_CONFIG in _get_conf_hashes("ssh") + + +def test_ssh_conf_unmanaged_and_manually_modified(mocker): + + _force_clear_hashes([SSHD_CONFIG]) + os.system("echo ' ' >> %s" % SSHD_CONFIG) + + assert SSHD_CONFIG not in _get_conf_hashes("ssh") + + regen_conf() + + assert SSHD_CONFIG in _get_conf_hashes("ssh") + assert SSHD_CONFIG in manually_modified_files() + + with message(mocker, "regenconf_need_to_explicitly_specify_ssh"): + regen_conf(force=True) + + assert SSHD_CONFIG in _get_conf_hashes("ssh") + assert SSHD_CONFIG in manually_modified_files() + + regen_conf(['ssh'], force=True) + + assert SSHD_CONFIG in _get_conf_hashes("ssh") + assert SSHD_CONFIG not in manually_modified_files() diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 3b683e967..9221e6c7d 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -369,6 +369,7 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False, service_start("yunohost-firewall") regen_conf(force=True) + regen_conf(names=["ssh"], force=True) # Restore original ssh conf, as chosen by the # admin during the initial install @@ -382,10 +383,6 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False, original_sshd_conf = '/etc/ssh/sshd_config.before_yunohost' if os.path.exists(original_sshd_conf): os.rename(original_sshd_conf, '/etc/ssh/sshd_config') - else: - # We need to explicitly ask the regen conf to regen ssh - # (by default, i.e. first argument = None, it won't because it's too touchy) - regen_conf(names=["ssh"], force=True) logger.success(m18n.n('yunohost_configured'))