diff --git a/keys/settings.py b/keys/settings.py index e8e8cc56cd2dd5a3ca1aa99b6010a299010bd454..e2be4f2437c9b7dd67dfb909024135d882cc0922 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 0000000000000000000000000000000000000000..e96dcda7c11c96d38ad542b3d10f82256940acfc --- /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 dc8a3c4971e9f830b376e0151ad7c06ab654db1f..7acaa720c004b07f82b9237fa8b324a8af1194e0 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 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/ssh/admin.py b/ssh/admin.py new file mode 100644 index 0000000000000000000000000000000000000000..0bb99ca96aa10a204231ab4b71b86f468b0d81b3 --- /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 0000000000000000000000000000000000000000..06aa1aa568b73a664cf93b823502273f68fb94a6 --- /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 0000000000000000000000000000000000000000..939e57ed6bd1b836052355068b59cb7e02ecb4b3 --- /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 0000000000000000000000000000000000000000..005daa3b8ba719a8bf3f1241d2a1cd793976cb0e --- /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 0000000000000000000000000000000000000000..9ac12e23b2af16ed351194af0eebf01785b767af --- /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 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/ssh/models.py b/ssh/models.py new file mode 100644 index 0000000000000000000000000000000000000000..c5635ceb4f335ed96e3a588d195af0b72f56a1e3 --- /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 0000000000000000000000000000000000000000..478fd5743d6671cd3ad3ebec54c9be26982e357c --- /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 0000000000000000000000000000000000000000..7ce503c2dd97ba78597f6ff6e4393132753573f6 --- /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 0000000000000000000000000000000000000000..409a87280651c41cdd4e45d18c2f8d7d41cd3b20 --- /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 0000000000000000000000000000000000000000..9725af443f195cc766bf2402e73b6fa5f0a094e1 --- /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'] +