nixos/tor: Remove IFD from key reading function

This commit is contained in:
Jonathan Davies
2026-05-13 23:49:06 +01:00
parent e3e0006eab
commit f183eb1a24

View File

@@ -87,24 +87,42 @@ let
# Pre-generate keys for all directory authorities
daKeysets = lib.genAttrs daNames mkDAKeys;
# Read a fingerprint text file from a derivation output
readFP = keys: file: builtins.readFile "${keys}/${file}";
# Script that writes /run/tor/dirauth.conf by reading the DA fingerprints
# from the pre-generated key derivations at runtime. The ${daKeysets.<name>}
# interpolations resolve to store-path strings at evaluation time (the
# derivations become build dependencies of the node closure), but their
# contents are only read when the script runs on the VM, so no
# import-from-derivation is required.
#
# Written into /run/tor (tor's RuntimeDirectory, which systemd bind-mounts
# into the service's chroot at RootDirectory=/run/tor/root) so that tor
# can read the file despite ProtectSystem=strict.
torDirAuthExecStartPre = pkgs.writeShellScript "tor-dirauth-conf" ''
set -eu
{
${lib.concatMapStringsSep "\n" (name: ''
printf 'DirAuthority %s orport=9001 ipv6=[%s]:9001 v3ident=%s %s:80 %s\n' \
${name} \
'${nodeIPv6 name}' \
"$(tr -d '\n' < ${daKeysets.${name}}/v3ident)" \
'${nodeIP name}' \
"$(tr -d '\n' < ${daKeysets.${name}}/relay-fingerprint)"
'') daNames}
} > /run/tor/dirauth.conf
chown tor:tor /run/tor/dirauth.conf
chmod 0400 /run/tor/dirauth.conf
'';
# Build DirAuthority lines from the pre-generated keys.
# Format: nickname orport=PORT v3ident=V3IDENT ip:dirport RELAY_FINGERPRINT
dirAuthorityLines = lib.mapAttrsToList (
name: keys:
"${name} orport=9001 ipv6=[${nodeIPv6 name}]:9001 v3ident=${readFP keys "v3ident"} ${nodeIP name}:80 ${readFP keys "relay-fingerprint"}"
) daKeysets;
# Tor settings shared by all node types
# Tor settings shared by all node types. DirAuthority lines are loaded
# from /run/tor/dirauth.conf, written by torDirAuthExecStartPre before
# tor starts.
commonTorSettings = {
TestingTorNetwork = true;
AssumeReachable = true;
AssumeReachableIPv6 = true;
ControlPort = 9051;
CookieAuthentication = true;
DirAuthority = dirAuthorityLines;
"%include" = "/run/tor/dirauth.conf";
};
# Tor settings shared by non-DA nodes (relays and exits)
@@ -277,8 +295,10 @@ let
};
};
# Arti configuration
artiConfig = (pkgs.formats.toml { }).generate "arti.toml" {
# Arti configuration - static parts only. The tor_network section
# embeds fingerprints from the DA key derivations, so it is generated
# at service startup instead (see artiNetworkConfigScript) to avoid IFD.
artiStaticConfig = (pkgs.formats.toml { }).generate "arti.toml" {
proxy.socks_listen = 9150;
storage = {
@@ -308,34 +328,36 @@ let
guard-min-filtered-sample-size = 2;
guard-n-primary-guards-to-use = 2;
};
tor_network = {
authorities = {
v3idents = lib.mapAttrsToList (_: keys: readFP keys "v3ident") daKeysets;
uploads = lib.mapAttrsToList (name: _: [
"${nodeIP name}:80"
"[${nodeIPv6 name}]:80"
]) daKeysets;
downloads = lib.mapAttrsToList (name: _: [
"${nodeIP name}:80"
"[${nodeIPv6 name}]:80"
]) daKeysets;
votes = lib.mapAttrsToList (name: _: [
"${nodeIP name}:80"
"[${nodeIPv6 name}]:80"
]) daKeysets;
};
fallback_caches = lib.mapAttrsToList (name: keys: {
rsa_identity = readFP keys "relay-fingerprint";
ed_identity = readFP keys "ed25519-identity";
orports = [
"${nodeIP name}:9001"
"[${nodeIPv6 name}]:9001"
];
}) daKeysets;
};
};
# Emit the tor_network section of the arti config at service startup,
# reading fingerprints from the DA key derivations at runtime. The
# ${daKeysets.<name>} interpolations resolve to store paths at
# evaluation time (build dependencies), but their contents are only
# read by the shell when the script runs - no import-from-derivation.
artiNetworkConfigScript = pkgs.writeShellScript "arti-network-config" ''
set -eu
out=/run/arti/network.toml
daIpPorts='[["${nodeIP "da1"}:80", "[${nodeIPv6 "da1"}]:80"], ["${nodeIP "da2"}:80", "[${nodeIPv6 "da2"}]:80"], ["${nodeIP "da3"}:80", "[${nodeIPv6 "da3"}]:80"]]'
{
echo '[tor_network.authorities]'
printf 'v3idents = ["%s", "%s", "%s"]\n' \
"$(tr -d '\n' < ${daKeysets.da1}/v3ident)" \
"$(tr -d '\n' < ${daKeysets.da2}/v3ident)" \
"$(tr -d '\n' < ${daKeysets.da3}/v3ident)"
echo "uploads = $daIpPorts"
echo "downloads = $daIpPorts"
echo "votes = $daIpPorts"
${lib.concatMapStringsSep "\n" (name: ''
echo
echo '[[tor_network.fallback_caches]]'
printf 'rsa_identity = "%s"\n' "$(tr -d '\n' < ${daKeysets.${name}}/relay-fingerprint)"
printf 'ed_identity = "%s"\n' "$(tr -d '\n' < ${daKeysets.${name}}/ed25519-identity)"
echo 'orports = ["${nodeIP name}:9001", "[${nodeIPv6 name}]:9001"]'
'') daNames}
} > "$out"
'';
# Arti client node
mkArtiClientNode = {
environment.systemPackages = [ pkgs.curl ];
@@ -347,7 +369,9 @@ let
wantedBy = [ "multi-user.target" ];
serviceConfig = {
ExecStart = "${lib.getExe pkgs.arti} proxy -c ${artiConfig}";
RuntimeDirectory = "arti";
ExecStartPre = artiNetworkConfigScript;
ExecStart = "${lib.getExe pkgs.arti} proxy -c ${artiStaticConfig} -c /run/arti/network.toml";
DynamicUser = true;
StateDirectory = "arti";
CacheDirectory = "arti";
@@ -359,6 +383,18 @@ in
name = "tor";
meta.maintainers = with lib.maintainers; [ jpds ];
defaults =
{ config, ... }:
lib.mkIf config.services.tor.enable {
# Generate /run/tor/dirauth.conf before tor's own `--verify-config`
# ExecStartPre runs. The `+` prefix makes this run as root in the
# full system context so it can write into tor's RuntimeDirectory
# and chown the file to tor:tor.
systemd.services.tor.serviceConfig.ExecStartPre = lib.mkBefore [
"+${torDirAuthExecStartPre}"
];
};
nodes =
lib.genAttrs daNames mkDANode
// lib.genAttrs relayNames mkRelayNode