store the forge webhook secret and grafana's secret key in bao, seeded from the existing files
This commit is contained in:
parent
50c1f2a3d1
commit
a4e48a7bca
3 changed files with 697 additions and 142 deletions
|
|
@ -483,6 +483,23 @@ let
|
|||
"update"
|
||||
];
|
||||
}
|
||||
{
|
||||
# Grafana's datasource key sits under the publisher's `services/*` write
|
||||
# too, and a planted value is the key Grafana would encrypt with from its
|
||||
# next start on, orphaning what it stored under the old one. The OIDC
|
||||
# leaf beside it is the control: still the prefix's.
|
||||
name = "the publisher may not write Grafana's datasource key";
|
||||
ok =
|
||||
let
|
||||
grants = grantsIn baoGrantHere.systemd.services.swarm-bao-secret-publisher-policy.script;
|
||||
in
|
||||
(grantFor grants "secret/data/swarm/services/swarm-grafana/secret-key").caps == [ "deny" ]
|
||||
&&
|
||||
(grantFor grants "secret/data/swarm/services/swarm-grafana/oidc/client").caps == [
|
||||
"create"
|
||||
"update"
|
||||
];
|
||||
}
|
||||
{
|
||||
# The review classifier's forge token sits under the publisher's
|
||||
# `services/*` write too, and the controller posts the classifier's
|
||||
|
|
@ -611,12 +628,12 @@ let
|
|||
# both, and the two are separate principals precisely because a
|
||||
# dashboard is not entitled to a collector's credential.
|
||||
#
|
||||
# The admin password and the metrics bearer are the leaves it may create,
|
||||
# The admin password, the metrics bearer and the datasource key are the leaves it may create,
|
||||
# and without `update`: a grant that could replace one would let a second
|
||||
# run rotate a value Grafana or the metrics store already holds. The
|
||||
# controller's Grafana token is the one leaf it may replace, which it
|
||||
# does when Grafana refuses the stored token.
|
||||
name = "the Grafana reader reads its own and its forwarder's client secrets, creates its admin password and metrics bearer, and writes the controller's Grafana token, and nothing else";
|
||||
name = "the Grafana reader reads its own and its forwarder's client secrets, creates its admin password, metrics bearer and datasource key, and writes the controller's Grafana token, and nothing else";
|
||||
ok =
|
||||
let
|
||||
s = baoGrantHere.systemd.services.swarm-bao-grafana-oidc-policy.script;
|
||||
|
|
@ -644,6 +661,13 @@ let
|
|||
"read"
|
||||
];
|
||||
}
|
||||
{
|
||||
path = "secret/data/swarm/services/swarm-grafana/secret-key";
|
||||
caps = [
|
||||
"create"
|
||||
"read"
|
||||
];
|
||||
}
|
||||
{
|
||||
path = "secret/data/swarm/controller/swarm-controller/grafana/token";
|
||||
caps = [
|
||||
|
|
@ -2028,6 +2052,24 @@ let
|
|||
"swarm-bao-grafana-oidc-policy"
|
||||
];
|
||||
}
|
||||
{
|
||||
# The webhook HMAC secret: the controller creates it, seeded from its
|
||||
# old state-directory file, and reads it. `create` without `update`, so
|
||||
# the first value stored is the one every start reads. Pinned as the
|
||||
# whole stanza, so an added capability fails; the sibling token's
|
||||
# stanza above is the presence control for the same matcher.
|
||||
name = "the controller creates and reads its webhook secret, and no other granting unit names it";
|
||||
ok =
|
||||
let
|
||||
s = baoGrantHere.systemd.services.swarm-bao-controller-policy.script;
|
||||
naming = lib.filter (
|
||||
u: lib.hasInfix "swarm-controller/forge/webhook-secret" baoGrantHere.systemd.services.${u}.script
|
||||
) grantingUnitNames;
|
||||
in
|
||||
lib.hasInfix "path \"secret/data/swarm/controller/swarm-controller/forge/webhook-secret\" {\n capabilities = [\"create\", \"read\"]\n}" s
|
||||
&& lib.hasInfix "path \"secret/data/swarm/controller/swarm-controller/forge/token\" {\n capabilities = [\"read\"]\n}" s
|
||||
&& naming == [ "swarm-bao-controller-policy" ];
|
||||
}
|
||||
{
|
||||
# The forge's OIDC client secret by name: the forge publisher reads it,
|
||||
# and the secret publisher writes it under its `swarm/services/*` grant,
|
||||
|
|
|
|||
|
|
@ -250,6 +250,37 @@ let
|
|||
&& lib.hasInfix "admin reset-admin-password --password-from-stdin < /run/swarm-grafana-secrets/admin-password" service.preStart
|
||||
&& lib.hasInfix "-config ${resetConfig}" service.script;
|
||||
}
|
||||
{
|
||||
# Grafana's datasource-encryption key reaches Grafana from the store,
|
||||
# seeded from the file an earlier release generated in the container,
|
||||
# which stays on disk. A different key leaves Grafana unable to read its
|
||||
# stored datasource secrets, so nothing may mint ahead of that seed, and
|
||||
# the superseded in-container generator must be gone: left in place it
|
||||
# would write a second key beside the delivered one. The last two arms
|
||||
# are the presence controls for the absence arms: the guest's `grafana`
|
||||
# unit is found by the same lookup, and the admin password's legacy file
|
||||
# is among the removals.
|
||||
name = "grafana's datasource key is seeded into the store from the earlier file, delivered from /run, and the earlier file is kept";
|
||||
ok =
|
||||
let
|
||||
m = grafanaRemoteAuthelia;
|
||||
unit = m.systemd.services.swarm-bao-grafana-secret-key;
|
||||
guest = m.containers.swarm-grafana.config;
|
||||
earlier = "/var/lib/nixos-containers/swarm-grafana/var/lib/grafana-secret/secret_key";
|
||||
in
|
||||
lib.hasInfix "bao kv put -cas=0 secret/swarm/services/swarm-grafana/secret-key value=-" unit.script
|
||||
&& lib.hasInfix "tr -d '\\n' < ${earlier}" unit.script
|
||||
&& lib.hasInfix "atomic_write_secret 0400 196:0 /run/swarm-grafana-secrets/secret-key" unit.script
|
||||
&& lib.elem "container@swarm-grafana.service" unit.before
|
||||
&& unit.environment.BAO_CLIENT_CERT == "/etc/pki/bao-grafana-oidc.pem"
|
||||
&&
|
||||
guest.services.grafana.settings.security.secret_key
|
||||
== "$__file{/run/swarm-grafana-secrets/secret-key}"
|
||||
&& !(guest.systemd.services ? swarm-grafana-secret-key)
|
||||
&& guest.systemd.services ? grafana
|
||||
&& !(lib.any (lib.hasInfix "grafana-secret") m.systemd.tmpfiles.rules)
|
||||
&& lib.elem "r /var/lib/nixos-containers/swarm-grafana/var/lib/grafana-admin/password" m.systemd.tmpfiles.rules;
|
||||
}
|
||||
{
|
||||
# swarm-controller reads alerts with this token, so the account it
|
||||
# belongs to is created and kept a Viewer, the token lands at the path
|
||||
|
|
|
|||
|
|
@ -1214,6 +1214,7 @@ let
|
|||
"swarm-bao-grafana-oidc"
|
||||
"swarm-bao-grafana-admin"
|
||||
"swarm-bao-grafana-metrics-bearer"
|
||||
"swarm-bao-grafana-secret-key"
|
||||
"swarm-bao-grafana-controller-token"
|
||||
"swarm-bao-nats-callout-xkey"
|
||||
"swarm-bao-ci-runner"
|
||||
|
|
@ -1239,9 +1240,9 @@ let
|
|||
!(lib.any (n: lib.elem n (s.after ++ s.wants ++ s.requires)) storeUnits);
|
||||
in
|
||||
lib.all waits readers
|
||||
&& lib.length readersHere == 9
|
||||
&& lib.length readersHere == 10
|
||||
&& lib.elem "swarm-bao-grafana-admin" readersHere
|
||||
&& lib.length readersElsewhere == 7
|
||||
&& lib.length readersElsewhere == 8
|
||||
&& lib.all namesNoStoreUnit readersElsewhere
|
||||
&& !(lib.any (u: storeOnlyNoHive.systemd.services ? ${u}) readers);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue