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:
Sunil Mohan Adapa 2016-01-14 13:47:54 +05:30
parent 70d85cbd6f
commit 6afe350fe5
No known key found for this signature in database
GPG Key ID: 36C361440C9BC971
4 changed files with 117 additions and 139 deletions

View File

@ -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/',

View File

@ -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 %}

View File

@ -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'),
] ]

View File

@ -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):