diff --git a/actions/ssh b/actions/ssh index 8c3c0c8eb..1c3035304 100755 --- a/actions/ssh +++ b/actions/ssh @@ -15,7 +15,6 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # - """ Configuration helper for SSH server. """ @@ -28,6 +27,8 @@ import stat import subprocess import sys +import augeas + from plinth import action_utils @@ -47,6 +48,13 @@ def parse_arguments(): set_keys.add_argument('--username') set_keys.add_argument('--keys') + subparsers.add_parser('get-password-config', + help='Get SSH password auth configuration') + + set_password_config = subparsers.add_parser( + 'set-password-config', help='Set SSH password auth configuration') + set_password_config.add_argument('--value') + subparsers.required = True return parser.parse_args() @@ -107,6 +115,33 @@ def subcommand_set_keys(arguments): os.chmod(key_file_path, stat.S_IRUSR | stat.S_IWUSR) +def _load_augeas(): + """Initialize augeas for this app's configuration file.""" + aug = augeas.Augeas( + flags=augeas.Augeas.NO_LOAD + augeas.Augeas.NO_MODL_AUTOLOAD) + aug.set('/augeas/load/Sshd/lens', 'Sshd.lns') + aug.set('/augeas/load/Sshd/incl[last() + 1]', '/etc/ssh/sshd_config') + aug.load() + + return aug + + +def subcommand_get_password_config(_): + """Retrieve value of password authentication from sshd configuration.""" + aug = _load_augeas() + field_path = '/files/etc/ssh/sshd_config/PasswordAuthentication' + get_value = aug.get(field_path) + print(get_value or 'yes') + + +def subcommand_set_password_config(arguments): + """Set value of password authentication in sshd configuration.""" + aug = _load_augeas() + aug.set('/files/etc/ssh/sshd_config/PasswordAuthentication', + arguments.value) + aug.save() + + def main(): """Parse arguments and perform all duties.""" arguments = parse_arguments() diff --git a/plinth/modules/ssh/__init__.py b/plinth/modules/ssh/__init__.py index 2169ab7a2..5a079d64b 100644 --- a/plinth/modules/ssh/__init__.py +++ b/plinth/modules/ssh/__init__.py @@ -105,3 +105,9 @@ def get_host_keys(): host_keys.append(match.groupdict()) return host_keys + + +def is_password_authentication_disabled(): + """Return if ssh password authentication is enabled.""" + return actions.superuser_run('ssh', + ['get-password-config']).strip() == 'no' diff --git a/plinth/modules/ssh/forms.py b/plinth/modules/ssh/forms.py new file mode 100644 index 000000000..439e55890 --- /dev/null +++ b/plinth/modules/ssh/forms.py @@ -0,0 +1,35 @@ +# +# This file is part of FreedomBox. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +""" +FreedomBox configuration form for OpenSSH server. +""" + +from django import forms +from django.utils.translation import ugettext_lazy as _ + +from plinth.forms import AppForm + + +class SSHServerForm(AppForm): + """SSH server configuration form.""" + password_auth_disabled = forms.BooleanField( + label=_('Disable password authentication'), + help_text=_('Improves security by preventing password guessing. ' + 'Ensure that you have setup SSH keys in your ' + 'administrator user account before enabling this option.'), + required=False, + ) diff --git a/plinth/modules/ssh/views.py b/plinth/modules/ssh/views.py index 83ce5b5f0..47d381e38 100644 --- a/plinth/modules/ssh/views.py +++ b/plinth/modules/ssh/views.py @@ -17,10 +17,16 @@ """ Views for the SSH module """ +from django.contrib import messages +from django.utils.translation import ugettext_lazy as _ +from plinth import action_utils, actions from plinth.modules import ssh from plinth.views import AppView +from . import is_password_authentication_disabled +from .forms import SSHServerForm + class SshAppView(AppView): app_id = 'ssh' @@ -28,9 +34,43 @@ class SshAppView(AppView): description = ssh.description port_forwarding_info = ssh.port_forwarding_info template_name = 'ssh.html' + form_class = SSHServerForm def get_context_data(self, *args, **kwargs): context = super().get_context_data(**kwargs) context['host_keys'] = ssh.get_host_keys() return context + + def get_initial(self): + """Initial form value""" + initial = super().get_initial() + initial.update({ + 'password_auth_disabled': is_password_authentication_disabled(), + }) + + return initial + + def form_valid(self, form): + """Apply changes from the form""" + old_config = self.get_initial() + new_config = form.cleaned_data + + def is_field_changed(field): + return old_config[field] != new_config[field] + + passwd_auth_changed = is_field_changed('password_auth_disabled') + if passwd_auth_changed: + if new_config['password_auth_disabled']: + passwd_auth = 'no' + message = _('SSH authentication with password disabled.') + else: + passwd_auth = 'yes' + message = _('SSH authentication with password enabled.') + + actions.superuser_run( + 'ssh', ['set-password-config', '--value', passwd_auth]) + actions.superuser_run('service', ['reload', 'ssh']) + messages.success(self.request, message) + + return super().form_valid(form)