summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2023-05-13 13:27:13 +0300
committerLars Wirzenius <liw@liw.fi>2023-05-13 14:24:36 +0300
commit333bbf3c577df8aeba223dc59f31f9cea9106ccc (patch)
tree0a75692033e74d5572100eecb801028d2b87ec88
parent11e681a9e0eed37ad0f2a2a5cf89a0a5d57673ad (diff)
downloadv-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.yaml4
-rwxr-xr-xconfigure-installer112
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: