summary refs log tree commit diff
path: root/nixos/modules/services/misc/taskserver/helper-tool.py
diff options
context:
space:
mode:
authoraszlig <aszlig@redmoonstudios.org>2016-04-12 01:08:34 +0200
committeraszlig <aszlig@redmoonstudios.org>2016-04-12 01:41:41 +0200
commit7889fcfa41c718b52e2161e74de38a8479cd50fb (patch)
treeb95148f98876aeb4642ecc9bb564feddb574de7f /nixos/modules/services/misc/taskserver/helper-tool.py
parent3008836feeed905908027c0d36340bc4b64246f5 (diff)
downloadnixlib-7889fcfa41c718b52e2161e74de38a8479cd50fb.tar
nixlib-7889fcfa41c718b52e2161e74de38a8479cd50fb.tar.gz
nixlib-7889fcfa41c718b52e2161e74de38a8479cd50fb.tar.bz2
nixlib-7889fcfa41c718b52e2161e74de38a8479cd50fb.tar.lz
nixlib-7889fcfa41c718b52e2161e74de38a8479cd50fb.tar.xz
nixlib-7889fcfa41c718b52e2161e74de38a8479cd50fb.tar.zst
nixlib-7889fcfa41c718b52e2161e74de38a8479cd50fb.zip
nixos/taskserver/helper: Implement deletion
Now we finally can delete organisations, groups and users along with
certificate revocation. The new subtests now make sure that the client
certificate is also revoked (both when removing the whole organisation
and just a single user).

If we use the imperative way to add and delete users, we have to restart
the Taskserver in order for the CRL to be effective.

However, by using the declarative configuration we now get this for
free, because removing a user will also restart the service and thus its
client certificate will end up in the CRL.

Signed-off-by: aszlig <aszlig@redmoonstudios.org>
Diffstat (limited to 'nixos/modules/services/misc/taskserver/helper-tool.py')
-rw-r--r--nixos/modules/services/misc/taskserver/helper-tool.py132
1 files changed, 114 insertions, 18 deletions
diff --git a/nixos/modules/services/misc/taskserver/helper-tool.py b/nixos/modules/services/misc/taskserver/helper-tool.py
index c255081f5657..cd712332e039 100644
--- a/nixos/modules/services/misc/taskserver/helper-tool.py
+++ b/nixos/modules/services/misc/taskserver/helper-tool.py
@@ -7,6 +7,7 @@ import string
 import subprocess
 import sys
 
+from contextlib import contextmanager
 from shutil import rmtree
 from tempfile import NamedTemporaryFile
 
@@ -86,6 +87,19 @@ def fetch_username(org, key):
     return None
 
 
+@contextmanager
+def create_template(contents):
+    """
+    Generate a temporary file with the specified contents as a list of strings
+    and yield its path as the context.
+    """
+    template = NamedTemporaryFile(mode="w", prefix="certtool-template")
+    template.writelines(map(lambda l: l + "\n", contents))
+    template.flush()
+    yield template.name
+    template.close()
+
+
 def generate_key(org, user):
     basedir = os.path.join(TASKD_DATA_DIR, "keys", org, user)
     if os.path.exists(basedir):
@@ -100,30 +114,57 @@ def generate_key(org, user):
         os.makedirs(basedir, mode=0700)
 
         cmd = [CERTTOOL_COMMAND, "-p", "--bits", "2048", "--outfile", privkey]
-        subprocess.call(cmd, preexec_fn=lambda: os.umask(0077))
+        subprocess.check_call(cmd, preexec_fn=lambda: os.umask(0077))
 
-        template = NamedTemporaryFile(mode="w", prefix="certtool-template")
-        template.writelines(map(lambda l: l + "\n", [
+        template_data = [
             "organization = {0}".format(org),
             "cn = {}".format(FQDN),
             "tls_www_client",
             "encryption_key",
             "signing_key"
-        ]))
-        template.flush()
+        ]
 
-        cmd = [CERTTOOL_COMMAND, "-c",
-               "--load-privkey", privkey,
-               "--load-ca-privkey", cakey,
-               "--load-ca-certificate", cacert,
-               "--template", template.name,
-               "--outfile", pubcert]
-        subprocess.call(cmd, preexec_fn=lambda: os.umask(0077))
+        with create_template(template_data) as template:
+            cmd = [CERTTOOL_COMMAND, "-c",
+                   "--load-privkey", privkey,
+                   "--load-ca-privkey", cakey,
+                   "--load-ca-certificate", cacert,
+                   "--template", template,
+                   "--outfile", pubcert]
+            subprocess.check_call(cmd, preexec_fn=lambda: os.umask(0077))
     except:
         rmtree(basedir)
         raise
 
 
+def revoke_key(org, user):
+    cakey = os.path.join(TASKD_DATA_DIR, "keys", "ca.key")
+    cacert = os.path.join(TASKD_DATA_DIR, "keys", "ca.cert")
+    crl = os.path.join(TASKD_DATA_DIR, "keys", "server.crl")
+
+    basedir = os.path.join(TASKD_DATA_DIR, "keys", org, user)
+    if not os.path.exists(basedir):
+        raise OSError("Keyfile directory for {} doesn't exist.".format(user))
+
+    pubcert = os.path.join(basedir, "public.cert")
+
+    with create_template(["expiration_days = 3650"]) as template:
+        oldcrl = NamedTemporaryFile(mode="wb", prefix="old-crl")
+        oldcrl.write(open(crl, "rb").read())
+        oldcrl.flush()
+        cmd = [CERTTOOL_COMMAND,
+               "--generate-crl",
+               "--load-crl", oldcrl.name,
+               "--load-ca-privkey", cakey,
+               "--load-ca-certificate", cacert,
+               "--load-certificate", pubcert,
+               "--template", template,
+               "--outfile", crl]
+        subprocess.check_call(cmd, preexec_fn=lambda: os.umask(0077))
+        oldcrl.close()
+    rmtree(basedir)
+
+
 def is_key_line(line, match):
     return line.startswith("---") and line.lstrip("- ").startswith(match)
 
@@ -215,8 +256,13 @@ class Organisation(object):
         """
         Delete a user and revoke its keys.
         """
-        sys.stderr.write("Delete user {}.".format(name))
-        # TODO: deletion!
+        if name in self.users.keys():
+            # Work around https://bug.tasktools.org/browse/TD-40:
+            user = self.get_user(name)
+            rmtree(mkpath(self.name, "users", user.key))
+
+            revoke_key(self.name, name)
+            del self._lazy_users[name]
 
     def add_group(self, name):
         """
@@ -235,8 +281,9 @@ class Organisation(object):
         """
         Delete a group.
         """
-        sys.stderr.write("Delete group {}.".format(name))
-        # TODO: deletion!
+        if name in self.users.keys():
+            taskd_cmd("remove", "group", self.name, name)
+            del self._lazy_groups[name]
 
     def get_user(self, name):
         return self.users.get(name)
@@ -281,8 +328,14 @@ class Manager(object):
         Delete and revoke keys of an organisation with all its users and
         groups.
         """
-        sys.stderr.write("Delete org {}.".format(name))
-        # TODO: deletion!
+        org = self.get_org(name)
+        if org is not None:
+            for user in org.users.keys():
+                org.del_user(user)
+            for group in org.groups.keys():
+                org.del_group(group)
+            taskd_cmd("remove", "org", name)
+            del self._lazy_orgs[name]
 
     def get_org(self, name):
         return self.orgs.get(name)
@@ -383,6 +436,22 @@ def add_org(name):
     taskd_cmd("add", "org", name)
 
 
+@cli.command("del-org")
+@click.argument("name")
+def del_org(name):
+    """
+    Delete the organisation with the specified name.
+
+    All of the users and groups will be deleted as well and client certificates
+    will be revoked.
+    """
+    Manager().del_org(name)
+    msg = ("Organisation {} deleted. Be sure to restart the Taskserver"
+           " using 'systemctl restart taskserver.service' in order for"
+           " the certificate revocation to apply.")
+    click.echo(msg.format(name), err=True)
+
+
 @cli.command("add-user")
 @click.argument("organisation", type=ORGANISATION)
 @click.argument("user")
@@ -400,6 +469,22 @@ def add_user(organisation, user):
         sys.exit(msg.format(user, organisation))
 
 
+@cli.command("del-user")
+@click.argument("organisation", type=ORGANISATION)
+@click.argument("user")
+def del_user(organisation, user):
+    """
+    Delete a user from the given organisation.
+
+    This will also revoke the client certificate of the given user.
+    """
+    organisation.del_user(user)
+    msg = ("User {} deleted. Be sure to restart the Taskserver using"
+           " 'systemctl restart taskserver.service' in order for the"
+           " certificate revocation to apply.")
+    click.echo(msg.format(user), err=True)
+
+
 @cli.command("add-group")
 @click.argument("organisation", type=ORGANISATION)
 @click.argument("group")
@@ -413,6 +498,17 @@ def add_group(organisation, group):
         sys.exit(msg.format(group, organisation))
 
 
+@cli.command("del-group")
+@click.argument("organisation", type=ORGANISATION)
+@click.argument("group")
+def del_group(organisation, group):
+    """
+    Delete a group from the given organisation.
+    """
+    organisation.del_group(group)
+    click("Group {} deleted.".format(group), err=True)
+
+
 def add_or_delete(old, new, add_fun, del_fun):
     """
     Given an 'old' and 'new' list, figure out the intersections and invoke