From: Radek Czajka Date: Mon, 1 Apr 2019 09:45:14 +0000 (+0200) Subject: More SSH management. X-Git-Url: https://git.mdrn.pl/cas.git/commitdiff_plain/35548a2c9ae7efae920ec7c90addad272b5520c3 More SSH management. --- diff --git a/src/cas/static/css/main.css b/src/cas/static/css/main.css index 134f3bf..1329ac0 100644 --- a/src/cas/static/css/main.css +++ b/src/cas/static/css/main.css @@ -135,5 +135,10 @@ footer, #content_push { } code.key { - word-wrap: break-word; + white-space: nowrap; + width: 100%; + overflow: hidden; + text-overflow: ellipsis; + display: block; + color: #666; } diff --git a/src/ssh_keys/admin.py b/src/ssh_keys/admin.py index b907416..6e75313 100644 --- a/src/ssh_keys/admin.py +++ b/src/ssh_keys/admin.py @@ -1,5 +1,12 @@ from django.contrib import admin +from django.db.models import F from .models import SSHKey -admin.site.register(SSHKey) +class SSHKeyAdmin(admin.ModelAdmin): + fields = ['user', 'key', 'algorithm', 'bit_length', 'md5_hash', 'created_at', 'last_seen_at'] + readonly_fields = ['algorithm', 'bit_length', 'md5_hash', 'created_at', 'last_seen_at'] + list_display = ['comment', 'last_seen_at', 'user', 'md5_hash', 'algorithm', 'bit_length', 'created_at'] + ordering = (F('last_seen_at').desc(nulls_last=True),) + +admin.site.register(SSHKey, SSHKeyAdmin) diff --git a/src/ssh_keys/locale/pl/LC_MESSAGES/django.mo b/src/ssh_keys/locale/pl/LC_MESSAGES/django.mo index 2268d5a..5daa4b4 100644 Binary files a/src/ssh_keys/locale/pl/LC_MESSAGES/django.mo and b/src/ssh_keys/locale/pl/LC_MESSAGES/django.mo differ diff --git a/src/ssh_keys/locale/pl/LC_MESSAGES/django.po b/src/ssh_keys/locale/pl/LC_MESSAGES/django.po index 29bf9f8..b2fc0b1 100644 --- a/src/ssh_keys/locale/pl/LC_MESSAGES/django.po +++ b/src/ssh_keys/locale/pl/LC_MESSAGES/django.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-03-30 23:22+0100\n" -"PO-Revision-Date: 2019-03-30 23:22+0100\n" +"POT-Creation-Date: 2019-04-01 11:41+0200\n" +"PO-Revision-Date: 2019-04-01 11:42+0200\n" "Last-Translator: \n" "Language-Team: \n" "Language: pl\n" @@ -20,27 +20,43 @@ msgstr "" "%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n" "X-Generator: Poedit 2.0.6\n" -#: apps.py:7 models.py:15 templates/ssh_keys/sshkey_list.html:13 +#: apps.py:7 models.py:20 templates/ssh_keys/sshkey_list.html:13 msgid "SSH keys" msgstr "Klucze SSH" -#: models.py:7 +#: models.py:8 msgid "user" msgstr "użytkownik" -#: models.py:8 +#: models.py:9 msgid "key" msgstr "klucz" -#: models.py:9 +#: models.py:10 msgid "comment" msgstr "komentarz" -#: models.py:10 +#: models.py:11 +msgid "algorithm" +msgstr "algorytm" + +#: models.py:12 +msgid "bit length" +msgstr "długość bitowa" + +#: models.py:13 +msgid "MD5 hash" +msgstr "skrót MD5" + +#: models.py:14 msgid "created at" msgstr "utworzony" -#: models.py:14 +#: models.py:15 +msgid "last seen at" +msgstr "ostatnio widziany" + +#: models.py:19 msgid "SSH key" msgstr "klucz SSH" @@ -48,7 +64,7 @@ msgstr "klucz SSH" msgid "Add SSH key" msgstr "Dodaj klucz SSH" -#: templates/ssh_keys/sshkey_add.html:12 templates/ssh_keys/sshkey_list.html:27 +#: templates/ssh_keys/sshkey_add.html:12 templates/ssh_keys/sshkey_list.html:31 msgid "Add" msgstr "Dodaj" @@ -61,11 +77,22 @@ msgid "Are you sure you want to delete this key?" msgstr "Czy na pewno chcesz usunąć ten klucz?" #: templates/ssh_keys/sshkey_confirm_delete.html:14 -#: templates/ssh_keys/sshkey_list.html:18 msgid "Added at" msgstr "Dodano" #: templates/ssh_keys/sshkey_confirm_delete.html:17 -#: templates/ssh_keys/sshkey_list.html:20 +#: templates/ssh_keys/sshkey_list.html:24 msgid "Delete" msgstr "Usuń" + +#: templates/ssh_keys/sshkey_list.html:20 +msgid "Added" +msgstr "Dodany" + +#: templates/ssh_keys/sshkey_list.html:21 +msgid "Last seen" +msgstr "Ostatnio widziany" + +#: views.py:34 +msgid "Key already in the database." +msgstr "Klucz jest już w bazie." diff --git a/src/ssh_keys/migrations/0003_auto_20190401_0923.py b/src/ssh_keys/migrations/0003_auto_20190401_0923.py new file mode 100644 index 0000000..8d7b1c5 --- /dev/null +++ b/src/ssh_keys/migrations/0003_auto_20190401_0923.py @@ -0,0 +1,35 @@ +# Generated by Django 2.1.7 on 2019-04-01 09:23 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ssh_keys', '0002_auto_20190330_2220'), + ] + + operations = [ + migrations.AddField( + model_name='sshkey', + name='algorithm', + field=models.CharField(default='', editable=False, max_length=32, verbose_name='algorithm'), + preserve_default=False, + ), + migrations.AddField( + model_name='sshkey', + name='bit_length', + field=models.IntegerField(editable=False, null=True, verbose_name='bit length'), + ), + migrations.AddField( + model_name='sshkey', + name='md5_hash', + field=models.CharField(default='', editable=False, max_length=128, verbose_name='MD5 hash'), + preserve_default=False, + ), + migrations.AlterField( + model_name='sshkey', + name='comment', + field=models.CharField(editable=False, max_length=255, verbose_name='comment'), + ), + ] diff --git a/src/ssh_keys/migrations/0004_sshkey_last_seen_at.py b/src/ssh_keys/migrations/0004_sshkey_last_seen_at.py new file mode 100644 index 0000000..9765120 --- /dev/null +++ b/src/ssh_keys/migrations/0004_sshkey_last_seen_at.py @@ -0,0 +1,18 @@ +# Generated by Django 2.1.7 on 2019-04-01 09:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ssh_keys', '0003_auto_20190401_0923'), + ] + + operations = [ + migrations.AddField( + model_name='sshkey', + name='last_seen_at', + field=models.DateTimeField(editable=False, null=True, verbose_name='last seen at'), + ), + ] diff --git a/src/ssh_keys/migrations/0005_auto_20190401_0938.py b/src/ssh_keys/migrations/0005_auto_20190401_0938.py new file mode 100644 index 0000000..b602dcf --- /dev/null +++ b/src/ssh_keys/migrations/0005_auto_20190401_0938.py @@ -0,0 +1,17 @@ +# Generated by Django 2.1.7 on 2019-04-01 09:38 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('ssh_keys', '0004_sshkey_last_seen_at'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='sshkey', + unique_together={('algorithm', 'bit_length', 'md5_hash')}, + ), + ] diff --git a/src/ssh_keys/models.py b/src/ssh_keys/models.py index 18b887e..e4841ca 100644 --- a/src/ssh_keys/models.py +++ b/src/ssh_keys/models.py @@ -1,22 +1,32 @@ from django.conf import settings from django.db import models from django.utils.translation import ugettext_lazy as _ +from .utils import get_key_details class SSHKey(models.Model): user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, verbose_name=_('user')) key = models.TextField(_('key')) - comment = models.CharField(_('comment'), max_length=255, blank=True) + comment = models.CharField(_('comment'), max_length=255, editable=False) + algorithm = models.CharField(_('algorithm'), max_length=32, editable=False) + bit_length = models.IntegerField(_('bit length'), null=True, editable=False) + md5_hash = models.CharField(_('MD5 hash'), max_length=128, editable=False) created_at = models.DateTimeField(_('created at'), auto_now_add=True) + last_seen_at = models.DateTimeField(_('last seen at'), null=True, editable=False) class Meta: ordering = ['created_at'] verbose_name = _('SSH key') verbose_name_plural = _('SSH keys') + unique_together = [('algorithm', 'bit_length', 'md5_hash')] def __str__(self): return self.comment def save(self, *args, **kwargs): - self.comment = self.key.rsplit()[-1][:255] + det = get_key_details(self.key) + self.comment = det['comment'][:255] + self.algorithm = det['algo'] + self.bit_length = det['bits'] + self.md5_hash = det['md5'] return super().save(*args, **kwargs) diff --git a/src/ssh_keys/templates/ssh_keys/sshkey_confirm_delete.html b/src/ssh_keys/templates/ssh_keys/sshkey_confirm_delete.html index c1217d1..ee54777 100644 --- a/src/ssh_keys/templates/ssh_keys/sshkey_confirm_delete.html +++ b/src/ssh_keys/templates/ssh_keys/sshkey_confirm_delete.html @@ -10,9 +10,13 @@
{% csrf_token %}

+ {{ object.comment }} ({{ object.algorithm}} {{ object.bit_length }})
+ {{ object.md5_hash }} {{ object.key }}
- {% trans "Added at" %} {{ object.created_at }}. -

+ {% trans "Added" %}: {{ object.created_at }}
+ {% trans "Last seen" %}: {{ object.last_seen_at|default:"–" }}
+

+ diff --git a/src/ssh_keys/templates/ssh_keys/sshkey_list.html b/src/ssh_keys/templates/ssh_keys/sshkey_list.html index 9dea954..abd1b10 100644 --- a/src/ssh_keys/templates/ssh_keys/sshkey_list.html +++ b/src/ssh_keys/templates/ssh_keys/sshkey_list.html @@ -13,14 +13,18 @@

{% trans "SSH keys" %}

{% for key in object_list %} -

- {{ key.key }}
- {% trans "Added at" %} {{ key.created_at }}
- - {% trans "Delete" %} - -

-
+

+ {{ key.comment }} ({{ key.algorithm}} {{ key.bit_length }})
+ {{ key.md5_hash }} + {{ key.key }}
+ {% trans "Added" %}: {{ key.created_at }}
+ {% trans "Last seen" %}: {{ key.last_seen_at|default:"–" }}
+ + + {% trans "Delete" %} + +

+
{% endfor %} diff --git a/src/ssh_keys/urls.py b/src/ssh_keys/urls.py index deabb5c..db879cb 100644 --- a/src/ssh_keys/urls.py +++ b/src/ssh_keys/urls.py @@ -7,4 +7,5 @@ urlpatterns = [ path('/delete/', views.DeleteSSHKeyView.as_view(), name='ssh_keys_delete'), path('add/', views.AddSSHKeyView.as_view(), name='ssh_keys_add'), + path('seen/', views.ssh_keys_seen), ] diff --git a/src/ssh_keys/utils.py b/src/ssh_keys/utils.py new file mode 100644 index 0000000..e8756a2 --- /dev/null +++ b/src/ssh_keys/utils.py @@ -0,0 +1,60 @@ +from datetime import datetime, timedelta +import re +import subprocess + + +def get_key_details(key): + """ + >>> get_key_details('ssh-dss AAAAB3NzaC1kc3MAAACBAJxrocPXtCxwgg5yvOc1NLFFz/Fql4+7sOgMOkwWO6pxpJ4bPZgzZ0B17/HGKxQaot3Nc7vzdkC3MBrDDbKrX4n9qB9yJBd0Kkr5X0K7SnBKU+7fbg+rloUdYE78LS6ap05+xlJ8dU918DnS3KqcT/YQQXaTLrt/2DUOM1qxCI1XAAAAFQCJXLN0vYx7SIYMQ0zhv9IUT5WhgQAAAIAT2new16avxvs56zU87t1QQe0qwbQEIUygWW6vqnc9Lo9aSf21sM5WAHTkEnTVyiFSI6K6Q6bD2OUMvS2oWaoariW8EFKzg7/pufmThG0oAxkloc3j8gMO2+xuw7yHzP2pd6xgosNkqivpsGT1PKo+vM6x8p9B6PvipHPqhgFHWQAAAIEAhgFE3+gfPpfDIhaPP5Adx4Hm0VO3xBgOtafvunv3kP54kvHuTaD2uLwgcdOsMedv1/tqhJddh4+9ibwhlKbxKLHrQIcGSHCIY/BoA4RnpSBlGoXEc2buLoZ9IwANCIa2mp19Q/v4wwLnTJHabdMkNCiUn8NPEiHUPjgIj1uoCgo= epsilon') + {'algo': 'DSA', 'bits': 1024, 'md5': 'f6:78:a7:9e:6b:41:dc:31:f4:39:12:5f:2b:0c:7a:11', 'comment': 'epsilon'} + """ + process = subprocess.run( + ['ssh-keygen', '-lEmd5', '-f-'], + input=key.encode('utf-8'), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + if process.returncode != 0: + raise ValueError(process.stderr.decode('utf-8')) + + output = process.stdout.decode('utf-8').rstrip() + m = re.match( + r'^(?P\d+) MD5:(?P[a-f0-9:]+) (?P.*) \((?P.*)\)$', + output + ) + return { + 'algo': m.group('algo'), + 'bits': int(m.group('bits')), + 'md5': m.group('md5'), + 'comment': m.group('comment'), + } + + +def parse_log_line(line, year=None, allow_future_days=7): + """ + >>> sorted(parse_log_line( + ... 'Jan 1 2:34:56 heta sshd[4112]: Accepted publickey for localuser from 0.0.0.0 port 33980 ssh2: RSA f6:78:a7:9e:6b:41:dc:31:f4:39:12:5f:2b:0c:7a:11', + ... year=2019).items()) + [('algo', 'RSA'), ('datetime', datetime.datetime(2019, 1, 1, 2, 34, 56)), ('host', 'heta'), ('ip', '0.0.0.0'), ('md5', 'f6:78:a7:9e:6b:41:dc:31:f4:39:12:5f:2b:0c:7a:11'), ('user', 'localuser')] + """ + logline_re = r'^(?P