module-eval: drop 18 redundant cases and merge assertion-only hosts
Refs #5379
This commit is contained in:
parent
e9f313e75d
commit
2ff253e520
15 changed files with 122 additions and 359 deletions
|
|
@ -21,104 +21,74 @@ let
|
|||
runGroup
|
||||
;
|
||||
|
||||
# The hive-name guards, with the collector explicitly OFF. That is the whole
|
||||
# One host per guard. Each guard reports every offender in one message
|
||||
# (`../host-modules/lib/name-guards.nix`), so one host carrying every
|
||||
# colliding name still tells each name's arm apart: a name its arm misses
|
||||
# is absent from the offenders line.
|
||||
#
|
||||
# Every fixture runs with the collector explicitly OFF. That is the whole
|
||||
# property: the guards live where `swarm.hives` is declared, so they run in a
|
||||
# deployment that has a secret store and no collector, rather than being
|
||||
# assertions inside swarm-otel's own `mkIf`.
|
||||
|
||||
# Every cert-auth subject an operator can override, set to a name with no
|
||||
# reserved fragment and taken by a hive. `controllerCommonName`'s default
|
||||
# (`swarm-controller`) contains `swarm` and is caught by the substring guard
|
||||
# whatever the cert-auth arm does, so the default could not tell the two
|
||||
# apart.
|
||||
#
|
||||
# ⚠️ `controllerCommonName` is overridden to a name containing NO reserved
|
||||
# fragment. Its default (`swarm-controller`) contains `swarm` and is caught
|
||||
# by the substring guard whatever the cert-auth arm does — so a fixture using
|
||||
# the default could not tell the two apart, and the arm under test would pass
|
||||
# on the neighbour's work.
|
||||
hiveNamedAfterCertSubject = hive {
|
||||
# The fixed subjects are a LIST, and a list with one consulted element and
|
||||
# one dead one looks identical from the first element's case: each element
|
||||
# needs its own name in the offenders line. matrix-ctl's grant names its
|
||||
# leaves, not a whole prefix, so a hive that inherited a dead entry there
|
||||
# would silently gain the ability to overwrite the swarm's matrix credential.
|
||||
#
|
||||
# 🩸 The two per-hive subjects have a different shape: those roles are
|
||||
# written PER HIVE, so the subject a hive must not be is
|
||||
# `<prefix>-<some hive's name>` rather than the prefix itself. Reserving only
|
||||
# the prefix would leave the composed spelling free, and a hive taking it
|
||||
# would present a leaf the other hive's role accepts — a hive reading another
|
||||
# hive's queue credential, the exact widening the split exists to avoid. The
|
||||
# collision is with the OTHER hive's role, hence `other` beside `qr-other`
|
||||
# and `hl-other`.
|
||||
hiveNamedAfterEverySubject = hive {
|
||||
deploy.swarm-otel.enable = false;
|
||||
deploy.bao.controllerCommonName = "ctl";
|
||||
swarm.hives.ctl.domain = "ctl.t.local";
|
||||
deploy.bao = {
|
||||
controllerCommonName = "ctl";
|
||||
secretPublisherCommonName = "pubctl";
|
||||
matrixCtlCommonName = "mintctl";
|
||||
grafanaOidcCommonName = "gfctl";
|
||||
otelOidcCommonName = "otctl";
|
||||
forwarderOidcCommonName = "fwctl";
|
||||
forgePublisherCommonName = "fpctl";
|
||||
granterCommonName = "grctl";
|
||||
queueAgentCommonNamePrefix = "qr";
|
||||
hiveLeafCommonNamePrefix = "hl";
|
||||
};
|
||||
swarm.hives = {
|
||||
ctl.domain = "ctl.t.local";
|
||||
pubctl.domain = "p.t.local";
|
||||
mintctl.domain = "m.t.local";
|
||||
gfctl.domain = "g.t.local";
|
||||
otctl.domain = "o.t.local";
|
||||
fwctl.domain = "f.t.local";
|
||||
fpctl.domain = "fp.t.local";
|
||||
grctl.domain = "gr.t.local";
|
||||
other.domain = "other.t.local";
|
||||
qr-other.domain = "q.t.local";
|
||||
hl-other.domain = "hl.t.local";
|
||||
};
|
||||
};
|
||||
|
||||
# The control for both arms below: same shape, a roster nothing objects to.
|
||||
# The control for every refusal below: same shape, a roster nothing objects
|
||||
# to. It carries both sides of the identifier rule's edges — 63 characters,
|
||||
# and digits and hyphens in every position (the rule has no
|
||||
# leading-character restriction).
|
||||
hiveNamesAllLegal = hive {
|
||||
deploy.swarm-otel.enable = false;
|
||||
deploy.bao.controllerCommonName = "ctl";
|
||||
};
|
||||
|
||||
# The reserved subjects are a LIST, and a list with one consulted element and
|
||||
# one dead one looks identical from the first element's case. This fixture
|
||||
# collides with the SECOND, leaving the controller's at its default.
|
||||
hiveNamedAfterPublisherSubject = hive {
|
||||
deploy.swarm-otel.enable = false;
|
||||
deploy.bao.secretPublisherCommonName = "pubctl";
|
||||
swarm.hives.pubctl.domain = "p.t.local";
|
||||
};
|
||||
|
||||
# The THIRD element of the same list, colliding on its own so neither of the
|
||||
# two above can carry it. matrix-ctl's grant names its leaves, not a whole
|
||||
# prefix, which is exactly why a dead entry here would be easy to miss: a
|
||||
# hive that inherited it would not obviously break anything, it would
|
||||
# silently gain the ability to overwrite the swarm's matrix credential.
|
||||
hiveNamedAfterMatrixCtlSubject = hive {
|
||||
deploy.swarm-otel.enable = false;
|
||||
deploy.bao.matrixCtlCommonName = "mintctl";
|
||||
swarm.hives.mintctl.domain = "m.t.local";
|
||||
};
|
||||
|
||||
# The two OIDC-secret readers' subjects, fixed strings like the three above.
|
||||
hiveNamedAfterGrafanaOidcSubject = hive {
|
||||
deploy.swarm-otel.enable = false;
|
||||
deploy.bao.grafanaOidcCommonName = "gfctl";
|
||||
swarm.hives.gfctl.domain = "g.t.local";
|
||||
};
|
||||
|
||||
hiveNamedAfterOtelOidcSubject = hive {
|
||||
deploy.swarm-otel.enable = false;
|
||||
deploy.bao.otelOidcCommonName = "otctl";
|
||||
swarm.hives.otctl.domain = "o.t.local";
|
||||
};
|
||||
|
||||
# The store forwarder's OIDC reader, the fifth fixed subject.
|
||||
hiveNamedAfterForwarderOidcSubject = hive {
|
||||
deploy.swarm-otel.enable = false;
|
||||
deploy.bao.forwarderOidcCommonName = "fwctl";
|
||||
swarm.hives.fwctl.domain = "f.t.local";
|
||||
};
|
||||
|
||||
# The forge publisher's, the one writer of the controller's forge token.
|
||||
hiveNamedAfterForgePublisherSubject = hive {
|
||||
deploy.swarm-otel.enable = false;
|
||||
deploy.bao.forgePublisherCommonName = "fpctl";
|
||||
swarm.hives.fpctl.domain = "fp.t.local";
|
||||
};
|
||||
|
||||
# The granter's, the one subject whose role may write every `swarm-*` grant.
|
||||
hiveNamedAfterGranterSubject = hive {
|
||||
deploy.swarm-otel.enable = false;
|
||||
deploy.bao.granterCommonName = "grctl";
|
||||
swarm.hives.grctl.domain = "gr.t.local";
|
||||
};
|
||||
|
||||
# 🩸 A different shape from every fixture above: the queue-credential roles
|
||||
# are written PER HIVE, so the subject a hive must not
|
||||
# be is `<prefix>-<some hive's name>` rather than the prefix itself. Reserving
|
||||
# only the prefix would leave the composed spelling free, and a hive taking it
|
||||
# would present a leaf the other hive's role accepts — which is a hive reading
|
||||
# another hive's queue credential, the exact widening the split exists to
|
||||
# avoid.
|
||||
#
|
||||
# Two hives here, not one: the collision is with the OTHER hive's role.
|
||||
hiveNamedAfterPerHiveReaderSubject = hive {
|
||||
deploy.swarm-otel.enable = false;
|
||||
deploy.bao.queueAgentCommonNamePrefix = "qr";
|
||||
swarm.hives.other.domain = "o.t.local";
|
||||
swarm.hives.qr-other.domain = "q.t.local";
|
||||
};
|
||||
|
||||
# The per-hive gateway-leaf subject, composed the same way.
|
||||
hiveNamedAfterHiveLeafSubject = hive {
|
||||
deploy.swarm-otel.enable = false;
|
||||
deploy.bao.hiveLeafCommonNamePrefix = "hl";
|
||||
swarm.hives.other.domain = "o.t.local";
|
||||
swarm.hives.hl-other.domain = "hl.t.local";
|
||||
swarm.hives.${lib.concatStrings (lib.replicate 63 "a")}.domain = "l.t.local";
|
||||
swarm.hives."0-h-9".domain = "h.t.local";
|
||||
};
|
||||
|
||||
hiveNameWithComposedWord = hive {
|
||||
|
|
@ -128,40 +98,36 @@ let
|
|||
|
||||
# The key characters are checked by the same module, so these run with the
|
||||
# collector off too. `a/b` adds a certificate-subject component; `../x`
|
||||
# leaves a directory named after the hive.
|
||||
hiveNameWithSlash = hive {
|
||||
# leaves a directory named after the hive; 64 characters is one over the cap.
|
||||
hiveNamesNotIdents = hive {
|
||||
deploy.swarm-otel.enable = false;
|
||||
swarm.hives."a/b".domain = "a.t.local";
|
||||
swarm.hives = {
|
||||
"a/b".domain = "a.t.local";
|
||||
"../x".domain = "x.t.local";
|
||||
${lib.concatStrings (lib.replicate 64 "a")}.domain = "l.t.local";
|
||||
};
|
||||
};
|
||||
|
||||
hiveNameWithParentDir = hive {
|
||||
deploy.swarm-otel.enable = false;
|
||||
swarm.hives."../x".domain = "x.t.local";
|
||||
};
|
||||
|
||||
# Both sides of the length cap: 63 characters passes, 64 does not.
|
||||
hiveNameAtLengthCap = hive {
|
||||
deploy.swarm-otel.enable = false;
|
||||
swarm.hives.${lib.concatStrings (lib.replicate 63 "a")}.domain = "l.t.local";
|
||||
};
|
||||
|
||||
hiveNameOverLengthCap = hive {
|
||||
deploy.swarm-otel.enable = false;
|
||||
swarm.hives.${lib.concatStrings (lib.replicate 64 "a")}.domain = "l.t.local";
|
||||
};
|
||||
|
||||
# Digits and hyphens in every position: the rule has no leading-character
|
||||
# restriction.
|
||||
hiveNameWithDigitsAndHyphens = hive {
|
||||
deploy.swarm-otel.enable = false;
|
||||
swarm.hives."0-h-9".domain = "h.t.local";
|
||||
};
|
||||
# The first line of a guard's message: the problem and its offenders. The
|
||||
# lines after it list the forbidden values, which name a subject whether or
|
||||
# not any hive took it.
|
||||
offendersLine = a: lib.head (lib.splitString "\n" a.message);
|
||||
|
||||
# Markers from `lib/name-guards.nix`'s `problem` strings. Matching the
|
||||
# problem rather than the `why` prose keeps the messages rewordable.
|
||||
equalityGuardFired =
|
||||
h: lib.any (a: !a.assertion && lib.hasInfix "has reserved name(s)" a.message) h.assertions;
|
||||
|
||||
# True when the equality guard names `name` as an offender.
|
||||
equalityGuardRefuses =
|
||||
h: name:
|
||||
lib.any (
|
||||
a:
|
||||
!a.assertion
|
||||
&& lib.hasInfix "has reserved name(s)" (offendersLine a)
|
||||
&& lib.hasInfix "'${name}'" (offendersLine a)
|
||||
) h.assertions;
|
||||
|
||||
fragmentGuardFired =
|
||||
h:
|
||||
lib.any (
|
||||
|
|
@ -181,44 +147,35 @@ let
|
|||
cases = [
|
||||
{
|
||||
name = "a hive key containing a slash is refused, naming the key";
|
||||
ok = identGuardRefuses hiveNameWithSlash "a/b";
|
||||
ok = identGuardRefuses hiveNamesNotIdents "a/b";
|
||||
}
|
||||
{
|
||||
name = "a hive key that climbs out of its directory is refused, naming the key";
|
||||
ok = identGuardRefuses hiveNameWithParentDir "../x";
|
||||
ok = identGuardRefuses hiveNamesNotIdents "../x";
|
||||
}
|
||||
{
|
||||
name = "a hive key over 63 characters is refused";
|
||||
ok = identGuardFired hiveNameOverLengthCap;
|
||||
ok = identGuardRefuses hiveNamesNotIdents (lib.concatStrings (lib.replicate 64 "a"));
|
||||
}
|
||||
{
|
||||
# Controls: without them an identifier guard that refuses everything
|
||||
# passes the three cases above.
|
||||
name = "63-character and digit-and-hyphen hive keys pass the identifier guard";
|
||||
ok =
|
||||
!(identGuardFired hiveNameAtLengthCap)
|
||||
&& !(identGuardFired hiveNameWithDigitsAndHyphens)
|
||||
&& !(identGuardFired hiveNamesAllLegal);
|
||||
ok = !(identGuardFired hiveNamesAllLegal);
|
||||
}
|
||||
{
|
||||
# `ctl` is in no deny list — it is reserved *because it is the subject a
|
||||
# cert-auth role accepts*, which is a value an operator sets, so a
|
||||
# literal deny entry could never have covered it.
|
||||
name = "a hive named after a cert-auth subject is refused, with the collector off";
|
||||
ok =
|
||||
equalityGuardFired hiveNamedAfterCertSubject
|
||||
&& lib.any (a: !a.assertion && lib.hasInfix "'ctl'" a.message) hiveNamedAfterCertSubject.assertions;
|
||||
ok = equalityGuardRefuses hiveNamedAfterEverySubject "ctl";
|
||||
}
|
||||
{
|
||||
# Every cert-auth subject is reserved, not just the first one in the
|
||||
# list. Without this case the second element could be dead and the case
|
||||
# above would still pass.
|
||||
name = "a hive named after the secret publisher's subject is refused too";
|
||||
ok =
|
||||
equalityGuardFired hiveNamedAfterPublisherSubject
|
||||
&& lib.any (
|
||||
a: !a.assertion && lib.hasInfix "'pubctl'" a.message
|
||||
) hiveNamedAfterPublisherSubject.assertions;
|
||||
ok = equalityGuardRefuses hiveNamedAfterEverySubject "pubctl";
|
||||
}
|
||||
{
|
||||
# And the third, for the reason the second one's comment gives one list
|
||||
|
|
@ -226,11 +183,7 @@ let
|
|||
# has to register itself, and nothing but a case per element notices when
|
||||
# one forgets.
|
||||
name = "a hive named after matrix-ctl's subject is refused too";
|
||||
ok =
|
||||
equalityGuardFired hiveNamedAfterMatrixCtlSubject
|
||||
&& lib.any (
|
||||
a: !a.assertion && lib.hasInfix "'mintctl'" a.message
|
||||
) hiveNamedAfterMatrixCtlSubject.assertions;
|
||||
ok = equalityGuardRefuses hiveNamedAfterEverySubject "mintctl";
|
||||
}
|
||||
{
|
||||
# The fourth and fifth, for the reason the case above gives: `certAuthCns`
|
||||
|
|
@ -240,43 +193,25 @@ let
|
|||
# client secrets.
|
||||
name = "a hive named after either OIDC-secret reader's subject is refused too";
|
||||
ok =
|
||||
equalityGuardFired hiveNamedAfterGrafanaOidcSubject
|
||||
&& lib.any (
|
||||
a: !a.assertion && lib.hasInfix "'gfctl'" a.message
|
||||
) hiveNamedAfterGrafanaOidcSubject.assertions
|
||||
&& equalityGuardFired hiveNamedAfterOtelOidcSubject
|
||||
&& lib.any (
|
||||
a: !a.assertion && lib.hasInfix "'otctl'" a.message
|
||||
) hiveNamedAfterOtelOidcSubject.assertions;
|
||||
equalityGuardRefuses hiveNamedAfterEverySubject "gfctl"
|
||||
&& equalityGuardRefuses hiveNamedAfterEverySubject "otctl";
|
||||
}
|
||||
{
|
||||
# And the store forwarder's, for the same reason one element later.
|
||||
name = "a hive named after the store forwarder's OIDC-reader subject is refused too";
|
||||
ok =
|
||||
equalityGuardFired hiveNamedAfterForwarderOidcSubject
|
||||
&& lib.any (
|
||||
a: !a.assertion && lib.hasInfix "'fwctl'" a.message
|
||||
) hiveNamedAfterForwarderOidcSubject.assertions;
|
||||
ok = equalityGuardRefuses hiveNamedAfterEverySubject "fwctl";
|
||||
}
|
||||
{
|
||||
# And the forge publisher's: a hive holding a leaf it accepts could
|
||||
# replace the token the controller provisions every forge object with.
|
||||
name = "a hive named after the forge publisher's subject is refused too";
|
||||
ok =
|
||||
equalityGuardFired hiveNamedAfterForgePublisherSubject
|
||||
&& lib.any (
|
||||
a: !a.assertion && lib.hasInfix "'fpctl'" a.message
|
||||
) hiveNamedAfterForgePublisherSubject.assertions;
|
||||
ok = equalityGuardRefuses hiveNamedAfterEverySubject "fpctl";
|
||||
}
|
||||
{
|
||||
# And the granter's, whose role is root-equivalent: a hive holding a leaf
|
||||
# it accepts could grant itself anything.
|
||||
name = "a hive named after the bao granter's subject is refused too";
|
||||
ok =
|
||||
equalityGuardFired hiveNamedAfterGranterSubject
|
||||
&& lib.any (
|
||||
a: !a.assertion && lib.hasInfix "'grctl'" a.message
|
||||
) hiveNamedAfterGranterSubject.assertions;
|
||||
ok = equalityGuardRefuses hiveNamedAfterEverySubject "grctl";
|
||||
}
|
||||
{
|
||||
# 🩸 The per-hive half, and the one a prefix-only reservation would miss:
|
||||
|
|
@ -285,22 +220,14 @@ let
|
|||
# written for hive `other` — a leaf that reads a credential belonging to a
|
||||
# hive that is not it.
|
||||
name = "a hive named after another hive's per-hive reader subject is refused";
|
||||
ok =
|
||||
equalityGuardFired hiveNamedAfterPerHiveReaderSubject
|
||||
&& lib.any (
|
||||
a: !a.assertion && lib.hasInfix "'qr-other'" a.message
|
||||
) hiveNamedAfterPerHiveReaderSubject.assertions;
|
||||
ok = equalityGuardRefuses hiveNamedAfterEverySubject "qr-other";
|
||||
}
|
||||
{
|
||||
# A hive taking this spelling would present a leaf hive `other`'s
|
||||
# gateway-leaf role accepts, and could ask the store for that hive's
|
||||
# certificate. The control is the legal roster below.
|
||||
name = "a hive named after another hive's gateway-leaf subject is refused";
|
||||
ok =
|
||||
equalityGuardFired hiveNamedAfterHiveLeafSubject
|
||||
&& lib.any (
|
||||
a: !a.assertion && lib.hasInfix "'hl-other'" a.message
|
||||
) hiveNamedAfterHiveLeafSubject.assertions;
|
||||
ok = equalityGuardRefuses hiveNamedAfterEverySubject "hl-other";
|
||||
}
|
||||
{
|
||||
# Without this the case above proves nothing: an arm that fires for every
|
||||
|
|
@ -320,7 +247,7 @@ let
|
|||
# pass while testing the arrangement they exist to rule out.
|
||||
name = "the guard fixtures really do have the collector disabled";
|
||||
ok =
|
||||
!hiveNamedAfterCertSubject.services.hyperhive.deploy.swarm-otel.enable
|
||||
!hiveNamedAfterEverySubject.services.hyperhive.deploy.swarm-otel.enable
|
||||
&& !hiveNamesAllLegal.services.hyperhive.deploy.swarm-otel.enable
|
||||
&& !hiveNameWithComposedWord.services.hyperhive.deploy.swarm-otel.enable;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue