From 136c8d1ff0aa79e9ccccaf091da020121c50bed5 Mon Sep 17 00:00:00 2001
From: Frank Sauerburger <frank@sauerburger.com>
Date: Fri, 19 Feb 2021 23:51:08 +0100
Subject: [PATCH] Add SSH key list

---
 keys/settings.py                              |  3 +-
 keys/templates/keys/base.html                 | 17 +++++++++++
 keys/urls.py                                  |  1 +
 ssh/__init__.py                               |  0
 ssh/admin.py                                  |  4 +++
 ssh/apps.py                                   |  5 ++++
 ssh/migrations/0001_initial.py                | 25 ++++++++++++++++
 ssh/migrations/0002_auto_20210219_1432.py     | 18 ++++++++++++
 .../0003_sshpublickey_client_cert.py          | 18 ++++++++++++
 ssh/migrations/__init__.py                    |  0
 ssh/models.py                                 | 29 +++++++++++++++++++
 ssh/templates/ssh/sshpublickey_list.html      | 17 +++++++++++
 ssh/tests.py                                  |  3 ++
 ssh/urls.py                                   |  9 ++++++
 ssh/views.py                                  | 14 +++++++++
 15 files changed, 162 insertions(+), 1 deletion(-)
 create mode 100644 keys/templates/keys/base.html
 create mode 100644 ssh/__init__.py
 create mode 100644 ssh/admin.py
 create mode 100644 ssh/apps.py
 create mode 100644 ssh/migrations/0001_initial.py
 create mode 100644 ssh/migrations/0002_auto_20210219_1432.py
 create mode 100644 ssh/migrations/0003_sshpublickey_client_cert.py
 create mode 100644 ssh/migrations/__init__.py
 create mode 100644 ssh/models.py
 create mode 100644 ssh/templates/ssh/sshpublickey_list.html
 create mode 100644 ssh/tests.py
 create mode 100644 ssh/urls.py
 create mode 100644 ssh/views.py

diff --git a/keys/settings.py b/keys/settings.py
index e8e8cc5..e2be4f2 100644
--- a/keys/settings.py
+++ b/keys/settings.py
@@ -39,6 +39,7 @@ INSTALLED_APPS = [
     'django.contrib.staticfiles',
     'owlca',
     'pgp',
+    'ssh',
     'guardian',
 ]
 
@@ -63,7 +64,7 @@ ROOT_URLCONF = 'keys.urls'
 TEMPLATES = [
     {
         'BACKEND': 'django.template.backends.django.DjangoTemplates',
-        'DIRS': [],
+        'DIRS': ["keys/templates"],
         'APP_DIRS': True,
         'OPTIONS': {
             'context_processors': [
diff --git a/keys/templates/keys/base.html b/keys/templates/keys/base.html
new file mode 100644
index 0000000..e96dcda
--- /dev/null
+++ b/keys/templates/keys/base.html
@@ -0,0 +1,17 @@
+<!doctype html>
+<html>
+<head>
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <meta charset="utf-8" />
+  <title>Sauerburger Keys</title>
+</head>
+<body>
+<nav>
+Nav
+</nav>
+<main>
+{% block content %}
+{% endblock %}
+</main>
+</body>
+</html>
diff --git a/keys/urls.py b/keys/urls.py
index dc8a3c4..7acaa72 100644
--- a/keys/urls.py
+++ b/keys/urls.py
@@ -19,5 +19,6 @@ from django.urls import path, include
 urlpatterns = [
     path('pki/', include('owlca.urls')),
     path('pgp/', include('pgp.urls')),
+    path('ssh/', include('ssh.urls')),
     path('admin/', admin.site.urls),
 ]
diff --git a/ssh/__init__.py b/ssh/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/ssh/admin.py b/ssh/admin.py
new file mode 100644
index 0000000..0bb99ca
--- /dev/null
+++ b/ssh/admin.py
@@ -0,0 +1,4 @@
+from django.contrib import admin
+from . import models
+
+admin.site.register(models.SSHPublicKey)
diff --git a/ssh/apps.py b/ssh/apps.py
new file mode 100644
index 0000000..06aa1aa
--- /dev/null
+++ b/ssh/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class SshConfig(AppConfig):
+    name = 'ssh'
diff --git a/ssh/migrations/0001_initial.py b/ssh/migrations/0001_initial.py
new file mode 100644
index 0000000..939e57e
--- /dev/null
+++ b/ssh/migrations/0001_initial.py
@@ -0,0 +1,25 @@
+# Generated by Django 3.1.3 on 2021-02-19 14:30
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='SSHPublicKey',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('hostname', models.CharField(max_length=128)),
+                ('post', models.IntegerField(blank=True, null=True)),
+                ('keytype', models.CharField(max_length=32)),
+                ('key', models.TextField()),
+                ('created', models.DateTimeField(auto_now_add=True)),
+            ],
+        ),
+    ]
diff --git a/ssh/migrations/0002_auto_20210219_1432.py b/ssh/migrations/0002_auto_20210219_1432.py
new file mode 100644
index 0000000..005daa3
--- /dev/null
+++ b/ssh/migrations/0002_auto_20210219_1432.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.1.3 on 2021-02-19 14:32
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('ssh', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.RenameField(
+            model_name='sshpublickey',
+            old_name='post',
+            new_name='port',
+        ),
+    ]
diff --git a/ssh/migrations/0003_sshpublickey_client_cert.py b/ssh/migrations/0003_sshpublickey_client_cert.py
new file mode 100644
index 0000000..9ac12e2
--- /dev/null
+++ b/ssh/migrations/0003_sshpublickey_client_cert.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.1.3 on 2021-02-19 22:42
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('ssh', '0002_auto_20210219_1432'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='sshpublickey',
+            name='client_cert',
+            field=models.BooleanField(default=False),
+        ),
+    ]
diff --git a/ssh/migrations/__init__.py b/ssh/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/ssh/models.py b/ssh/models.py
new file mode 100644
index 0000000..c5635ce
--- /dev/null
+++ b/ssh/models.py
@@ -0,0 +1,29 @@
+from base64 import b64encode, b64decode
+import hashlib
+from django.db import models
+
+class SSHPublicKey(models.Model):
+    hostname = models.CharField(max_length=128)
+    port = models.IntegerField(null=True, blank=True)
+    keytype = models.CharField(max_length=32)
+    key = models.TextField()
+    created = models.DateTimeField(auto_now_add=True)
+    client_cert = models.BooleanField(default=False)
+
+    def server(self):
+        if self.port:
+            return f"{self.hostname}:{self.port}"
+        return self.hostname
+
+    def __str__(self):
+        return f"{self.hostname} ({self.keytype})"
+
+    def fingerprint_sha256(self):
+        raw_key = b64decode(self.key)
+        digest = b64encode(hashlib.sha256(raw_key).digest())
+        return digest.decode().replace("=", "")
+
+    def fingerprint_md5(self):
+        raw_key = b64decode(self.key)
+        digest = hashlib.md5(raw_key).hexdigest()
+        return ":".join(a + b for a, b in zip(digest[::2], digest[1::2]))
diff --git a/ssh/templates/ssh/sshpublickey_list.html b/ssh/templates/ssh/sshpublickey_list.html
new file mode 100644
index 0000000..478fd57
--- /dev/null
+++ b/ssh/templates/ssh/sshpublickey_list.html
@@ -0,0 +1,17 @@
+{% extends 'keys/base.html' %}
+
+{% block content %}
+{% if sshpublickey_list %}
+<ul>
+{% for publickey in sshpublickey_list %}
+    <li>{{ publickey.server }}:<br />
+        SHA256: {{ publickey.fingerprint_sha256 }}<br /> 
+        MD5: {{ publickey.fingerprint_md5 }}<br /> 
+        <pre>{{ publickey.keytype }} {{ publickey.key }} {{ publickey.hostname }}</pre>
+    </li>
+{% endfor %}
+</ul>
+{% else %}
+<p>There are not public keys.</p>
+{% endif %}
+{% endblock %}
diff --git a/ssh/tests.py b/ssh/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/ssh/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/ssh/urls.py b/ssh/urls.py
new file mode 100644
index 0000000..409a872
--- /dev/null
+++ b/ssh/urls.py
@@ -0,0 +1,9 @@
+
+from django.urls import path
+
+from . import views
+
+urlpatterns = [
+    path("", views.PublicKeyListView.as_view(), name="ssh-list"),
+    path("<int:pk>", views.PublicKeyDetailView.as_view(), name="ssh-detail"),
+]
diff --git a/ssh/views.py b/ssh/views.py
new file mode 100644
index 0000000..9725af4
--- /dev/null
+++ b/ssh/views.py
@@ -0,0 +1,14 @@
+from django.shortcuts import render
+from django.views.generic import DetailView, ListView, CreateView
+from guardian.mixins import PermissionRequiredMixin, PermissionListMixin
+from . import models
+
+
+class PublicKeyListView(PermissionListMixin, ListView):
+    model = models.SSHPublicKey
+    permission_required = ['view_sshpublickey']
+
+class PublicKeyDetailView(PermissionRequiredMixin, DetailView):
+    model = models.SSHPublicKey
+    permission_required = ['view_sshpublickey']
+
-- 
GitLab