diff --git a/keys/settings.py b/keys/settings.py index b49e3b5ce44942fd2475a56e9f2db4c69d4e903b..9585f937254f88d5edf3df9ed3bba4d52a12a78d 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 be6cb66f48ffec884c049fd79739fb7092366d5a..5eb36bf1c565d109f5b68c540ff34feed820ff7f 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 364cd7096837dff372ffd756262f455b09532888..38c9a4c018b2c3a17f877e33168a16ebe5bedbf4 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 d7786cb28ce350b22947ca038f471f8f0546bbf1..bf56ccfc0bd32e48bbd28203d05fe4222e1ee67f 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 c5635ceb4f335ed96e3a588d195af0b72f56a1e3..4a0d3d526571d1206e7b6dd1240d032b376cbd8a 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 4f257bcd75319b674e757da0b1708a63b60d1a7e..23f3f0a445fedc20790394a326899042600cbc58 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 0000000000000000000000000000000000000000..0e15af314d7737a17dc716010fe2ebd79e1cbf58 --- /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 0000000000000000000000000000000000000000..c24b941212044247f1584ae989b6383df26b0189 --- /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 8347f17877bac83581b5b43ab11f73715febdd8a..1a8824ec57b643d03318549a2013cf5d6b371e2a 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 8e1257d0b738407be1db6574021e1d614e37118c..ce0fde00444605b80aebdf8267cb5360bd4fc6af 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 3a8cd2bf657d7571ba00ede81e6dc7034832bea8..72224f6c783682d6cd61b0bba10fcc0969d3403f 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']