# The binary-cache facts hive-priv (root) trusts, kept where only root can # change them. # # hive-priv downloads an agent's closure from the swarm cache when a rebuild # asks it to. What decides whether a download is accepted — which cache, and # which key must have signed it — must not come from a file the unprivileged # hive-c0re writes, or a compromised hive-c0re could have root accept a # closure from a cache of its own. So: # # - the cache's address is this host's configuration # ({option}`services.hyperhive.swarm.attic.domain` and `cache`), handed to # hive-priv in its unit's environment; # - the cache's public key and the pull token are read from the store by a root # oneshot, `hive-priv-cache-facts`, under this hive's own store identity, and # land in `/run/hyperhive-priv/attic/` (root, `0700`; files `0600`). # # hive-c0re keeps its own copies under `/run/hyperhive/attic/` for the probe, # which only reports. { pkgs, lib, config, ... }: let hyperhiveCfg = config.services.hyperhive; hiveController = hyperhiveCfg.deploy.hive-controller; tls = hiveController.tls; baoCfg = hyperhiveCfg.swarm.bao; baoDeploy = hyperhiveCfg.deploy.bao; atticCfg = hyperhiveCfg.swarm.attic; # Without a cache to name, a hive name to read under or an identity to log # in with there is nothing to fetch, and hive-priv has no cache to download # from. enabled = hiveController.enable && atticCfg.domain != null && atticCfg.cache != null && hiveController.hiveName != null && tls.baoHiveIdentityCertFile != null && tls.baoHiveIdentityKeyFile != null; dir = "/run/hyperhive-priv/attic"; # Where the store runs on this host, its container is up before the first # login attempt instead of failing against a store that is still starting. storeContainer = lib.optional baoDeploy.enable "container@${baoCfg.machine}.service"; storeRetry = import ./lib/store-retry.nix { }; storeRefreshTimer = import ./lib/store-refresh-timer.nix { }; in { config = lib.mkIf enabled { systemd.tmpfiles.rules = [ "d /run/hyperhive-priv 0700 root root - -" ]; # The URL nix is told is the only substituter for the download; no request # chooses it. systemd.services.hive-priv.environment.HIVE_PRIV_CACHE_URL = "https://${atticCfg.domain}/${atticCfg.cache}"; systemd.services.hive-priv-cache-facts = { description = "Read the swarm cache's public key and pull token for hive-priv"; wantedBy = [ "multi-user.target" ]; wants = [ "network-online.target" ] ++ storeContainer; after = [ "network-online.target" "systemd-tmpfiles-setup.service" ] ++ storeContainer; path = [ baoDeploy.package pkgs.coreutils pkgs.gnugrep pkgs.systemd ]; environment = { BAO_ADDR = "https://${baoCfg.domain}:${toString baoCfg.port}"; BAO_CLIENT_CERT = tls.baoHiveIdentityCertFile; BAO_CLIENT_KEY = tls.baoHiveIdentityKeyFile; } // lib.optionalAttrs (baoDeploy.serverCaFile != null) { BAO_CACERT = baoDeploy.serverCaFile; }; # ./lib/store-retry.nix: the login fails for as long as the store is # sealed or down. inherit (storeRetry) startLimitBurst startLimitIntervalSec; serviceConfig = storeRetry.serviceConfig // { Type = "oneshot"; UMask = "0077"; SyslogIdentifier = "hive-priv-cache-facts"; TimeoutStartSec = "5min"; }; script = import ./lib/attic-root-facts.nix { inherit lib; } { hive = hiveController.hiveName; host = atticCfg.domain; inherit dir; }; }; systemd.timers.hive-priv-cache-facts = storeRefreshTimer "re-read the swarm cache's facts for hive-priv"; }; }