swarm: refuse swarm.hives keys that are not valid hive identifiers
A hive key becomes a certificate subject and a directory name, but only the reserved-name and fragment guards looked at it, so `a/b` or `../x` evaluated and misbehaved later. Assert at eval time that every key is 1-63 characters of [a-z0-9-], the rule `Ident` applies in hive-types, and name the offending keys in the message.
This commit is contained in:
parent
48a728df17
commit
7ee6e776a3
3 changed files with 91 additions and 1 deletions
|
|
@ -63,4 +63,27 @@ in
|
|||
problem = "has name(s) containing a reserved word";
|
||||
forbiddenLabel = "Forbidden as substrings";
|
||||
};
|
||||
|
||||
# Every name must be a valid hive `Ident` (hive-types/src/lib.rs): 1-63
|
||||
# characters of `[a-z0-9-]`, with no leading-character rule. A hive name is
|
||||
# spliced into certificate subjects and filesystem paths, where `/` and `..`
|
||||
# change meaning.
|
||||
mustBeIdent =
|
||||
{ option, names }:
|
||||
let
|
||||
offenders = lib.filter (n: builtins.match "[a-z0-9-]+" n == null || lib.stringLength n > 63) names;
|
||||
in
|
||||
{
|
||||
assertion = offenders == [ ];
|
||||
message = ''
|
||||
${option} has name(s) that are not valid identifiers: ${
|
||||
lib.concatMapStringsSep ", " (x: "'${x}'") offenders
|
||||
}
|
||||
Required: 1-63 characters, each one of [a-z0-9-]
|
||||
|
||||
A hive name becomes a certificate subject and a directory name, so a
|
||||
`/` or `..` in it names something else. `Ident` in
|
||||
hive-types/src/lib.rs applies the same rule. Rename the hive.
|
||||
'';
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -369,6 +369,10 @@ in
|
|||
designed for it.
|
||||
'';
|
||||
}
|
||||
(nameGuards.mustBeIdent {
|
||||
option = "services.hyperhive.swarm.hives";
|
||||
names = hiveNames;
|
||||
})
|
||||
(nameGuards.mustNotEqual {
|
||||
option = "services.hyperhive.swarm.hives";
|
||||
names = hiveNames;
|
||||
|
|
|
|||
|
|
@ -126,7 +126,38 @@ let
|
|||
swarm.hives."h1-agent".domain = "a.t.local";
|
||||
};
|
||||
|
||||
# Markers from `lib/name-guards.nix`'s two `problem` strings. Matching the
|
||||
# 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 {
|
||||
deploy.swarm-otel.enable = false;
|
||||
swarm.hives."a/b".domain = "a.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";
|
||||
};
|
||||
|
||||
# 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;
|
||||
|
|
@ -136,7 +167,39 @@ let
|
|||
lib.any (
|
||||
a: !a.assertion && lib.hasInfix "has name(s) containing a reserved word" a.message
|
||||
) h.assertions;
|
||||
|
||||
# True when the identifier guard refuses `key`.
|
||||
identGuardRefuses =
|
||||
h: key:
|
||||
lib.any (
|
||||
a:
|
||||
!a.assertion && lib.hasInfix "not valid identifiers" a.message && lib.hasInfix "'${key}'" a.message
|
||||
) h.assertions;
|
||||
|
||||
identGuardFired =
|
||||
h: lib.any (a: !a.assertion && lib.hasInfix "not valid identifiers" a.message) h.assertions;
|
||||
cases = [
|
||||
{
|
||||
name = "a hive key containing a slash is refused, naming the key";
|
||||
ok = identGuardRefuses hiveNameWithSlash "a/b";
|
||||
}
|
||||
{
|
||||
name = "a hive key that climbs out of its directory is refused, naming the key";
|
||||
ok = identGuardRefuses hiveNameWithParentDir "../x";
|
||||
}
|
||||
{
|
||||
name = "a hive key over 63 characters is refused";
|
||||
ok = identGuardFired hiveNameOverLengthCap;
|
||||
}
|
||||
{
|
||||
# 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);
|
||||
}
|
||||
{
|
||||
# `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
|
||||
|
|
|
|||
Loading…
Reference in a new issue