mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-01-28 08:03:36 +00:00
Verify SSH hostkey before mounting
Signed-off-by: Joseph Nuthalapati <njoseph@thoughtworks.com>
This commit is contained in:
parent
7684814a5c
commit
3a6dcbe7a7
@ -294,7 +294,7 @@ def get_env(arguments, use_credentials=False):
|
||||
password = read_password()
|
||||
if password:
|
||||
env['SSHPASS'] = password
|
||||
env['BORG_RSH'] = 'sshpass -e ssh -o StrictHostKeyChecking=no'
|
||||
env['BORG_RSH'] = 'sshpass -e ssh -o StrictHostKeyChecking=yes'
|
||||
else:
|
||||
raise ValueError('could not find credentials')
|
||||
|
||||
|
||||
@ -44,6 +44,9 @@ def parse_arguments():
|
||||
required=True)
|
||||
mount.add_argument('--ssh-keyfile', help='Path of private ssh key',
|
||||
default=None, required=False)
|
||||
mount.add_argument('--user-known-hosts-file',
|
||||
help='Path to a custom known_hosts file',
|
||||
default='/dev/null')
|
||||
umount = subparsers.add_parser('umount', help='unmount an ssh filesystem')
|
||||
umount.add_argument('--mountpoint', help='Mountpoint to unmount',
|
||||
required=True)
|
||||
@ -69,7 +72,8 @@ def subcommand_mount(arguments):
|
||||
remote_path = remote_path.replace('~/', '').replace('~', '')
|
||||
cmd = [
|
||||
'sshfs', remote_path, arguments.mountpoint, '-o',
|
||||
'UserKnownHostsFile=/dev/null', '-o', 'StrictHostKeyChecking=no'
|
||||
f'UserKnownHostsFile={arguments.user_known_hosts_file}', '-o',
|
||||
'StrictHostKeyChecking=yes'
|
||||
]
|
||||
if arguments.ssh_keyfile:
|
||||
cmd += ['-o', 'IdentityFile=' + arguments.ssh_keyfile]
|
||||
|
||||
@ -21,6 +21,7 @@ FreedomBox app to manage backup archives.
|
||||
import json
|
||||
import os
|
||||
|
||||
import paramiko
|
||||
from django.utils.text import get_valid_filename
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
@ -142,3 +143,16 @@ def restore_from_upload(path, apps=None):
|
||||
"""Restore files from an uploaded .tar.gz backup file"""
|
||||
api.restore_apps(_restore_exported_archive_handler, app_names=apps,
|
||||
create_subvolume=False, backup_file=path)
|
||||
|
||||
|
||||
def is_ssh_hostkey_verified(hostname):
|
||||
"""Check whether SSH Hostkey has already been verified.
|
||||
hostname: Domain name or IP address of the host
|
||||
"""
|
||||
known_hosts_path = os.path.join(cfg.data_dir, '.ssh', 'known_hosts')
|
||||
if not os.path.exists(known_hosts_path):
|
||||
return False
|
||||
|
||||
known_hosts = paramiko.hostkeys.HostKeys(known_hosts_path)
|
||||
host_keys = known_hosts.lookup(hostname)
|
||||
return host_keys is not None
|
||||
|
||||
@ -54,7 +54,8 @@ def _get_repository_choices():
|
||||
choices = [('root', ROOT_REPOSITORY_NAME)]
|
||||
storages = network_storage.get_storages()
|
||||
for storage in storages.values():
|
||||
choices += [(storage['uuid'], storage['path'])]
|
||||
if storage['verified']:
|
||||
choices += [(storage['uuid'], storage['path'])]
|
||||
return choices
|
||||
|
||||
|
||||
@ -119,18 +120,18 @@ class AddRepositoryForm(forms.Form):
|
||||
|
||||
def clean(self):
|
||||
super(AddRepositoryForm, self).clean()
|
||||
passphrase = self.cleaned_data.get("encryption_passphrase")
|
||||
passphrase = self.cleaned_data.get('encryption_passphrase')
|
||||
confirm_passphrase = self.cleaned_data.get(
|
||||
"confirm_encryption_passphrase")
|
||||
'confirm_encryption_passphrase')
|
||||
|
||||
if passphrase != confirm_passphrase:
|
||||
raise forms.ValidationError(
|
||||
_("The entered encryption passphrases do not match"))
|
||||
_('The entered encryption passphrases do not match'))
|
||||
|
||||
return self.cleaned_data
|
||||
|
||||
def clean_repository(self):
|
||||
path = self.cleaned_data.get("repository")
|
||||
path = self.cleaned_data.get('repository')
|
||||
# Avoid creation of duplicate ssh remotes
|
||||
self._check_if_duplicate_remote(path)
|
||||
return path
|
||||
|
||||
@ -26,11 +26,12 @@ from uuid import uuid1
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from plinth import actions
|
||||
from plinth import actions, cfg
|
||||
from plinth.errors import ActionError
|
||||
|
||||
from . import (ROOT_REPOSITORY, ROOT_REPOSITORY_NAME, ROOT_REPOSITORY_UUID,
|
||||
_backup_handler, api, network_storage, restore_archive_handler)
|
||||
_backup_handler, api, is_ssh_hostkey_verified, network_storage,
|
||||
restore_archive_handler)
|
||||
from .errors import BorgError, BorgRepositoryDoesNotExistError, SshfsError
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -236,7 +237,7 @@ class SshBorgRepository(BorgRepository):
|
||||
storage_type = 'ssh'
|
||||
uuid = None
|
||||
|
||||
def __init__(self, uuid=None, path=None, credentials=None, automount=True,
|
||||
def __init__(self, uuid=None, path=None, credentials=None, automount=False,
|
||||
**kwargs):
|
||||
"""
|
||||
Instanciate a new repository.
|
||||
@ -259,7 +260,8 @@ class SshBorgRepository(BorgRepository):
|
||||
self._load_from_kvstore()
|
||||
|
||||
if automount:
|
||||
self.mount()
|
||||
if is_ssh_hostkey_verified(path):
|
||||
self.mount()
|
||||
|
||||
@property
|
||||
def repo_path(self):
|
||||
@ -328,8 +330,10 @@ class SshBorgRepository(BorgRepository):
|
||||
def mount(self):
|
||||
if self.is_mounted:
|
||||
return
|
||||
known_hosts_path = os.path.join(cfg.data_dir, '.ssh', 'known_hosts')
|
||||
arguments = [
|
||||
'mount', '--mountpoint', self.mountpoint, '--path', self._path
|
||||
'mount', '--mountpoint', self.mountpoint, '--path', self._path,
|
||||
'--user-known-hosts-file', known_hosts_path
|
||||
]
|
||||
arguments, kwargs = self._append_sshfs_arguments(
|
||||
arguments, self.credentials)
|
||||
@ -382,7 +386,7 @@ def get_ssh_repositories():
|
||||
"""Get all SSH Repositories including the archive content"""
|
||||
repositories = {}
|
||||
for storage in network_storage.get_storages().values():
|
||||
repository = SshBorgRepository(automount=False, **storage)
|
||||
repository = SshBorgRepository(**storage)
|
||||
repositories[storage['uuid']] = repository.get_view_content()
|
||||
|
||||
return repositories
|
||||
|
||||
@ -26,10 +26,9 @@ import uuid
|
||||
|
||||
import pytest
|
||||
|
||||
from plinth import actions
|
||||
from plinth.modules import backups
|
||||
from plinth.modules.backups.repository import BorgRepository, SshBorgRepository
|
||||
from plinth import actions
|
||||
|
||||
from plinth.tests import config as test_config
|
||||
|
||||
pytestmark = pytest.mark.usefixtures('needs_root', 'needs_borg', 'load_cfg')
|
||||
@ -164,8 +163,7 @@ def test_sshfs_mount_password():
|
||||
credentials = _get_credentials()
|
||||
ssh_path = test_config.backups_ssh_path
|
||||
|
||||
repository = SshBorgRepository(path=ssh_path, credentials=credentials,
|
||||
automount=False)
|
||||
repository = SshBorgRepository(path=ssh_path, credentials=credentials)
|
||||
repository.mount()
|
||||
assert repository.is_mounted
|
||||
repository.umount()
|
||||
@ -178,8 +176,7 @@ def test_sshfs_mount_keyfile():
|
||||
credentials = _get_credentials()
|
||||
ssh_path = test_config.backups_ssh_path
|
||||
|
||||
repository = SshBorgRepository(path=ssh_path, credentials=credentials,
|
||||
automount=False)
|
||||
repository = SshBorgRepository(path=ssh_path, credentials=credentials)
|
||||
repository.mount()
|
||||
assert repository.is_mounted
|
||||
repository.umount()
|
||||
@ -189,8 +186,8 @@ def test_sshfs_mount_keyfile():
|
||||
def test_access_nonexisting_url():
|
||||
"""Test accessing a non-existent URL."""
|
||||
repo_url = "user@%s.com.au:~/repo" % str(uuid.uuid1())
|
||||
repository = SshBorgRepository(
|
||||
path=repo_url, credentials=_dummy_credentials, automount=False)
|
||||
repository = SshBorgRepository(path=repo_url,
|
||||
credentials=_dummy_credentials)
|
||||
with pytest.raises(backups.errors.BorgRepositoryDoesNotExistError):
|
||||
repository.get_info()
|
||||
|
||||
@ -198,8 +195,8 @@ def test_access_nonexisting_url():
|
||||
def test_inaccessible_repo_url():
|
||||
"""Test accessing an existing URL with wrong credentials."""
|
||||
repo_url = 'user@heise.de:~/repo'
|
||||
repository = SshBorgRepository(
|
||||
path=repo_url, credentials=_dummy_credentials, automount=False)
|
||||
repository = SshBorgRepository(path=repo_url,
|
||||
credentials=_dummy_credentials)
|
||||
with pytest.raises(backups.errors.BorgError):
|
||||
repository.get_info()
|
||||
|
||||
|
||||
@ -18,12 +18,12 @@
|
||||
Views for the backups app.
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import tempfile
|
||||
from contextlib import contextmanager
|
||||
from datetime import datetime
|
||||
from urllib.parse import unquote
|
||||
|
||||
@ -32,20 +32,19 @@ from django.contrib import messages
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.forms import ValidationError
|
||||
from django.http import Http404, StreamingHttpResponse
|
||||
from django.shortcuts import redirect, render
|
||||
from django.shortcuts import redirect
|
||||
from django.urls import reverse, reverse_lazy
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import ugettext_lazy
|
||||
from django.views.generic import FormView, TemplateView, View
|
||||
from paramiko.hostkeys import HostKeys
|
||||
|
||||
from plinth import cfg
|
||||
from plinth.errors import PlinthError
|
||||
from plinth.modules import backups, storage
|
||||
|
||||
from . import (ROOT_REPOSITORY, SESSION_PATH_VARIABLE, api, forms,
|
||||
network_storage)
|
||||
is_ssh_hostkey_verified, network_storage)
|
||||
from .decorators import delete_tmp_backup_file
|
||||
from .errors import BorgRepositoryDoesNotExistError
|
||||
from .repository import (BorgRepository, SshBorgRepository, get_repository,
|
||||
@ -196,6 +195,10 @@ class BaseRestoreView(SuccessMessageMixin, FormView):
|
||||
context['uuid'] = self.kwargs.get('uuid', None)
|
||||
return context
|
||||
|
||||
def _get_included_apps(self):
|
||||
"""To be overridden."""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class RestoreFromUploadView(BaseRestoreView):
|
||||
"""View to restore files from an (uploaded) exported archive."""
|
||||
@ -260,12 +263,10 @@ class DownloadArchiveView(View):
|
||||
|
||||
|
||||
class AddRepositoryView(SuccessMessageMixin, FormView):
|
||||
"""View to verify the SSH Hostkey of the server and save the
|
||||
new SSH repository."""
|
||||
"""View to verify the SSH Hostkey of the server and save repository."""
|
||||
form_class = forms.AddRepositoryForm
|
||||
template_name = 'backups_repository_add.html'
|
||||
success_url = reverse_lazy('backups:index')
|
||||
success_message = _('Added new remote ssh repository.')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
"""Return additional context for rendering the template."""
|
||||
@ -274,51 +275,20 @@ class AddRepositoryView(SuccessMessageMixin, FormView):
|
||||
context['subsubmenu'] = subsubmenu
|
||||
return context
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
form = self.form_class(data=request.POST)
|
||||
repository = None
|
||||
if form.is_valid():
|
||||
path = form.cleaned_data.get("repository")
|
||||
_, hostname, _ = re.split('[@:]', path)
|
||||
credentials = _get_credentials(form.cleaned_data)
|
||||
if not self._is_ssh_hostkey_verified(hostname):
|
||||
# Cannot mount at this point because we cannot connect
|
||||
# and validate the directory.
|
||||
repository = SshBorgRepository(
|
||||
path=path, credentials=credentials, automount=False)
|
||||
# Save for now, verify in the next view
|
||||
repository.save(verified=False)
|
||||
uuid = repository.uuid
|
||||
url = reverse('backups:verify-ssh-hostkey', args=[uuid])
|
||||
return redirect(url)
|
||||
else:
|
||||
try:
|
||||
repository = _validate_remote_repository(path, credentials)
|
||||
except ValidationError as err:
|
||||
messages.error(request, err.message)
|
||||
context_data = self.get_context_data()
|
||||
context_data['form'] = form
|
||||
return render(request, self.template_name, context_data)
|
||||
def form_valid(self, form):
|
||||
"""Create and save Borg repository.
|
||||
|
||||
_create_borg_repository(repository,
|
||||
form.cleaned_data['encryption'])
|
||||
return redirect(self.success_url)
|
||||
else:
|
||||
context_data = self.get_context_data()
|
||||
context_data['form'] = form
|
||||
return render(request, self.template_name, context_data)
|
||||
|
||||
def _is_ssh_hostkey_verified(self, hostname):
|
||||
"""Check whether SSH Hostkey has already been verified.
|
||||
hostname: Domain name or IP address of the host
|
||||
Present the Host key verification form if necessary.
|
||||
"""
|
||||
KNOWN_HOSTS = os.path.join(cfg.data_dir, '.ssh', 'known_hosts')
|
||||
if os.path.exists(KNOWN_HOSTS):
|
||||
known_hosts = HostKeys(KNOWN_HOSTS)
|
||||
host_keys = known_hosts.lookup(hostname)
|
||||
return host_keys is not None
|
||||
else:
|
||||
return False
|
||||
super().form_valid(form)
|
||||
path = form.cleaned_data.get('repository')
|
||||
_, hostname, _ = re.split('[@:]', path)
|
||||
credentials = _get_credentials(form.cleaned_data)
|
||||
repository = SshBorgRepository(path=path, credentials=credentials)
|
||||
repository.save(verified=False)
|
||||
|
||||
url = reverse('backups:verify-ssh-hostkey', args=[repository.uuid])
|
||||
return redirect(url)
|
||||
|
||||
|
||||
class VerifySshHostkeyView(SuccessMessageMixin, FormView):
|
||||
@ -326,7 +296,6 @@ class VerifySshHostkeyView(SuccessMessageMixin, FormView):
|
||||
form_class = forms.VerifySshHostkeyForm
|
||||
template_name = 'verify_ssh_hostkey.html'
|
||||
success_url = reverse_lazy('backups:index')
|
||||
success_message = _('Added new remote ssh repository.')
|
||||
repo_data = {}
|
||||
|
||||
def get_form_kwargs(self):
|
||||
@ -355,28 +324,45 @@ class VerifySshHostkeyView(SuccessMessageMixin, FormView):
|
||||
_, hostname, _ = re.split('[@:]', self._get_repo_data()['path'])
|
||||
return hostname
|
||||
|
||||
def _add_ssh_hostkey(self, hostname, key_type):
|
||||
@staticmethod
|
||||
def _add_ssh_hostkey(hostname, key_type):
|
||||
"""Add the given SSH key to known_hosts."""
|
||||
KNOWN_HOSTS = os.path.join(cfg.data_dir, '.ssh', 'known_hosts')
|
||||
if not os.path.exists(KNOWN_HOSTS):
|
||||
os.makedirs(KNOWN_HOSTS.rsplit('/', maxsplit=1)[0])
|
||||
open(KNOWN_HOSTS, 'w').close()
|
||||
with open(KNOWN_HOSTS, 'a') as known_hosts_file:
|
||||
known_hosts_path = os.path.join(cfg.data_dir, '.ssh', 'known_hosts')
|
||||
if not os.path.exists(known_hosts_path):
|
||||
os.makedirs(known_hosts_path.rsplit('/', maxsplit=1)[0])
|
||||
open(known_hosts_path, 'w').close()
|
||||
|
||||
with open(known_hosts_path, 'a') as known_hosts_file:
|
||||
key_line = subprocess.run(
|
||||
['ssh-keyscan', '-t', key_type, hostname],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.DEVNULL,
|
||||
check=True).stdout.decode().strip()
|
||||
known_hosts_file.write('\n')
|
||||
known_hosts_file.write(key_line)
|
||||
known_hosts_file.write('\n')
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
if is_ssh_hostkey_verified(self._get_hostname()):
|
||||
self._add_remote_repository()
|
||||
messages.success(self.request,
|
||||
_('Added new remote ssh repository.'))
|
||||
return redirect(reverse_lazy('backups:index'))
|
||||
else:
|
||||
return super().get(*args, **kwargs)
|
||||
|
||||
def form_valid(self, form):
|
||||
"""Create and store the repository."""
|
||||
key_type = form.cleaned_data['ssh_public_key']
|
||||
self._add_ssh_hostkey(self._get_hostname(), key_type)
|
||||
self._add_remote_repository()
|
||||
messages.success(self.request, _('Added new remote ssh repository.'))
|
||||
return super().form_valid(form)
|
||||
|
||||
def _add_remote_repository(self):
|
||||
repo_data = self._get_repo_data()
|
||||
path = repo_data.get("path")
|
||||
path = repo_data['path']
|
||||
credentials = repo_data['credentials']
|
||||
uuid = self.kwargs['uuid']
|
||||
|
||||
try:
|
||||
repository = _validate_remote_repository(path, credentials,
|
||||
uuid=uuid)
|
||||
@ -386,12 +372,14 @@ class VerifySshHostkeyView(SuccessMessageMixin, FormView):
|
||||
# so that the user can have another go at creating it.
|
||||
network_storage.delete(uuid)
|
||||
return redirect(reverse_lazy('backups:repository-add'))
|
||||
|
||||
_create_borg_repository(repository, repo_data.get(
|
||||
'encryption', 'none'))
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
def _create_borg_repository(repository, encryption='none'):
|
||||
if not repository.is_mounted:
|
||||
repository.mount()
|
||||
try:
|
||||
repository.get_info()
|
||||
except BorgRepositoryDoesNotExistError:
|
||||
@ -410,40 +398,32 @@ def _get_credentials(data):
|
||||
|
||||
|
||||
def _validate_remote_repository(path, credentials, uuid=None):
|
||||
"""
|
||||
Validation of SSH remote
|
||||
"""Validation of SSH remote
|
||||
|
||||
* Create empty directory if not exists
|
||||
* Check if the directory is empty
|
||||
- if not empty, check if it's an existing backup repository
|
||||
- else throw an error
|
||||
"""
|
||||
KNOWN_HOSTS = os.path.join(cfg.data_dir, '.ssh', 'known_hosts')
|
||||
username, hostname, dir_path = re.split('[@:]', path)
|
||||
dir_path = dir_path.replace('~', f'/home/{username}')
|
||||
dir_path = dir_path.replace('~', '.')
|
||||
password = credentials['ssh_password']
|
||||
ssh_client = paramiko.SSHClient()
|
||||
ssh_client.load_host_keys(KNOWN_HOSTS)
|
||||
repository = None
|
||||
try:
|
||||
ssh_client.connect(hostname, username=username, password=password)
|
||||
except Exception as err:
|
||||
msg = _(f'Accessing the remote repository failed. Details: {err}')
|
||||
raise ValidationError(msg, params={'err': str(err)})
|
||||
else:
|
||||
sftp_client = ssh_client.open_sftp()
|
||||
try:
|
||||
dir_contents = sftp_client.listdir(dir_path)
|
||||
except FileNotFoundError:
|
||||
logger.info(_(f"Directory {dir_path} doesn't exist. Creating ..."))
|
||||
sftp_client.mkdir(dir_path)
|
||||
repository = SshBorgRepository(uuid=uuid, path=path,
|
||||
credentials=credentials)
|
||||
else:
|
||||
with _ssh_connection(hostname, username, password) as ssh_client:
|
||||
with _sftp_client(ssh_client) as sftp_client:
|
||||
dir_contents = None
|
||||
try:
|
||||
dir_contents = sftp_client.listdir(dir_path)
|
||||
except FileNotFoundError:
|
||||
logger.info(
|
||||
_(f"Directory {dir_path} doesn't exist. Creating..."))
|
||||
sftp_client.mkdir(dir_path)
|
||||
|
||||
if dir_contents:
|
||||
try:
|
||||
repository = SshBorgRepository(uuid=uuid, path=path,
|
||||
credentials=credentials)
|
||||
repository.mount()
|
||||
repository.get_info()
|
||||
except BorgRepositoryDoesNotExistError:
|
||||
msg = _(f'Directory {dir_path} is neither empty nor '
|
||||
@ -452,12 +432,36 @@ def _validate_remote_repository(path, credentials, uuid=None):
|
||||
else:
|
||||
repository = SshBorgRepository(uuid=uuid, path=path,
|
||||
credentials=credentials)
|
||||
finally:
|
||||
sftp_client.close()
|
||||
|
||||
return repository
|
||||
|
||||
|
||||
@contextmanager
|
||||
def _ssh_connection(hostname, username, password):
|
||||
"""Context manager to create and close an SSH connection."""
|
||||
ssh_client = paramiko.SSHClient()
|
||||
|
||||
known_hosts_path = os.path.join(cfg.data_dir, '.ssh', 'known_hosts')
|
||||
ssh_client.load_host_keys(known_hosts_path)
|
||||
|
||||
try:
|
||||
ssh_client.connect(hostname, username=username, password=password)
|
||||
yield ssh_client
|
||||
except Exception as err:
|
||||
msg = _('Accessing the remote repository failed. Details: %(err)s')
|
||||
raise ValidationError(msg, params={'err': str(err)})
|
||||
finally:
|
||||
ssh_client.close()
|
||||
|
||||
return repository
|
||||
|
||||
@contextmanager
|
||||
def _sftp_client(ssh_client):
|
||||
"""Context manager to create and close an SFTP client."""
|
||||
sftp_client = ssh_client.open_sftp()
|
||||
try:
|
||||
yield sftp_client
|
||||
finally:
|
||||
sftp_client.close()
|
||||
|
||||
|
||||
class RemoveRepositoryView(SuccessMessageMixin, TemplateView):
|
||||
@ -468,12 +472,12 @@ class RemoveRepositoryView(SuccessMessageMixin, TemplateView):
|
||||
"""Return additional context for rendering the template."""
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['title'] = _('Remove Repository')
|
||||
context['repository'] = SshBorgRepository(uuid=uuid, automount=False)
|
||||
context['repository'] = SshBorgRepository(uuid=uuid)
|
||||
return context
|
||||
|
||||
def post(self, request, uuid):
|
||||
"""Delete the archive."""
|
||||
repository = SshBorgRepository(uuid, automount=False)
|
||||
repository = SshBorgRepository(uuid)
|
||||
repository.remove_repository()
|
||||
messages.success(
|
||||
request,
|
||||
@ -490,7 +494,10 @@ def umount_repository(request, uuid):
|
||||
|
||||
|
||||
def mount_repository(request, uuid):
|
||||
repository = SshBorgRepository(uuid=uuid, automount=False)
|
||||
# Do not mount unverified ssh repositories. Prompt for verification.
|
||||
if not network_storage.get(uuid).get('verified'):
|
||||
return redirect('backups:verify-ssh-hostkey', uuid=uuid)
|
||||
repository = SshBorgRepository(uuid=uuid)
|
||||
try:
|
||||
repository.mount()
|
||||
except Exception as err:
|
||||
|
||||
@ -14,7 +14,6 @@
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
"""
|
||||
Django settings for test modules.
|
||||
"""
|
||||
@ -31,18 +30,18 @@ DATABASES = {
|
||||
}
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'captcha',
|
||||
'bootstrapform',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.messages',
|
||||
'stronghold',
|
||||
'plinth',
|
||||
]
|
||||
'captcha',
|
||||
'bootstrapform',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.messages',
|
||||
'stronghold',
|
||||
'plinth',
|
||||
]
|
||||
|
||||
# These are included here solely to suppress Django warnings
|
||||
# during testing setup
|
||||
MIDDLEWARE_CLASSES = (
|
||||
MIDDLEWARE = (
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user