fetch the self-check's password as a systemd credential
This commit is contained in:
parent
b7adb90f53
commit
fd63b402e1
5 changed files with 146 additions and 9 deletions
|
|
@ -25,6 +25,7 @@
|
||||||
./hive-priv.nix
|
./hive-priv.nix
|
||||||
./hive-priv-cache.nix
|
./hive-priv-cache.nix
|
||||||
./hive-squid.nix
|
./hive-squid.nix
|
||||||
|
./hive-squid-credential.nix
|
||||||
./hive-tls.nix
|
./hive-tls.nix
|
||||||
./otel.nix
|
./otel.nix
|
||||||
./glue-bao-readers-policy-order.nix
|
./glue-bao-readers-policy-order.nix
|
||||||
|
|
|
||||||
103
nix/host-modules/hive-squid-credential.nix
Normal file
103
nix/host-modules/hive-squid-credential.nix
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
# The password this hive's squid self-check logs in to squid with, kept where
|
||||||
|
# only root can read it.
|
||||||
|
#
|
||||||
|
# swarm-controller mints it at `swarm/hives/<hive>/squid-selfcheck`
|
||||||
|
# (`swarm_secret_client::proxy::selfcheck_path`). hive-c0re reads the same path
|
||||||
|
# to put its hash in squid's users file; this root oneshot reads it under the
|
||||||
|
# hive's own store identity and lands it as the systemd credential
|
||||||
|
# `squid-selfcheck` imports (`/run/credstore/<name>`, root, `0400`, tmpfs).
|
||||||
|
#
|
||||||
|
# A hive with no minted credential yet leaves the file alone and the self-check
|
||||||
|
# reports `credential=absent`.
|
||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
hyperhiveCfg = config.services.hyperhive;
|
||||||
|
hiveController = hyperhiveCfg.deploy.hive-controller;
|
||||||
|
tls = hiveController.tls;
|
||||||
|
baoCfg = hyperhiveCfg.swarm.bao;
|
||||||
|
baoDeploy = hyperhiveCfg.deploy.bao;
|
||||||
|
|
||||||
|
enabled =
|
||||||
|
hiveController.enable
|
||||||
|
&& hiveController.hiveName != null
|
||||||
|
&& tls.baoHiveIdentityCertFile != null
|
||||||
|
&& tls.baoHiveIdentityKeyFile != null;
|
||||||
|
|
||||||
|
credentialFile = "/run/credstore/${hyperhiveCfg.squid.selfcheck.credentialName}";
|
||||||
|
|
||||||
|
storeRetry = import ./lib/store-retry.nix { };
|
||||||
|
storeRefreshTimer = import ./lib/store-refresh-timer.nix { };
|
||||||
|
in
|
||||||
|
{
|
||||||
|
config = lib.mkIf enabled {
|
||||||
|
systemd.services.squid-selfcheck-credential = {
|
||||||
|
description = "Read the squid self-check's password from the swarm secret store";
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
before = [ "squid-selfcheck.service" ];
|
||||||
|
wants = [ "network-online.target" ];
|
||||||
|
after = [
|
||||||
|
"network-online.target"
|
||||||
|
"systemd-tmpfiles-setup.service"
|
||||||
|
];
|
||||||
|
path = [
|
||||||
|
baoDeploy.package
|
||||||
|
pkgs.coreutils
|
||||||
|
pkgs.gnugrep
|
||||||
|
];
|
||||||
|
environment = {
|
||||||
|
BAO_ADDR = "https://${baoCfg.domain}:${toString baoCfg.port}";
|
||||||
|
BAO_CLIENT_CERT = tls.baoHiveIdentityCertFile;
|
||||||
|
BAO_CLIENT_KEY = tls.baoHiveIdentityKeyFile;
|
||||||
|
}
|
||||||
|
// lib.optionalAttrs (baoDeploy.serverCaFile != null) {
|
||||||
|
BAO_CACERT = baoDeploy.serverCaFile;
|
||||||
|
};
|
||||||
|
# ./lib/store-retry.nix: the login fails for as long as the store is
|
||||||
|
# sealed or down.
|
||||||
|
inherit (storeRetry) startLimitBurst startLimitIntervalSec;
|
||||||
|
serviceConfig = storeRetry.serviceConfig // {
|
||||||
|
Type = "oneshot";
|
||||||
|
UMask = "0077";
|
||||||
|
SyslogIdentifier = "squid-selfcheck-credential";
|
||||||
|
TimeoutStartSec = "5min";
|
||||||
|
};
|
||||||
|
script = ''
|
||||||
|
set -euo pipefail
|
||||||
|
${import ./lib/store-seeded-secret.nix { }}
|
||||||
|
${import ./lib/atomic-write-secret.nix { }}
|
||||||
|
${import ./lib/refresh-consumer.nix { }}
|
||||||
|
|
||||||
|
install -d -m 0700 /run/credstore
|
||||||
|
store_init
|
||||||
|
store_login
|
||||||
|
if [ "$STORE_UP" != 1 ]; then
|
||||||
|
echo "could not read the squid self-check credential; ${credentialFile} stays as it is." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ! value="$(store_bao kv get -field=value ${lib.escapeShellArg "secret/swarm/hives/${hiveController.hiveName}/squid-selfcheck"} 2>"$STORE_ERR")"; then
|
||||||
|
if grep -qi 'no value found' "$STORE_ERR"; then
|
||||||
|
echo "the swarm has not minted this hive's squid self-check credential yet; ${credentialFile} stays as it is."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
cat "$STORE_ERR" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [[ ! "$value" =~ ^[A-Za-z0-9_-]+$ ]]; then
|
||||||
|
echo "the stored squid self-check credential is not one base64url word; ${credentialFile} stays as it is." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
# Written only when the value changed, so the file keeps its mtime.
|
||||||
|
if secret_differs ${credentialFile} "$value"; then
|
||||||
|
atomic_write_secret 0400 root:root ${credentialFile} "$value"
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.timers.squid-selfcheck-credential = storeRefreshTimer "re-read the squid self-check's password";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -942,12 +942,13 @@ let
|
||||||
inherit hiveName;
|
inherit hiveName;
|
||||||
}) hyperhiveCfg.swarm.hives;
|
}) hyperhiveCfg.swarm.hives;
|
||||||
|
|
||||||
# What a hive's gateway-leaf identity may do: issue from its own role, and
|
# What a hive's gateway-leaf identity may do: issue from its own role, read
|
||||||
# read two of its own cache facts. The root unit on the hive host
|
# two of its own cache facts, and read its own squid self-check credential.
|
||||||
# (`hive-priv-cache-facts`, ./hive-priv-cache.nix) reads them under this
|
# The root unit on the hive host (`hive-priv-cache-facts`,
|
||||||
# identity: the key hive-priv's downloads must be signed by, and the token it
|
# ./hive-priv-cache.nix) reads the facts under this identity: the key
|
||||||
# pulls with. The cache's URL and name come from the host's configuration,
|
# hive-priv's downloads must be signed by, and the token it pulls with. The
|
||||||
# not from the store.
|
# cache's URL and name come from the host's configuration, not from the
|
||||||
|
# store. `squid-selfcheck-credential` (./hive-squid.nix) reads the credential.
|
||||||
hiveLeafPolicyText =
|
hiveLeafPolicyText =
|
||||||
obj:
|
obj:
|
||||||
''
|
''
|
||||||
|
|
@ -961,7 +962,8 @@ let
|
||||||
[
|
[
|
||||||
"public-key"
|
"public-key"
|
||||||
"pull-token"
|
"pull-token"
|
||||||
];
|
]
|
||||||
|
+ readStanza "${credentialMountPath}/data/swarm/hives/${obj.hiveName}/squid-selfcheck";
|
||||||
|
|
||||||
# The PKI mount agent client certificates are issued from. Its root is
|
# The PKI mount agent client certificates are issued from. Its root is
|
||||||
# generated inside the store, so the agent CA's key never exists outside it.
|
# generated inside the store, so the agent CA's key never exists outside it.
|
||||||
|
|
|
||||||
|
|
@ -1496,9 +1496,10 @@ let
|
||||||
{
|
{
|
||||||
# One role, policy and login role per hive in the directory, each
|
# One role, policy and login role per hive in the directory, each
|
||||||
# naming only its own hive: the policy text in the unit is exactly the
|
# naming only its own hive: the policy text in the unit is exactly the
|
||||||
# `update` on its own role and `read` on its own two cache facts, and each
|
# `update` on its own role and `read` on its own two cache facts and its own
|
||||||
|
# squid self-check credential, and each
|
||||||
# hive's block names neither the other's role nor its subject.
|
# hive's block names neither the other's role nor its subject.
|
||||||
name = "each hive's gateway-leaf identity may issue from its own role and read its own two cache facts, and nothing else";
|
name = "each hive's gateway-leaf identity may issue from its own role and read its own two cache facts and squid self-check credential, and nothing else";
|
||||||
ok =
|
ok =
|
||||||
let
|
let
|
||||||
s = baoGrantTwoHives.systemd.services.swarm-bao-hive-leaf-policy.script;
|
s = baoGrantTwoHives.systemd.services.swarm-bao-hive-leaf-policy.script;
|
||||||
|
|
@ -1536,6 +1537,10 @@ let
|
||||||
path = "secret/data/swarm/hives/h1/attic/pull-token";
|
path = "secret/data/swarm/hives/h1/attic/pull-token";
|
||||||
caps = [ "read" ];
|
caps = [ "read" ];
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
path = "secret/data/swarm/hives/h1/squid-selfcheck";
|
||||||
|
caps = [ "read" ];
|
||||||
|
}
|
||||||
{
|
{
|
||||||
path = "pki/issue/swarm-hive-leaf-h2";
|
path = "pki/issue/swarm-hive-leaf-h2";
|
||||||
caps = [ "update" ];
|
caps = [ "update" ];
|
||||||
|
|
@ -1548,6 +1553,10 @@ let
|
||||||
path = "secret/data/swarm/hives/h2/attic/pull-token";
|
path = "secret/data/swarm/hives/h2/attic/pull-token";
|
||||||
caps = [ "read" ];
|
caps = [ "read" ];
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
path = "secret/data/swarm/hives/h2/squid-selfcheck";
|
||||||
|
caps = [ "read" ];
|
||||||
|
}
|
||||||
]
|
]
|
||||||
&& issues "h1" "h1.t.local" h1
|
&& issues "h1" "h1.t.local" h1
|
||||||
&& issues "h2" "h2.example.org" h2
|
&& issues "h2" "h2.example.org" h2
|
||||||
|
|
|
||||||
|
|
@ -800,6 +800,28 @@ let
|
||||||
noService.systemd.services.hive-priv-cache-facts.environment.BAO_CLIENT_CERT
|
noService.systemd.services.hive-priv-cache-facts.environment.BAO_CLIENT_CERT
|
||||||
== "/etc/pki/bao-hive.pem";
|
== "/etc/pki/bao-hive.pem";
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
# Control: the lookup finds the sibling root unit of the same shape. The
|
||||||
|
# squid self-check's password is read under this hive's own identity and
|
||||||
|
# lands where only root reads it, as the credential `squid-selfcheck`
|
||||||
|
# imports.
|
||||||
|
name = "the squid self-check credential is read by a root oneshot under this hive's own identity into the credstore";
|
||||||
|
ok =
|
||||||
|
let
|
||||||
|
u = units.squid-selfcheck-credential;
|
||||||
|
in
|
||||||
|
lib.hasInfix "secret/swarm/hives/h1/squid-selfcheck" u.script
|
||||||
|
&& !(lib.hasInfix "swarm/hives/h2" u.script)
|
||||||
|
&& lib.hasInfix "/run/credstore/hive-squid.selfcheck" u.script
|
||||||
|
&& lib.hasInfix "atomic_write_secret 0400 root:root" u.script
|
||||||
|
&& u.environment.BAO_CLIENT_CERT == units.hive-priv-cache-facts.environment.BAO_CLIENT_CERT
|
||||||
|
&& u.environment.BAO_CLIENT_KEY == units.hive-priv-cache-facts.environment.BAO_CLIENT_KEY
|
||||||
|
&& u.serviceConfig.Type == "oneshot"
|
||||||
|
&& !(remainsAfterExit u)
|
||||||
|
&& allLocal.systemd.timers ? squid-selfcheck-credential
|
||||||
|
&& !(noIdentity.systemd.services ? squid-selfcheck-credential)
|
||||||
|
&& !(noIdentity.systemd.timers ? squid-selfcheck-credential);
|
||||||
|
}
|
||||||
{
|
{
|
||||||
name = "hive-priv is told the cache's address by the host's configuration";
|
name = "hive-priv is told the cache's address by the host's configuration";
|
||||||
ok =
|
ok =
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue