The mint creates each client's secret in the swarm secret store and writes only the digest to disk. It mints only when the store answers and holds none. A new unit moves the plaintext files an earlier release left into the store and removes each once the store holds the same value. The bridge reads its secret from the store into /run before each start. The secret publisher unit and its path unit are gone.
522 lines
23 KiB
Nix
522 lines
23 KiB
Nix
# `checks.module-eval-builder` — see ./lib.nix for the shared rationale (why
|
|
# this suite exists, naming convention, "evaluates not executes"). The swarm
|
|
# builder's container, its default and its queue and IdP registrations; its
|
|
# store grants are in ./bao-grants.nix beside every other principal's.
|
|
{
|
|
pkgs,
|
|
lib,
|
|
self,
|
|
nixosSystem,
|
|
shared,
|
|
}:
|
|
let
|
|
inherit
|
|
(import ./lib.nix {
|
|
inherit
|
|
pkgs
|
|
lib
|
|
self
|
|
nixosSystem
|
|
;
|
|
})
|
|
hive
|
|
hiveWithController
|
|
runGroup
|
|
;
|
|
|
|
bare = shared.bareWithController;
|
|
|
|
# On the store's host, which mints the builder's store identity.
|
|
withBuilder = hive {
|
|
deploy.bao.enable = true;
|
|
deploy.builder.enable = true;
|
|
swarm.attic.domain = "cache.example.org";
|
|
swarm.attic.cache = "grid";
|
|
};
|
|
|
|
servicesHere = shared.swarmServicesWithController;
|
|
# Short-circuits on `assertion`, so only a failing one has its message read.
|
|
refusedWithoutIdentity =
|
|
m:
|
|
lib.any (a: !a.assertion && lib.hasInfix "deploy.builder.enable requires" a.message) m.assertions;
|
|
|
|
# The one fixture that fires the cache assertion: a domain with no cache name.
|
|
halfCache = hive {
|
|
deploy.bao.enable = true;
|
|
deploy.builder.enable = true;
|
|
swarm.attic.domain = "cache.example.org";
|
|
swarm.attic.cache = null;
|
|
};
|
|
refusedHalfCache =
|
|
m:
|
|
lib.any (
|
|
a:
|
|
!a.assertion && lib.hasInfix "swarm.attic.domain and services.hyperhive.swarm.attic.cache" a.message
|
|
) m.assertions;
|
|
# Only the option is read, so the host around it is never evaluated.
|
|
cacheNamed = name: (hive { swarm.attic.cache = name; }).services.hyperhive.swarm.attic.cache;
|
|
|
|
natsHere = shared.natsQueueOnly;
|
|
|
|
autheliaHere = hive {
|
|
deploy.bao.enable = true;
|
|
deploy.authelia.enable = true;
|
|
};
|
|
|
|
container = withBuilder.containers.swarm-builder;
|
|
inner = container.config;
|
|
svc = inner.systemd.services.swarm-builder;
|
|
env = svc.environment;
|
|
hasOnPath = name: s: lib.any (p: lib.getName p == name) s.path;
|
|
# A builder that opts out of pushing: both options null.
|
|
withBuilderNoCache = hive {
|
|
deploy.bao.enable = true;
|
|
deploy.builder.enable = true;
|
|
swarm.attic.domain = null;
|
|
swarm.attic.cache = null;
|
|
};
|
|
withoutCache = withBuilderNoCache.containers.swarm-builder.config.systemd.services.swarm-builder;
|
|
|
|
# A builder on a host that doesn't run the cache and names nothing.
|
|
builderDefaults = hive {
|
|
deploy.bao.enable = true;
|
|
deploy.builder.enable = true;
|
|
};
|
|
defaultsEnv =
|
|
builderDefaults.containers.swarm-builder.config.systemd.services.swarm-builder.environment;
|
|
|
|
# Hosting the shared services runs the swarm's own cache, which names itself.
|
|
atticHere = servicesHere;
|
|
atticDeploy = atticHere.services.hyperhive.deploy.attic;
|
|
atticSwarm = m: m.services.hyperhive.swarm.attic;
|
|
atticUnit = atticHere.containers.swarm-attic.config.systemd.services.swarm-attic;
|
|
atticSecret = atticHere.systemd.services.swarm-bao-attic-secret;
|
|
atticTokens = atticHere.systemd.services.swarm-bao-attic-tokens;
|
|
atticVhost = atticHere.services.hyperhive.gateway.vhosts."attic.t.local";
|
|
|
|
# The cache switched on while the operator overrides the options.
|
|
atticByHand = hiveWithController {
|
|
deploy.bao.enable = true;
|
|
deploy.attic.enable = true;
|
|
swarm.attic.domain = "cache.example.org";
|
|
swarm.attic.cache = "grid";
|
|
};
|
|
|
|
# An external cache on a host that hosts the shared services: the switch forced
|
|
# off, the two options named.
|
|
atticExternal = hiveWithController {
|
|
deploy.allSwarmServices = true;
|
|
deploy.attic.enable = lib.mkForce false;
|
|
swarm.attic.domain = "cache.example.org";
|
|
swarm.attic.cache = "grid";
|
|
};
|
|
atticWarnings =
|
|
m:
|
|
lib.length (
|
|
lib.filter (lib.hasInfix "services.hyperhive.deploy.attic.enable is on while") m.warnings
|
|
);
|
|
hasAtticHost =
|
|
m:
|
|
m.containers ? swarm-attic
|
|
|| m.systemd.services ? swarm-bao-attic-secret
|
|
|| m.systemd.services ? swarm-bao-attic-tokens;
|
|
|
|
# The check flake on the inputs the builder's unit names, called as
|
|
# `nix build` calls it. `hyperhive` is entered through the filtered source's
|
|
# own `flake.nix`.
|
|
builderCheck =
|
|
agentModule:
|
|
let
|
|
src = env.SWARM_BUILDER_HYPERHIVE_FLAKE;
|
|
hyperhive = (import "${src}/flake.nix").outputs (
|
|
self.inputs
|
|
// {
|
|
self = hyperhive // {
|
|
outPath = src;
|
|
inherit (self) inputs;
|
|
};
|
|
}
|
|
);
|
|
in
|
|
((import "${env.SWARM_BUILDER_CHECK_FLAKE}/flake.nix").outputs {
|
|
inherit hyperhive;
|
|
agent.nixosModules.default = agentModule;
|
|
}).packages.x86_64-linux.check;
|
|
|
|
cases = [
|
|
{
|
|
name = "the swarm builder's container exists only where its host enables it";
|
|
ok = !(bare.containers ? swarm-builder) && withBuilder.containers ? swarm-builder;
|
|
}
|
|
{
|
|
# The store's host mints the builder's leaf, so the switch needs no
|
|
# manual identity step: the pair is set and the module's identity
|
|
# assertion, which nothing else reads in this fixture, passes. `nats` is
|
|
# the control that the switch is on.
|
|
name = "hosting the shared services turns the builder on, with its store identity";
|
|
ok =
|
|
servicesHere.services.hyperhive.deploy.builder.enable
|
|
&& servicesHere.containers ? swarm-builder
|
|
&& servicesHere.services.hyperhive.deploy.bao.builderClientCertFile != null
|
|
&& servicesHere.services.hyperhive.deploy.bao.builderClientKeyFile != null
|
|
&& !(refusedWithoutIdentity servicesHere)
|
|
&& servicesHere.services.hyperhive.deploy.bao.enable
|
|
&& servicesHere.services.hyperhive.deploy.nats.enable;
|
|
}
|
|
{
|
|
# A config PR is unreviewed input to an evaluation: the container keeps
|
|
# its own netns, and reaches the host's nix daemon through its socket
|
|
# alone, with no daemon of its own racing the bind.
|
|
name = "the builder's container has its own netns and builds only through the host's daemon socket";
|
|
ok =
|
|
container.privateNetwork
|
|
&& inner.services.hyperhive.swarmContainer.privateNetwork
|
|
&& container.bindMounts."/nix/var/nix/daemon-socket".hostPath == "/nix/var/nix/daemon-socket"
|
|
&& !inner.systemd.services.nix-daemon.enable
|
|
&& !inner.systemd.sockets.nix-daemon.enable
|
|
&& inner.systemd.globalEnvironment.NIX_REMOTE == "daemon";
|
|
}
|
|
{
|
|
# The daemon holds its store leaf, handed in by nspawn without a copy on
|
|
# disk; the forge token is read per build and reaches only `git`.
|
|
name = "the builder loads only its store leaf, runs as a dynamic user and carries no token in its unit";
|
|
ok =
|
|
lib.elem "builder-bao-key" svc.serviceConfig.LoadCredential
|
|
&& env.BAO_CLIENT_KEY == "/run/credentials/swarm-builder.service/builder-bao-key"
|
|
&& env.SWARM_BUILDER_OIDC_CLIENT_ID == "swarm-builder"
|
|
&& lib.elem "--load-credential=builder-bao-key:/var/lib/swarm-bao-pki/builder-key.pem" container.extraFlags
|
|
&& svc.serviceConfig.DynamicUser
|
|
&& !(lib.any (lib.hasSuffix "TOKEN") (lib.attrNames env));
|
|
}
|
|
{
|
|
# The closure is pushed with `attic` from the unit, whose config file holds
|
|
# the push token: it goes in a runtime directory only the dynamic user can
|
|
# enter, and the token is in no variable. The host without a cache is the
|
|
# control: same unit, none of it, with `git` still on the path to show
|
|
# the lookup finds what is there.
|
|
name = "the builder pushes to the configured cache with attic from a private runtime directory, and pushes nowhere without one";
|
|
ok =
|
|
env.SWARM_BUILDER_ATTIC_DOMAIN == "cache.example.org"
|
|
&& env.SWARM_BUILDER_ATTIC_CACHE == "grid"
|
|
&& hasOnPath "attic" svc
|
|
&& svc.serviceConfig.RuntimeDirectory == "swarm-builder"
|
|
&& svc.serviceConfig.RuntimeDirectoryMode == "0700"
|
|
&& !(lib.any (lib.hasSuffix "TOKEN") (lib.attrNames env))
|
|
&& hasOnPath "git" withoutCache
|
|
&& !(hasOnPath "attic" withoutCache)
|
|
&& !(withoutCache.environment ? SWARM_BUILDER_ATTIC_DOMAIN)
|
|
&& !(withoutCache.environment ? SWARM_BUILDER_ATTIC_CACHE);
|
|
}
|
|
{
|
|
# A domain with no cache name would crash-loop the builder at start, so
|
|
# evaluation refuses it. `withBuilder`, with both set, is the control: the
|
|
# same assertion list holds the entry and it passes.
|
|
name = "the builder refuses a cache domain without a cache name";
|
|
ok =
|
|
refusedHalfCache halfCache
|
|
&& !(refusedHalfCache withBuilder)
|
|
&& lib.any (a: lib.hasInfix "swarm.attic.domain and" a.message) withBuilder.assertions;
|
|
}
|
|
{
|
|
# attic refuses a cache name outside its rule, which would fail every push.
|
|
# `grid` is the control that the lookup reads a name.
|
|
name = "the cache name must follow attic's rule";
|
|
ok =
|
|
cacheNamed "grid" == "grid"
|
|
&& cacheNamed "A-b_c+d" == "A-b_c+d"
|
|
&& lib.all (n: !(builtins.tryEval (cacheNamed n)).success) [
|
|
"grid.v2"
|
|
"-grid"
|
|
""
|
|
(lib.concatStrings (lib.genList (_: "a") 51))
|
|
];
|
|
}
|
|
{
|
|
# The minting unit goes inactive after each run, so a start that pulls it
|
|
# runs it again and mints a leaf that is missing; an active
|
|
# `RemainAfterExit` unit would satisfy the pull without running.
|
|
name = "the builder's container runs the leaf-minting unit before every start";
|
|
ok =
|
|
let
|
|
pki = withBuilder.systemd.services.swarm-bao-pki;
|
|
c = withBuilder.systemd.services."container@swarm-builder";
|
|
in
|
|
pki.serviceConfig.Type == "oneshot"
|
|
&& !(pki.serviceConfig.RemainAfterExit or false)
|
|
&& lib.elem "multi-user.target" pki.wantedBy
|
|
&& lib.elem "swarm-bao-pki.service" c.wants
|
|
&& lib.elem "swarm-bao-pki.service" c.after;
|
|
}
|
|
{
|
|
# The check builds on the sources this host was deployed with, so a green check means
|
|
# the same hyperhive and nixpkgs base.
|
|
name = "the builder builds on the hyperhive and nixpkgs this host was deployed with";
|
|
ok =
|
|
env.SWARM_BUILDER_HYPERHIVE_FLAKE == withBuilder.services.hyperhive.deploy.hyperhiveFlake
|
|
&& env.SWARM_BUILDER_NIXPKGS_FLAKE == withBuilder.services.hyperhive.deploy.builder.nixpkgsFlake
|
|
&& lib.hasSuffix "-check-flake" env.SWARM_BUILDER_CHECK_FLAKE
|
|
&& env.SWARM_BUILDER_SYSTEM == pkgs.stdenv.hostPlatform.system
|
|
&& env.SWARM_BUILDER_CONFIG_ORG == "agent-configs"
|
|
&& env.SWARM_BUILDER_FORGE_URL == "https://${withBuilder.services.hyperhive.swarm.forge.domain}";
|
|
}
|
|
{
|
|
# The builder evaluates `builtins.mapAttrs (_: a: a.meta)` over this
|
|
# output and prints it as JSON. A derivation or a path in `meta` would
|
|
# make that evaluation build or copy something; a function would not
|
|
# print at all.
|
|
name = "the built-in archetypes carry a module and a meta of plain data, and reviewer can run commands and android-dev installs its toolchain and reverse-engineer turns rea on";
|
|
ok =
|
|
let
|
|
plain =
|
|
v:
|
|
if lib.isDerivation v || lib.isFunction v || builtins.isPath v then
|
|
false
|
|
else if lib.isAttrs v then
|
|
lib.all plain (lib.attrValues v)
|
|
else if lib.isList v then
|
|
lib.all plain v
|
|
else
|
|
true;
|
|
archetypes = self.hyperhiveArchetypes;
|
|
metas = builtins.mapAttrs (_: a: a.meta) archetypes;
|
|
reviewer = metas.reviewer.permissions;
|
|
in
|
|
lib.all (a: a ? module && a.meta.description != "") (lib.attrValues archetypes)
|
|
&& plain metas
|
|
&& lib.elem "messaging" reviewer
|
|
&& lib.elem "inbox" reviewer
|
|
&& lib.elem "execution" reviewer
|
|
&& !(lib.elem "meta" reviewer)
|
|
&&
|
|
lib.elem pkgs.android-tools
|
|
(archetypes.android-dev.module { inherit pkgs; }).environment.systemPackages
|
|
&& archetypes.reverse-engineer.module.services.hyperhive.agent.rea.enable;
|
|
}
|
|
{
|
|
# `docs.enable` reads `docs.source`, whose default builds from the
|
|
# `docs/` the `hyperhive` source has none of; an eval error there fails
|
|
# this whole group.
|
|
name = "the builder's check evaluates a config with the reference docs on";
|
|
ok =
|
|
lib.hasSuffix ".drv"
|
|
(builderCheck {
|
|
services.hyperhive.agent.user.name = "a1";
|
|
services.hyperhive.agent.docs.enable = true;
|
|
}).drvPath;
|
|
}
|
|
{
|
|
# The responder denies a client id it does not know, and a NATS denial
|
|
# arrives as a timeout.
|
|
name = "the queue's responder grants the builder's client its subject";
|
|
ok = lib.hasInfix "--builder-client swarm-builder" natsHere.containers.swarm-nats.config.systemd.services.swarm-nats-auth.serviceConfig.ExecStart;
|
|
}
|
|
{
|
|
name = "the builder's client is registered as a machine, and the mint stores its secret";
|
|
ok =
|
|
let
|
|
c = lib.findFirst (
|
|
c: c.id == "swarm-builder"
|
|
) null autheliaHere.services.hyperhive.swarm.authelia.oidc.clients;
|
|
in
|
|
c != null
|
|
&& c.kind == "machine"
|
|
&& lib.hasInfix "secret/swarm/services/swarm-builder/oidc/client" (
|
|
autheliaHere.containers.swarm-authelia.config.systemd.services.authelia-swarm-secrets.script
|
|
);
|
|
}
|
|
{
|
|
# The switch is on wherever the shared services are and the host holds the
|
|
# cache's leaf.
|
|
# The builder on the same host pushes to it. `bare` is the control that no
|
|
# cache renders where nothing enables it.
|
|
name = "hosting the shared services runs the swarm's cache, and the builder pushes to it";
|
|
ok =
|
|
atticDeploy.enable
|
|
&& atticHere.containers ? swarm-attic
|
|
&& atticHere.services.hyperhive.deploy.bao.atticClientCertFile != null
|
|
&& atticHere.services.hyperhive.deploy.bao.atticClientKeyFile != null
|
|
&& (atticSwarm atticHere).domain == "attic.t.local"
|
|
&& (atticSwarm atticHere).cache == "swarm"
|
|
&&
|
|
atticHere.containers.swarm-builder.config.systemd.services.swarm-builder.environment.SWARM_BUILDER_ATTIC_DOMAIN
|
|
== "attic.t.local"
|
|
&&
|
|
atticHere.containers.swarm-builder.config.systemd.services.swarm-builder.environment.SWARM_BUILDER_ATTIC_CACHE
|
|
== "swarm"
|
|
&& !(hasAtticHost bare);
|
|
}
|
|
{
|
|
# The options default to the swarm's own cache on every host, so a
|
|
# builder host that runs no cache pushes to it. `atticHere` is the control
|
|
# that the cache host reads the same values.
|
|
name = "a builder host that doesn't run the cache pushes to the swarm's own by default";
|
|
ok =
|
|
!(hasAtticHost builderDefaults)
|
|
&& (atticSwarm builderDefaults).domain == "attic.t.local"
|
|
&& (atticSwarm builderDefaults).cache == "swarm"
|
|
&& (atticSwarm bare).domain == (atticSwarm atticHere).domain
|
|
&& (atticSwarm bare).cache == (atticSwarm atticHere).cache
|
|
&& defaultsEnv.SWARM_BUILDER_ATTIC_DOMAIN == "attic.t.local"
|
|
&& defaultsEnv.SWARM_BUILDER_ATTIC_CACHE == "swarm"
|
|
&& !(builderDefaults.systemd.services ? swarm-bao-attic-tokens);
|
|
}
|
|
{
|
|
# Names the operator gives win over the cache's own, and the gateway then
|
|
# serves the cache under the operator's name. `atticHere` is the control
|
|
# that the same lookups find the defaults.
|
|
name = "options named by hand win over the cache's defaults";
|
|
ok =
|
|
atticByHand.services.hyperhive.deploy.attic.enable
|
|
&& atticByHand.containers ? swarm-attic
|
|
&& (atticSwarm atticByHand).domain == "cache.example.org"
|
|
&& (atticSwarm atticByHand).cache == "grid"
|
|
&& atticByHand.services.hyperhive.gateway.vhosts ? "cache.example.org"
|
|
&& !(atticByHand.services.hyperhive.gateway.vhosts ? "attic.t.local")
|
|
&& atticHere.services.hyperhive.gateway.vhosts ? "attic.t.local";
|
|
}
|
|
{
|
|
# The cache running here under a name other than its own warns once, naming
|
|
# the fix. `atticHere` (default name) and `atticExternal` (cache off) are
|
|
# the controls that the same lookup finds no warning.
|
|
name = "the cache running under another name warns once, and the default and an external cache do not";
|
|
ok =
|
|
atticWarnings atticByHand == 1
|
|
&& lib.any (lib.hasInfix "lib.mkForce false") atticByHand.warnings
|
|
&& lib.any (lib.hasInfix "cache.example.org") atticByHand.warnings
|
|
&& atticWarnings atticHere == 0
|
|
&& atticWarnings atticExternal == 0;
|
|
}
|
|
{
|
|
# An operator's external cache on a host that hosts the shared services:
|
|
# with the switch forced off nothing of the cache is rendered, and the
|
|
# options keep what was named. `atticHere` is the control that the same
|
|
# lookups find the cache's pieces where it runs.
|
|
name = "with the cache switched off the options are the operator's and no cache runs";
|
|
ok =
|
|
!atticExternal.services.hyperhive.deploy.attic.enable
|
|
&& !(hasAtticHost atticExternal)
|
|
&& !(atticExternal.services.hyperhive.gateway.vhosts ? "attic.t.local")
|
|
&& (atticSwarm atticExternal).domain == "cache.example.org"
|
|
&& (atticSwarm atticExternal).cache == "grid"
|
|
&&
|
|
atticExternal.containers.swarm-builder.config.systemd.services.swarm-builder.environment.SWARM_BUILDER_ATTIC_DOMAIN
|
|
== "cache.example.org"
|
|
&& hasAtticHost atticHere
|
|
&& atticHere.systemd.services ? swarm-bao-attic-secret
|
|
&& atticHere.systemd.services ? swarm-bao-attic-tokens;
|
|
}
|
|
{
|
|
# The hive collector's journal allow-list carries the cache's init units
|
|
# on a hive host with the cache and no swarm collector (`atticByHand`).
|
|
# Where the swarm collector runs too (`atticHere`) it reads the whole
|
|
# journal, so the units are not named twice; without the cache
|
|
# (`atticExternal`) they are absent. `hive-c0re.service` is the control
|
|
# that each list is the one the collector reads.
|
|
name = "the hive collector ships the cache's init units only where nothing else reads the host journal";
|
|
ok =
|
|
let
|
|
units = m: m.services.opentelemetry-collector.settings.receivers.journald.units;
|
|
initUnits = [
|
|
"swarm-bao-attic-secret.service"
|
|
"swarm-bao-attic-tokens.service"
|
|
];
|
|
has = m: lib.all (u: lib.elem u (units m)) initUnits;
|
|
hasNone = m: lib.all (u: !(lib.elem u (units m))) initUnits;
|
|
in
|
|
has atticByHand
|
|
&& hasNone atticHere
|
|
&& hasNone atticExternal
|
|
&& lib.all (m: lib.elem "hive-c0re.service" (units m)) [
|
|
atticByHand
|
|
atticHere
|
|
atticExternal
|
|
];
|
|
}
|
|
{
|
|
# The hive collector ships the container units' start output (the rootfs
|
|
# check's lines) unless the swarm collector already reads the whole
|
|
# journal. `hive-c0re.service` is the control that the lookup finds the
|
|
# list.
|
|
name = "the hive collector ships the container units' journal only where nothing else reads the host journal";
|
|
ok =
|
|
let
|
|
units = m: m.services.opentelemetry-collector.settings.receivers.journald.units;
|
|
glob = "container@*.service";
|
|
in
|
|
lib.elem glob (units atticByHand)
|
|
&& !(lib.elem glob (units atticHere))
|
|
&& lib.elem "hive-c0re.service" (units atticHere);
|
|
}
|
|
{
|
|
# Both host units log in as the cache's principal, not the hive's, and
|
|
# the container reaches the key through a read-only directory. The
|
|
# unit script holds the create calls only in ./lib/attic-init.nix, which a
|
|
# script test runs.
|
|
name = "the cache's host units use its own store leaf, and the container gets the key read-only";
|
|
ok =
|
|
atticSecret.environment.BAO_CLIENT_CERT == "/var/lib/swarm-bao-pki/attic.pem"
|
|
&& atticSecret.environment.BAO_CLIENT_KEY == "/var/lib/swarm-bao-pki/attic-key.pem"
|
|
&& atticTokens.environment.BAO_CLIENT_CERT == "/var/lib/swarm-bao-pki/attic.pem"
|
|
&& atticSecret.serviceConfig.Type == "oneshot"
|
|
&& lib.elem "container@swarm-attic.service" atticSecret.before
|
|
&& lib.elem "container@swarm-attic.service" atticTokens.after
|
|
&& lib.elem "swarm-bao-attic-policy.service" atticSecret.after
|
|
&& lib.elem "swarm-bao-attic-policy.service" atticTokens.after
|
|
&& atticHere.containers.swarm-attic.bindMounts."/run/swarm-attic-secrets".isReadOnly
|
|
&& !atticHere.containers.swarm-attic.privateNetwork
|
|
&& atticUnit.serviceConfig.EnvironmentFile == "/run/swarm-attic-secrets/server.env"
|
|
&& lib.hasInfix "--mode monolithic" atticUnit.serviceConfig.ExecStart
|
|
&& atticDeploy.retentionPeriod == "14 days";
|
|
}
|
|
{
|
|
# atticd's journal reaches the swarm collector through the container's own
|
|
# forwarder, under the cache's store leaf. The host with the switch forced
|
|
# off is the control that nothing of it renders there, and `atticHere`
|
|
# that the same lookups find it where the cache runs.
|
|
name = "the cache's container forwards its journal to the swarm collector as its own client, and only where the cache runs";
|
|
ok =
|
|
let
|
|
inner = atticHere.containers.swarm-attic;
|
|
f = inner.config.services.hyperhive.swarmJournalForwarder;
|
|
fetch = inner.config.systemd.services.swarm-journal-forwarder-oidc;
|
|
collector = inner.config.systemd.services.opentelemetry-collector;
|
|
in
|
|
f.enable
|
|
&& f.serviceName == "attic"
|
|
&& f.clientId == "swarm-attic-collector"
|
|
&& f.endpoint == "https://${atticHere.services.hyperhive.swarm.otel.domain}/swarm-attic"
|
|
&& f.store.secretPath == "secret/swarm/services/swarm-attic-collector/oidc/client"
|
|
&& f.store.clientCertFile == "/run/credentials/swarm-journal-forwarder-oidc.service/attic-bao-cert"
|
|
&& lib.elem "attic-bao-key" fetch.serviceConfig.LoadCredential
|
|
&& lib.elem "--load-credential=attic-bao-cert:/var/lib/swarm-bao-pki/attic.pem" inner.extraFlags
|
|
&& !(lib.elem "--link-journal=host" inner.extraFlags)
|
|
&& collector.enable
|
|
&&
|
|
atticHere.services.hyperhive.swarm.otel.serviceProducers.swarm-attic.clientId
|
|
== "swarm-attic-collector"
|
|
&& lib.hasInfix "services/swarm-attic-collector/oidc/client" atticHere.systemd.services.swarm-bao-attic-policy.script
|
|
&& !(atticExternal.containers ? swarm-attic)
|
|
&& atticExternal.services.hyperhive.swarm.otel.serviceProducers ? swarm-attic;
|
|
}
|
|
{
|
|
# attic checks its own tokens and a nix client cannot follow a login
|
|
# redirect, so the vhost carries no `auth_request`; the tokens ride in a
|
|
# header, so it is TLS only; and a store path is one long upload.
|
|
name = "the cache's vhost is TLS only, unauthenticated at the gateway and streams uploads, and its name is a swarm service name on every host";
|
|
ok =
|
|
let
|
|
names = m: m.services.hyperhive.swarm.serviceDomains;
|
|
in
|
|
atticVhost.forceSSL
|
|
&& atticVhost.locations."/".proxyPass == "http://127.0.0.1:8910"
|
|
&& !(lib.hasInfix "auth_request" atticVhost.locations."/".extraConfig)
|
|
&& lib.hasInfix "client_max_body_size 0" atticVhost.locations."/".extraConfig
|
|
&& lib.hasInfix "proxy_request_buffering off" atticVhost.locations."/".extraConfig
|
|
&& lib.elem "attic.t.local" (names atticHere)
|
|
&& names atticHere == names bare
|
|
&& names atticExternal == names bare
|
|
&& !(lib.elem "cache.example.org" (names atticExternal));
|
|
}
|
|
];
|
|
in
|
|
runGroup "builder" cases
|