mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-01-21 07:55:00 +00:00
monkeysphere: Drop app as it is not being used
Closes #2157. Signed-off-by: Joseph Nuthalapati <njoseph@riseup.net> [sunil: Split diaspora and tahoe-lafs into separate commits] [sunil: Remove monkeysphere from help/tests/test_views.py] [sunil: Add to configuration file removal in Debian package and setup.py] Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org> Reviewed-by: Sunil Mohan Adapa <sunil@medhas.org>
This commit is contained in:
parent
f72505d300
commit
ce5274d9ee
@ -1,318 +0,0 @@
|
||||
#!/usr/bin/python3
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""
|
||||
Configuration helper for monkeysphere.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import contextlib
|
||||
import copy
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import signal
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
import augeas
|
||||
import psutil
|
||||
|
||||
|
||||
def parse_arguments():
|
||||
"""Return parsed command line arguments as dictionary."""
|
||||
parser = argparse.ArgumentParser()
|
||||
subparsers = parser.add_subparsers(dest='subcommand', help='Sub command')
|
||||
|
||||
host_show_keys = subparsers.add_parser(
|
||||
'host-show-keys', help='Show imported/importable keys')
|
||||
host_show_keys.add_argument('key_id', nargs='?',
|
||||
help='Optional KEYID to retrieve details for')
|
||||
|
||||
host_import_key = subparsers.add_parser(
|
||||
'host-import-key', help='Import a key into monkeysphere')
|
||||
host_import_key.add_argument('ssh_fingerprint',
|
||||
help='SSH fingerprint of the key to import')
|
||||
host_import_key.add_argument('domains', nargs='*',
|
||||
help='List of available domains')
|
||||
|
||||
host_publish_key = subparsers.add_parser('host-publish-key',
|
||||
help='Push host key to keyserver')
|
||||
host_publish_key.add_argument('key_ids', nargs='*',
|
||||
help='Optional list of KEYIDs')
|
||||
|
||||
host_cancel_publish = subparsers.add_parser(
|
||||
'host-cancel-publish', help='Cancel a running publish operation')
|
||||
host_cancel_publish.add_argument('pid', help='PID of the publish process')
|
||||
|
||||
subparsers.required = True
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def get_ssh_keys(fingerprint_hash):
|
||||
"""Return all SSH keys."""
|
||||
keys = {}
|
||||
|
||||
key_files = ['/etc/ssh/ssh_host_rsa_key']
|
||||
for key_file in key_files:
|
||||
output = subprocess.check_output(
|
||||
['ssh-keygen', '-l', '-E', fingerprint_hash, '-f', key_file])
|
||||
fingerprint = output.decode().split()[1]
|
||||
keys[fingerprint] = {
|
||||
'ssh_fingerprint': fingerprint,
|
||||
'service': 'ssh',
|
||||
'key_file': key_file,
|
||||
'available_domains': ['*']
|
||||
}
|
||||
|
||||
return keys
|
||||
|
||||
|
||||
def get_pem_ssh_fingerprint(pem_file, fingerprint_hash):
|
||||
"""Return the SSH fingerprint of a PEM file."""
|
||||
public_key = subprocess.check_output(
|
||||
['openssl', 'rsa', '-in', pem_file, '-pubout'],
|
||||
stderr=subprocess.DEVNULL)
|
||||
ssh_public_key = subprocess.check_output(
|
||||
['ssh-keygen', '-i', '-m', 'PKCS8', '-f', '/dev/stdin'],
|
||||
input=public_key)
|
||||
fingerprint = subprocess.check_output(
|
||||
['ssh-keygen', '-l', '-E', fingerprint_hash, '-f', '/dev/stdin'],
|
||||
input=ssh_public_key)
|
||||
|
||||
return fingerprint.decode().split()[1]
|
||||
|
||||
|
||||
def get_https_keys(fingerprint_hash):
|
||||
"""Return all HTTPS keys."""
|
||||
aug = augeas.Augeas(flags=augeas.Augeas.NO_LOAD +
|
||||
augeas.Augeas.NO_MODL_AUTOLOAD)
|
||||
aug.set('/augeas/load/Httpd/lens', 'Httpd.lns')
|
||||
aug.set('/augeas/load/Httpd/incl[last() + 1]',
|
||||
'/etc/apache2/sites-available/*')
|
||||
aug.set('/augeas/load/Httpd/incl[last() + 1]',
|
||||
'/etc/apache2/conf-available/*')
|
||||
aug.load()
|
||||
|
||||
# Read from default-tls.conf and default-ssl.conf
|
||||
keys = {}
|
||||
path = '/files/etc/apache2/sites-available//VirtualHost'
|
||||
for match in aug.match(path):
|
||||
host = {'available_domains': ['*'], 'service': 'https'}
|
||||
for directive in aug.match(match + '/directive'):
|
||||
name = aug.get(directive)
|
||||
if name == 'ServerName':
|
||||
host['available_domains'] = [aug.get(directive + '/arg')]
|
||||
elif name in ('GnuTLSKeyFile', 'SSLCertificateKeyFile'):
|
||||
host['key_file'] = aug.get(directive + '/arg')
|
||||
|
||||
if 'key_file' in host:
|
||||
host['ssh_fingerprint'] = get_pem_ssh_fingerprint(
|
||||
host['key_file'], fingerprint_hash)
|
||||
keys[host['ssh_fingerprint']] = host
|
||||
|
||||
# Read from FreedomBox configured domains with proper SSL certs.
|
||||
path = "/files/etc/apache2/sites-available//" \
|
||||
"directive[. = 'Use'][arg[1] = 'FreedomBoxTLSSiteMacro']"
|
||||
key_file = ("/files/etc/apache2//Macro[arg[1] = 'FreedomBoxTLSSiteMacro']"
|
||||
"//VirtualHost/directive[. = 'GnuTLSKeyFile']/arg")
|
||||
key_file = aug.get(key_file)
|
||||
for match in aug.match(path):
|
||||
domain = aug.get(match + '/arg[2]')
|
||||
host = {
|
||||
'available_domains': [domain],
|
||||
'service': 'https',
|
||||
'key_file': key_file.replace('$domain', domain)
|
||||
}
|
||||
host['ssh_fingerprint'] = get_pem_ssh_fingerprint(
|
||||
host['key_file'], fingerprint_hash)
|
||||
keys[host['ssh_fingerprint']] = host
|
||||
|
||||
return keys
|
||||
|
||||
|
||||
def get_monkeysphere_keys(key_id=None):
|
||||
"""Return the list of keys imported into monkeysphere."""
|
||||
try:
|
||||
key_ids = [] if not key_id else [key_id]
|
||||
output = subprocess.check_output(['monkeysphere-host', 'show-keys'] +
|
||||
key_ids, stderr=subprocess.DEVNULL)
|
||||
except subprocess.CalledProcessError:
|
||||
# no keys available
|
||||
return {}
|
||||
|
||||
# parse output
|
||||
default_dict = {'imported_domains': [], 'available_domains': []}
|
||||
keys = [copy.deepcopy(default_dict)]
|
||||
lines = output.decode().strip().split('\n')
|
||||
for line in lines:
|
||||
if line.startswith('pub'):
|
||||
data = line.lstrip('pub').split()
|
||||
keys[-1]['pub'] = data[0]
|
||||
keys[-1]['date'] = data[1]
|
||||
elif line.startswith('uid'):
|
||||
uid = line.lstrip('uid').strip()
|
||||
if uid.startswith('['):
|
||||
uid = uid[uid.index(']') + 1:].strip()
|
||||
|
||||
keys[-1].setdefault('uids', []).append(uid)
|
||||
matches = re.match(r'([a-zA-Z]*)://(.*)(:\d*)?', uid)
|
||||
keys[-1]['service'] = matches.group(1)
|
||||
keys[-1]['imported_domains'].append(matches.group(2))
|
||||
elif line.startswith('OpenPGP fingerprint:'):
|
||||
keys[-1]['openpgp_fingerprint'] = \
|
||||
line.lstrip('OpenPGP fingerprint:')
|
||||
elif line.startswith('ssh fingerprint:'):
|
||||
data = line.lstrip('ssh fingerprint:').split()
|
||||
keys[-1]['ssh_key_size'] = data[0]
|
||||
keys[-1]['ssh_fingerprint'] = data[1]
|
||||
keys[-1]['ssh_key_type'] = data[2].strip('()')
|
||||
elif line == '':
|
||||
keys.append(copy.deepcopy(default_dict))
|
||||
|
||||
return {key['ssh_fingerprint']: key for key in keys}
|
||||
|
||||
|
||||
def get_merged_keys(key_id=None):
|
||||
"""Return merged list of system and monkeysphere keys."""
|
||||
keys = get_monkeysphere_keys(key_id)
|
||||
|
||||
# Monkeysphere used use MD5 for fingerprint hash and recently
|
||||
# changed to SHA256. In case of SHA256 the string 'SHA256:' is
|
||||
# being prepended to the fingerprint. Hoping that such a prefix
|
||||
# will be available in all future changes, extract it from one key
|
||||
# (assuming all the others will be the same) and use it.
|
||||
fingerprint_hash = 'SHA256'
|
||||
if keys:
|
||||
fingerprint_hash = list(keys.keys())[0].split(':')[0]
|
||||
|
||||
system_keys = list(get_ssh_keys(fingerprint_hash).items()) + \
|
||||
list(get_https_keys(fingerprint_hash).items())
|
||||
for ssh_fingerprint, key in system_keys:
|
||||
if key_id and ssh_fingerprint not in keys:
|
||||
continue
|
||||
|
||||
if ssh_fingerprint in keys:
|
||||
keys[ssh_fingerprint].update({
|
||||
'available_domains': key['available_domains'],
|
||||
'key_file': key['key_file']
|
||||
})
|
||||
else:
|
||||
keys[ssh_fingerprint] = key
|
||||
|
||||
return keys
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _get_ssh_key_file_for_import(original_key_file, service):
|
||||
"""Return an SSH key file that can be imported into monkeysphere.
|
||||
|
||||
If the key file is in PEM format, the key file can be used as it is.
|
||||
Otherwise, if the file is in the OpenSSH key format, which is default since
|
||||
7.8, then convert it to PEM format.
|
||||
|
||||
"""
|
||||
if service != 'ssh':
|
||||
yield original_key_file
|
||||
return
|
||||
|
||||
first_line = open(original_key_file, 'r').readline()
|
||||
if '--BEGIN OPENSSH PRIVATE KEY--' not in first_line:
|
||||
yield original_key_file
|
||||
return
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp_directory:
|
||||
key_file = os.path.join(temp_directory, 'ssh_key_file')
|
||||
shutil.copy2(original_key_file, key_file)
|
||||
# Convert OpenSSH format to PEM
|
||||
subprocess.run(
|
||||
['ssh-keygen', '-p', '-N', '', '-m', 'PEM', '-f', key_file],
|
||||
check=True)
|
||||
yield key_file
|
||||
|
||||
|
||||
def subcommand_host_show_keys(arguments):
|
||||
"""Show host key fingerprints."""
|
||||
print(json.dumps({'keys': get_merged_keys(arguments.key_id)}))
|
||||
|
||||
|
||||
def subcommand_host_import_key(arguments, second_run=False):
|
||||
"""Import host SSH key."""
|
||||
keys = get_merged_keys()
|
||||
if arguments.ssh_fingerprint not in keys:
|
||||
raise Exception('Unknown SSH fingerprint')
|
||||
|
||||
key = keys[arguments.ssh_fingerprint]
|
||||
if '*' in key['available_domains']:
|
||||
key['available_domains'] = arguments.domains
|
||||
|
||||
if 'openpgp_fingerprint' not in key and not second_run:
|
||||
env = dict(os.environ, MONKEYSPHERE_PROMPT='false')
|
||||
with _get_ssh_key_file_for_import(key['key_file'],
|
||||
key['service']) as key_file:
|
||||
subprocess.check_call([
|
||||
'monkeysphere-host', 'import-key', key_file,
|
||||
key['service'] + '://' + key['available_domains'][0]
|
||||
], env=env)
|
||||
|
||||
subcommand_host_import_key(arguments, second_run=True)
|
||||
else:
|
||||
for domain in key['available_domains']:
|
||||
if domain in key['imported_domains']:
|
||||
continue
|
||||
|
||||
env = dict(os.environ, MONKEYSPHERE_PROMPT='false')
|
||||
subprocess.check_call([
|
||||
'monkeysphere-host', 'add-servicename',
|
||||
key['service'] + '://' + domain, key['openpgp_fingerprint']
|
||||
], env=env)
|
||||
|
||||
|
||||
def subcommand_host_publish_key(arguments):
|
||||
"""Push host key to keyserver."""
|
||||
# setting TMPDIR as workaround for Debian bug #656750
|
||||
proc = subprocess.Popen(
|
||||
['monkeysphere-host', 'publish-keys'] + arguments.key_ids,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
||||
env=dict(os.environ,
|
||||
TMPDIR='/var/lib/monkeysphere/authentication/tmp/',
|
||||
MONKEYSPHERE_PROMPT='false'))
|
||||
output, error = proc.communicate()
|
||||
output, error = output.decode(), error.decode()
|
||||
if proc.returncode != 0:
|
||||
raise Exception(output, error)
|
||||
|
||||
print(output)
|
||||
|
||||
|
||||
def subcommand_host_cancel_publish(arguments):
|
||||
"""Kill a running publish process."""
|
||||
process = psutil.Process(int(arguments.pid))
|
||||
|
||||
# Perform tight checks on the process before killing for security.
|
||||
arguments = process.cmdline()
|
||||
if not arguments:
|
||||
# Process already completed
|
||||
return
|
||||
|
||||
# Remove the sudo prefix if present
|
||||
while arguments[0] == 'sudo' or arguments[0].startswith('-'):
|
||||
arguments = arguments[1:]
|
||||
|
||||
if len(arguments) >= 2 and \
|
||||
arguments[0].split('/')[-1] == 'monkeysphere' and \
|
||||
arguments[1] == 'host-publish-key':
|
||||
process.send_signal(signal.SIGTERM)
|
||||
|
||||
|
||||
def main():
|
||||
"""Parse arguments and perform all duties."""
|
||||
arguments = parse_arguments()
|
||||
|
||||
subcommand = arguments.subcommand.replace('-', '_')
|
||||
subcommand_method = globals()['subcommand_' + subcommand]
|
||||
subcommand_method(arguments)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
1
debian/freedombox.maintscript
vendored
1
debian/freedombox.maintscript
vendored
@ -13,3 +13,4 @@ rm_conffile /etc/apt/preferences.d/50freedombox3.pref 20.5~
|
||||
rm_conffile /etc/plinth/plinth.config 20.12~
|
||||
rm_conffile /etc/plinth/custom-shortcuts.json 20.12~
|
||||
rm_conffile /etc/plinth/modules-enabled/coquelicot 20.14~
|
||||
rm_conffile /etc/plinth/modules-enabled/monkeysphere 21.16~
|
||||
|
||||
@ -109,17 +109,16 @@ MANUAL_PAGES = ('Apache_userdir', 'APU', 'Backups', 'BananaPro', 'BeagleBone',
|
||||
'freedombox-manual', 'GettingHelp', 'GitWeb', 'Hardware',
|
||||
'I2P', 'Ikiwiki', 'Infinoted', 'Introduction', 'JSXC',
|
||||
'LetsEncrypt', 'Maker', 'MatrixSynapse', 'MediaWiki',
|
||||
'Minetest', 'MiniDLNA', 'MLDonkey', 'Monkeysphere', 'Mumble',
|
||||
'NameServices', 'Networks', 'OpenVPN', 'OrangePiZero',
|
||||
'PageKite', 'pcDuino3', 'Performance', 'PineA64+',
|
||||
'PioneerEdition', 'Plinth', 'Power', 'Privoxy', 'Quassel',
|
||||
'QuickStart', 'Radicale', 'RaspberryPi2', 'RaspberryPi3B+',
|
||||
'RaspberryPi3B', 'RaspberryPi4B', 'ReleaseNotes', 'Rock64',
|
||||
'RockPro64', 'Roundcube', 'Samba', 'Searx', 'SecureShell',
|
||||
'Security', 'ServiceDiscovery', 'Shadowsocks', 'Sharing',
|
||||
'Snapshots', 'Storage', 'Syncthing', 'TinyTinyRSS', 'Tor',
|
||||
'Transmission', 'Upgrades', 'USBWiFi', 'Users', 'VirtualBox',
|
||||
'WireGuard')
|
||||
'Minetest', 'MiniDLNA', 'MLDonkey', 'Mumble', 'NameServices',
|
||||
'Networks', 'OpenVPN', 'OrangePiZero', 'PageKite', 'pcDuino3',
|
||||
'Performance', 'PineA64+', 'PioneerEdition', 'Plinth', 'Power',
|
||||
'Privoxy', 'Quassel', 'QuickStart', 'Radicale', 'RaspberryPi2',
|
||||
'RaspberryPi3B+', 'RaspberryPi3B', 'RaspberryPi4B',
|
||||
'ReleaseNotes', 'Rock64', 'RockPro64', 'Roundcube', 'Samba',
|
||||
'Searx', 'SecureShell', 'Security', 'ServiceDiscovery',
|
||||
'Shadowsocks', 'Sharing', 'Snapshots', 'Storage', 'Syncthing',
|
||||
'TinyTinyRSS', 'Tor', 'Transmission', 'Upgrades', 'USBWiFi',
|
||||
'Users', 'VirtualBox', 'WireGuard')
|
||||
_restricted_reason = ('Needs installed manual. '
|
||||
'CI speed-optimized workspace does not provide it.')
|
||||
not_restricted_environment = pytest.mark.skipif(not canary.exists(),
|
||||
|
||||
@ -1,74 +0,0 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""
|
||||
FreedomBox app for monkeysphere.
|
||||
"""
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from plinth import app as app_module
|
||||
from plinth import menu
|
||||
from plinth.modules.backups.components import BackupRestore
|
||||
from plinth.modules.users.components import UsersAndGroups
|
||||
from plinth.package import Packages
|
||||
|
||||
from . import manifest
|
||||
|
||||
_description = [
|
||||
_('With Monkeysphere, an OpenPGP key can be generated for each configured '
|
||||
'domain serving SSH. The OpenPGP public key can then be uploaded to the '
|
||||
'OpenPGP keyservers. Users connecting to this machine through SSH can '
|
||||
'verify that they are connecting to the correct host. For users to '
|
||||
'trust the key, at least one person (usually the machine owner) must '
|
||||
'sign the key using the regular OpenPGP key signing process. See the '
|
||||
'<a href="http://web.monkeysphere.info/getting-started-ssh/"> '
|
||||
'Monkeysphere SSH documentation</a> for more details.'),
|
||||
_('Monkeysphere can also generate an OpenPGP key for each Secure Web '
|
||||
'Server (HTTPS) certificate installed on this machine. The OpenPGP '
|
||||
'public key can then be uploaded to the OpenPGP keyservers. Users '
|
||||
'accessing the web server through HTTPS can verify that they are '
|
||||
'connecting to the correct host. To validate the certificate, the user '
|
||||
'will need to install some software that is available on the '
|
||||
'<a href="https://web.monkeysphere.info/download/"> Monkeysphere '
|
||||
'website</a>.')
|
||||
]
|
||||
|
||||
app = None
|
||||
|
||||
|
||||
class MonkeysphereApp(app_module.App):
|
||||
"""FreedomBox app for Monkeysphere."""
|
||||
|
||||
app_id = 'monkeysphere'
|
||||
|
||||
_version = 1
|
||||
|
||||
def __init__(self):
|
||||
"""Create components for the app."""
|
||||
super().__init__()
|
||||
|
||||
info = app_module.Info(app_id=self.app_id, version=self._version,
|
||||
name=_('Monkeysphere'), icon='fa-certificate',
|
||||
description=_description,
|
||||
manual_page='Monkeysphere')
|
||||
self.add(info)
|
||||
|
||||
menu_item = menu.Menu('menu-monkeysphere', info.name, None, info.icon,
|
||||
'monkeysphere:index', parent_url_name='system',
|
||||
advanced=True)
|
||||
self.add(menu_item)
|
||||
|
||||
packages = Packages('packages-monkeysphere', ['monkeysphere'])
|
||||
self.add(packages)
|
||||
|
||||
users_and_groups = UsersAndGroups('users-and-groups-monkeysphere',
|
||||
reserved_usernames=['monkeysphere'])
|
||||
self.add(users_and_groups)
|
||||
|
||||
backup_restore = BackupRestore('backup-restore-monkeysphere',
|
||||
**manifest.backup)
|
||||
self.add(backup_restore)
|
||||
|
||||
|
||||
def setup(helper, old_version=None):
|
||||
"""Install and configure the module."""
|
||||
app.setup(old_version)
|
||||
@ -1 +0,0 @@
|
||||
plinth.modules.monkeysphere
|
||||
@ -1,13 +0,0 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""
|
||||
Application manfiest for monkeysphere.
|
||||
"""
|
||||
|
||||
backup = {
|
||||
'config': {
|
||||
'directories': ['/etc/monkeysphere/']
|
||||
},
|
||||
'secrets': {
|
||||
'directories': ['/var/lib/monkeysphere/']
|
||||
}
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
/*
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
td li {
|
||||
list-style: none;
|
||||
line-height: 2rem;
|
||||
}
|
||||
|
||||
td ul {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.form-action.pull-right {
|
||||
margin-right: 0.625rem;
|
||||
}
|
||||
|
||||
.form-action button {
|
||||
width: 7.875rem;
|
||||
}
|
||||
@ -1,136 +0,0 @@
|
||||
{% extends "app.html" %}
|
||||
{% comment %}
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
{% endcomment %}
|
||||
|
||||
{% load bootstrap %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
|
||||
{% block page_head %}
|
||||
<link type="text/css" rel="stylesheet"
|
||||
href="{% static 'monkeysphere/monkeysphere.css' %}"/>
|
||||
{% endblock %}
|
||||
|
||||
{% block configuration %}
|
||||
|
||||
{% if running %}
|
||||
<p class="running-status-parent">
|
||||
<span class="running-status loading"></span>
|
||||
{% trans "Publishing key to keyserver..." %}
|
||||
|
||||
<form class="form" method="post"
|
||||
action="{% url 'monkeysphere:cancel' %}">
|
||||
{% csrf_token %}
|
||||
|
||||
<button type="submit" class="btn btn-warning btn-sm">
|
||||
{% trans "Cancel" %}</button>
|
||||
</form>
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Service" %}</th>
|
||||
<th>{% trans "Domains" %}</th>
|
||||
<th>{% trans "OpenPGP Fingerprint" %}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for key in status.keys.values|dictsort:"ssh_fingerprint" %}
|
||||
<tr class="monkeysphere-service-{{ key.service }}">
|
||||
<td>
|
||||
{% if key.service == 'ssh' %}
|
||||
{% trans "Secure Shell" %}
|
||||
{% elif key.service == 'https' %}
|
||||
{% trans "Web Server" %}
|
||||
{% else %}
|
||||
{% trans "Other" %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<ul>
|
||||
{% for domain in key.all_domains %}
|
||||
{% if domain not in key.imported_domains %}
|
||||
<li>
|
||||
<span class="badge badge-secondary"
|
||||
><span class="fa fa-times"
|
||||
aria-hidden="true"></span></span>
|
||||
<span class="monkeysphere-importable-domain"
|
||||
>{{ domain }}</span>
|
||||
</li>
|
||||
{% elif domain not in key.available_domains %}
|
||||
<li>
|
||||
<span class="badge badge-warning"
|
||||
><span class="fa fa-exclamation-triangle"
|
||||
aria-hidden="true"></span></span>
|
||||
<span class="monkeysphere-unavailable-domain"
|
||||
>{{ domain }}</span>
|
||||
</li>
|
||||
{% else %}
|
||||
<li>
|
||||
<span class="badge badge-success"
|
||||
><span class="fa fa-check"
|
||||
aria-hidden="true"></span></span>
|
||||
<span class="monkeysphere-imported-domain"
|
||||
>{{ domain }}</span>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</td>
|
||||
<td>
|
||||
{% if key.openpgp_fingerprint %}
|
||||
<a href="{% url 'monkeysphere:details' key.openpgp_fingerprint %}"
|
||||
title="{% blocktrans trimmed with fingerprint=key.openpgp_fingerprint %}
|
||||
Show details for key {{ fingerprint }}
|
||||
{% endblocktrans %}">
|
||||
{{ key.openpgp_fingerprint }}
|
||||
</a>
|
||||
{% else %}
|
||||
{% trans "-" %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if not key.openpgp_fingerprint %}
|
||||
<form class="form pull-right form-action form-import"
|
||||
method="post"
|
||||
action="{% url 'monkeysphere:import' key.ssh_fingerprint %}">
|
||||
{% csrf_token %}
|
||||
|
||||
<button type="submit" class="btn btn-primary btn-sm pull-right button-import">
|
||||
{% trans "Import Key" %}</button>
|
||||
</form>
|
||||
{% else %}
|
||||
{% if not running %}
|
||||
<form class="form pull-right form-action form-publish"
|
||||
method="post"
|
||||
action="{% url 'monkeysphere:publish' key.openpgp_fingerprint %}">
|
||||
{% csrf_token %}
|
||||
|
||||
<button type="submit" class="btn btn-warning btn-sm pull-right button-publish">
|
||||
{% trans "Publish Key" %}</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if key.importable_domains %}
|
||||
<form class="form pull-right form-action form-add-domain"
|
||||
method="post"
|
||||
action="{% url 'monkeysphere:import' key.ssh_fingerprint %}">
|
||||
{% csrf_token %}
|
||||
|
||||
<button type="submit" class="btn btn-primary btn-sm pull-right button-add-domains">
|
||||
{% trans "Add Domains" %}</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@ -1,85 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% comment %}
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
{% endcomment %}
|
||||
|
||||
{% load bootstrap %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h2>{% trans "Monkeysphere" %}</h2>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{% trans "OpenPGP Fingerprint" %}</td>
|
||||
<td>{{ key.openpgp_fingerprint }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans "OpenPGP User IDs" %}</td>
|
||||
<td>{{ key.uids|join:', ' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans "Key Import Date" %}</td>
|
||||
<td>{{ key.date }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans "SSH Key Type" %}</td>
|
||||
<td>{{ key.ssh_key_type }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans "SSH Key Size" %}</td>
|
||||
<td>{{ key.ssh_key_size }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans "SSH Fingerprint" %}</td>
|
||||
<td>{{ key.ssh_fingerprint }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans "Service" %}</td>
|
||||
<td>
|
||||
{% if key.service == 'ssh' %}
|
||||
{% trans "Secure Shell" %}
|
||||
{% elif key.service == 'https' %}
|
||||
{% trans "Web Server" %}
|
||||
{% else %}
|
||||
{% trans "Other" %}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans "Key File" %}</td>
|
||||
<td>{{ key.key_file }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans "Available Domains" %}</td>
|
||||
<td>{{ key.available_domains|join:', ' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans "Added Domains" %}</td>
|
||||
<td>{{ key.imported_domains|join:', ' }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
{% blocktrans trimmed %}
|
||||
After this key is published to the keyservers, it can be signed using
|
||||
<a href="https://www.gnupg.org/">GnuPG</a> with the following commands:
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
<pre>
|
||||
# {% trans "Download the key" %}
|
||||
gpg --recv-key {{ key.openpgp_fingerprint }}
|
||||
|
||||
# {% trans "Sign the key" %}
|
||||
gpg --sign-key {{ key.openpgp_fingerprint }}
|
||||
|
||||
# {% trans "Send the key back to the keyservers" %}
|
||||
gpg --send-key {{ key.openpgp_fingerprint }}
|
||||
</pre>
|
||||
|
||||
{% endblock %}
|
||||
@ -1,100 +0,0 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""
|
||||
Functional, browser based tests for monkeysphere app.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from plinth.tests import functional
|
||||
|
||||
pytestmark = [pytest.mark.system, pytest.mark.monkeysphere]
|
||||
|
||||
|
||||
@pytest.fixture(scope='module', autouse=True)
|
||||
def fixture_background(session_browser):
|
||||
"""Login and install the app."""
|
||||
functional.login(session_browser)
|
||||
functional.set_advanced_mode(session_browser, True)
|
||||
functional.install(session_browser, 'monkeysphere')
|
||||
functional.set_domain_name(session_browser, 'mydomain.example')
|
||||
yield
|
||||
|
||||
|
||||
def test_import_ssh_keys(session_browser):
|
||||
"""Test importing SSH keys."""
|
||||
_import_key(session_browser, 'ssh', 'mydomain.example')
|
||||
_assert_imported_key(session_browser, 'ssh', 'mydomain.example')
|
||||
|
||||
|
||||
def test_import_https_keys(session_browser):
|
||||
"""Test importing HTTPS keys."""
|
||||
_import_key(session_browser, 'https', 'mydomain.example')
|
||||
_assert_imported_key(session_browser, 'https', 'mydomain.example')
|
||||
|
||||
|
||||
def test_publish_ssh_keys(session_browser):
|
||||
"""Test publishing SSH keys."""
|
||||
_import_key(session_browser, 'ssh', 'mydomain.example')
|
||||
_publish_key(session_browser, 'ssh', 'mydomain.example')
|
||||
|
||||
|
||||
def test_publish_https_keys(session_browser):
|
||||
"""Test publishing HTTPS keys."""
|
||||
_import_key(session_browser, 'https', 'mydomain.example')
|
||||
_publish_key(session_browser, 'https', 'mydomain.example')
|
||||
|
||||
|
||||
def test_backup_restore(session_browser):
|
||||
"""Test backup and restore of keys"""
|
||||
_import_key(session_browser, 'ssh', 'mydomain.example')
|
||||
_import_key(session_browser, 'https', 'mydomain.example')
|
||||
functional.backup_create(session_browser, 'monkeysphere',
|
||||
'test_monkeysphere')
|
||||
|
||||
functional.backup_restore(session_browser, 'monkeysphere',
|
||||
'test_monkeysphere')
|
||||
_assert_imported_key(session_browser, 'ssh', 'mydomain.example')
|
||||
_assert_imported_key(session_browser, 'https', 'mydomain.example')
|
||||
|
||||
|
||||
def _find_domain(browser, key_type, domain_type, domain):
|
||||
"""Iterate every domain of a given type which given key type."""
|
||||
keys_of_type = browser.find_by_css(
|
||||
'.monkeysphere-service-{}'.format(key_type))
|
||||
for key_of_type in keys_of_type:
|
||||
search_domains = key_of_type.find_by_css(
|
||||
'.monkeysphere-{}-domain'.format(domain_type))
|
||||
for search_domain in search_domains:
|
||||
if search_domain.text == domain:
|
||||
return key_of_type, search_domain
|
||||
|
||||
raise IndexError('Domain not found')
|
||||
|
||||
|
||||
def _import_key(browser, key_type, domain):
|
||||
"""Import a key of specified type for given domain into monkeysphere."""
|
||||
try:
|
||||
_assert_imported_key(browser, key_type, domain)
|
||||
except IndexError:
|
||||
pass
|
||||
else:
|
||||
return
|
||||
|
||||
key, _ = _find_domain(browser, key_type, 'importable', domain)
|
||||
with functional.wait_for_page_update(browser):
|
||||
key.find_by_css('.button-import').click()
|
||||
|
||||
|
||||
def _assert_imported_key(browser, key_type, domain):
|
||||
"""Assert that a key of specified type for given domain was imported.."""
|
||||
functional.nav_to_module(browser, 'monkeysphere')
|
||||
return _find_domain(browser, key_type, 'imported', domain)
|
||||
|
||||
|
||||
def _publish_key(browser, key_type, domain):
|
||||
"""Publish a key of specified type for given domain from monkeysphere."""
|
||||
functional.nav_to_module(browser, 'monkeysphere')
|
||||
key, _ = _find_domain(browser, key_type, 'imported', domain)
|
||||
with functional.wait_for_page_update(browser):
|
||||
key.find_by_css('.button-publish').click()
|
||||
|
||||
functional.wait_for_config_update(browser, 'monkeysphere')
|
||||
@ -1,19 +0,0 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""
|
||||
URLs for the monkeysphere module.
|
||||
"""
|
||||
|
||||
from django.urls import re_path
|
||||
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
re_path(r'^sys/monkeysphere/$', views.index, name='index'),
|
||||
re_path(r'^sys/monkeysphere/(?P<ssh_fingerprint>[0-9A-Za-z:+/]+)/import/$',
|
||||
views.import_key, name='import'),
|
||||
re_path(r'^sys/monkeysphere/(?P<fingerprint>[0-9A-Fa-f]+)/details/$',
|
||||
views.details, name='details'),
|
||||
re_path(r'^sys/monkeysphere/(?P<fingerprint>[0-9A-Fa-f]+)/publish/$',
|
||||
views.publish, name='publish'),
|
||||
re_path(r'^sys/monkeysphere/cancel/$', views.cancel, name='cancel'),
|
||||
]
|
||||
@ -1,135 +0,0 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""
|
||||
Views for the monkeysphere module.
|
||||
"""
|
||||
|
||||
import json
|
||||
|
||||
from django.contrib import messages
|
||||
from django.shortcuts import redirect
|
||||
from django.template.response import TemplateResponse
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils.translation import gettext as _
|
||||
from django.views.decorators.http import require_POST
|
||||
|
||||
from plinth import actions
|
||||
from plinth.modules import monkeysphere, names
|
||||
|
||||
publish_process = None
|
||||
|
||||
|
||||
def index(request):
|
||||
"""Serve configuration page."""
|
||||
_collect_publish_result(request)
|
||||
status = get_status()
|
||||
return TemplateResponse(
|
||||
request, 'monkeysphere.html', {
|
||||
'app_info': monkeysphere.app.info,
|
||||
'title': monkeysphere.app.info.name,
|
||||
'status': status,
|
||||
'running': bool(publish_process),
|
||||
'refresh_page_sec': 3 if bool(publish_process) else None,
|
||||
})
|
||||
|
||||
|
||||
@require_POST
|
||||
def import_key(request, ssh_fingerprint):
|
||||
"""Import a key into monkeysphere."""
|
||||
keys = get_keys()
|
||||
available_domains = keys[ssh_fingerprint]['available_domains']
|
||||
try:
|
||||
actions.superuser_run('monkeysphere',
|
||||
['host-import-key', ssh_fingerprint] +
|
||||
list(available_domains))
|
||||
messages.success(request, _('Imported key.'))
|
||||
except actions.ActionError as exception:
|
||||
messages.error(request, str(exception))
|
||||
|
||||
return redirect(reverse_lazy('monkeysphere:index'))
|
||||
|
||||
|
||||
def details(request, fingerprint):
|
||||
"""Get details for an OpenPGP key."""
|
||||
return TemplateResponse(request, 'monkeysphere_details.html', {
|
||||
'title': monkeysphere.app.info.name,
|
||||
'key': get_key(fingerprint)
|
||||
})
|
||||
|
||||
|
||||
@require_POST
|
||||
def publish(request, fingerprint):
|
||||
"""Publish OpenPGP key for SSH service."""
|
||||
global publish_process
|
||||
if not publish_process:
|
||||
publish_process = actions.superuser_run(
|
||||
'monkeysphere', ['host-publish-key', fingerprint],
|
||||
run_in_background=True)
|
||||
|
||||
return redirect(reverse_lazy('monkeysphere:index'))
|
||||
|
||||
|
||||
@require_POST
|
||||
def cancel(request):
|
||||
"""Cancel ongoing process."""
|
||||
global publish_process
|
||||
if publish_process:
|
||||
actions.superuser_run(
|
||||
'monkeysphere', ['host-cancel-publish',
|
||||
str(publish_process.pid)])
|
||||
publish_process = None
|
||||
messages.info(request, _('Cancelled key publishing.'))
|
||||
|
||||
return redirect(reverse_lazy('monkeysphere:index'))
|
||||
|
||||
|
||||
def get_status():
|
||||
"""Get the current status."""
|
||||
return {'keys': get_keys()}
|
||||
|
||||
|
||||
def get_keys(fingerprint=None):
|
||||
"""Get keys."""
|
||||
fingerprint = [fingerprint] if fingerprint else []
|
||||
output = actions.superuser_run('monkeysphere',
|
||||
['host-show-keys'] + fingerprint)
|
||||
keys = json.loads(output)['keys']
|
||||
|
||||
domains = names.components.DomainName.list_names()
|
||||
for key in keys.values():
|
||||
key['imported_domains'] = set(key.get('imported_domains', []))
|
||||
key['available_domains'] = set(key.get('available_domains', []))
|
||||
if '*' in key['available_domains']:
|
||||
key['available_domains'] = set(domains)
|
||||
|
||||
key['all_domains'] = sorted(key['available_domains'].union(
|
||||
key['imported_domains']))
|
||||
key['importable_domains'] = key['available_domains'] \
|
||||
.difference(key['imported_domains'])
|
||||
|
||||
return keys
|
||||
|
||||
|
||||
def get_key(fingerprint):
|
||||
"""Get key by fingerprint."""
|
||||
keys = get_keys(fingerprint)
|
||||
return list(keys.values())[0] if len(keys) else None
|
||||
|
||||
|
||||
def _collect_publish_result(request):
|
||||
"""Handle publish process completion."""
|
||||
global publish_process
|
||||
if not publish_process:
|
||||
return
|
||||
|
||||
return_code = publish_process.poll()
|
||||
|
||||
# Publish process is not complete yet
|
||||
if return_code is None:
|
||||
return
|
||||
|
||||
if not return_code:
|
||||
messages.success(request, _('Published key to keyserver.'))
|
||||
else:
|
||||
messages.error(request, _('Error occurred while publishing key.'))
|
||||
|
||||
publish_process = None
|
||||
@ -50,9 +50,9 @@ _site_url = {
|
||||
|
||||
_sys_modules = [
|
||||
'avahi', 'backups', 'bind', 'cockpit', 'config', 'datetime', 'diagnostics',
|
||||
'dynamicdns', 'firewall', 'letsencrypt', 'monkeysphere', 'names',
|
||||
'networks', 'pagekite', 'performance', 'power', 'security', 'snapshot',
|
||||
'ssh', 'storage', 'upgrades', 'users'
|
||||
'dynamicdns', 'firewall', 'letsencrypt', 'names', 'networks', 'pagekite',
|
||||
'performance', 'power', 'security', 'snapshot', 'ssh', 'storage',
|
||||
'upgrades', 'users'
|
||||
]
|
||||
|
||||
|
||||
|
||||
@ -40,7 +40,6 @@ markers = [
|
||||
"minetest",
|
||||
"minidlna",
|
||||
"mldonkey",
|
||||
"monkeysphere",
|
||||
"mumble",
|
||||
"openvpn",
|
||||
"pagekite",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user