mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-03-18 09:10:49 +00:00
backups: Copy SSH client public key to remote
Tests: - In development VM, add a remote backup location of "tester@localhost:~/backups". Verify the SSH host key. plinth@freedombox key is listed in /home/tester/.ssh/authorized_keys. - Remove the remote backup location, and delete /home/tester/.ssh/authorized_keys. Add the same remote backup location again. plinth@freedombox key is again listed in /home/tester/.ssh/authorized_keys. Signed-off-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
parent
7fb41313cd
commit
f689e1b3cf
@ -160,6 +160,23 @@ def get_ssh_client_public_key() -> str:
|
||||
return pubkey
|
||||
|
||||
|
||||
def copy_ssh_client_public_key(hostname: str, username: str,
|
||||
password: str) -> tuple[bool, str]:
|
||||
"""Copy the SSH client public key to the remote server.
|
||||
|
||||
Returns whether the copy was successful, and any error message.
|
||||
"""
|
||||
pubkey_path = pathlib.Path(cfg.data_dir) / '.ssh' / 'id_ed25519.pub'
|
||||
env = os.environ.copy()
|
||||
env['SSHPASS'] = password
|
||||
process = subprocess.run([
|
||||
'sshpass', '-e', 'ssh-copy-id', '-i',
|
||||
str(pubkey_path), f'{username}@{hostname}'
|
||||
], stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False, env=env)
|
||||
error_message = process.stderr.decode() if process.returncode else ''
|
||||
return (process.returncode == 0, error_message)
|
||||
|
||||
|
||||
def is_ssh_hostkey_verified(hostname):
|
||||
"""Check whether SSH Hostkey has already been verified.
|
||||
|
||||
|
||||
@ -315,11 +315,25 @@ class SshBorgRepository(BaseBorgRepository):
|
||||
self._umount_ignore_errors()
|
||||
|
||||
@property
|
||||
def hostname(self):
|
||||
def hostname(self) -> str:
|
||||
"""Return hostname from the remote path."""
|
||||
_, hostname, _ = split_path(self._path)
|
||||
return hostname.split('%')[0] # XXX: Likely incorrect to split
|
||||
|
||||
@property
|
||||
def username(self) -> str:
|
||||
"""Return username from the remote path."""
|
||||
username, _, _ = split_path(self._path)
|
||||
return username
|
||||
|
||||
@property
|
||||
def ssh_password(self) -> str | None:
|
||||
"""Return SSH password if it is stored, otherwise None."""
|
||||
if 'ssh_password' in self.credentials:
|
||||
return self.credentials['ssh_password']
|
||||
|
||||
return None
|
||||
|
||||
@property
|
||||
def _mountpoint(self):
|
||||
"""Return the local mount point where repository is to be mounted."""
|
||||
|
||||
@ -24,8 +24,8 @@ from plinth.errors import PlinthError
|
||||
from plinth.modules import backups, storage
|
||||
from plinth.views import AppView
|
||||
|
||||
from . import (SESSION_PATH_VARIABLE, api, errors, forms,
|
||||
generate_ssh_client_auth_key, get_known_hosts_path,
|
||||
from . import (SESSION_PATH_VARIABLE, api, copy_ssh_client_public_key, errors,
|
||||
forms, generate_ssh_client_auth_key, get_known_hosts_path,
|
||||
get_ssh_client_public_key, is_ssh_hostkey_verified, privileged)
|
||||
from .decorators import delete_tmp_backup_file
|
||||
from .repository import (BorgRepository, SshBorgRepository, get_instance,
|
||||
@ -436,16 +436,38 @@ class VerifySshHostkeyView(FormView):
|
||||
with known_hosts_path.open('a', encoding='utf-8') as known_hosts_file:
|
||||
known_hosts_file.write(ssh_public_key + '\n')
|
||||
|
||||
def _check_copy_ssh_client_public_key(self):
|
||||
""" Try to copy FreedomBox's SSH client public key to the host."""
|
||||
repo = self._get_repository()
|
||||
result, message = copy_ssh_client_public_key(repo.hostname,
|
||||
repo.username,
|
||||
repo.ssh_password)
|
||||
if result:
|
||||
logger.info(
|
||||
"Copied SSH client public key to remote host's authorized "
|
||||
"keys.")
|
||||
if _save_repository(self.request, repo):
|
||||
return redirect(reverse_lazy('backups:index'))
|
||||
else:
|
||||
logger.warning('Failed to copy SSH client public key: %s', message)
|
||||
messages.error(self.request, message)
|
||||
# Remove the repository so that the user can have another go at
|
||||
# creating it.
|
||||
try:
|
||||
repo.remove()
|
||||
messages.error(self.request, _('Repository removed.'))
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
return redirect(reverse_lazy('backups:add-remote-repository'))
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
"""Skip this view if host is already verified."""
|
||||
if not is_ssh_hostkey_verified(self._get_repository().hostname):
|
||||
return super().get(*args, **kwargs)
|
||||
|
||||
messages.success(self.request, _('SSH host already verified.'))
|
||||
if _save_repository(self.request, self._get_repository()):
|
||||
return redirect(reverse_lazy('backups:index'))
|
||||
|
||||
return redirect(reverse_lazy('backups:add-remote-repository'))
|
||||
return self._check_copy_ssh_client_public_key()
|
||||
|
||||
def form_valid(self, form):
|
||||
"""Create and store the repository."""
|
||||
@ -453,10 +475,7 @@ class VerifySshHostkeyView(FormView):
|
||||
with handle_common_errors(self.request):
|
||||
self._add_ssh_hostkey(ssh_public_key)
|
||||
messages.success(self.request, _('SSH host verified.'))
|
||||
if _save_repository(self.request, self._get_repository()):
|
||||
return redirect(reverse_lazy('backups:index'))
|
||||
|
||||
return redirect(reverse_lazy('backups:add-remote-repository'))
|
||||
return self._check_copy_ssh_client_public_key()
|
||||
|
||||
|
||||
def _save_repository(request, repository):
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user