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 @@
+
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