diff options
author | Lars Wirzenius <liw@liw.fi> | 2023-05-13 13:27:13 +0300 |
---|---|---|
committer | Lars Wirzenius <liw@liw.fi> | 2023-05-13 14:24:36 +0300 |
commit | 333bbf3c577df8aeba223dc59f31f9cea9106ccc (patch) | |
tree | 0a75692033e74d5572100eecb801028d2b87ec88 | |
parent | 11e681a9e0eed37ad0f2a2a5cf89a0a5d57673ad (diff) | |
download | v-i-333bbf3c577df8aeba223dc59f31f9cea9106ccc.tar.gz |
feat: allow installer configuration to fetch things from commands
Instead of having to store user CA public key and host key and
certificate in files, allow user to specify commands to run to get
them. This is more convenient. Also, the certificate can be
re-generate every time the installer image is configured, which
reduces the likelihood it'll expire.
Sponsored-by: author
-rw-r--r-- | config.yaml | 4 | ||||
-rwxr-xr-x | configure-installer | 112 |
2 files changed, 101 insertions, 15 deletions
diff --git a/config.yaml b/config.yaml new file mode 100644 index 0000000..e6dd414 --- /dev/null +++ b/config.yaml @@ -0,0 +1,4 @@ +user_ca_pub_cmd: sshca ca public-key liw.fi/ca/user/v5 +host_key_cmd: sshca host private-key $HOST +host_cert_cmd: sshca host certify liw.fi/ca/host/v5 $HOST +cmd_as_user: liw diff --git a/configure-installer b/configure-installer index 7851acd..fce3695 100755 --- a/configure-installer +++ b/configure-installer @@ -4,13 +4,19 @@ import argparse import glob import os import subprocess +import sys import tempfile import yaml +verbose = False + + def log(msg): - if True: - print(msg) + if verbose: + sys.stderr.write(msg) + sys.stderr.write("\n") + sys.stderr.flush() class Config: @@ -18,7 +24,11 @@ class Config: "authorized_keys_file": None, "host_cert_file": None, "host_key_file": None, + "host_cert_cmd": None, + "host_key_cmd": None, "user_ca_pub_file": None, + "user_ca_pub_cmd": None, + "cmd_as_user": None, } exandable = [ @@ -42,7 +52,55 @@ class Config: for key in self.exandable: if self.config[key] is not None: self.config[key] = os.path.expanduser(self.config[key]) - log(f"config: {self.config}") + log(f"config:") + for key in self.config: + log(f" {key}={self.config[key]!r}") + + def user_ca_pub(self): + return self._get_from_file_or_cmd("user_ca_pub", "user CA public key", None) + + def host_key(self, hostname): + return self._get_from_file_or_cmd("host_key", "host private key", hostname) + + def host_cert(self, hostname): + return self._get_from_file_or_cmd("host_cert", "host certificate", hostname) + + def _get_from_file_or_cmd(self, prefix, msg, hostname): + log("_get: A") + filename = self.config.get(f"{prefix}_file") + log("_get: B") + if filename is not None: + log("_get: C") + log(f"reading {msg} from {filename}") + log("_get: D") + return cat(filename) + + log("_get: E") + cmd = self.config.get(f"{prefix}_cmd") + log("_get: F") + if hostname is not None: + log("_get: G") + cmd = hostname.join(cmd.split("$HOST")) + log("_get: H") + if cmd is not None: + log("_get: I") + user = self.config.get("cmd_as_user") + log("_get: J") + if user is not None: + log("_get: K") + log(f"reading {msg} from command (as {user}): {cmd}") + log("_get: L") + return run(cmd, user=user) + else: + log("_get: M") + log(f"reading {msg} from command: {cmd}") + log("_get: N") + return run(cmd) + + log("_get: O") + log(f"can't read {msg}") + log("_get: Z") + return None def mount(drive, mp): @@ -55,6 +113,30 @@ def unmount(path): subprocess.run(["umount", path]) +def run(cmd, user=None): + log(f"run: A - user={user!r}") + if user is not None: + log("run: B") + argv = ["sudo", "-u", user, "--", "/bin/bash", "-c", cmd] + log("run: C") + log(f"argv: {argv}") + log("run: D") + p = subprocess.run(argv, capture_output=True) + log("run: E") + else: + log("run: F") + log(f"cmd={cmd!r}") + p = subprocess.run(cmd, shell=True, capture_output=True) + log("run: G") + if p.returncode != 0: + log("run: H") + sys.stderr.write(p.stderr.decode()) + log("run: J") + sys.exit(1) + log("run: Z") + return p.stdout.decode() + + def cat(path): log(f"reading {path}") with open(path) as f: @@ -75,13 +157,11 @@ def dir_exists(mp, path): raise Exception(f"{full} does not exist") -def host_id(config, mp): - key_path = config["host_key_file"] - cert_path = config["host_cert_file"] - if key_path is None or cert_path is None: - return - key = cat(key_path) - cert = cat(cert_path) +def host_id(config, mp, installer_hostname): + key = config.host_key(installer_hostname) + cert = config.host_cert(installer_hostname) + if key is None: + sys.exit("could not find host key for installer") config_d = "/etc/ssh/sshd_config.d" host_key = "/etc/ssh/ssh_host_key" @@ -117,10 +197,7 @@ def authorized_keys(config, mp): def user_ca(config, mp): - ca_path = config["user_ca_pub_file"] - if ca_path is None: - return - ca_key = cat(ca_path) + ca_key = config.user_ca_pub() include = f"{mp}/etc/ssh/sshd_config.d/user_ca.conf" write(include, "TrustedUserCAKeys /etc/ssh/user_ca_pubs\n", 0, 0, 0o644) @@ -133,6 +210,8 @@ def main(): log("configure-image starting") p = argparse.ArgumentParser() + p.add_argument("--verbose", action="store_true", help="be verbose") + p.add_argument("--hostname", default="v-i", help="host name of installer image") p.add_argument("config", metavar="FILE", help="configuration file") p.add_argument( "drive", @@ -141,6 +220,9 @@ def main(): ) args = p.parse_args() + global verbose + verbose = args.verbose + config = Config() config.read(args.config) @@ -151,7 +233,7 @@ def main(): mount(drive2, mp) try: dir_exists(mp, "etc/ssh") - host_id(config, mp) + host_id(config, mp, args.hostname) authorized_keys(config, mp) user_ca(config, mp) finally: |