diff --git a/configuration.nix b/configuration.nix index 66977a7..dc0981d 100644 --- a/configuration.nix +++ b/configuration.nix @@ -5,9 +5,16 @@ { config, pkgs, ... }: { + + + disabledModules = [ "services/security/tor.nix" ]; + imports = - [ # Include the results of the hardware scan. + [ ./modules/default.nix + ./modules/tor.nix + ./modules/onionnode.nix + ]; networking.hostName = "nix-bitcoin"; # Define your hostname. @@ -18,8 +25,6 @@ ]; services.openssh.enable = true; - services.tor.enable = true; - services.tor.client.enable = true; # users.users.root = { # openssh.authorizedKeys.keys = [ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILacgZRwLsiICNHGHY2TG2APeuxFsrw6Cg13ZTMQpNqA nickler@rick" ]; @@ -31,6 +36,15 @@ networking.firewall.enable = true; services.bitcoin.enable = true; + # make an onion listen node + services.tor.enable = true; + services.tor.client.enable = true; + #services.bitcoin.proxy = services.tor.client.socksListenAddress; + services.onionnode.enable = true; + + + # turn off binary cache by passing the empty list + nix.binaryCaches = []; # Configure network proxy if necessary # networking.proxy.default = "http://user:password@proxy:port/"; diff --git a/modules/default.nix b/modules/default.nix index 6fe4e95..25bbf1a 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -6,11 +6,13 @@ let cfg = config.services.bitcoin; home = "/var/lib/bitcoin"; configFile = pkgs.writeText "bitcoin.conf" '' - listen=0 - onlynet=onion + listen=${if cfg.listen then "1" else "0"} prune=1001 assumevalid=0000000000000000000726d186d6298b5054b9a5c49639752294b322a305d240 - proxy=127.0.0.1:9050 + ${optionalString (cfg.proxy != null) "proxy=${cfg.proxy}"} + addnode=ecoc5q34tmbq54wl.onion + discover=0 + ${optionalString (cfg.port != null) "port=${toString cfg.port}"} ''; in { options.services.bitcoin = { @@ -21,28 +23,50 @@ in { If enabled, the bitcoin service will be installed. ''; }; + listen = mkOption { + type = types.bool; + default = false; + description = '' + If enabled, the bitcoin service will listen. + ''; + }; + proxy = mkOption { + type = types.nullOr types.string; + default = null; + description = '' + proxy + ''; + }; + port = mkOption { + type = types.nullOr types.ints.u16; + default = null; +# type = types.int; + #default = 8333; + description = "Override the default port on which to listen for connections."; + }; + }; config = mkIf cfg.enable { - users.users.bitcoin = - { - description = "Bitcoind User"; - createHome = true; - inherit home; - }; - systemd.services.bitcoind = - { description = "Run bitcoind"; - path = [ pkgs.bitcoin ]; - wantedBy = [ "multi-user.target" ]; - preStart = '' - mkdir -p ${home}/.bitcoin - ln -sf ${configFile} ${home}/.bitcoin/bitcoin.conf - ''; - serviceConfig = - { - ExecStart = "${pkgs.bitcoin}/bin/bitcoind"; - User = "bitcoin"; - }; + users.users.bitcoin = + { + description = "Bitcoind User"; + createHome = true; + inherit home; }; - }; + systemd.services.bitcoind = + { description = "Run bitcoind"; + path = [ pkgs.bitcoin ]; + wantedBy = [ "multi-user.target" ]; + preStart = '' + mkdir -p ${home}/.bitcoin + ln -sf ${configFile} ${home}/.bitcoin/bitcoin.conf + ''; + serviceConfig = + { + ExecStart = "${pkgs.bitcoin}/bin/bitcoind"; + User = "bitcoin"; + }; + }; + }; } diff --git a/modules/onionnode.nix b/modules/onionnode.nix new file mode 100644 index 0000000..008eab6 --- /dev/null +++ b/modules/onionnode.nix @@ -0,0 +1,33 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.onionnode; +in { + options.services.onionnode = { + enable = mkOption { + type = types.bool; + default = false; + description = '' + If enabled, the onion service will be installed. + ''; + }; + }; + + config = mkIf cfg.enable { + services.bitcoin.enable = true; + services.bitcoin.listen = true; + # make an onion listen node + services.tor.enable = true; + services.tor.client.enable = true; + services.bitcoin.proxy = config.services.tor.client.socksListenAddress; + services.tor.hiddenServices = + { "bitcoind".map = [ { + #port = config.services.bitcoin.port; + port = 8333; + } ]; + "bitcoind".version = 3;}; + #services.tor.hiddenServices.bitcoind.version = 3; + }; +} diff --git a/modules/tor.nix b/modules/tor.nix new file mode 100644 index 0000000..1b49115 --- /dev/null +++ b/modules/tor.nix @@ -0,0 +1,776 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.tor; + torDirectory = "/var/lib/tor"; + torRunDirectory = "/run/tor"; + + opt = name: value: optionalString (value != null) "${name} ${value}"; + optint = name: value: optionalString (value != null && value != 0) "${name} ${toString value}"; + + isolationOptions = { + type = types.listOf (types.enum [ + "IsolateClientAddr" + "IsolateSOCKSAuth" + "IsolateClientProtocol" + "IsolateDestPort" + "IsolateDestAddr" + ]); + default = []; + example = [ + "IsolateClientAddr" + "IsolateSOCKSAuth" + "IsolateClientProtocol" + "IsolateDestPort" + "IsolateDestAddr" + ]; + description = "Tor isolation options"; + }; + + + torRc = '' + User tor + DataDirectory ${torDirectory} + ${optionalString cfg.enableGeoIP '' + GeoIPFile ${pkgs.tor.geoip}/share/tor/geoip + GeoIPv6File ${pkgs.tor.geoip}/share/tor/geoip6 + ''} + + ${optint "ControlPort" cfg.controlPort} + ${optionalString cfg.controlSocket.enable "ControlPort unix:${torRunDirectory}/control GroupWritable RelaxDirModeCheck"} + '' + # Client connection config + + optionalString cfg.client.enable '' + SOCKSPort ${cfg.client.socksListenAddress} ${toString cfg.client.socksIsolationOptions} + SOCKSPort ${cfg.client.socksListenAddressFaster} + ${opt "SocksPolicy" cfg.client.socksPolicy} + + ${optionalString cfg.client.transparentProxy.enable '' + TransPort ${cfg.client.transparentProxy.listenAddress} ${toString cfg.client.transparentProxy.isolationOptions} + ''} + + ${optionalString cfg.client.dns.enable '' + DNSPort ${cfg.client.dns.listenAddress} ${toString cfg.client.dns.isolationOptions} + AutomapHostsOnResolve 1 + AutomapHostsSuffixes ${concatStringsSep "," cfg.client.dns.automapHostsSuffixes} + ''} + '' + # Explicitly disable the SOCKS server if the client is disabled. In + # particular, this makes non-anonymous hidden services possible. + + optionalString (! cfg.client.enable) '' + SOCKSPort 0 + '' + # Relay config + + optionalString cfg.relay.enable '' + ORPort ${toString cfg.relay.port} + ${opt "Address" cfg.relay.address} + ${opt "Nickname" cfg.relay.nickname} + ${opt "ContactInfo" cfg.relay.contactInfo} + + ${optint "RelayBandwidthRate" cfg.relay.bandwidthRate} + ${optint "RelayBandwidthBurst" cfg.relay.bandwidthBurst} + ${opt "AccountingMax" cfg.relay.accountingMax} + ${opt "AccountingStart" cfg.relay.accountingStart} + + ${if (cfg.relay.role == "exit") then + opt "ExitPolicy" cfg.relay.exitPolicy + else + "ExitPolicy reject *:*"} + + ${optionalString (elem cfg.relay.role ["bridge" "private-bridge"]) '' + BridgeRelay 1 + ServerTransportPlugin obfs2,obfs3 exec ${pkgs.pythonPackages.obfsproxy}/bin/obfsproxy managed + ExtORPort auto + ${optionalString (cfg.relay.role == "private-bridge") '' + ExtraInfoStatistics 0 + PublishServerDescriptor 0 + ''} + ''} + '' + # Hidden services + + concatStrings (flip mapAttrsToList cfg.hiddenServices (n: v: '' + HiddenServiceDir ${torDirectory}/onion/${v.name} + ${optionalString (v.version != null) "HiddenServiceVersion ${toString v.version}"} + ${flip concatMapStrings v.map (p: '' + HiddenServicePort ${toString p.port} ${p.destination} + '')} + ${optionalString (v.authorizeClient != null) '' + HiddenServiceAuthorizeClient ${v.authorizeClient.authType} ${concatStringsSep "," v.authorizeClient.clientNames} + ''} + '')) + + cfg.extraConfig; + + torRcFile = pkgs.writeText "torrc" torRc; + +in +{ + options = { + services.tor = { + enable = mkOption { + type = types.bool; + default = false; + description = '' + Enable the Tor daemon. By default, the daemon is run without + relay, exit, bridge or client connectivity. + ''; + }; + + enableGeoIP = mkOption { + type = types.bool; + default = true; + description = '' + Whenever to configure Tor daemon to use GeoIP databases. + + Disabling this will disable by-country statistics for + bridges and relays and some client and third-party software + functionality. + ''; + }; + + extraConfig = mkOption { + type = types.lines; + default = ""; + description = '' + Extra configuration. Contents will be added verbatim to the + configuration file at the end. + ''; + }; + + controlPort = mkOption { + type = types.nullOr (types.either types.int types.str); + default = null; + example = 9051; + description = '' + If set, Tor will accept connections on the specified port + and allow them to control the tor process. + ''; + }; + + controlSocket = { + enable = mkOption { + type = types.bool; + default = false; + description = '' + Wheter to enable Tor control socket. Control socket is created + in ${torRunDirectory}/control + ''; + }; + }; + + client = { + enable = mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable Tor daemon to route application + connections. You might want to disable this if you plan + running a dedicated Tor relay. + ''; + }; + + socksListenAddress = mkOption { + type = types.str; + default = "127.0.0.1:9050"; + example = "192.168.0.1:9100"; + description = '' + Bind to this address to listen for connections from + Socks-speaking applications. Provides strong circuit + isolation, separate circuit per IP address. + ''; + }; + + socksListenAddressFaster = mkOption { + type = types.str; + default = "127.0.0.1:9063"; + example = "192.168.0.1:9101"; + description = '' + Bind to this address to listen for connections from + Socks-speaking applications. Same as + but uses weaker + circuit isolation to provide performance suitable for a + web browser. + ''; + }; + + socksPolicy = mkOption { + type = types.nullOr types.str; + default = null; + example = "accept 192.168.0.0/16, reject *"; + description = '' + Entry policies to allow/deny SOCKS requests based on IP + address. First entry that matches wins. If no SocksPolicy + is set, we accept all (and only) requests from + . + ''; + }; + + socksIsolationOptions = mkOption (isolationOptions // { + default = ["IsolateDestAddr"]; + }); + + transparentProxy = { + enable = mkOption { + type = types.bool; + default = false; + description = "Whether to enable tor transparent proxy"; + }; + + listenAddress = mkOption { + type = types.str; + default = "127.0.0.1:9040"; + example = "192.168.0.1:9040"; + description = '' + Bind transparent proxy to this address. + ''; + }; + + isolationOptions = mkOption isolationOptions; + }; + + dns = { + enable = mkOption { + type = types.bool; + default = false; + description = "Whether to enable tor dns resolver"; + }; + + listenAddress = mkOption { + type = types.str; + default = "127.0.0.1:9053"; + example = "192.168.0.1:9053"; + description = '' + Bind tor dns to this address. + ''; + }; + + isolationOptions = mkOption isolationOptions; + + automapHostsSuffixes = mkOption { + type = types.listOf types.str; + default = [".onion" ".exit"]; + example = [".onion"]; + description = "List of suffixes to use with automapHostsOnResolve"; + }; + }; + + privoxy.enable = mkOption { + type = types.bool; + default = true; + description = '' + Whether to enable and configure the system Privoxy to use Tor's + faster port, suitable for HTTP. + + To have anonymity, protocols need to be scrubbed of identifying + information, and this can be accomplished for HTTP by Privoxy. + + Privoxy can also be useful for KDE torification. A good setup would be: + setting SOCKS proxy to the default Tor port, providing maximum + circuit isolation where possible; and setting HTTP proxy to Privoxy + to route HTTP traffic over faster, but less isolated port. + ''; + }; + }; + + relay = { + enable = mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable relaying TOR traffic for others. + + See + for details. + + Setting this to true requires setting + + and + + options. + ''; + }; + + role = mkOption { + type = types.enum [ "exit" "relay" "bridge" "private-bridge" ]; + description = '' + Your role in Tor network. There're several options: + + + + exit + + + An exit relay. This allows Tor users to access regular + Internet services through your public IP. + + + + Running an exit relay may expose you to abuse + complaints. See + + for more info. + + + + You can specify which services Tor users may access via + your exit relay using option. + + + + + + relay + + + Regular relay. This allows Tor users to relay onion + traffic to other Tor nodes, but not to public + Internet. + + + + Note that some misconfigured and/or disrespectful + towards privacy sites will block you even if your + relay is not an exit relay. That is, just being listed + in a public relay directory can have unwanted + consequences. + + Which means you might not want to use + this role if you browse public Internet from the same + network as your relay, unless you want to write + e-mails to those sites (you should!). + + + + See + + for more info. + + + + + + bridge + + + Regular bridge. Works like a regular relay, but + doesn't list you in the public relay directory and + hides your Tor node behind obfsproxy. + + + + Using this option will make Tor advertise your bridge + to users through various mechanisms like + , though. + + + + + WARNING: THE FOLLOWING PARAGRAPH IS NOT LEGAL ADVICE. + Consult with your lawer when in doubt. + + + + This role should be safe to use in most situations + (unless the act of forwarding traffic for others is + a punishable offence under your local laws, which + would be pretty insane as it would make ISP + illegal). + + + + + See + for more info. + + + + + + private-bridge + + + Private bridge. Works like regular bridge, but does + not advertise your node in any way. + + + + Using this role means that you won't contribute to Tor + network in any way unless you advertise your node + yourself in some way. + + + + Use this if you want to run a private bridge, for + example because you'll give out your bridge address + manually to your friends. + + + + Switching to this role after measurable time in + "bridge" role is pretty useless as some Tor users + would have learned about your node already. In the + latter case you can still change + option. + + + + See + for more info. + + + + + ''; + }; + + nickname = mkOption { + type = types.str; + default = "anonymous"; + description = '' + A unique handle for your TOR relay. + ''; + }; + + contactInfo = mkOption { + type = types.nullOr types.str; + default = null; + example = "admin@relay.com"; + description = '' + Contact information for the relay owner (e.g. a mail + address and GPG key ID). + ''; + }; + + accountingMax = mkOption { + type = types.nullOr types.str; + default = null; + example = "450 GBytes"; + description = '' + Specify maximum bandwidth allowed during an accounting period. This + allows you to limit overall tor bandwidth over some time period. + See the AccountingMax option by looking at the + tor manual tor + 1 for more. + + Note this limit applies individually to upload and + download; if you specify "500 GBytes" + here, then you may transfer up to 1 TBytes of overall + bandwidth (500 GB upload, 500 GB download). + ''; + }; + + accountingStart = mkOption { + type = types.nullOr types.str; + default = null; + example = "month 1 1:00"; + description = '' + Specify length of an accounting period. This allows you to limit + overall tor bandwidth over some time period. See the + AccountingStart option by looking at the tor + manual tor + 1 for more. + ''; + }; + + bandwidthRate = mkOption { + type = types.nullOr types.int; + default = null; + example = 100; + description = '' + Specify this to limit the bandwidth usage of relayed (server) + traffic. Your own traffic is still unthrottled. Units: bytes/second. + ''; + }; + + bandwidthBurst = mkOption { + type = types.nullOr types.int; + default = cfg.relay.bandwidthRate; + example = 200; + description = '' + Specify this to allow bursts of the bandwidth usage of relayed (server) + traffic. The average usage will still be as specified in relayBandwidthRate. + Your own traffic is still unthrottled. Units: bytes/second. + ''; + }; + + address = mkOption { + type = types.nullOr types.str; + default = null; + example = "noname.example.com"; + description = '' + The IP address or full DNS name for advertised address of your relay. + Leave unset and Tor will guess. + ''; + }; + + port = mkOption { + type = types.either types.int types.str; + example = 143; + description = '' + What port to advertise for Tor connections. This corresponds to the + ORPort section in the Tor manual; see + tor + 1 for more details. + + At a minimum, you should just specify the port for the + relay to listen on; a common one like 143, 22, 80, or 443 + to help Tor users who may have very restrictive port-based + firewalls. + ''; + }; + + exitPolicy = mkOption { + type = types.nullOr types.str; + default = null; + example = "accept *:6660-6667,reject *:*"; + description = '' + A comma-separated list of exit policies. They're + considered first to last, and the first match wins. If you + want to _replace_ the default exit policy, end this with + either a reject *:* or an accept *:*. Otherwise, you're + _augmenting_ (prepending to) the default exit policy. + Leave commented to just use the default, which is + available in the man page or at + . + + Look at + + for issues you might encounter if you use the default + exit policy. + + If certain IPs and ports are blocked externally, e.g. by + your firewall, you should update your exit policy to + reflect this -- otherwise Tor users will be told that + those destinations are down. + ''; + }; + }; + + hiddenServices = mkOption { + description = '' + A set of static hidden services that terminate their Tor + circuits at this node. + + Every element in this set declares a virtual onion host. + + You can specify your onion address by putting corresponding + private key to an appropriate place in ${torDirectory}. + + For services without private keys in ${torDirectory} Tor + daemon will generate random key pairs (which implies random + onion addresses) on restart. The latter could take a while, + please be patient. + + + Hidden services can be useful even if you don't intend to + actually hide them, since they can + also be seen as a kind of NAT traversal mechanism. + + E.g. the example will make your sshd, whatever runs on + "8080" and your mail server available from anywhere where + the Tor network is available (which, with the help from + bridges, is pretty much everywhere), even if both client + and server machines are behind NAT you have no control + over. + + ''; + default = {}; + example = literalExample '' + { "my-hidden-service-example".map = [ + { port = 22; } # map ssh port to this machine's ssh + { port = 80; toPort = 8080; } # map http port to whatever runs on 8080 + { port = "sip"; toHost = "mail.example.com"; toPort = "imap"; } # because we can + ]; + } + ''; + type = types.loaOf (types.submodule ({name, ...}: { + options = { + + name = mkOption { + type = types.str; + description = '' + Name of this tor hidden service. + + This is purely descriptive. + + After restarting Tor daemon you should be able to + find your .onion address in + ${torDirectory}/onion/$name/hostname. + ''; + }; + + map = mkOption { + default = []; + description = "Port mapping for this hidden service."; + type = types.listOf (types.submodule ({config, ...}: { + options = { + + port = mkOption { + type = types.either types.int types.str; + example = 80; + description = '' + Hidden service port to "bind to". + ''; + }; + + destination = mkOption { + internal = true; + type = types.str; + description = "Forward these connections where?"; + }; + + toHost = mkOption { + type = types.str; + default = "127.0.0.1"; + description = "Mapping destination host."; + }; + + toPort = mkOption { + type = types.either types.int types.str; + example = 8080; + description = "Mapping destination port."; + }; + + }; + + config = { + toPort = mkDefault config.port; + destination = mkDefault "${config.toHost}:${toString config.toPort}"; + }; + })); + }; + + authorizeClient = mkOption { + default = null; + description = "If configured, the hidden service is accessible for authorized clients only."; + type = types.nullOr (types.submodule ({...}: { + + options = { + + authType = mkOption { + type = types.enum [ "basic" "stealth" ]; + description = '' + Either "basic" for a general-purpose authorization protocol + or "stealth" for a less scalable protocol + that also hides service activity from unauthorized clients. + ''; + }; + + clientNames = mkOption { + type = types.nonEmptyListOf (types.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 ${torDirectory}/onion/$name/hostname. + Clients need to put this authorization data in their configuration file using HidServAuth. + ''; + }; + }; + })); + }; + version = mkOption { + default = null; + description = "If configured, the hidden service uses version 3"; + type = types.nullOr types.int // { check = (x: x == 2 || x == 3); }; + }; + }; + + config = { + name = mkDefault name; + }; + })); + }; + }; + }; + + config = mkIf cfg.enable { + # Not sure if `cfg.relay.role == "private-bridge"` helps as tor + # sends a lot of stats + warnings = optional (cfg.relay.enable && cfg.hiddenServices != {}) + '' + Running Tor hidden services on a public relay makes the + presence of hidden services visible through simple statistical + analysis of publicly available data. + + You can safely ignore this warning if you don't intend to + actually hide your hidden services. In either case, you can + always create a container/VM with a separate Tor daemon instance. + ''; + + users.groups.tor.gid = config.ids.gids.tor; + users.users.tor = + { description = "Tor Daemon User"; + createHome = true; + home = torDirectory; + group = "tor"; + uid = config.ids.uids.tor; + }; + + # We have to do this instead of using RuntimeDirectory option in + # the service below because systemd has no way to set owners of + # RuntimeDirectory and putting this into the service below + # requires that service to relax it's sandbox since this needs + # writable /run + systemd.services.tor-init = + { description = "Tor Daemon Init"; + wantedBy = [ "tor.service" ]; + after = [ "local-fs.target" ]; + script = '' + install -m 0700 -o tor -g tor -d ${torDirectory} ${torDirectory}/onion + install -m 0750 -o tor -g tor -d ${torRunDirectory} + ''; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + }; + }; + + systemd.services.tor = + { description = "Tor Daemon"; + path = [ pkgs.tor ]; + + wantedBy = [ "multi-user.target" ]; + after = [ "tor-init.service" "network.target" ]; + restartTriggers = [ torRcFile ]; + + serviceConfig = + { Type = "simple"; + # Translated from the upstream contrib/dist/tor.service.in + ExecStartPre = "${pkgs.tor}/bin/tor -f ${torRcFile} --verify-config"; + ExecStart = "${pkgs.tor}/bin/tor -f ${torRcFile}"; + ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; + KillSignal = "SIGINT"; + TimeoutSec = 30; + Restart = "on-failure"; + LimitNOFILE = 32768; + + # Hardening + # this seems to unshare /run despite what systemd.exec(5) says + PrivateTmp = mkIf (!cfg.controlSocket.enable) "yes"; + PrivateDevices = "yes"; + ProtectHome = "yes"; + ProtectSystem = "strict"; + InaccessiblePaths = "/home"; + ReadOnlyPaths = "/"; + ReadWritePaths = [ torDirectory torRunDirectory ]; + NoNewPrivileges = "yes"; + + # tor.service.in has this in, but this line it fails to spawn a namespace when using hidden services + #CapabilityBoundingSet = "CAP_SETUID CAP_SETGID CAP_NET_BIND_SERVICE"; + }; + }; + + environment.systemPackages = [ pkgs.tor ]; + + services.privoxy = mkIf (cfg.client.enable && cfg.client.privoxy.enable) { + enable = true; + extraConfig = '' + forward-socks4a / ${cfg.client.socksListenAddressFaster} . + toggle 1 + enable-remote-toggle 0 + enable-edit-actions 0 + enable-remote-http-toggle 0 + ''; + }; + }; +}