From 70b305711555027d1a360e896a4cc00540539ea3 Mon Sep 17 00:00:00 2001 From: Frank Sauerburger <frank@sauerburger.com> Date: Tue, 23 Feb 2021 13:19:48 +0100 Subject: [PATCH] Add fully styled ssh directory --- keys/settings.py | 5 +- keys_home/static/keys_home/css/main.css | 4 + keys_home/templates/keys_home/base.html | 12 ++- requirements.txt | 1 + ssh/models.py | 37 ++++++++ ssh/templates/ssh/sshpublickey_create.html | 7 +- .../ssh/sshpublickey_download_openssh.html | 1 + .../ssh/sshpublickey_download_rfc4716.html | 1 + ssh/templates/ssh/sshpublickey_list.html | 85 +++++++++++++++++-- ssh/urls.py | 4 + ssh/views.py | 12 +++ 11 files changed, 154 insertions(+), 15 deletions(-) create mode 100644 ssh/templates/ssh/sshpublickey_download_openssh.html create mode 100644 ssh/templates/ssh/sshpublickey_download_rfc4716.html diff --git a/keys/settings.py b/keys/settings.py index b49e3b5..9585f93 100644 --- a/keys/settings.py +++ b/keys/settings.py @@ -41,9 +41,12 @@ INSTALLED_APPS = [ 'pgp', 'ssh', 'guardian', - 'fontawesome-free' + 'fontawesome-free', + 'crispy_forms', ] +CRISPY_TEMPLATE_PACK = 'bootstrap4' + AUTHENTICATION_BACKENDS = ( 'django.contrib.auth.backends.ModelBackend', # this is default 'guardian.backends.ObjectPermissionBackend', diff --git a/keys_home/static/keys_home/css/main.css b/keys_home/static/keys_home/css/main.css index be6cb66..5eb36bf 100644 --- a/keys_home/static/keys_home/css/main.css +++ b/keys_home/static/keys_home/css/main.css @@ -39,3 +39,7 @@ body { .mainlist a:hover { text-decoration: none; } + +pre { + margin-bottom: 0; +} diff --git a/keys_home/templates/keys_home/base.html b/keys_home/templates/keys_home/base.html index 364cd70..38c9a4c 100644 --- a/keys_home/templates/keys_home/base.html +++ b/keys_home/templates/keys_home/base.html @@ -41,9 +41,17 @@ </a> </li> <li class="ml-4 nav-item"> - <a class="nav-link" href=""> + {% if request.user.is_anonymous %} + <a class="nav-link" href=""> + <i class="fas fa-sign-in-alt"></i> Login - </a> + </a> + {% else %} + <a class="nav-link" href=""> + <i class="fas fa-user"></i> + {{ request.user.username }} + </a> + {% endif %} </li> </ul> </div> diff --git a/requirements.txt b/requirements.txt index d7786cb..bf56ccf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ django-guardian pgpy fontawesome-free +django-crispy-forms diff --git a/ssh/models.py b/ssh/models.py index c5635ce..4a0d3d5 100644 --- a/ssh/models.py +++ b/ssh/models.py @@ -1,4 +1,5 @@ from base64 import b64encode, b64decode +import struct import hashlib from django.db import models @@ -27,3 +28,39 @@ class SSHPublicKey(models.Model): 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])) + + def key_length(self): + decoded = b64decode(self.key) + + def pop(binary): + if len(binary) < 4: + return b'', b'' + length, = struct.unpack('>i', binary[:4]) + content = binary[4:4 + length] + return content, binary[4 + length:] + + algo, decoded = pop(decoded) + if algo == b"ssh-rsa": + e, decoded = pop(decoded) + n, decoded = pop(decoded) + return len(n) * 8 - 8 + elif algo == b"ssh-ed25519": + a, decoded = pop(decoded) + return len(a) * 8 + + return None + + def key_algo(self): + return self.keytype.split("-", 1)[-1].upper() + + def key_openssh(self): + return f"{self.keytype} {self.key} {self.hostname}" + + def key_rfc4716(self): + LINE_LEN = 70 + lines = (self.key[i:LINE_LEN+i] for i in range(0, len(self.key), LINE_LEN)) + lines = "\n".join(lines) + return "---- BEGIN SSH2 PUBLIC KEY ----\n" \ + f"Comment: \"{self.hostname}\"\n" \ + f"{lines}\n" \ + "---- END SSH2 PUBLIC KEY ----\n" diff --git a/ssh/templates/ssh/sshpublickey_create.html b/ssh/templates/ssh/sshpublickey_create.html index 4f257bc..23f3f0a 100644 --- a/ssh/templates/ssh/sshpublickey_create.html +++ b/ssh/templates/ssh/sshpublickey_create.html @@ -1,11 +1,12 @@ {% extends 'keys_home/base.html' %} +{% load crispy_forms_tags %} {% block content %} -<h2>Add public key</h2> +<h2>Add SSH Public Key</h2> <form action="" method="post"> {% csrf_token %} - {{ form.as_p }} - <button type="submit">Add</button> + {{ form|crispy }} + <button class="btn btn-primary" type="submit">Add public key</button> </form> {% endblock %} diff --git a/ssh/templates/ssh/sshpublickey_download_openssh.html b/ssh/templates/ssh/sshpublickey_download_openssh.html new file mode 100644 index 0000000..0e15af3 --- /dev/null +++ b/ssh/templates/ssh/sshpublickey_download_openssh.html @@ -0,0 +1 @@ +{{ sshpublickey.key_openssh|safe }} diff --git a/ssh/templates/ssh/sshpublickey_download_rfc4716.html b/ssh/templates/ssh/sshpublickey_download_rfc4716.html new file mode 100644 index 0000000..c24b941 --- /dev/null +++ b/ssh/templates/ssh/sshpublickey_download_rfc4716.html @@ -0,0 +1 @@ +{{ sshpublickey.key_rfc4716|safe }} diff --git a/ssh/templates/ssh/sshpublickey_list.html b/ssh/templates/ssh/sshpublickey_list.html index 8347f17..1a8824e 100644 --- a/ssh/templates/ssh/sshpublickey_list.html +++ b/ssh/templates/ssh/sshpublickey_list.html @@ -2,18 +2,85 @@ {% block content %} -{% if perms.ssh.add_sshpublickey %} -<a href="{% url 'ssh-create' %}">Add public key</a> -{% endif %} +<h2 class="d-flex justify-content-between"> + <div>SSH Public Keys</div> + {% if perms.ssh.add_sshpublickey %} + <a class="btn btn-outline-primary" href="{% url 'ssh-create' %}"> + <i class="fas fa-plus"></i> + </a> + {% endif %} +</h2> + {% if sshpublickey_list %} -<ul> +<ul class="list-unstyled"> {% 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> + <li class="border my-4 rounded"> + <h3 class="bg-dark text-light p-2 rounded-top d-lg-flex justify-content-between"> + <div> + {% if publickey.client_cert %} + <i class="fas fa-user mx-2" title="Client key"></i> {{ publickey.server }} + {% else %} + <i class="fas fa-server mx-2" title="Host key"></i> {{ publickey.server }} + {% endif %} + </div> + <div style="text-muted"><small> + {{ publickey.key_algo }}{% if publickey.key_length %}, {{ publickey.key_length}} bits {% endif %} + </small></div> + </h3> + <div class="px-4 py-2 border-bottom bg-light"> + <div class="text-muted mb-1"> + <i class="fas fa-fingerprint mr-1"></i> Fingerprint + {% if not publickey.client_cert %}<small>compare on first connect</small>{% endif %} + </div> + + <div class="input-group mb-2"> + <input style="background-color: inherit" title="SHA256 fingerprint" type="text" class="form-control" readonly value="{{publickey.fingerprint_sha256 }}" /> + <div class="input-group-append"> + <div class="input-group-text">Base64 â—¦ SHA256</div> + </div> + </div> + <div class="input-group mb-2"> + <input style="background-color: inherit" title="MD5 fingerprint" type="text" class="form-control" readonly value="{{publickey.fingerprint_md5 }}" /> + <div class="input-group-append"> + <div class="input-group-text">Hex â—¦ MD5</div> + </div> + </div> + </div> + <div class="px-4 py-2 border-bottom"> + <div class="d-lg-flex mb-1 justify-content-between"> + <div class="text-muted"> + <i class="fas fa-key mr-1"></i> Public key (OpenSSH) + {% if publickey.client_cert %}<small>use in <code>.ssh/authorized_keys</code></small>{% endif %} + </div> + <div> + <a href="{% url 'ssh-download-openssh' publickey.pk %}"> + <i class="fas fa-download mr-1"></i> + {{ request.scheme }}://{{ request.get_host}}{% url 'ssh-download-openssh' publickey.pk %} + </a> + </div> + </div> + <pre style="white-space: normal; word-break: break-all;"> + {{ publickey.key_openssh }} + </pre> + </div> + <div class="px-4 py-2"> + <div class="d-lg-flex mb-1 justify-content-between"> + <div class="text-muted"> + <i class="fas fa-key mr-1"></i> Public key (RFC4716) + {% if publickey.client_cert %}<small>use in <code>.sftp/authorized_keys</code></small>{% endif %} + </div> + + <div> + <a href="{% url 'ssh-download-rfc4716' publickey.pk %}"> + <i class="fas fa-download mr-1"></i> + {{ request.scheme }}://{{ request.get_host}}{% url 'ssh-download-rfc4716' publickey.pk %} + </a> + </div> + </div> + <pre>{{ publickey.key_rfc4716 }}</pre> + </div> + </li> {% endfor %} </ul> {% else %} diff --git a/ssh/urls.py b/ssh/urls.py index 8e1257d..ce0fde0 100644 --- a/ssh/urls.py +++ b/ssh/urls.py @@ -6,4 +6,8 @@ from . import views urlpatterns = [ path("", views.PublicKeyListView.as_view(), name="ssh-list"), path("new", views.publickey_create, name="ssh-create"), + path("openssh/<int:pk>", views.PublicKeyDownloadOpenSshView.as_view(), + name="ssh-download-openssh"), + path("rfc4716/<int:pk>", views.PublicKeyDownloadRfc4716View.as_view(), + name="ssh-download-rfc4716"), ] diff --git a/ssh/views.py b/ssh/views.py index 3a8cd2b..72224f6 100644 --- a/ssh/views.py +++ b/ssh/views.py @@ -41,3 +41,15 @@ def publickey_create(request): return render(request, 'ssh/sshpublickey_create.html', {'form': form}) + +class PublicKeyDownloadOpenSshView(PermissionRequiredMixin, DetailView): + model = models.SSHPublicKey + template_name = "ssh/sshpublickey_download_openssh.html" + content_type = "text/plain" + permission_required = ['view_sshpublickey'] + +class PublicKeyDownloadRfc4716View(PermissionRequiredMixin, DetailView): + model = models.SSHPublicKey + template_name = "ssh/sshpublickey_download_rfc4716.html" + content_type = "text/plain" + permission_required = ['view_sshpublickey'] -- GitLab