From e675a609e9fbfb15181969207cd7331589e15df7 Mon Sep 17 00:00:00 2001 From: dawn Date: Wed, 21 Oct 2026 06:11:07 +0300 Subject: [PATCH] spindle: drop the emudhra roots from the workflow images Signed-off-by: dawn --- nix/microvm/base.nix | 7 + nix/microvm/spindle-vm.nix | 18 +- nix/pkgs/spindle-almalinux-image.nix | 16 +- nix/pkgs/spindle-alpine-image.nix | 2 + .../distrust-roots.py | 227 ++++++++++++++++++ .../roots/aaa-certificate-services.crt | 25 ++ .../roots/emsign-ecc-root-ca-g3.crt | 15 ++ .../roots/emsign-root-cs-ca-g3.crt | 14 ++ .../roots/emsign-root-tls-ca-g3.crt | 14 ++ nix/pkgs/spindle-image-helpers.nix | 10 + 10 files changed, 346 insertions(+), 2 deletions(-) create mode 100644 nix/pkgs/spindle-distrusted-roots/distrust-roots.py create mode 100644 nix/pkgs/spindle-distrusted-roots/roots/aaa-certificate-services.crt create mode 100644 nix/pkgs/spindle-distrusted-roots/roots/emsign-ecc-root-ca-g3.crt create mode 100644 nix/pkgs/spindle-distrusted-roots/roots/emsign-root-cs-ca-g3.crt create mode 100644 nix/pkgs/spindle-distrusted-roots/roots/emsign-root-tls-ca-g3.crt diff --git a/nix/microvm/base.nix b/nix/microvm/base.nix index a5e038654..19bb9aede 100644 --- a/nix/microvm/base.nix +++ b/nix/microvm/base.nix @@ -229,6 +229,13 @@ in { nixPath = ["nixpkgs=${config.nix.registry.nixpkgs.to.path}"]; }; + # nss labels of the roots behind the leaked emudhra intermediates. the other two + # certs in nix/pkgs/spindle-distrusted-roots were never in nss + security.pki.caCertificateBlacklist = [ + "Comodo AAA Services root" + "emSign ECC Root CA - G3" + ]; + systemd.tmpfiles.rules = [ "d /run/spindle 0755 root root -" "d /workspace 0755 spindle-workflow spindle-workflow -" diff --git a/nix/microvm/spindle-vm.nix b/nix/microvm/spindle-vm.nix index 2b5d89c40..5440f3a52 100644 --- a/nix/microvm/spindle-vm.nix +++ b/nix/microvm/spindle-vm.nix @@ -7,9 +7,25 @@ ./base.nix runnerModule self.nixosModules.shuttle - ({pkgs, ...}: { + ({ + config, + pkgs, + ... + }: { services.tangled.shuttle.enable = true; + # the blacklist goes by label, so a rename in nixpkgs would quietly trust them + # again. the main bundle keeps blacklisted roots as rejected TRUSTED CERTIFICATE + # blocks, so look in the no-trust-rules one + system.checks = [ + (pkgs.runCommand "spindle-distrusted-roots-check" {} '' + ${pkgs.python3.interpreter} ${../pkgs/spindle-distrusted-roots/distrust-roots.py} \ + --roots ${../pkgs/spindle-distrusted-roots/roots} \ + ${dirOf config.environment.etc."ssl/certs/ca-certificates.crt".source}/ca-no-trust-rules-bundle.crt + touch $out + '') + ]; + environment.etc = { "spindle/nixos/base.nix".source = ./base.nix; "spindle/nixos/runner.nix".source = runnerModule; diff --git a/nix/pkgs/spindle-almalinux-image.nix b/nix/pkgs/spindle-almalinux-image.nix index 7ef9f1157..240be74f6 100644 --- a/nix/pkgs/spindle-almalinux-image.nix +++ b/nix/pkgs/spindle-almalinux-image.nix @@ -191,7 +191,7 @@ in mkdir -p rootfs bsdtar -xpf ${rootfs} -C rootfs --no-xattrs # we'll need to write to here later - chmod -R u+w rootfs/{usr/{bin,sbin},etc/shadow} + chmod -R u+w rootfs/{usr/{bin,sbin},etc/shadow,etc/pki} # set up the pseudo definitions to restore setuid/caps at the end python3 ${pseudoDefinitionsGenerator} < ${rootfs} > pseudo-file.txt @@ -261,6 +261,20 @@ in # want any of the symlinks (since AL already ships coreutils) cp ${busybox}/bin/busybox rootfs/usr/local/bin + # the blocklist keeps the roots out of p11-kit (gnutls, nss) and out of + # whatever a later update-ca-trust in the guest extracts + install -m 0644 ${spindle-image-helpers.distrustedRoots}/*.crt rootfs/etc/pki/ca-trust/source/blocklist/ + ${spindle-image-helpers.distrustRoots} rootfs \ + --allow /etc/pki/ca-trust/source/blocklist \ + --allow /usr/share/pki/ca-trust-source/ca-bundle.trust.p11-kit \ + /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem \ + /etc/pki/ca-trust/extracted/pem/email-ca-bundle.pem \ + /etc/pki/ca-trust/extracted/pem/objsign-ca-bundle.pem \ + /etc/pki/ca-trust/extracted/pem/directory-hash \ + /etc/pki/ca-trust/extracted/openssl/ca-bundle.trust.crt \ + /etc/pki/ca-trust/extracted/java/cacerts \ + /etc/pki/ca-trust/extracted/edk2/cacerts.bin + mkdir -p "$out" mksquashfs rootfs "$out/store-disk" \ -comp zstd -Xcompression-level 19 \ diff --git a/nix/pkgs/spindle-alpine-image.nix b/nix/pkgs/spindle-alpine-image.nix index a56320607..e640683ee 100644 --- a/nix/pkgs/spindle-alpine-image.nix +++ b/nix/pkgs/spindle-alpine-image.nix @@ -134,6 +134,8 @@ in install -D -m 0644 ${apkRepositories} rootfs/etc/apk/repositories + ${spindle-image-helpers.distrustRoots} rootfs /etc/ssl/certs/ca-certificates.crt + mkdir -p "$out" mksquashfs rootfs "$out/store-disk" -comp zstd -Xcompression-level 19 -noappend -no-xattrs -all-root -quiet \ -p '/sbin/apk m 4755 0 0' # suid apk so spindle-workflow can use it without having to doas or smth diff --git a/nix/pkgs/spindle-distrusted-roots/distrust-roots.py b/nix/pkgs/spindle-distrusted-roots/distrust-roots.py new file mode 100644 index 000000000..b061f559c --- /dev/null +++ b/nix/pkgs/spindle-distrusted-roots/distrust-roots.py @@ -0,0 +1,227 @@ +# drops distrusted roots from the trust stores in a rootfs, then fails if any +# copy of them is left anywhere in it, so a rootfs bump can't bring them back + +import argparse +import base64 +import hashlib +import os +import re +import struct +import sys + +PEM_RE = re.compile(rb"-----BEGIN (CERTIFICATE|TRUSTED CERTIFICATE)-----\r?\n(.*?)-----END \1-----\r?\n?", re.S) +EFI_CERT_X509 = bytes.fromhex("a159c0a5e494a74a87b5ab155c2bf072") +JKS_MAGIC = b"\xfe\xed\xfe\xed" +JKS_PASSWORD = "changeit" + + +def first_tlv(der): + # a TRUSTED CERTIFICATE is the cert followed by openssl's trust settings + n = der[1] + if n < 0x80: + return der[: 2 + n] + size = int.from_bytes(der[2 : 2 + (n & 0x7F)], "big") + return der[: 2 + (n & 0x7F) + size] + + +def fingerprint(der): + return hashlib.sha256(first_tlv(der)).hexdigest() + + +def load_roots(path): + roots = {} + for name in sorted(os.listdir(path)): + if name.endswith(".crt"): + with open(os.path.join(path, name), "rb") as f: + for m in PEM_RE.finditer(f.read()): + der = base64.b64decode(m.group(2)) + roots[fingerprint(der)] = (name, der) + if not roots: + sys.exit(f"no roots in {path}") + return roots + + +def filter_pem(data, roots): + out, pos, found = [], 0, 0 + for m in PEM_RE.finditer(data): + if fingerprint(base64.b64decode(m.group(2))) not in roots: + continue + # also drop the label comments p11-kit writes above each cert + start = m.start() + while True: + prev = data.rfind(b"\n", 0, start - 1) + 1 + if start == 0 or not data[prev:start].startswith(b"#"): + break + start = prev + out.append(data[pos:start]) + pos = m.end() + found += 1 + out.append(data[pos:]) + return b"".join(out), found + + +def filter_edk2(data, roots): + out, pos, found = [], 0, 0 + while pos < len(data): + guid = data[pos : pos + 16] + list_size, header_size, sig_size = struct.unpack_from("II", body, 4) + pos, entries, found = 12, [], 0 + for _ in range(count): + start = pos + tag, alias_len = struct.unpack_from(">IH", body, pos) + if tag != 2: + sys.exit("jks keystore has a private key entry, only trusted certs are supported") + pos += 6 + alias_len + 8 + (type_len,) = struct.unpack_from(">H", body, pos) + pos += 2 + type_len + (cert_len,) = struct.unpack_from(">I", body, pos) + pos += 4 + der = body[pos : pos + cert_len] + pos += cert_len + if fingerprint(der) in roots: + found += 1 + else: + entries.append(body[start:pos]) + body = JKS_MAGIC + struct.pack(">II", version, len(entries)) + b"".join(entries) + return body + jks_digest(body), found + + +def filter_file(data, roots): + if data.startswith(JKS_MAGIC): + return filter_jks(data, roots) + if data.startswith(EFI_CERT_X509): + return filter_edk2(data, roots) + return filter_pem(data, roots) + + +def resolve(rootfs, path): + # symlinks in the rootfs point at absolute paths of the guest + for _ in range(40): + if not os.path.islink(path): + return path + target = os.readlink(path) + path = os.path.join(rootfs, target.lstrip("/")) if target.startswith("/") else os.path.join(os.path.dirname(path), target) + sys.exit(f"symlink loop at {path}") + + +def rel(rootfs, path): + return "/" + os.path.relpath(path, rootfs) + + +def distrust(rootfs, store, roots, removed): + path = os.path.join(rootfs, store.lstrip("/")) + real = resolve(rootfs, path) + if not os.path.exists(real): + sys.exit(f"trust store {store} is missing from the rootfs") + if os.path.isdir(real): + # drop_dangling cleans up the hash symlinks that pointed at these + for name in sorted(os.listdir(real)): + entry = os.path.join(real, name) + if not os.path.islink(entry) and os.path.isfile(entry): + with open(entry, "rb") as f: + if filter_pem(f.read(), roots)[1]: + os.remove(entry) + removed.add(entry) + print(f"distrust-roots: removed {rel(rootfs, entry)}") + return + with open(real, "rb") as f: + data, found = filter_file(f.read(), roots) + if found: + os.chmod(real, os.stat(real).st_mode | 0o200) + with open(real, "wb") as f: + f.write(data) + print(f"distrust-roots: dropped {found} cert(s) from {store}") + + +def drop_dangling(rootfs, removed): + for dirpath, dirnames, filenames in os.walk(rootfs): + for name in filenames + dirnames: + entry = os.path.join(dirpath, name) + if os.path.islink(entry) and resolve(rootfs, entry) in removed: + os.remove(entry) + + +def read_any(path): + # the rootfs keeps modes like 0000 on /etc/shadow + mode = os.stat(path).st_mode + if mode & 0o400: + with open(path, "rb") as f: + return f.read() + os.chmod(path, mode | 0o400) + try: + with open(path, "rb") as f: + return f.read() + finally: + os.chmod(path, mode) + + +def scan(rootfs, roots, allow): + # look for the der and for the first base64 line every pem copy starts with + needles = {} + for name, der in roots.values(): + needles[der] = name + needles[base64.b64encode(der[:48])] = name + single = os.path.isfile(rootfs) + paths = [rootfs] if single else (os.path.join(d, f) for d, _, fs in os.walk(rootfs) for f in fs) + bad = [] + for path in paths: + if os.path.islink(path) or not os.path.isfile(path): + continue + shown = rootfs if single else rel(rootfs, path) + if any(shown == a or shown.startswith(a.rstrip("/") + "/") for a in allow): + continue + data = read_any(path) + if data.startswith(JKS_MAGIC) or data.startswith(EFI_CERT_X509): + if filter_file(data, roots)[1]: + bad.append(shown) + continue + for needle, name in needles.items(): + if needle in data: + bad.append(f"{shown} ({name})") + if bad: + sys.exit("distrusted roots are still in:\n " + "\n ".join(bad)) + + +def main(): + p = argparse.ArgumentParser() + p.add_argument("--roots", required=True, help="dir of the distrusted root certs") + p.add_argument("--allow", action="append", default=[], help="path where the roots may stay (blocklists)") + p.add_argument("rootfs", help="rootfs dir, or a single bundle to check") + p.add_argument("stores", nargs="*", help="trust stores in the rootfs to drop the roots from") + args = p.parse_args() + + roots = load_roots(args.roots) + for fp, (name, _) in roots.items(): + print(f"distrust-roots: {name} sha256 {fp}") + removed = set() + for store in args.stores: + distrust(args.rootfs, store, roots, removed) + if removed: + drop_dangling(args.rootfs, removed) + scan(args.rootfs, roots, args.allow) + + +main() diff --git a/nix/pkgs/spindle-distrusted-roots/roots/aaa-certificate-services.crt b/nix/pkgs/spindle-distrusted-roots/roots/aaa-certificate-services.crt new file mode 100644 index 000000000..33c71ba9d --- /dev/null +++ b/nix/pkgs/spindle-distrusted-roots/roots/aaa-certificate-services.crt @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEb +MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow +GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmlj +YXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVowezEL +MAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE +BwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNVBAMM +GEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQua +BtDFcCLNSS1UY8y2bmhGC1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe +3M/vg4aijJRPn2jymJBGhCfHdr/jzDUsi14HZGWCwEiwqJH5YZ92IFCokcdmtet4 +YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszWY19zjNoFmag4qMsXeDZR +rOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjHYpy+g8cm +ez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQU +oBEKIz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF +MAMBAf8wewYDVR0fBHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20v +QUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29t +b2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDANBgkqhkiG9w0BAQUF +AAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm7l3sAg9g1o1Q +GE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz +Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2 +G9w84FoVxp7Z8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsi +l2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3 +smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg== +-----END CERTIFICATE----- diff --git a/nix/pkgs/spindle-distrusted-roots/roots/emsign-ecc-root-ca-g3.crt b/nix/pkgs/spindle-distrusted-roots/roots/emsign-ecc-root-ca-g3.crt new file mode 100644 index 000000000..060e943bf --- /dev/null +++ b/nix/pkgs/spindle-distrusted-roots/roots/emsign-ecc-root-ca-g3.crt @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQG +EwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNo +bm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g +RzMwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4MTgzMDAwWjBrMQswCQYDVQQGEwJJ +TjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9s +b2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMw +djAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0 +WXTsuwYc58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4PPwT01xyS +fvalY8L1X44uT6EYGQIrMgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuB +zhccLikenEhjQjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggq +hkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+DCBeQyh+KTOgNG3qxrdWB +CUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7jHvrZQnD ++JbNR6iC8hZVdyR+EhCVBCyj +-----END CERTIFICATE----- diff --git a/nix/pkgs/spindle-distrusted-roots/roots/emsign-root-cs-ca-g3.crt b/nix/pkgs/spindle-distrusted-roots/roots/emsign-root-cs-ca-g3.crt new file mode 100644 index 000000000..d6c94fd1c --- /dev/null +++ b/nix/pkgs/spindle-distrusted-roots/roots/emsign-root-cs-ca-g3.crt @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICKDCCAa2gAwIBAgIQAY/OySfBFvWcmX7cDM0uKzAKBggqhkjOPQQDAzBVMQsw +CQYDVQQGEwJJTjElMCMGA1UECgwcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRl +ZDEfMB0GA1UEAwwWZW1TaWduIFJvb3QgQ1MgQ0EgLSBHMzAeFw0yNDAyMDgwODMz +MTRaFw00OTAyMDcwODMzMTRaMFUxCzAJBgNVBAYTAklOMSUwIwYDVQQKDBxlTXVk +aHJhIFRlY2hub2xvZ2llcyBMaW1pdGVkMR8wHQYDVQQDDBZlbVNpZ24gUm9vdCBD +UyBDQSAtIEczMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAESl9kM5D0VA/Og76dz5Xz +w1hnGQ4fSR9993/qBkkvI48pXQVKOdyHCgAZThgWejQ8OpFRiTISjpabn4tfRFOY +uzhApR/XxGbkg9X+2cEtbyghF9CdoF91W22V/R2EVe4xo0IwQDAdBgNVHQ4EFgQU +aHQD+IXKjVRQVx0W4MjeRQ2rzS0wDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF +MAMBAf8wCgYIKoZIzj0EAwMDaQAwZgIxAK9OlACrFod3UgWY1ai+DF9sGFhahU59 +rv4jJyV01QaxGn0HErZ8gjHkngqe+tcFogIxAPTdW0hBmrGjhxBWdv9mZNdVFiJ4 +lrZ3ahvimsLhTQpnDBgiGKv6Ov1Fx/CE6yjIAg== +-----END CERTIFICATE----- diff --git a/nix/pkgs/spindle-distrusted-roots/roots/emsign-root-tls-ca-g3.crt b/nix/pkgs/spindle-distrusted-roots/roots/emsign-root-tls-ca-g3.crt new file mode 100644 index 000000000..3b8be38cb --- /dev/null +++ b/nix/pkgs/spindle-distrusted-roots/roots/emsign-root-tls-ca-g3.crt @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICKTCCAa+gAwIBAgIQDnYGcvFDRZ/I/gqwvAXjlDAKBggqhkjOPQQDAzBWMQsw +CQYDVQQGEwJJTjElMCMGA1UECgwcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRl +ZDEgMB4GA1UEAwwXZW1TaWduIFJvb3QgVExTIENBIC0gRzMwHhcNMjQwMjA4MDgx +OTMyWhcNNDkwMjA3MDgxOTMyWjBWMQswCQYDVQQGEwJJTjElMCMGA1UECgwcZU11 +ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAwwXZW1TaWduIFJvb3Qg +VExTIENBIC0gRzMwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARfD9w/q1cWxL6D9bMG +H6CQ4u+gAV6GN9s+PwQx9Bl4hC+0iU8QOk8mWO/RwXQy10DCSxT8vSIJClHKp+Ao +4qJKdYeV0SwZ7LMQziyLfxIUOY6Grja/sQFwLKYf8EdDXAyjQjBAMB0GA1UdDgQW +BBThOqshfPHjTVSu1HK3/YtZQiCQVDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjBhCFTkly7Yl7+s4hiw+k6ZJTXT2AiI +rvmsmfqPr5E7ecwXb6kXA5LTFd6rjwTuV3sCMQC9w6ZSbVq1mveMO6dKFziOzClT +KCT2RFT6yXtq6nzgRICgBmiXbJAXsmZ8yAwjyWc= +-----END CERTIFICATE----- diff --git a/nix/pkgs/spindle-image-helpers.nix b/nix/pkgs/spindle-image-helpers.nix index dba184e1a..27c4f60ab 100644 --- a/nix/pkgs/spindle-image-helpers.nix +++ b/nix/pkgs/spindle-image-helpers.nix @@ -4,6 +4,7 @@ writeShellScript, writeText, publicsuffix-list, + python3, }: let nixConf = writeText "nix.conf" '' # mirror nix/microvm/base.nix and nix/modules/shuttle.nix @@ -49,6 +50,15 @@ busybox ip route add default via 10.0.3.2 ''; in { + # roots behind the leaked emudhra intermediates, nothing in the guest should trust them + distrustedRoots = ./spindle-distrusted-roots/roots; + + # fails the build if a copy is left anywhere else in the rootfs + distrustRoots = writeShellScript "distrust-roots" '' + exec ${python3.interpreter} ${./spindle-distrusted-roots/distrust-roots.py} \ + --roots ${./spindle-distrusted-roots/roots} "$@" + ''; + setupRootfs = writeShellScript "setup-rootfs" '' set -eu -- 2.54.0