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";
|
problem = "has name(s) containing a reserved word";
|
||||||
forbiddenLabel = "Forbidden as substrings";
|
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.
|
designed for it.
|
||||||
'';
|
'';
|
||||||
}
|
}
|
||||||
|
(nameGuards.mustBeIdent {
|
||||||
|
option = "services.hyperhive.swarm.hives";
|
||||||
|
names = hiveNames;
|
||||||
|
})
|
||||||
(nameGuards.mustNotEqual {
|
(nameGuards.mustNotEqual {
|
||||||
option = "services.hyperhive.swarm.hives";
|
option = "services.hyperhive.swarm.hives";
|
||||||
names = hiveNames;
|
names = hiveNames;
|
||||||
|
|
|
||||||
|
|
@ -126,7 +126,38 @@ let
|
||||||
swarm.hives."h1-agent".domain = "a.t.local";
|
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.
|
# problem rather than the `why` prose keeps the messages rewordable.
|
||||||
equalityGuardFired =
|
equalityGuardFired =
|
||||||
h: lib.any (a: !a.assertion && lib.hasInfix "has reserved name(s)" a.message) h.assertions;
|
h: lib.any (a: !a.assertion && lib.hasInfix "has reserved name(s)" a.message) h.assertions;
|
||||||
|
|
@ -136,7 +167,39 @@ let
|
||||||
lib.any (
|
lib.any (
|
||||||
a: !a.assertion && lib.hasInfix "has name(s) containing a reserved word" a.message
|
a: !a.assertion && lib.hasInfix "has name(s) containing a reserved word" a.message
|
||||||
) h.assertions;
|
) 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 = [
|
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
|
# `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
|
# cert-auth role accepts*, which is a value an operator sets, so a
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue