diff --git a/locales/en.json b/locales/en.json index 0266f4fdb..8e85f815a 100644 --- a/locales/en.json +++ b/locales/en.json @@ -397,6 +397,9 @@ "global_settings_setting_pop3_enabled_help": "Enable the POP3 protocol for the mail server", "global_settings_setting_postfix_compatibility": "Postfix Compatibility", "global_settings_setting_postfix_compatibility_help": "Compatibility vs. security tradeoff for the Postfix server. Affects the ciphers (and other security-related aspects)", + "global_settings_setting_root_access_explain": "On Linux systems, 'root' is the absolute admin. In YunoHost context, direct 'root' SSH login is by default disable - except from the local network of the server. Members of the 'admins' group can use the sudo command to act as root from the command line. However, it can be helpful to have a (robust) root password to debug the system if for some reason regular admins can not login anymore.", + "global_settings_setting_root_password": "New root password", + "global_settings_setting_root_password_confirm": "New root password (confirm)", "global_settings_setting_security_experimental_enabled": "Experimental security features", "global_settings_setting_security_experimental_enabled_help": "Enable experimental security features (don't enable this if you don't know what you're doing!)", "global_settings_setting_smtp_allow_ipv6": "Allow IPv6", @@ -582,6 +585,7 @@ "pattern_password_app": "Sorry, passwords can not contain the following characters: {forbidden_chars}", "pattern_port_or_range": "Must be a valid port number (i.e. 0-65535) or range of ports (e.g. 100:200)", "pattern_username": "Must be lower-case alphanumeric and underscore characters only", + "password_confirmation_not_the_same": "The password and its confirmation do not match", "permission_already_allowed": "Group '{group}' already has permission '{permission}' enabled", "permission_already_disallowed": "Group '{group}' already has permission '{permission}' disabled", "permission_already_exist": "Permission '{permission}' already exists", @@ -637,6 +641,7 @@ "restore_running_hooks": "Running restoration hooks...", "restore_system_part_failed": "Could not restore the '{part}' system part", "root_password_desynchronized": "The admin password was changed, but YunoHost could not propagate this to the root password!", + "root_password_changed": "root's password was changed", "server_reboot": "The server will reboot", "server_reboot_confirm": "The server will reboot immediatly, are you sure? [{answers}]", "server_shutdown": "The server will shut down", diff --git a/share/config_global.toml b/share/config_global.toml index f64ef65a7..27f8d47dc 100644 --- a/share/config_global.toml +++ b/share/config_global.toml @@ -12,7 +12,7 @@ name = "Security" choices.2 = "ditto, but also require at least one digit, one lower and one upper char" choices.3 = "ditto, but also require at least one special char" choices.4 = "ditto, but also require at least 12 chars" - default = 1 + default = "1" [security.password.user_strength] type = "select" @@ -20,7 +20,7 @@ name = "Security" choices.2 = "ditto, but also require at least one digit, one lower and one upper char" choices.3 = "ditto, but also require at least one special char" choices.4 = "ditto, but also require at least 12 chars" - default = 1 + default = "1" [security.ssh] name = "SSH" @@ -70,6 +70,24 @@ name = "Security" optional = true default = "" + [security.root_access] + name = "Change root password" + + [security.root_access.root_access_explain] + type = "alert" + style = "info" + icon = "info" + + [security.root_access.root_password] + type = "password" + optional = true + default = "" + + [security.root_access.root_password_confirm] + type = "password" + optional = true + default = "" + [security.experimental] name = "Experimental" [security.experimental.security_experimental_enabled] diff --git a/src/settings.py b/src/settings.py index 17fe97bf5..2795d5562 100644 --- a/src/settings.py +++ b/src/settings.py @@ -108,6 +108,29 @@ class SettingsConfigPanel(ConfigPanel): super().__init__("settings") def _apply(self): + + root_password = self.new_values.pop("root_password") + root_password_confirm = self.new_values.pop("root_password_confirm") + + if "root_password" in self.values: + del self.values["root_password"] + if "root_password_confirm" in self.values: + del self.values["root_password_confirm"] + if "root_password" in self.new_values: + del self.new_values["root_password"] + if "root_password_confirm" in self.new_values: + del self.new_values["root_password_confirm"] + + assert "root_password" not in self.future_values + + if root_password and root_password.strip(): + + if root_password != root_password_confirm: + raise YunohostValidationError("password_confirmation_not_the_same") + + from yunohost.tools import tools_rootpw + tools_rootpw(root_password, check_strength=True) + super()._apply() settings = { @@ -122,7 +145,18 @@ class SettingsConfigPanel(ConfigPanel): logger.error(f"Post-change hook for setting failed : {e}") raise + def _load_current_values(self): + + super()._load_current_values() + + # Specific logic for those settings who are "virtual" settings + # and only meant to have a custom setter mapped to tools_rootpw + self.values["root_password"] = "" + self.values["root_password_confirm"] = "" + + def get(self, key="", mode="classic"): + result = super().get(key=key, mode=mode) if mode == "full": diff --git a/src/tools.py b/src/tools.py index 09574c36e..a06ce8637 100644 --- a/src/tools.py +++ b/src/tools.py @@ -94,6 +94,8 @@ def tools_rootpw(new_password, check_strength=True): except (IOError, KeyError): logger.warning(m18n.n("root_password_desynchronized")) return + else: + logger.info(m18n.n("root_password_changed")) def tools_maindomain(new_main_domain=None): diff --git a/src/utils/password.py b/src/utils/password.py index f55acf5c0..4b81f5a54 100644 --- a/src/utils/password.py +++ b/src/utils/password.py @@ -85,7 +85,7 @@ class PasswordValidator: # from settings.py because this file is also meant to be # use as a script by ssowat. # (or at least that's my understanding -- Alex) - settings = yaml.load(open("/etc/yunohost/settings.yml", "r")) + settings = yaml.safe_load(open("/etc/yunohost/settings.yml", "r")) setting_key = profile + "_strength" self.validation_strength = int(settings[setting_key]) except Exception: