Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions docs/pki.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,4 @@ test "$(clan vars get "$machine" acme-db/key.sealed | wc -c)" -gt 0

If this check fails, re-run `acme-db-seal "$machine"` and check again.

4. Deploy the machine. This installs and starts both `step-ca-db-mount.service` and `step-ca-acme.service`.

Without a sealed key, or if TPM unsealing fails, `step-ca-acme.service` does not start.
4. Deploy the machine. This installs socket activation (`step-ca-proxy.socket`) and on-demand ACME services.
131 changes: 92 additions & 39 deletions modules/tags/acme.nix
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,23 @@
machineData = import (inputs.self + /inventory/data.nix);
common = import ./tpm12/common.nix {inherit config lib pkgs;};
dbPath = "${acme.stepPath}/db";
dbCryptPath = "${acme.stepPath}/db.crypt";
dbCryptPath = "${dbPath}.crypt";
dbKeySealed = config.clan.core.vars.generators.acme-db.files."key.sealed".path;
backendAddress = "127.0.0.1:${toString (acme.port + 1)}";
dbExtpass = pkgs.writeShellApplication {
name = "step-ca-db-extpass";
runtimeInputs = with pkgs; [coreutils tpm-tools];
text = ''
set -euo pipefail

if [[ ! -s ${dbKeySealed} ]]; then
echo "sealed DB key is missing (${dbKeySealed})" >&2
exit 1
fi

exec tpm_unsealdata -z -i ${dbKeySealed}
'';
};

acmeFqdns = map (name: "${name}.${config.networking.domain}") (builtins.attrNames config.homelab.acme.hosts);
acmeClients = lib.unique ((machineData.tags.acme_client or []) ++ (machineData.tags.acme_client_bootstrap or []));
Expand All @@ -28,12 +43,12 @@
root = config.clan.core.vars.generators.tls-ca.files."ca.crt".path;
crt = "${common.tpm}/${common.crt}";
key = common.pkcs11.key;
address = ":${toString acme.port}";
address = backendAddress;
dnsNames = acmeFqdns;
logger.format = "text";
db = {
type = "badgerv2";
dataSource = "${acme.stepPath}/db";
dataSource = "${dbPath}";
};
authority.provisioners = [
{
Expand Down Expand Up @@ -68,61 +83,32 @@ in {
];

services = {
step-ca-db-mount = let
step-ca-db-prepare = let
after = [
"tcsd.service"
"systemd-tmpfiles-setup.service"
];
in {
inherit after;
description = "Mount encrypted Step CA Badger DB";
description = "Prepare encrypted Step CA Badger DB";
wants = after;
wantedBy = ["multi-user.target"];
before = ["step-ca-acme.service"];
path = with pkgs; [coreutils findutils gocryptfs tpm-tools util-linux];
unitConfig.StopWhenUnneeded = true;

serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
UMask = "0077";
};

script = ''
set -euo pipefail

if mountpoint -q ${dbPath}; then
mounted_source="$(findmnt -n -T ${dbPath} -o SOURCE || true)"
if [[ "$mounted_source" == "${dbCryptPath}" ]]; then
exit 0
fi
echo "unexpected mount at ${dbPath}: $mounted_source" >&2
exit 1
fi

if [[ ! -s ${dbKeySealed} ]]; then
echo "sealed DB key is missing (${dbKeySealed})" >&2
exit 1
fi

key="$(mktemp /run/step-ca-db-key.XXXXXX)"
cleanup() {
rm -f "$key"
}
trap cleanup EXIT

tpm_unsealdata -z -i ${dbKeySealed} -o "$key"
chmod 0600 "$key"

if [[ ! -f ${dbCryptPath}/gocryptfs.conf ]]; then
gocryptfs -q -init -passfile "$key" ${dbCryptPath}
fi

gocryptfs -q -passfile "$key" ${dbCryptPath} ${dbPath}
'';

postStop = ''
if mountpoint -q ${dbPath}; then
umount ${dbPath}
${lib.getExe' pkgs.gocryptfs "gocryptfs"} -q -init -extpass ${lib.getExe dbExtpass} ${dbCryptPath}
fi
'';
};
Expand All @@ -131,15 +117,16 @@ in {
after = [
"tcsd.service"
"systemd-tmpfiles-setup.service"
"step-ca-db-mount.service"
];
in {
inherit after;
description = "Step CA ACME service";
wants = after;
requires = ["step-ca-db-mount.service"];
wantedBy = ["multi-user.target"];
path = with pkgs; [step-ca step-cli step-kms-plugin];
unitConfig = {
StopWhenUnneeded = true;
RequiresMountsFor = [dbPath];
};

environment = {
HOME = common.tpm;
Expand All @@ -156,8 +143,74 @@ in {
RestartSec = "10s";
};
};

step-ca-proxy = let
after = [
"step-ca-acme.service"
"step-ca-proxy.socket"
];
in {
inherit after;
description = "Socket-activated proxy to Step CA ACME backend";
wants = after;
requires = after;

serviceConfig = {
Type = "notify";
ExecStartPre = lib.getExe (pkgs.writeShellApplication {
name = "wait-step-ca-backend";
runtimeInputs = with pkgs; [coreutils netcat-openbsd];
text = ''
set -euo pipefail

deadline=$((SECONDS + 30))
while ! nc -z 127.0.0.1 ${toString (acme.port + 1)}; do
if (( SECONDS >= deadline )); then
echo "step-ca backend did not become ready on ${backendAddress}" >&2
exit 1
fi
sleep 0.2
done
'';
});
ExecStart = "${pkgs.systemd}/lib/systemd/systemd-socket-proxyd --exit-idle-time=2min ${backendAddress}";
};
};
};

mounts = [
{
description = "Encrypted Step CA Badger DB";
what = dbCryptPath;
where = dbPath;
type = "fuse.gocryptfs";
options = "extpass=${lib.getExe dbExtpass},nosyslog";
startLimitBurst = 3;
unitConfig = {
Requires = ["step-ca-db-prepare.service"];
After = ["step-ca-db-prepare.service"];
};
}
];

automounts = [
{
description = "Automount for encrypted Step CA Badger DB";
where = dbPath;
wantedBy = ["multi-user.target"];
automountConfig.TimeoutIdleSec = "10min";
}
];

sockets.step-ca-proxy = {
description = "Socket activation for Step CA ACME endpoint";
wantedBy = ["sockets.target"];
listenStreams = [(toString acme.port)];
socketConfig.Accept = false;
};
};

system.fsPackages = [pkgs.gocryptfs];

environment.systemPackages = [inputs.acme-eab.packages.${system}.acme-eab];
}
7 changes: 5 additions & 2 deletions modules/tags/all.nix
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,10 @@ in {
system.disableInstallerTools = true;
environment.defaultPackages = lib.mkForce [];

# D-Bus defaults to X11 autolaunch support.
services.dbus.dbusPackage = pkgs.dbus.override {x11Support = false;};
services = {
# D-Bus defaults to X11 autolaunch support.
dbus.dbusPackage = pkgs.dbus.override {x11Support = false;};
openssh.startWhenNeeded = true;
};
};
}