From 35548a2c9ae7efae920ec7c90addad272b5520c3 Mon Sep 17 00:00:00 2001
From: Radek Czajka
Date: Mon, 1 Apr 2019 11:45:14 +0200
Subject: [PATCH] More SSH management.
---
src/cas/static/css/main.css | 7 +-
src/ssh_keys/admin.py | 9 ++-
src/ssh_keys/locale/pl/LC_MESSAGES/django.mo | Bin 1056 -> 1410 bytes
src/ssh_keys/locale/pl/LC_MESSAGES/django.po | 49 ++++++++++----
.../migrations/0003_auto_20190401_0923.py | 35 ++++++++++
.../migrations/0004_sshkey_last_seen_at.py | 18 ++++++
.../migrations/0005_auto_20190401_0938.py | 17 +++++
src/ssh_keys/models.py | 14 +++-
.../ssh_keys/sshkey_confirm_delete.html | 8 ++-
.../templates/ssh_keys/sshkey_list.html | 20 +++---
src/ssh_keys/urls.py | 1 +
src/ssh_keys/utils.py | 60 ++++++++++++++++++
src/ssh_keys/views.py | 58 ++++++++++++++++-
13 files changed, 269 insertions(+), 27 deletions(-)
create mode 100644 src/ssh_keys/migrations/0003_auto_20190401_0923.py
create mode 100644 src/ssh_keys/migrations/0004_sshkey_last_seen_at.py
create mode 100644 src/ssh_keys/migrations/0005_auto_20190401_0938.py
create mode 100644 src/ssh_keys/utils.py
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 2268d5af7da8126f2c2380a1c453e977076c486d..5daa4b478f2302405daaa71dec18e35a564853cc 100644
GIT binary patch
delta 767
zcmZY6J!lj`6bJCPxoEzO(L^C6MqVLE5KS&5A_2i7g_wv%gn)=JcRSo}_6u)kv+NaF
zNE53xOR6+h7PbkbIV*F0QrHFr!PeG7@c-7m%135@yF0UQ{&V>_yf>C#P7WRt>K4Ww
zj4s9;##;*))H$rd3pfV9!7K1P9ELyPJp2V`;miQhRrnZ6zo&2nzJapdhC?v;_ncEw
zE!c&lnD|gFD5~cpC;>jfYw$B1gs1QXn|7fDdXJYAFohCezw!Xy$MYd%_yx-POE^tA
z(GT3*#EU;r0!(0563;++z6T}w0+e-6s`(d{uOa^E9WF9&L;0n4Ao7(pg)7>ZedP50
zn%uyhoK7~B)Hn>?z>t$lUB{4rjWm`pQqsI%vayt$e7u;q=89+5R=%z?R(@M6J7YIw
z<7l>(Q4JOAN3@}0!?D&Oy461
zC%rDG+)y1C!Cu8inb3_mb?uI;!i-|C{q@)|a*|hmg`iY^uUT%(n3)O4?(HS5sCRU>
Z&j=i)s`y=-y&L`45c_4ui_*~b=r2zGh}!@F
delta 417
zcmXxfu}i~16vy$GHq~kyMd?{f)X=GYzvwqaS4dK!ciS{bQBa9
zMHe0XCvw>OVkOqKusuJo+VPm7FKW#%ea9n
z*v2|0IE808jTfkMmwA7Uv-CHpc@JodWb(}Bj(VX=9HgAiV}=@diVZwR7oSk)K5zy<
zQG>to{yWzuUFYUdA6`SQ_?^{Wf4C~mzgOx-I;?^JOaFo_eWeuiqj|HI&BW?7y4_7T
zj#9}zkZ{=VM}tJd{iv5jJ8mx#&dq(H?F6fV=la3A?>D{Hty9b_W9&{X-W!ESE&;Nw
WyGXZYt5mO!ULVsW9v%*MWBCDv@j6QY
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