From: Michael Tremer Date: Thu, 18 Oct 2018 09:34:06 +0000 (+0100) Subject: people: Add UI to upload/delete SSH keys X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0d1fb712c8ec91f0c34f56e20135c6030d0aa9f4;p=ipfire.org.git people: Add UI to upload/delete SSH keys Signed-off-by: Michael Tremer --- diff --git a/Makefile.am b/Makefile.am index 64283de7..f5bc4f17 100644 --- a/Makefile.am +++ b/Makefile.am @@ -178,7 +178,11 @@ templates_people_modulesdir = $(templates_peopledir)/modules templates_people_ssh_keys_DATA = \ src/templates/people/ssh-keys/base.html \ - src/templates/people/ssh-keys/index.html + src/templates/people/ssh-keys/delete.html \ + src/templates/people/ssh-keys/error.html \ + src/templates/people/ssh-keys/error-invalid-key.html \ + src/templates/people/ssh-keys/index.html \ + src/templates/people/ssh-keys/upload.html templates_people_ssh_keysdir = $(templates_peopledir)/ssh-keys diff --git a/src/backend/accounts.py b/src/backend/accounts.py index 361f29b7..9f67e4a4 100644 --- a/src/backend/accounts.py +++ b/src/backend/accounts.py @@ -247,6 +247,32 @@ class Account(Object): def _set_string(self, key, value): return self._set_strings(key, [value,]) + def _add(self, key, values): + modlist = [ + (ldap.MOD_ADD, key, values), + ] + + self._modify(modlist) + + def _add_strings(self, key, values): + return self._add(key, [e.encode() for e in values]) + + def _add_string(self, key, value): + return self._add_strings(key, [value,]) + + def _delete(self, key, values): + modlist = [ + (ldap.MOD_DELETE, key, values), + ] + + self._modify(modlist) + + def _delete_strings(self, key, values): + return self._delete(key, [e.encode() for e in values]) + + def _delete_string(self, key, value): + return self._delete_strings(key, [value,]) + def passwd(self, new_password): """ Sets a new password @@ -260,6 +286,9 @@ class Account(Object): Raises exceptions from the server on any other errors. """ + if not password: + return + logging.debug("Checking credentials for %s" % self.dn) # Create a new LDAP connection @@ -597,6 +626,38 @@ class Account(Object): return key + def add_ssh_key(self, key): + k = sshpubkeys.SSHKey() + + # Try to parse the key + k.parse(key) + + # Check for types and sufficient sizes + if k.key_type == b"ssh-rsa": + if k.bits < 4096: + raise sshpubkeys.TooShortKeyError("RSA keys cannot be smaller than 4096 bits") + + elif k.key_type == b"ssh-dss": + raise sshpubkeys.InvalidKeyError("DSA keys are not supported") + + # Ignore any duplicates + if key in (k.keydata for k in self.ssh_keys): + logging.debug("SSH Key has already been added for %s: %s" % (self, key)) + return + + # Save key to LDAP + self._add_string("sshPublicKey", key) + + # Append to cache + self.ssh_keys.append(k) + + def delete_ssh_key(self, key): + if not key in (k.keydata for k in self.ssh_keys): + return + + # Delete key from LDAP + self._delete_string("sshPublicKey", key) + if __name__ == "__main__": a = Accounts() diff --git a/src/templates/people/ssh-keys/delete.html b/src/templates/people/ssh-keys/delete.html new file mode 100644 index 00000000..3ab79a76 --- /dev/null +++ b/src/templates/people/ssh-keys/delete.html @@ -0,0 +1,38 @@ +{% extends "base.html" %} + +{% block title %}{{ account }} - {{ _("Delete SSH Key") }}{% end block %} + +{% block main %} +
+
+

{{ _("Delete SSH Key") }}

+ +
+ {% raw xsrf_form_html() %} + +
+ + + {% for h in (key.hash_md5(), key.hash_sha256(), key.hash_sha512()) %} +

+ {{ h }} +

+ {% end %} +
+ +
+ + + + + + {{ _("To authorize uploading a new SSH key, your password is required") }} + +
+ + +
+
+
+{% end block %} diff --git a/src/templates/people/ssh-keys/error-invalid-key.html b/src/templates/people/ssh-keys/error-invalid-key.html new file mode 100644 index 00000000..9a2a10cd --- /dev/null +++ b/src/templates/people/ssh-keys/error-invalid-key.html @@ -0,0 +1,7 @@ +{% extends "error.html" %} + +{% block reason %} +

+ {{ _("The uploaded key is invalid: %s") % exception }} +

+{% end block %} diff --git a/src/templates/people/ssh-keys/error.html b/src/templates/people/ssh-keys/error.html new file mode 100644 index 00000000..a2e11a69 --- /dev/null +++ b/src/templates/people/ssh-keys/error.html @@ -0,0 +1,16 @@ +{% extends "base.html" %} + +{% block title %}{{ account }} - {{ _("Error Adding SSH Key") }}{% end block %} + +{% block main %} +

{{ _("Error") }}

+
{{ _("Your SSH Key could not be added") }}
+ +
+ {% block reason %}{% end block %} + +

+ {{ _("Please go back and try again") }} +

+
+{% end block %} diff --git a/src/templates/people/ssh-keys/upload.html b/src/templates/people/ssh-keys/upload.html new file mode 100644 index 00000000..7a5c7b6a --- /dev/null +++ b/src/templates/people/ssh-keys/upload.html @@ -0,0 +1,42 @@ +{% extends "base.html" %} + +{% block title %}{{ account }} - {{ _("Upload New SSH Key") }}{% end block %} + +{% block main %} +
+
+

{{ _("Upload New SSH Key") }}

+ +
+ {% raw xsrf_form_html() %} + +
+ + + + {{ _("The SSH key must be conforming to these criteria:") }} + +
    +
  • {{ _("Supported key types are: Ed25519, ECDSA and RSA") }}
  • +
  • {{ _("RSA keys must be at least 4096 bits long") }}
  • +
+
+
+ +
+ + + + + + {{ _("To authorize uploading a new SSH key, your password is required") }} + +
+ + +
+
+
+{% end block %} diff --git a/src/web/__init__.py b/src/web/__init__.py index f0099c19..041839ce 100644 --- a/src/web/__init__.py +++ b/src/web/__init__.py @@ -261,7 +261,9 @@ class Application(tornado.web.Application): (r"/users/(\w+)/edit", people.UserEditHandler), (r"/users/(\w+)/passwd", people.UserPasswdHandler), (r"/users/(\w+)/ssh-keys", people.SSHKeysIndexHandler), + (r"/users/(\w+)/ssh-keys/(MD5\:.*)/delete", people.SSHKeysDeleteHandler), (r"/users/(\w+)/ssh-keys/(MD5\:.*)", people.SSHKeysDownloadHandler), + (r"/users/(\w+)/ssh-keys/upload", people.SSHKeysUploadHandler), (r"/users/(\w+)/sip", people.SIPHandler), ] + authentication_handlers) diff --git a/src/web/people.py b/src/web/people.py index d714cae3..8b537bfe 100644 --- a/src/web/people.py +++ b/src/web/people.py @@ -3,6 +3,7 @@ import datetime import ldap import logging +import sshpubkeys import tornado.web from . import handlers_base as base @@ -131,6 +132,83 @@ class SSHKeysDownloadHandler(base.BaseHandler): self.finish(key.keydata) +class SSHKeysUploadHandler(base.BaseHandler): + @tornado.web.authenticated + def get(self, uid): + account = self.backend.accounts.get_by_uid(uid) + if not account: + raise tornado.web.HTTPError(404, "Could not find account %s" % uid) + + # Check for permissions + if not account.can_be_managed_by(self.current_user): + raise tornado.web.HTTPError(403, "%s cannot manage %s" % (self.current_user, account)) + + self.render("people/ssh-keys/upload.html", account=account) + + @tornado.web.authenticated + def post(self, uid): + account = self.backend.accounts.get_by_uid(uid) + if not account: + raise tornado.web.HTTPError(404, "Could not find account %s" % uid) + + # Check for permissions + if not account.can_be_managed_by(self.current_user): + raise tornado.web.HTTPError(403, "%s cannot manage %s" % (self.current_user, account)) + + key = self.get_argument("key") + + # Verify password + password = self.get_argument("password") + if not account.check_password(password): + raise tornado.web.HTTPError(403, "Incorrect password for %s" % account) + + # Try to add new SSH key + try: + account.add_ssh_key(key) + + except sshpubkeys.InvalidKeyException as e: + self.render("people/ssh-keys/error-invalid-key.html", account=account, exception=e) + return + + self.redirect("/users/%s/ssh-keys" % account.uid) + + +class SSHKeysDeleteHandler(base.BaseHandler): + @tornado.web.authenticated + def get(self, uid, hash_md5): + account = self.backend.accounts.get_by_uid(uid) + if not account: + raise tornado.web.HTTPError(404, "Could not find account %s" % uid) + + # Get SSH key + key = account.get_ssh_key_by_hash_md5(hash_md5) + if not key: + raise tornado.web.HTTPError(404, "Could not find key: %s" % hash_md5) + + self.render("people/ssh-keys/delete.html", account=account, key=key) + + @tornado.web.authenticated + def post(self, uid, hash_md5): + account = self.backend.accounts.get_by_uid(uid) + if not account: + raise tornado.web.HTTPError(404, "Could not find account %s" % uid) + + # Get SSH key + key = account.get_ssh_key_by_hash_md5(hash_md5) + if not key: + raise tornado.web.HTTPError(404, "Could not find key: %s" % hash_md5) + + # Verify password + password = self.get_argument("password") + if not account.check_password(password): + raise tornado.web.HTTPError(403, "Incorrect password for %s" % account) + + # Delete the key + account.delete_ssh_key(key.keydata) + + self.redirect("/users/%s/ssh-keys" % account.uid) + + class SIPHandler(base.BaseHandler): @tornado.web.authenticated def get(self, uid):