← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~cjwatson/lp-signing:charm-action-register-client into lp-signing:master

 

Colin Watson has proposed merging ~cjwatson/lp-signing:charm-action-register-client into lp-signing:master.

Commit message:
charm: Add a register-client action

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/lp-signing/+git/lp-signing/+merge/448278

Registering a new client with the signing service currently involves manually running a couple of undocumented commands.  Package these up as an action.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/lp-signing:charm-action-register-client into lp-signing:master.
diff --git a/charm/lp-signing/README.md b/charm/lp-signing/README.md
index 03d5325..36ccca6 100644
--- a/charm/lp-signing/README.md
+++ b/charm/lp-signing/README.md
@@ -9,3 +9,10 @@ lp-signing/leader -- /srv/lp-signing/code/env/bin/lp-signing
 generate-key-pair` to do this; the private halves should be stored in each
 of `juju config lp-signing service_private_keys` and `juju config lp-signing
 key_storage_private_keys`, each of which is a JSON-encoded list of strings.
+
+To register a new client, run:
+
+    juju run-action --wait lp-signing/leader register-client name=<client name>
+
+This will return the new private and public keys; you will need to configure
+the client service with these.
diff --git a/charm/lp-signing/actions.yaml b/charm/lp-signing/actions.yaml
new file mode 100644
index 0000000..f841a39
--- /dev/null
+++ b/charm/lp-signing/actions.yaml
@@ -0,0 +1,14 @@
+register-client:
+  description: >
+    Generate a key pair and use it to register a new client with the signing
+    service.  This returns the new private and public key, which should be
+    used to configure the client.
+  params:
+    name:
+      type: string
+      description: >
+        The name of the client.  This should typically be an identifier for
+        the client's role, rather than something like a hostname which might
+        change when the client system is redeployed.
+  required:
+    - name
diff --git a/charm/lp-signing/actions/actions.py b/charm/lp-signing/actions/actions.py
new file mode 100755
index 0000000..7e76442
--- /dev/null
+++ b/charm/lp-signing/actions/actions.py
@@ -0,0 +1,86 @@
+#! /usr/bin/python3
+# Copyright 2023 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+import subprocess
+import sys
+import traceback
+from pathlib import Path
+from tempfile import TemporaryDirectory
+
+sys.path.append("lib")
+
+from charms.layer import basic  # noqa: E402
+
+basic.bootstrap_charm_deps()
+basic.init_config_states()
+
+from charmhelpers.core import hookenv  # noqa: E402
+from ols import base  # noqa: E402
+
+
+def register_client():
+    params = hookenv.action_get()
+    hookenv.log("Generating key pair.")
+    script = Path(base.code_dir(), "env", "bin", "lp-signing")
+    with TemporaryDirectory() as tmp:
+        subprocess.run(["chown", f"{base.user()}:", tmp], check=True)
+        private_key_path = Path(tmp, "private")
+        public_key_path = Path(tmp, "public")
+        subprocess.run(
+            [
+                "sudo",
+                "-H",
+                "-u",
+                base.user(),
+                script,
+                "generate-key-pair",
+                "--private-key-path",
+                private_key_path,
+                "--public-key-path",
+                public_key_path,
+            ],
+            check=True,
+        )
+        with open(private_key_path) as private_key_file, open(
+            public_key_path
+        ) as public_key_file:
+            private_key = private_key_file.read().rstrip("\n")
+            public_key = public_key_file.read().rstrip("\n")
+        subprocess.run(
+            [
+                "sudo",
+                "-H",
+                "-u",
+                base.user(),
+                f"SERVICE_CONFIG={base.service_config_path()}",
+                script,
+                "register-client",
+                params["name"],
+                public_key,
+            ],
+            check=True,
+        )
+    # Yes, we include the private key in the output; the person running this
+    # action will need it to configure the calling service.
+    hookenv.action_set({"private": private_key, "public": public_key})
+
+
+def main(argv):
+    action = Path(argv[0]).name
+    try:
+        if action == "register-client":
+            register_client()
+        else:
+            hookenv.action_fail(f"Action {action} not implemented.")
+    except Exception:
+        hookenv.action_fail("Unhandled exception")
+        tb = traceback.format_exc()
+        hookenv.action_set(dict(traceback=tb))
+        hookenv.log(f"Unhandled exception in action {action}:")
+        for line in tb.splitlines():
+            hookenv.log(line)
+
+
+if __name__ == "__main__":
+    main(sys.argv)
diff --git a/charm/lp-signing/actions/register-client b/charm/lp-signing/actions/register-client
new file mode 120000
index 0000000..405a394
--- /dev/null
+++ b/charm/lp-signing/actions/register-client
@@ -0,0 +1 @@
+actions.py
\ No newline at end of file