mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-09-19 04:59:01 +00:00
monkeysphere: Proper domain handling
Action: - Don't use const for HOST_TOOL, it is unlikely to be ever changed. - Don't pass multiple key ids as single string to monkeysphere-host. - Use JSON for data transfer with action instead of custom format and parsing. - Minor styling fixes. Template: - More consistent indentation. - Improve the description. - Add headers to the table. - List domains instead of domain types. URLs: - Take domain as argument for key generation. - Narrow down fingerprint matching regex. Views: - Take domain as argument for key generation. Verify that domain is valid. - Minor grammer fix to cancel message. - Use JSON format for getting key status. - List domains instead of domain types.
This commit is contained in:
parent
70d85cbd6f
commit
6afe350fe5
@ -21,42 +21,39 @@ Configuration helper for monkeysphere.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
HOST_TOOL = 'monkeysphere-host'
|
|
||||||
|
|
||||||
|
|
||||||
def parse_arguments():
|
def parse_arguments():
|
||||||
"""Return parsed command line arguments as dictionary."""
|
"""Return parsed command line arguments as dictionary."""
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
subparsers = parser.add_subparsers(dest='subcommand', help='Sub command')
|
subparsers = parser.add_subparsers(dest='subcommand', help='Sub command')
|
||||||
|
|
||||||
host_show_key = subparsers.add_parser('host-show-key',
|
host_show_keys = subparsers.add_parser(
|
||||||
help='Show host key fingerprint')
|
'host-show-keys', help='Show host key fingerprints')
|
||||||
host_show_key.add_argument('keyid', nargs='*',
|
host_show_keys.add_argument(
|
||||||
help='Optional list of KEYIDs')
|
'key_ids', nargs='*', help='Optional list of KEYIDs')
|
||||||
|
|
||||||
host_import_ssh_key = subparsers.add_parser('host-import-ssh-key',
|
host_import_ssh_key = subparsers.add_parser(
|
||||||
help='Import host SSH key')
|
'host-import-ssh-key', help='Import host SSH key')
|
||||||
host_import_ssh_key.add_argument('hostname',
|
host_import_ssh_key.add_argument(
|
||||||
help='Fully-qualified hostname')
|
'hostname', help='Fully-qualified hostname')
|
||||||
|
|
||||||
host_publish_key = subparsers.add_parser(
|
host_publish_key = subparsers.add_parser(
|
||||||
'host-publish-key',
|
'host-publish-key', help='Push host key to keyserver')
|
||||||
help='Push host key to keyserver')
|
|
||||||
host_publish_key.add_argument(
|
host_publish_key.add_argument(
|
||||||
'keyid', nargs='*',
|
'key_ids', nargs='*', help='Optional list of KEYIDs')
|
||||||
help='Optional list of KEYIDs')
|
|
||||||
|
|
||||||
return parser.parse_args()
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
def subcommand_host_show_key(arguments):
|
def subcommand_host_show_keys(arguments):
|
||||||
"""Show host key fingerprint."""
|
"""Show host key fingerprints."""
|
||||||
keyid = ' '.join(arguments.keyid)
|
|
||||||
try:
|
try:
|
||||||
output = subprocess.check_output([HOST_TOOL, 'show-key', keyid])
|
output = subprocess.check_output(
|
||||||
|
['monkeysphere-host', 'show-keys'] + arguments.key_ids)
|
||||||
except subprocess.CalledProcessError:
|
except subprocess.CalledProcessError:
|
||||||
# no keys available
|
# no keys available
|
||||||
return
|
return
|
||||||
@ -75,32 +72,29 @@ def subcommand_host_show_key(arguments):
|
|||||||
keys[-1]['pgp_fingerprint'] = line.lstrip('Open PGP fingerprint:')
|
keys[-1]['pgp_fingerprint'] = line.lstrip('Open PGP fingerprint:')
|
||||||
elif line.startswith('ssh fingerprint:'):
|
elif line.startswith('ssh fingerprint:'):
|
||||||
data = line.lstrip('ssh fingerprint:').split()
|
data = line.lstrip('ssh fingerprint:').split()
|
||||||
keys[-1]['ssh_keysize'] = data[0]
|
keys[-1]['ssh_key_size'] = data[0]
|
||||||
keys[-1]['ssh_fingerprint'] = data[1]
|
keys[-1]['ssh_fingerprint'] = data[1]
|
||||||
keys[-1]['ssh_keytype'] = data[2].strip('()')
|
keys[-1]['ssh_key_type'] = data[2].strip('()')
|
||||||
elif line == '':
|
elif line == '':
|
||||||
keys.append(dict())
|
keys.append(dict())
|
||||||
|
|
||||||
for key in keys:
|
print(json.dumps({'keys': keys}))
|
||||||
print(key['pub'], key['date'], key['uid'], key['pgp_fingerprint'],
|
|
||||||
key['ssh_keysize'], key['ssh_fingerprint'], key['ssh_keytype'])
|
|
||||||
|
|
||||||
|
|
||||||
def subcommand_host_import_ssh_key(arguments):
|
def subcommand_host_import_ssh_key(arguments):
|
||||||
"""Import host SSH key."""
|
"""Import host SSH key."""
|
||||||
output = subprocess.check_output(
|
output = subprocess.check_output(
|
||||||
[HOST_TOOL, 'import-key',
|
['monkeysphere-host', 'import-key',
|
||||||
'/etc/ssh/ssh_host_rsa_key', arguments.hostname])
|
'/etc/ssh/ssh_host_rsa_key', arguments.hostname])
|
||||||
print(output.decode())
|
print(output.decode())
|
||||||
|
|
||||||
|
|
||||||
def subcommand_host_publish_key(arguments):
|
def subcommand_host_publish_key(arguments):
|
||||||
"""Push host key to keyserver."""
|
"""Push host key to keyserver."""
|
||||||
keyid = ' '.join(arguments.keyid)
|
|
||||||
# setting TMPDIR as workaround for Debian bug #656750
|
# setting TMPDIR as workaround for Debian bug #656750
|
||||||
proc = subprocess.Popen(
|
proc = subprocess.Popen(
|
||||||
[HOST_TOOL, 'publish-keys', keyid],
|
['monkeysphere-host', 'publish-keys'] + arguments.key_ids,
|
||||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=False,
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
||||||
env=dict(
|
env=dict(
|
||||||
os.environ,
|
os.environ,
|
||||||
TMPDIR='/var/lib/monkeysphere/authentication/tmp/',
|
TMPDIR='/var/lib/monkeysphere/authentication/tmp/',
|
||||||
|
|||||||
@ -32,77 +32,81 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<h2>{% trans "Monkeysphere" %}</h2>
|
<h2>{% trans "Monkeysphere" %}</h2>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
{% blocktrans trimmed with box_name=cfg.box_name %}
|
{% blocktrans trimmed %}
|
||||||
With Monkeysphere, a PGP key can be generated for each domain
|
With Monkeysphere, a PGP key can be generated for each configured domain
|
||||||
serving SSH on this {{ box_name }}. The PGP public key can then be
|
serving SSH. The PGP public key can then be uploaded to the PGP
|
||||||
uploaded to the PGP keyservers. Users connecting to this {{ box_name }}
|
keyservers. Users connecting to this machine through SSH can verify that
|
||||||
through SSH can verify that they are connecting to the correct
|
they are connecting to the correct host. For users to trust the key, at
|
||||||
host. See the
|
least one person (usually the machine owner) must sign the key using the
|
||||||
<a href="http://web.monkeysphere.info/getting-started-ssh/">
|
regular PGP key signing process. See the
|
||||||
Monkeysphere SSH documentation</a> for more details.
|
<a href="http://web.monkeysphere.info/getting-started-ssh/">
|
||||||
{% endblocktrans %}
|
Monkeysphere SSH documentation</a> for more details.
|
||||||
</p>
|
{% endblocktrans %}
|
||||||
|
|
||||||
{% if running %}
|
|
||||||
<p class="running-status-parent">
|
|
||||||
<span class="running-status active"></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>
|
</p>
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div class="row">
|
{% if running %}
|
||||||
<div class="col-sm-8">
|
<p class="running-status-parent">
|
||||||
<table class="table table-bordered table-condensed table-striped">
|
<span class="running-status active"></span>
|
||||||
{% for name_service in status.name_services %}
|
{% trans "Publishing key to keyserver..." %}
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<b>{{ name_service.type }}</b><br>
|
|
||||||
<i>{{ name_service.name }}</i>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{% if name_service.key %}
|
|
||||||
{{ name_service.key.pgp_fingerprint }}
|
|
||||||
{% else %}
|
|
||||||
{% trans "Not Available" %}
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{% if name_service.available %}
|
|
||||||
{% if not name_service.key %}
|
|
||||||
<form class="form" method="post"
|
|
||||||
action="{% url 'monkeysphere:generate' name_service.short_type %}">
|
|
||||||
{% csrf_token %}
|
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary btn-sm pull-right">
|
<form class="form" method="post"
|
||||||
{% trans "Generate PGP Key" %}</button>
|
action="{% url 'monkeysphere:cancel' %}">
|
||||||
</form>
|
{% csrf_token %}
|
||||||
|
|
||||||
{% elif not running %}
|
<button type="submit" class="btn btn-warning btn-sm">
|
||||||
<form class="form" method="post"
|
{% trans "Cancel" %}</button>
|
||||||
action="{% url 'monkeysphere:publish' name_service.key.pgp_fingerprint %}">
|
</form>
|
||||||
{% csrf_token %}
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<button type="submit" class="btn btn-warning btn-sm pull-right">
|
<div class="row">
|
||||||
{% trans "Publish Key" %}</button>
|
<div class="col-sm-8">
|
||||||
</form>
|
<table class="table table-bordered table-condensed table-striped">
|
||||||
{% endif %}
|
<thead>
|
||||||
{% endif %}
|
<tr>
|
||||||
</td>
|
<th>{% trans "Domain" %}</th>
|
||||||
</tr>
|
<th>{% trans "GPG Fingerprint" %}</th>
|
||||||
{% endfor %}
|
<th>{% trans "Actions" %}</th>
|
||||||
</table>
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for domain in status.domains %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ domain.name }}</td>
|
||||||
|
<td>
|
||||||
|
{% if domain.key %}
|
||||||
|
{{ domain.key.pgp_fingerprint }}
|
||||||
|
{% else %}
|
||||||
|
{% trans "Not Available" %}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% if not domain.key %}
|
||||||
|
<form class="form" method="post"
|
||||||
|
action="{% url 'monkeysphere:generate' domain.name %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary btn-sm pull-right">
|
||||||
|
{% trans "Generate PGP Key" %}</button>
|
||||||
|
</form>
|
||||||
|
{% elif not running %}
|
||||||
|
<form class="form" method="post"
|
||||||
|
action="{% url 'monkeysphere:publish' domain.key.pgp_fingerprint %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-warning btn-sm pull-right">
|
||||||
|
{% trans "Publish Key" %}</button>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -26,9 +26,9 @@ from . import views
|
|||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^sys/monkeysphere/$', views.index, name='index'),
|
url(r'^sys/monkeysphere/$', views.index, name='index'),
|
||||||
url(r'^sys/monkeysphere/(?P<service>[\w]+)/generate/$',
|
url(r'^sys/monkeysphere/(?P<domain>[^/]+)/generate/$',
|
||||||
views.generate, name='generate'),
|
views.generate, name='generate'),
|
||||||
url(r'^sys/monkeysphere/(?P<fingerprint>[\w]+)/publish/$',
|
url(r'^sys/monkeysphere/(?P<fingerprint>[0-9A-Fa-f]+)/publish/$',
|
||||||
views.publish, name='publish'),
|
views.publish, name='publish'),
|
||||||
url(r'^sys/monkeysphere/cancel/$', views.cancel, name='cancel'),
|
url(r'^sys/monkeysphere/cancel/$', views.cancel, name='cancel'),
|
||||||
]
|
]
|
||||||
|
|||||||
@ -25,6 +25,7 @@ from django.shortcuts import redirect
|
|||||||
from django.template.response import TemplateResponse
|
from django.template.response import TemplateResponse
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.views.decorators.http import require_POST
|
from django.views.decorators.http import require_POST
|
||||||
|
import json
|
||||||
|
|
||||||
from plinth import actions
|
from plinth import actions
|
||||||
from plinth import package
|
from plinth import package
|
||||||
@ -46,19 +47,17 @@ def index(request):
|
|||||||
|
|
||||||
|
|
||||||
@require_POST
|
@require_POST
|
||||||
def generate(request, service):
|
def generate(request, domain):
|
||||||
"""Generate PGP key for SSH service."""
|
"""Generate PGP key for SSH service."""
|
||||||
for domain_type in sorted(names.get_domain_types()):
|
valid_domain = any((domain in domains
|
||||||
if domain_type == service:
|
for domains in names.domains.values()))
|
||||||
domain = names.get_domain(domain_type)
|
if valid_domain:
|
||||||
|
try:
|
||||||
try:
|
actions.superuser_run(
|
||||||
actions.superuser_run(
|
'monkeysphere', ['host-import-ssh-key', 'ssh://' + domain])
|
||||||
'monkeysphere',
|
messages.success(request, _('Generated PGP key.'))
|
||||||
['host-import-ssh-key', 'ssh://' + domain])
|
except actions.ActionError as exception:
|
||||||
messages.success(request, _('Generated PGP key'))
|
messages.error(request, str(exception))
|
||||||
except actions.ActionError as exception:
|
|
||||||
messages.error(request, str(exception))
|
|
||||||
|
|
||||||
return redirect(reverse_lazy('monkeysphere:index'))
|
return redirect(reverse_lazy('monkeysphere:index'))
|
||||||
|
|
||||||
@ -81,47 +80,28 @@ def cancel(request):
|
|||||||
if publish_process:
|
if publish_process:
|
||||||
publish_process.terminate()
|
publish_process.terminate()
|
||||||
publish_process = None
|
publish_process = None
|
||||||
messages.info(request, _('Cancelled publish key.'))
|
messages.info(request, _('Cancelled key publishing.'))
|
||||||
|
|
||||||
return redirect(reverse_lazy('monkeysphere:index'))
|
return redirect(reverse_lazy('monkeysphere:index'))
|
||||||
|
|
||||||
|
|
||||||
def get_status():
|
def get_status():
|
||||||
"""Get the current status."""
|
"""Get the current status."""
|
||||||
output = actions.superuser_run('monkeysphere', ['host-show-key'])
|
output = actions.superuser_run('monkeysphere', ['host-show-keys'])
|
||||||
keys = []
|
keys = {}
|
||||||
for line in output.split('\n'):
|
for key in json.loads(output)['keys']:
|
||||||
data = line.strip().split()
|
key['name'] = key['uid'].replace('ssh://', '')
|
||||||
if data and len(data) == 7:
|
keys[key['name']] = key
|
||||||
keys.append(dict())
|
|
||||||
keys[-1]['pub'] = data[0]
|
|
||||||
keys[-1]['date'] = data[1]
|
|
||||||
keys[-1]['uid'] = data[2]
|
|
||||||
keys[-1]['name'] = data[2].replace('ssh://', '')
|
|
||||||
keys[-1]['pgp_fingerprint'] = data[3]
|
|
||||||
keys[-1]['ssh_keysize'] = data[4]
|
|
||||||
keys[-1]['ssh_fingerprint'] = data[5]
|
|
||||||
keys[-1]['ssh_keytype'] = data[6]
|
|
||||||
|
|
||||||
name_services = []
|
domains = []
|
||||||
for domain_type in sorted(names.get_domain_types()):
|
for domains_of_a_type in names.domains.values():
|
||||||
domain = names.get_domain(domain_type)
|
for domain in domains_of_a_type:
|
||||||
name_services.append({
|
domains.append({
|
||||||
'type': names.get_description(domain_type),
|
'name': domain,
|
||||||
'short_type': domain_type,
|
'key': keys.get(domain),
|
||||||
'name': domain or _('Not Available'),
|
})
|
||||||
'available': bool(domain),
|
|
||||||
'key': None,
|
|
||||||
})
|
|
||||||
|
|
||||||
# match up keys with name services
|
return {'domains': domains}
|
||||||
for key in keys:
|
|
||||||
for name_service in name_services:
|
|
||||||
if key['name'] == name_service['name']:
|
|
||||||
name_service['key'] = key
|
|
||||||
continue
|
|
||||||
|
|
||||||
return {'name_services': name_services}
|
|
||||||
|
|
||||||
|
|
||||||
def _collect_publish_result(request):
|
def _collect_publish_result(request):
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user