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 json
import os
import subprocess
HOST_TOOL = 'monkeysphere-host'
def parse_arguments():
"""Return parsed command line arguments as dictionary."""
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='subcommand', help='Sub command')
host_show_key = subparsers.add_parser('host-show-key',
help='Show host key fingerprint')
host_show_key.add_argument('keyid', nargs='*',
help='Optional list of KEYIDs')
host_show_keys = subparsers.add_parser(
'host-show-keys', help='Show host key fingerprints')
host_show_keys.add_argument(
'key_ids', nargs='*', help='Optional list of KEYIDs')
host_import_ssh_key = subparsers.add_parser('host-import-ssh-key',
help='Import host SSH key')
host_import_ssh_key.add_argument('hostname',
help='Fully-qualified hostname')
host_import_ssh_key = subparsers.add_parser(
'host-import-ssh-key', help='Import host SSH key')
host_import_ssh_key.add_argument(
'hostname', help='Fully-qualified hostname')
host_publish_key = subparsers.add_parser(
'host-publish-key',
help='Push host key to keyserver')
'host-publish-key', help='Push host key to keyserver')
host_publish_key.add_argument(
'keyid', nargs='*',
help='Optional list of KEYIDs')
'key_ids', nargs='*', help='Optional list of KEYIDs')
return parser.parse_args()
def subcommand_host_show_key(arguments):
"""Show host key fingerprint."""
keyid = ' '.join(arguments.keyid)
def subcommand_host_show_keys(arguments):
"""Show host key fingerprints."""
try:
output = subprocess.check_output([HOST_TOOL, 'show-key', keyid])
output = subprocess.check_output(
['monkeysphere-host', 'show-keys'] + arguments.key_ids)
except subprocess.CalledProcessError:
# no keys available
return
@ -75,32 +72,29 @@ def subcommand_host_show_key(arguments):
keys[-1]['pgp_fingerprint'] = line.lstrip('Open PGP fingerprint:')
elif line.startswith('ssh fingerprint:'):
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_keytype'] = data[2].strip('()')
keys[-1]['ssh_key_type'] = data[2].strip('()')
elif line == '':
keys.append(dict())
for key in keys:
print(key['pub'], key['date'], key['uid'], key['pgp_fingerprint'],
key['ssh_keysize'], key['ssh_fingerprint'], key['ssh_keytype'])
print(json.dumps({'keys': keys}))
def subcommand_host_import_ssh_key(arguments):
"""Import host SSH key."""
output = subprocess.check_output(
[HOST_TOOL, 'import-key',
['monkeysphere-host', 'import-key',
'/etc/ssh/ssh_host_rsa_key', arguments.hostname])
print(output.decode())
def subcommand_host_publish_key(arguments):
"""Push host key to keyserver."""
keyid = ' '.join(arguments.keyid)
# setting TMPDIR as workaround for Debian bug #656750
proc = subprocess.Popen(
[HOST_TOOL, 'publish-keys', keyid],
stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=False,
['monkeysphere-host', 'publish-keys'] + arguments.key_ids,
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
env=dict(
os.environ,
TMPDIR='/var/lib/monkeysphere/authentication/tmp/',

View File

@ -32,77 +32,81 @@
{% block content %}
<h2>{% trans "Monkeysphere" %}</h2>
<h2>{% trans "Monkeysphere" %}</h2>
<p>
{% blocktrans trimmed with box_name=cfg.box_name %}
With Monkeysphere, a PGP key can be generated for each domain
serving SSH on this {{ box_name }}. The PGP public key can then be
uploaded to the PGP keyservers. Users connecting to this {{ box_name }}
through SSH can verify that they are connecting to the correct
host. See the
<a href="http://web.monkeysphere.info/getting-started-ssh/">
Monkeysphere SSH documentation</a> for more details.
{% endblocktrans %}
</p>
{% 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>
{% blocktrans trimmed %}
With Monkeysphere, a PGP key can be generated for each configured domain
serving SSH. The PGP public key can then be uploaded to the PGP
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 PGP key signing process. See the
<a href="http://web.monkeysphere.info/getting-started-ssh/">
Monkeysphere SSH documentation</a> for more details.
{% endblocktrans %}
</p>
{% endif %}
<div class="row">
<div class="col-sm-8">
<table class="table table-bordered table-condensed table-striped">
{% for name_service in status.name_services %}
<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 %}
{% if running %}
<p class="running-status-parent">
<span class="running-status active"></span>
{% trans "Publishing key to keyserver..." %}
<button type="submit" class="btn btn-primary btn-sm pull-right">
{% trans "Generate PGP Key" %}</button>
</form>
<form class="form" method="post"
action="{% url 'monkeysphere:cancel' %}">
{% csrf_token %}
{% elif not running %}
<form class="form" method="post"
action="{% url 'monkeysphere:publish' name_service.key.pgp_fingerprint %}">
{% csrf_token %}
<button type="submit" class="btn btn-warning btn-sm">
{% trans "Cancel" %}</button>
</form>
</p>
{% endif %}
<button type="submit" class="btn btn-warning btn-sm pull-right">
{% trans "Publish Key" %}</button>
</form>
{% endif %}
{% endif %}
</td>
</tr>
{% endfor %}
</table>
<div class="row">
<div class="col-sm-8">
<table class="table table-bordered table-condensed table-striped">
<thead>
<tr>
<th>{% trans "Domain" %}</th>
<th>{% trans "GPG Fingerprint" %}</th>
<th>{% trans "Actions" %}</th>
</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>
{% endblock %}

View File

@ -26,9 +26,9 @@ from . import views
urlpatterns = [
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'),
url(r'^sys/monkeysphere/(?P<fingerprint>[\w]+)/publish/$',
url(r'^sys/monkeysphere/(?P<fingerprint>[0-9A-Fa-f]+)/publish/$',
views.publish, name='publish'),
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.utils.translation import ugettext as _
from django.views.decorators.http import require_POST
import json
from plinth import actions
from plinth import package
@ -46,19 +47,17 @@ def index(request):
@require_POST
def generate(request, service):
def generate(request, domain):
"""Generate PGP key for SSH service."""
for domain_type in sorted(names.get_domain_types()):
if domain_type == service:
domain = names.get_domain(domain_type)
try:
actions.superuser_run(
'monkeysphere',
['host-import-ssh-key', 'ssh://' + domain])
messages.success(request, _('Generated PGP key'))
except actions.ActionError as exception:
messages.error(request, str(exception))
valid_domain = any((domain in domains
for domains in names.domains.values()))
if valid_domain:
try:
actions.superuser_run(
'monkeysphere', ['host-import-ssh-key', 'ssh://' + domain])
messages.success(request, _('Generated PGP key.'))
except actions.ActionError as exception:
messages.error(request, str(exception))
return redirect(reverse_lazy('monkeysphere:index'))
@ -81,47 +80,28 @@ def cancel(request):
if publish_process:
publish_process.terminate()
publish_process = None
messages.info(request, _('Cancelled publish key.'))
messages.info(request, _('Cancelled key publishing.'))
return redirect(reverse_lazy('monkeysphere:index'))
def get_status():
"""Get the current status."""
output = actions.superuser_run('monkeysphere', ['host-show-key'])
keys = []
for line in output.split('\n'):
data = line.strip().split()
if data and len(data) == 7:
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]
output = actions.superuser_run('monkeysphere', ['host-show-keys'])
keys = {}
for key in json.loads(output)['keys']:
key['name'] = key['uid'].replace('ssh://', '')
keys[key['name']] = key
name_services = []
for domain_type in sorted(names.get_domain_types()):
domain = names.get_domain(domain_type)
name_services.append({
'type': names.get_description(domain_type),
'short_type': domain_type,
'name': domain or _('Not Available'),
'available': bool(domain),
'key': None,
})
domains = []
for domains_of_a_type in names.domains.values():
for domain in domains_of_a_type:
domains.append({
'name': domain,
'key': keys.get(domain),
})
# match up keys with name services
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}
return {'domains': domains}
def _collect_publish_result(request):