From f4601e7b050f7e6ab2e2548fc26cb8bbd64947a9 Mon Sep 17 00:00:00 2001
From: Sunil Mohan Adapa
Date: Tue, 8 Mar 2016 23:19:52 +0530
Subject: [PATCH] monkeysphere: Reorganize around keys instead
- Read Apache configuration to find the list of all available
certificates and their associated domains. Use this for setting UIDs
properly.
- Solve the issue of re-importing renewed certficiate. Use the SSH
fingerprints as unique keys instead of domain names. Compute SSH
fingerprints for SSH keys and HTTPS certficates inorder accurately
identify if they are currently imported into monkeysphere.
- Allow having more than one domains for a certficiate. Add action to
import new domains to an existing monkeysphere OpenPGP key.
- Import only once for a given certficiate and keep adding UIDs when
domains get added.
- Merge services SSH and HTTPS giving us the ability to deals with many
more services. Remove special handling for different kinds of
certificate sources.
- Supress monkeysphere prompts in case of reusing UIDs.
---
actions/monkeysphere | 195 +++++++++++------
.../monkeysphere/templates/monkeysphere.html | 203 +++++++-----------
.../templates/monkeysphere_details.html | 38 +++-
plinth/modules/monkeysphere/urls.py | 8 +-
plinth/modules/monkeysphere/views.py | 130 ++++-------
5 files changed, 278 insertions(+), 296 deletions(-)
diff --git a/actions/monkeysphere b/actions/monkeysphere
index ef37de3f7..ed280dc85 100755
--- a/actions/monkeysphere
+++ b/actions/monkeysphere
@@ -21,8 +21,10 @@ Configuration helper for monkeysphere.
"""
import argparse
+import augeas
import json
import os
+import re
import subprocess
@@ -32,24 +34,16 @@ def parse_arguments():
subparsers = parser.add_subparsers(dest='subcommand', help='Sub command')
host_show_keys = subparsers.add_parser(
- 'host-show-keys', help='Show host key fingerprints')
+ 'host-show-keys', help='Show imported/importable keys')
host_show_keys.add_argument(
- 'key_ids', nargs='*', help='Optional list of KEYIDs')
+ 'key_id', nargs='?', help='Optional KEYID to retrieve details for')
- host_import_ssh_key = subparsers.add_parser(
- 'host-import-ssh-key', help='Import host SSH key')
- host_import_ssh_key.add_argument(
- 'domain', help='Fully-qualified domain name')
-
- host_import_snakeoil_key = subparsers.add_parser(
- 'host-import-snakeoil-key', help='Import host snakeoil key')
- host_import_snakeoil_key.add_argument(
- 'domain', help='Fully-qualified domain name')
-
- host_import_letsencrypt_key = subparsers.add_parser(
- 'host-import-letsencrypt-key', help="Import Let's Encrypt key")
- host_import_letsencrypt_key.add_argument(
- 'domain', help='Fully-qualified domain name')
+ 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')
@@ -59,16 +53,75 @@ def parse_arguments():
return parser.parse_args()
-def subcommand_host_show_keys(arguments):
- """Show host key fingerprints."""
- try:
+def get_ssh_keys():
+ """Return all SSH keys."""
+ keys = {}
+
+ key_files = ['/etc/ssh/ssh_host_rsa_key']
+ for key_file in key_files:
output = subprocess.check_output(
- ['monkeysphere-host', 'show-keys'] + arguments.key_ids,
+ ['ssh-keygen', '-l', '-E', 'MD5', '-f', key_file])
+ fingerprint = output.decode().split()[1].lstrip('MD5:')
+ keys[fingerprint] = {'ssh_fingerprint': fingerprint,
+ 'service': 'ssh',
+ 'key_file': key_file,
+ 'available_domains': ['*']}
+
+ return keys
+
+
+def get_pem_ssh_fingerprint(pem_file):
+ """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', 'md5', '-f', '/dev/stdin'],
+ input=ssh_public_key)
+
+ return fingerprint.decode().split()[1].lstrip('MD5:')
+
+
+def get_https_keys():
+ """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.load()
+
+ 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'])
+ 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
- print(json.dumps({'keys': []}))
- return
+ return {}
# parse output
keys = [dict()]
@@ -79,9 +132,15 @@ def subcommand_host_show_keys(arguments):
keys[-1]['pub'] = data[0]
keys[-1]['date'] = data[1]
elif line.startswith('uid'):
- keys[-1]['uid'] = line.lstrip('uid').strip()
+ uid = line.lstrip('uid').strip()
+ keys[-1].setdefault('uids', []).append(uid)
+ matches = re.match('([a-zA-Z]*)://(.*)(:\d*)?', uid)
+ keys[-1]['service'] = matches.group(1)
+ keys[-1].setdefault('imported_domains', []) \
+ .append(matches.group(2))
elif line.startswith('OpenPGP fingerprint:'):
- keys[-1]['pgp_fingerprint'] = line.lstrip('Open PGP 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]
@@ -90,52 +149,60 @@ def subcommand_host_show_keys(arguments):
elif line == '':
keys.append(dict())
- print(json.dumps({'keys': keys}))
+ return {key['ssh_fingerprint']: key for key in keys}
-def subcommand_host_import_ssh_key(arguments):
+def get_merged_keys(key_id=None):
+ """Return merged list of system and monkeysphere keys."""
+ keys = get_monkeysphere_keys(key_id)
+
+ system_keys = list(get_ssh_keys().items()) + list(get_https_keys().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
+
+
+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."""
- output = subprocess.check_output(
- ['monkeysphere-host', 'import-key',
- '/etc/ssh/ssh_host_rsa_key', 'ssh://' + arguments.domain])
- print(output.decode())
+ 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
-def subcommand_host_import_snakeoil_key(arguments):
- """Import host snakeoil key."""
- proc = subprocess.Popen(
- ['monkeysphere-host', 'import-key',
- '/etc/ssl/private/ssl-cert-snakeoil.key',
- 'https://' + arguments.domain],
- stdout=subprocess.PIPE, stderr=subprocess.PIPE,
- env=dict(
- os.environ,
- MONKEYSPHERE_PROMPT='false'))
- output, error = proc.communicate()
- output, error = output.decode(), error.decode()
- if proc.returncode != 0:
- raise Exception(output, error)
+ if 'openpgp_fingerprint' not in key and not second_run:
+ env = dict(os.environ, MONKEYSPHERE_PROMPT='false')
+ subprocess.check_call(
+ ['monkeysphere-host', 'import-key',
+ 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
- print(output)
-
-
-def subcommand_host_import_letsencrypt_key(arguments):
- """Import Let's Encrypt key."""
- proc = subprocess.Popen(
- ['monkeysphere-host', 'import-key',
- os.path.join('/etc/letsencrypt/live',
- arguments.domain, 'privkey.pem'),
- 'https://' + arguments.domain],
- stdout=subprocess.PIPE, stderr=subprocess.PIPE,
- env=dict(
- os.environ,
- MONKEYSPHERE_PROMPT='false'))
- output, error = proc.communicate()
- output, error = output.decode(), error.decode()
- if proc.returncode != 0:
- raise Exception(output, error)
-
- print(output)
+ 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):
diff --git a/plinth/modules/monkeysphere/templates/monkeysphere.html b/plinth/modules/monkeysphere/templates/monkeysphere.html
index d9a0204f2..ec9985601 100644
--- a/plinth/modules/monkeysphere/templates/monkeysphere.html
+++ b/plinth/modules/monkeysphere/templates/monkeysphere.html
@@ -27,6 +27,25 @@
{% endif %}
+
+
{% endblock %}
@@ -47,155 +66,87 @@
{% endif %}
- {% trans "Secure Shell (SSH)" %}
-
-
-
+
+
- | {% trans "Domain" %} |
+ {% trans "Service" %} |
+ {% trans "Domains" %} |
{% trans "OpenPGP Fingerprint" %} |
- {% trans "Actions" %} |
+ |
- {% for domain in status.domains %}
+ {% for key in status.keys.values %}
- | {{ domain.name }} |
- {% if domain.key %}
-
- {{ domain.key.pgp_fingerprint }}
-
+ {% if key.service == 'ssh' %}
+ {% trans "Secure Shell" %}
+ {% elif key.service == 'https' %}
+ {% trans "Web Server" %}
{% else %}
- {% trans "Not Available" %}
+ {% trans "Other" %}
{% endif %}
|
- {% if not domain.key %}
- |
+
+ {% if key.openpgp_fingerprint %}
+
+ {{ key.openpgp_fingerprint }}
+
+ {% else %}
+ {% trans "-" %}
+ {% endif %}
+ |
+
+ {% if not key.openpgp_fingerprint %}
+
- {% elif not running %}
-
- {% endif %}
- |
-
- {% endfor %}
-
-
-
-
+ {% endif %}
+ {% if key.imported_domains != key.available_domains %}
+