inherit (config.users) users;
inherit (config.security) gnupg;
ports = lib.flatten (map (map (p: p.port)) [tor.settings.ORPort tor.settings.DirPort]);
+ onion = "5spcvlzbaxwo4knhwnrekjtnakxmvekmlc5qwsigi33rn45hd5gewlyd";
in
{
environment.systemPackages = [
pkgs.nyx
];
networking.nftables.ruleset = ''
- add rule inet filter net2fw tcp dport {${lib.concatMapStringsSep "," toString ports}} counter accept comment "Tor"
add rule inet filter fw2net meta skuid ${users.tor.name} meta l4proto tcp counter accept comment "Tor"
+'' + lib.optionalString (ports != []) ''
+ add rule inet filter net2fw tcp dport {${lib.concatMapStringsSep "," toString ports}} counter accept comment "Tor"
'';
-systemd.services.tor-init.script = ''
- install -d -m 700 -o tor -g tor /var/lib/tor/onion/${networking.domain}
-'';
-systemd.services.tor.serviceConfig.StateDirectoryMode = "0700";
#security.gnupg.secrets."tor/auth/julm" = {};
+security.gnupg.secrets."tor/onion/${onion}/hs_ed25519_secret_key" = {};
services.tor = {
enable = true;
- #enableGeoIP = false;
+ enableGeoIP = true;
controlSocket.enable = true;
- #controlPort = [9051];
- relay.enable = true;
- relay.role = "private-bridge";
client.enable = true;
- client.privoxy.enable = true;
- /*
- hiddenServices = {
- "${networking.domain}/${networking.hostName}" = {
- version = 3;
- map = [
- { port = 22; target = { addr = "127.0.0.1"; port = 22; }; }
+ relay.enable = false;
+ relay.role = "private-bridge";
+ #client.privoxy.enable = false;
+ relay.onionServices = {
+ "ssh/${networking.domain}/${networking.hostName}" = {
+ secretKey = gnupg.secrets."tor/onion/${onion}/hs_ed25519_secret_key".path;
+ map = [ 22 ];
+ /*
+ authorizedClients = [
+ "descriptor:x25519:2EZQ3AOZXERDVSN6WO5LNSCOIIPL2AT2A7KOS4ZIYNVQDR5EFM2Q" # julm
];
+ */
settings = {
#HiddenServiceNumIntroductionPoints = 20;
};
};
};
- */
settings.ORPort = [
#{ addr = "[::]"; port = 2222; NoAdvertise = false; IPv6Only = true; }
- { port = 2222; NoAdvertise = false; IPv4Only = false; }
+ { port = 2222; IPv4Only = false; }
#{ addr = "80.67.180.251"; port = 222; IPv4Only = true; NoAdvertise = true; }
];
settings.BandwidthRate = 500000;
"reject [2001:6b0:e:2a18::119]"
"reject [2600:3c02::f03c:91ff:fe59:7d2e]"
];
+ /*
settings.TransPort = { port = 9040; };
settings.DNSPort = { port = 9053; };
#settings.ExtORPort = lib.mkForce null;
settings.AutomapHostsOnResolve = true;
+ */
};
-/*
-# copy your onion folder
+
boot.initrd.secrets = {
- "/etc/tor/onion/bootup" = /home/tony/tor/onion; # maybe find a better spot to store this.
+ "/var/lib/tor/onion/ssh/hs_ed25519_secret_key" =
+ gnupg.secrets."tor/onion/${onion}/hs_ed25519_secret_key".path;
};
-
-# copy tor to you initrd
boot.initrd.extraUtilsCommands = ''
copy_bin_and_libs ${pkgs.tor}/bin/tor
'';
-
-# start tor during boot process
boot.initrd.network.postCommands = let
- torRc = (pkgs.writeText "tor.rc" ''
- DataDirectory /etc/tor
- SOCKSPort 127.0.0.1:9050 IsolateDestAddr
- SOCKSPort 127.0.0.1:9063
- HiddenServiceDir /etc/tor/onion/bootup
- HiddenServicePort 22 127.0.0.1:22
- '');
-in ''
+ torRc = pkgs.writeText "tor.rc" ''
+ DataDirectory /var/lib/tor
+ HiddenServicePort 22 127.0.0.1:2222
+ HiddenServiceDir /var/lib/tor/onion/ssh
+ '';
+ in ''
echo "tor: preparing onion folder"
# have to do this otherwise tor does not want to start
- chmod -R 700 /etc/tor
+ install -d -m 700 /var/lib/tor
echo "make sure localhost is up"
- ip a a 127.0.0.1/8 dev lo
+ ip addr add 127.0.0.1/8 dev lo
ip link set lo up
echo "tor: starting tor"
tor -f ${torRc} --verify-config
tor -f ${torRc} &
'';
-*/
}
cfg = config.services.tor;
stateDir = "/var/lib/tor";
runDir = "/run/tor";
- rootDir = "${runDir}/mnt-root";
descriptionGeneric = option: ''
See <link xlink:href="https://2019.www.torproject.org/docs/tor-manual.html.en#${option}"/>.
'';
bindsPrivilegedPort =
any (p0:
let p1 = if p0 ? "port" then p0.port else p0; in
- p1 == "auto" || (
- let p2 = if isInt p1 then p1 else toInt p1;
- in p1 != null && 0 < p2 && p2 < 1024))
+ if p1 == "auto" then false
+ else let p2 = if isInt p1 then p1 else toInt p1; in
+ p1 != null && 0 < p2 && p2 < 1024)
(flatten [
cfg.settings.ORPort
cfg.settings.DirPort
settings));
torrc = pkgs.writeText "torrc" (
genTorrc cfg.settings +
- concatStrings (mapAttrsToList (name: config:
- "HiddenServiceDir ${config.path}\n" +
- genTorrc config.settings) cfg.relay.hiddenServices)
+ concatStrings (mapAttrsToList (name: onion:
+ "HiddenServiceDir ${onion.path}\n" +
+ genTorrc onion.settings) cfg.relay.onionServices)
);
in
{
(mkRemovedOptionModule [ "services" "tor" "client" "dns" "listenAddress" ] "Use services.tor.settings.DNSPort instead.")
(mkRemovedOptionModule [ "services" "tor" "client" "dns" "isolationOptions" ] "Use services.tor.settings.DNSPort instead.")
(mkRenamedOptionModule [ "services" "tor" "client" "dns" "automapHostsSuffixes" ] [ "services" "tor" "settings" "AutomapHostsSuffixes" ])
- (mkRenamedOptionModule [ "services" "tor" "hiddenServices" ] [ "services" "tor" "relay" "hiddenServices" ])
+ (mkRenamedOptionModule [ "services" "tor" "hiddenServices" ] [ "services" "tor" "relay" "onionServices" ])
];
options = {
to route HTTP traffic is set over a faster, but less isolated port (9063).
'';
- hiddenServices = mkOption {
+ onionServices = mkOption {
description = descriptionGeneric "HiddenServiceDir";
default = {};
example = {
'';
};
- hiddenServices = mkOption {
+ onionServices = mkOption {
description = descriptionGeneric "HiddenServiceDir";
default = {};
example = {
type = types.attrsOf (types.submodule ({name, config, ...}: {
options.path = mkOption {
type = types.path;
- default = stateDir + "/onion/${name}";
- description = "Path where to store the data files of the hidden service.";
+ description = ''
+ Path where to store the data files of the hidden service.
+ If the <option>secretKey</option> is null
+ this defaults to <literal>${stateDir}/onion/$onion</literal>,
+ otherwise to <literal>${runDir}/onion/$onion</literal>.
+ '';
};
- /*
- options.hostname = mkOption {
- type = types.str;
+ options.secretKey = mkOption {
+ type = with types; nullOr path;
default = null;
+ example = "/run/keys/tor/onion/expyuzz4wqqyqhjn/hs_ed25519_secret_key";
description = ''
- Path where to store the data files of the hidden service.
+ Secret key of the onion service.
+ If null, Tor reuses any preexisting secret key (in <option>path</option>)
+ or generates a new one.
+ The associated public key and hostname are deterministically regenerated
+ from this file if they do not exist.
'';
};
- */
options.authorizeClient = mkOption {
description = descriptionGeneric "HiddenServiceAuthorizeClient";
default = null;
'';
};
clientNames = mkOption {
- type = types.nonEmptyListOf (types.strMatching "[A-Za-z0-9+-_]+");
+ type = with types; nonEmptyListOf (strMatching "[A-Za-z0-9+-_]+");
description = ''
Only clients that are listed here are authorized to access the hidden service.
Generated authorization data can be found in <filename>${stateDir}/onion/$name/hostname</filename>.
};
};
config = {
+ path = mkDefault ((if config.secretKey == null then stateDir else runDir) + "/onion/${name}");
settings.HiddenServiceVersion = config.version;
settings.HiddenServiceAuthorizeClient =
if config.authorizeClient != null then
};
}))
]);
- apply = p:
- if isInt p || isString p
- then { port = p; }
- else p;
+ apply = p: if isInt p || isString p then { port = p; } else p;
};
options.ExtORPortCookieAuthFile = optionPath "ExtORPortCookieAuthFile";
options.ExtORPortCookieAuthFileGroupReadable = optionBool "ExtORPortCookieAuthFileGroupReadable";
(submodule ({config, ...}: {
options = {
onion = mkOption {
- # FIXME: is the regexp correctly written?
- type = strMatching "[a-z2-7]\\{16\\}\\(\\.onion\\)?";
+ type = strMatching "[a-z2-7]{16}(\\.onion)?";
example = "xxxxxxxxxxxxxxxx.onion";
};
auth = mkOption {
- type = strMatching "[A-Za-z0-9+/]\\{22\\}";
+ type = strMatching "[A-Za-z0-9+/]{22}";
};
};
}))
# Not sure if `cfg.relay.role == "private-bridge"` helps as tor
# sends a lot of stats
warnings = optional (cfg.settings.BridgeRelay &&
- flatten (mapAttrsToList (n: h: h.map) cfg.relay.hiddenServices) != [])
+ flatten (mapAttrsToList (n: o: o.map) cfg.relay.onionServices) != [])
''
Running Tor hidden services on a public relay makes the
presence of hidden services visible through simple statistical
actually hide your hidden services. In either case, you can
always create a container/VM with a separate Tor daemon instance.
'' ++
- flatten (mapAttrsToList (n: h:
- optional (h.settings.HiddenServiceVersion == 2) [
- (optional (h.settings.HiddenServiceExportCircuitID != null) ''
+ flatten (mapAttrsToList (n: o:
+ optional (o.settings.HiddenServiceVersion == 2) [
+ (optional (o.settings.HiddenServiceExportCircuitID != null) ''
HiddenServiceExportCircuitID is used in the HiddenService: ${n}
but this option is only for v3 hidden services.
'')
] ++
- optional (h.settings.HiddenServiceVersion != 2) [
- (optional (h.settings.HiddenServiceAuthorizeClient != null) ''
+ optional (o.settings.HiddenServiceVersion != 2) [
+ (optional (o.settings.HiddenServiceAuthorizeClient != null) ''
HiddenServiceAuthorizeClient is used in the HiddenService: ${n}
but this option is only for v2 hidden services.
'')
- (optional (h.settings.RendPostPeriod != null) ''
+ (optional (o.settings.RendPostPeriod != null) ''
RendPostPeriod is used in the HiddenService: ${n}
but this option is only for v2 hidden services.
'')
]
- ) cfg.relay.hiddenServices);
+ ) cfg.relay.onionServices);
users.groups.tor.gid = config.ids.gids.tor;
users.users.tor =
PublishServerDescriptor = false;
}
))
+ (mkIf (!cfg.relay.enable) {
+ # Avoid surprises when leaving ORPort/DirPort configurations in cfg.settings,
+ # because it would still enable Tor as a relay,
+ # which can trigger all sort of problems when not carefully done,
+ # like the blocklisting of the machine's IP addresses
+ # by some hosting providers...
+ DirPort = mkForce [];
+ ORPort = mkForce [];
+ PublishServerDescriptor = mkForce false;
+ })
(mkIf cfg.client.enable (
{ SOCKSPort = [
{ addr = "127.0.0.1"; port = 9050; IsolateDestAddr = true; }
DNSPort = [{ addr = "127.0.0.1"; port = 9053; }];
AutomapHostsOnResolve = true;
AutomapHostsSuffixes = cfg.client.dns.automapHostsSuffixes;
- } // optionalAttrs (flatten (mapAttrsToList (n: h: h.clientAuthorizations) cfg.client.hiddenServices) != []) {
+ } // optionalAttrs (flatten (mapAttrsToList (n: o: o.clientAuthorizations) cfg.client.onionServices) != []) {
ClientOnionAuthDir = runDir + "/ClientOnionAuthDir";
}
))
networking.firewall = mkIf cfg.openFirewall {
allowedTCPPorts =
- map (o: optional (isInt o && o > 0 || o ? "port" && isInt o.port && o.port > 0) o.port)
+ concatMap (o: optional (isInt o && o > 0 || o ? "port" && isInt o.port && o.port > 0) o.port)
(flatten [
cfg.settings.ORPort
cfg.settings.DirPort
Group = "tor";
ExecStartPre = [
"${cfg.package}/bin/tor -f ${torrc} --verify-config"
- ] ++
- # DOC: Appendix G of https://spec.torproject.org/rend-spec-v3
- [("+" + pkgs.writeShellScript "auth" (concatStringsSep "\n" (flatten (
- mapAttrsToList (name: h:
- optionals (h.authorizedClients != []) ([''
- out="${h.path}/authorized_clients"
- rm -rf "$out"
- install -d -o tor -g tor -m 0700 "$out"
- ''] ++ imap0 (i: pubKey: ''
- echo ${pubKey} |
- install -o tor -g tor -m 0400 /dev/stdin $out/${toString i}.auth
- '') h.authorizedClients
- )
- ) cfg.relay.hiddenServices ++
- mapAttrsToList (name: h: imap0 (i: prvKeyPath:
- let onion = removeSuffix ".onion" name; in ''
- printf "${onion}:" | cat - '${prvKeyPath}' |
- install -o tor -g tor -m 0700 /dev/stdin \
- ${runDir}/ClientOnionAuthDir/${onion}.${toString i}.auth_private
- '') h.clientAuthorizations)
- cfg.client.hiddenServices
- ))))];
+ # DOC: Appendix G of https://spec.torproject.org/rend-spec-v3
+ ("+" + pkgs.writeShellScript "ExecStartPre" (concatStringsSep "\n" (flatten (["set -eu"] ++
+ mapAttrsToList (name: onion:
+ optional (onion.authorizedClients != []) ''
+ rm -rf '${onion.path}/authorized_clients'
+ install -d -o tor -g tor -m 0700 '${onion.path}' '${onion.path}/authorized_clients'
+ '' ++
+ imap0 (i: pubKey: ''
+ echo ${pubKey} |
+ install -o tor -g tor -m 0400 /dev/stdin '${onion.path}/authorized_clients/${toString i}.auth'
+ '') onion.authorizedClients ++
+ optional (onion.secretKey != null) ''
+ install -d -o tor -g tor -m 0700 '${onion.path}'
+ key="$(cut -f1 -d: '${onion.secretKey}')"
+ case "$key" in
+ ("== ed25519v"*"-secret")
+ install -o tor -g tor -m 0400 '${onion.secretKey}' '${onion.path}/hs_ed25519_secret_key';;
+ (*) echo >&2 "NixOS does not (yet) support secret key type for onion: ${name}"; exit 1;;
+ esac
+ ''
+ ) cfg.relay.onionServices ++
+ mapAttrsToList (name: onion: imap0 (i: prvKeyPath:
+ let hostname = removeSuffix ".onion" name; in ''
+ printf "%s:" "${hostname}" | cat - '${prvKeyPath}' |
+ install -o tor -g tor -m 0700 /dev/stdin \
+ ${runDir}/ClientOnionAuthDir/${hostname}.${toString i}.auth_private
+ '') onion.clientAuthorizations)
+ cfg.client.onionServices
+ ))))
+ ];
ExecStart = "${cfg.package}/bin/tor -f ${torrc}";
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
KillSignal = "SIGINT";
TimeoutSec = 30;
Restart = "on-failure";
LimitNOFILE = 32768;
- RuntimeDirectory = [ "tor" "tor/mnt-root" "tor/ClientOnionAuthDir" ];
- RuntimeDirectoryMode = "0750";
+ RuntimeDirectory = [
+ # g+x allows access to the control socket
+ "tor"
+ "tor/mnt-root"
+ # g+x can't be removed in ExecStart=, but will be removed by Tor
+ "tor/ClientOnionAuthDir"
+ ];
+ RuntimeDirectoryMode = "0710";
StateDirectoryMode = "0700";
- StateDirectory =
- [ "tor" "tor/onion" ] ++
- mapAttrsToList (name: v: "tor/onion/${name}") cfg.relay.hiddenServices;
-
- # The following are only to optimize:
+ StateDirectory = [
+ "tor"
+ "tor/onion"
+ ] ++
+ flatten (mapAttrsToList (name: onion:
+ optional (onion.secretKey == null) "tor/onion/${name}"
+ ) cfg.relay.onionServices);
+ # The following options are only to optimize:
# systemd-analyze security tor
- RootDirectory = rootDir;
+ RootDirectory = runDir + "/mnt-root";
RootDirectoryStartOnly = true;
- #InaccessiblePaths = [ "-+${rootDir}" ];
+ #InaccessiblePaths = [ "-+${runDir}/mnt-root" ];
UMask = "0066";
- BindPaths = [
- stateDir
- ];
- BindReadOnlyPaths = [
- storeDir
- "/etc"
- ];
+ BindPaths = [ stateDir ];
+ BindReadOnlyPaths = [ storeDir "/etc" ];
AmbientCapabilities = [""] ++ lib.optional bindsPrivilegedPort "CAP_NET_BIND_SERVICE";
CapabilityBoundingSet = [""] ++ lib.optional bindsPrivilegedPort "CAP_NET_BIND_SERVICE";
# ProtectClock= adds DeviceAllow=char-rtc r