diff --git a/README.md b/README.md index 48bd4d1..fbd9ddd 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ nix-bitcoin === -Nix packages and nixos modules for easily installing Bitcoin nodes and higher layer protocols. +Nix packages and nixos modules for easily installing Bitcoin nodes and higher layer protocols with an emphasis on security. This is a work in progress - don't expect it to be bug free or secure. The default configuration sets up a Bitcoin Core node and c-lightning. The user can enable spark-wallet in `configuration.nix` to make c-lightning accessible with a smartphone using spark-wallet. @@ -49,6 +49,19 @@ The easiest way is to run `nix-shell` (on a Linux machine) in the nix-bitcoin di Fix the FIXMEs in configuration.nix and deploy with nixops in nix-shell. See [install.md](docs/install.md) for a detailed tutorial. +Security +--- +* Nix package manager, NixOS and packages can be built from source to reduce reliance on binary caches. +* Builds happen in a [sandboxed environment](https://nixos.org/nix/manual/). +* Packages dependencies are [pinned](pkgs/nixpkgs-pinned.nix). Most packages are built from the [nixos stable channel](https://github.com/NixOS/nixpkgs-channels/tree/nixos-19.03), with a few exceptions that are built from the nixpkgs unstable channel. +* nix-bitcoin merge commits are signed. +* nix-bitcoin is built with a [hardened kernel](https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/profiles/hardened.nix) by default. +* Services operate with least privileges. They each have their own user and are restricted further with [systemd options](modules/nix-bitcoin-services.nix). +* There's a non-root user *operator* to interact with the various services. + +Note that nix-bitcoin is still experimental. +Also, by design if the machine you're deploying *from* is insecure, there is nothing nix-bitcoin can do to protect itself. + Hardware requirements --- * Disk space: 300 GB (235GB for Bitcoin blockchain + some room) diff --git a/docs/install.md b/docs/install.md index a441ee3..e839ebb 100644 --- a/docs/install.md +++ b/docs/install.md @@ -169,6 +169,10 @@ This is borrowed from the [NixOS manual](https://nixos.org/nixos/manual/index.ht swapon /dev/sda2 ``` +4. Option 3: Set up encrypted partitions: + + Follow the guide at https://gist.github.com/martijnvermaat/76f2e24d0239470dd71050358b4d5134. + 5. Generate NixOS config ``` diff --git a/modules/bitcoind.nix b/modules/bitcoind.nix index bf44035..38f4f7c 100644 --- a/modules/bitcoind.nix +++ b/modules/bitcoind.nix @@ -3,6 +3,7 @@ with lib; let + nix-bitcoin-services = pkgs.callPackage ./nix-bitcoin-services.nix { }; cfg = config.services.bitcoind; pidFile = "${cfg.dataDir}/bitcoind.pid"; configFile = pkgs.writeText "bitcoin.conf" '' @@ -192,6 +193,7 @@ in { to stay under the specified target size in MiB) ''; }; + enforceTor = nix-bitcoin-services.enforceTor; }; }; @@ -235,7 +237,11 @@ in { # Permission for preStart PermissionsStartOnly = "true"; - }; + } // nix-bitcoin-services.defaultHardening + // (if cfg.enforceTor + then nix-bitcoin-services.allowTor + else nix-bitcoin-services.allowAnyIP + ); }; systemd.services.bitcoind-import-banlist = { description = "Bitcoin daemon banlist importer"; @@ -269,16 +275,10 @@ in { ExecStart = "${pkgs.bash}/bin/bash ${pkgs.banlist}/bin/banlist ${pkgs.altcoins.bitcoind}"; StateDirectory = "bitcoind"; - # Hardening measures - PrivateTmp = "true"; - ProtectSystem = "full"; - NoNewPrivileges = "true"; - PrivateDevices = "true"; - MemoryDenyWriteExecute = "true"; - # Permission for preStart PermissionsStartOnly = "true"; - }; + } // nix-bitcoin-services.defaultHardening + // nix-bitcoin-services.allowTor; }; users.users.${cfg.user} = { diff --git a/modules/clightning.nix b/modules/clightning.nix index 6d02cb4..235be41 100644 --- a/modules/clightning.nix +++ b/modules/clightning.nix @@ -3,6 +3,7 @@ with lib; let + nix-bitcoin-services = pkgs.callPackage ./nix-bitcoin-services.nix { }; cfg = config.services.clightning; configFile = pkgs.writeText "config" '' autolisten=${if cfg.autolisten then "true" else "false"} @@ -56,6 +57,7 @@ in { default = "/var/lib/clightning"; description = "The data directory for clightning."; }; + enforceTor = nix-bitcoin-services.enforceTor; }; config = mkIf cfg.enable { @@ -93,12 +95,11 @@ in { User = "clightning"; Restart = "on-failure"; RestartSec = "10s"; - PrivateTmp = "true"; - ProtectSystem = "full"; - NoNewPrivileges = "true"; - PrivateDevices = "true"; - MemoryDenyWriteExecute = "true"; - }; + } // nix-bitcoin-services.defaultHardening + // (if cfg.enforceTor + then nix-bitcoin-services.allowTor + else nix-bitcoin-services.allowAnyIP + ); }; }; } diff --git a/modules/electrs.nix b/modules/electrs.nix index bb9795a..524bbd1 100644 --- a/modules/electrs.nix +++ b/modules/electrs.nix @@ -3,6 +3,7 @@ with lib; let + nix-bitcoin-services = pkgs.callPackage ./nix-bitcoin-services.nix { }; cfg = config.services.electrs; index-batch-size = "${if cfg.high-memory then "" else "--index-batch-size=10"}"; jsonrpc-import = "${if cfg.high-memory then "" else "--jsonrpc-import"}"; @@ -42,6 +43,7 @@ in { default = 50003; description = "Override the default port on which to listen for connections."; }; + enforceTor = nix-bitcoin-services.enforceTor; }; config = mkIf cfg.enable { @@ -74,11 +76,11 @@ in { User = "electrs"; Restart = "on-failure"; RestartSec = "10s"; - PrivateTmp = "true"; - ProtectSystem = "full"; - NoNewPrivileges = "true"; - PrivateDevices = "true"; - }; + } // nix-bitcoin-services.defaultHardening + // (if cfg.enforceTor + then nix-bitcoin-services.allowTor + else nix-bitcoin-services.allowAnyIP + ); }; services.nginx = { diff --git a/modules/lightning-charge.nix b/modules/lightning-charge.nix index 277d5f3..bd0e371 100644 --- a/modules/lightning-charge.nix +++ b/modules/lightning-charge.nix @@ -3,6 +3,7 @@ with lib; let + nix-bitcoin-services = pkgs.callPackage ./nix-bitcoin-services.nix { }; cfg = config.services.lightning-charge; in { options.services.lightning-charge = { @@ -37,11 +38,9 @@ in { User = "clightning"; Restart = "on-failure"; RestartSec = "10s"; - PrivateTmp = "true"; - ProtectSystem = "full"; - NoNewPrivileges = "true"; - PrivateDevices = "true"; - }; + } // nix-bitcoin-services.defaultHardening + // nix-bitcoin-services.nodejs + // nix-bitcoin-services.allowTor; }; }; } diff --git a/modules/liquid.nix b/modules/liquid.nix index 7b7bd6b..acff282 100644 --- a/modules/liquid.nix +++ b/modules/liquid.nix @@ -3,6 +3,7 @@ with lib; let + nix-bitcoin-services = pkgs.callPackage ./nix-bitcoin-services.nix { }; cfg = config.services.liquidd; pidFile = "${cfg.dataDir}/liquidd.pid"; configFile = pkgs.writeText "liquid.conf" '' @@ -165,6 +166,7 @@ in { to stay under the specified target size in MiB) ''; }; + enforceTor = nix-bitcoin-services.enforceTor; }; }; @@ -195,20 +197,16 @@ in { PIDFile = "${pidFile}"; Restart = "on-failure"; - # Hardening measures - PrivateTmp = "true"; - ProtectSystem = "full"; - NoNewPrivileges = "true"; - PrivateDevices = "true"; - MemoryDenyWriteExecute = "true"; - # Permission for preStart PermissionsStartOnly = "true"; - }; + } // nix-bitcoin-services.defaultHardening + // (if cfg.enforceTor + then nix-bitcoin-services.allowTor + else nix-bitcoin-services.allowAnyIP + ); }; users.users.${cfg.user} = { name = cfg.user; - #uid = config.ids.uids.liquid; group = cfg.group; extraGroups = [ "keys" ]; description = "Liquid daemon user"; @@ -216,7 +214,6 @@ in { }; users.groups.${cfg.group} = { name = cfg.group; - #gid = config.ids.gids.liquid; }; }; } diff --git a/modules/nanopos.nix b/modules/nanopos.nix index e018182..300da4b 100644 --- a/modules/nanopos.nix +++ b/modules/nanopos.nix @@ -3,6 +3,7 @@ with lib; let + nix-bitcoin-services = pkgs.callPackage ./nix-bitcoin-services.nix { }; cfg = config.services.nanopos; defaultItemsFile = pkgs.writeText "items.yaml" '' tea: @@ -73,11 +74,9 @@ in { User = "nanopos"; Restart = "on-failure"; RestartSec = "10s"; - PrivateTmp = "true"; - ProtectSystem = "full"; - NoNewPrivileges = "true"; - PrivateDevices = "true"; - }; + } // nix-bitcoin-services.defaultHardening + // nix-bitcoin-services.nodejs + // nix-bitcoin-services.allowTor; }; }; } diff --git a/modules/nix-bitcoin-services.nix b/modules/nix-bitcoin-services.nix new file mode 100644 index 0000000..685d668 --- /dev/null +++ b/modules/nix-bitcoin-services.nix @@ -0,0 +1,44 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + defaultHardening = { + PrivateTmp = "true"; + ProtectSystem = "full"; + ProtectHome = "true"; + NoNewPrivileges = "true"; + PrivateDevices = "true"; + MemoryDenyWriteExecute = "true"; + ProtectKernelTunables = "true"; + ProtectKernelModules = "true"; + ProtectControlGroups = "true"; + RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6"; + RestrictNamespaces = "true"; + LockPersonality = "true"; + IPAddressDeny = "any"; + }; +in +{ + inherit defaultHardening; + # nodejs applications apparently rely on memory write execute + nodejs = { MemoryDenyWriteExecute = "false"; }; + # Allow tor traffic. Allow takes precedence over Deny. + allowTor = { + IPAddressAllow = "127.0.0.1/32 ::1/128"; + }; + # Allow any traffic + allowAnyIP = { IPAddressAllow = "any"; }; + + enforceTor = mkOption { + type = types.bool; + default = false; + description = '' + "Whether to force Tor on a service by only allowing connections from and + to 127.0.0.1;"; + ''; + }; +} + + + diff --git a/modules/nix-bitcoin-webindex.nix b/modules/nix-bitcoin-webindex.nix index c8748a4..96849bf 100644 --- a/modules/nix-bitcoin-webindex.nix +++ b/modules/nix-bitcoin-webindex.nix @@ -3,6 +3,7 @@ with lib; let + nix-bitcoin-services = pkgs.callPackage ./nix-bitcoin-services.nix { }; cfg = config.services.nix-bitcoin-webindex; indexFile = pkgs.writeText "index.html" '' @@ -43,6 +44,7 @@ in { If enabled, the webindex service will be installed. ''; }; + enforceTor = nix-bitcoin-services.enforceTor; }; config = mkIf cfg.enable { @@ -80,7 +82,11 @@ in { RemainAfterExit="yes"; Restart = "on-failure"; RestartSec = "10s"; - }; + } // nix-bitcoin-services.defaultHardening + // (if cfg.enforceTor + then nix-bitcoin-services.allowTor + else nix-bitcoin-services.allowAnyIP + ); }; }; } diff --git a/modules/nix-bitcoin.nix b/modules/nix-bitcoin.nix index ac277f9..55fc4df 100644 --- a/modules/nix-bitcoin.nix +++ b/modules/nix-bitcoin.nix @@ -60,6 +60,7 @@ in { services.bitcoind.sysperms = if config.services.electrs.enable then true else null; services.bitcoind.disablewallet = if config.services.electrs.enable then true else null; services.bitcoind.proxy = config.services.tor.client.socksListenAddress; + services.bitcoind.enforceTor = true; services.bitcoind.port = 8333; services.bitcoind.rpcuser = "bitcoinrpc"; services.bitcoind.extraConfig = '' @@ -82,6 +83,7 @@ in { # clightning services.clightning.bitcoin-rpcuser = config.services.bitcoind.rpcuser; services.clightning.proxy = config.services.tor.client.socksListenAddress; + services.clightning.enforceTor = true; services.clightning.always-use-proxy = true; services.clightning.bind-addr = "127.0.0.1:9735"; services.tor.hiddenServices.clightning = { @@ -128,6 +130,8 @@ in { }; }; + services.nix-bitcoin-webindex.enforceTor = true; + services.liquidd.rpcuser = "liquidrpc"; services.liquidd.prune = 1000; services.liquidd.extraConfig = " @@ -136,6 +140,7 @@ in { "; services.liquidd.listen = true; services.liquidd.proxy = config.services.tor.client.socksListenAddress; + services.liquidd.enforceTor = true; services.liquidd.port = 7042; services.tor.hiddenServices.liquidd = { map = [{ @@ -143,9 +148,10 @@ in { }]; version = 3; }; - + services.spark-wallet.onion-service = true; services.electrs.port = 50001; + services.electrs.enforceTor = true; services.electrs.onionport = 50002; services.electrs.nginxport = 50003; services.electrs.high-memory = false; diff --git a/modules/onion-chef.nix b/modules/onion-chef.nix index 9a71e0b..c222015 100644 --- a/modules/onion-chef.nix +++ b/modules/onion-chef.nix @@ -8,6 +8,7 @@ with lib; let + nix-bitcoin-services = pkgs.callPackage ./nix-bitcoin-services.nix { }; cfg = config.services.onion-chef; dataDir = "/var/lib/onion-chef/"; onion-chef-script = pkgs.writeScript "onion-chef.sh" '' @@ -77,11 +78,7 @@ in { ExecStart = "${pkgs.bash}/bin/bash ${onion-chef-script}"; User = "root"; Type = "oneshot"; - PrivateTmp = "true"; - ProtectSystem = "full"; - NoNewPrivileges = "true"; - PrivateDevices = "true"; - }; + } // nix-bitcoin-services.defaultHardening; }; }; } diff --git a/modules/recurring-donations.nix b/modules/recurring-donations.nix index 00384bc..a4f46b0 100644 --- a/modules/recurring-donations.nix +++ b/modules/recurring-donations.nix @@ -3,6 +3,7 @@ with lib; let + nix-bitcoin-services = pkgs.callPackage ./nix-bitcoin-services.nix { }; cfg = config.services.recurring-donations; recurring-donations-script = pkgs.writeScript "recurring-donations.sh" '' LNCLI="lightning-cli --lightning-dir=${config.services.clightning.dataDir}" @@ -88,11 +89,8 @@ in { # working inside the shell script User = "clightning"; Type = "oneshot"; - PrivateTmp = "true"; - ProtectSystem = "full"; - NoNewPrivileges = "true"; - PrivateDevices = "true"; - }; + } // nix-bitcoin-services.defaultHardening + // nix-bitcoin-services.allowTor; }; systemd.timers.recurring-donations = { requires = [ "clightning.service" ]; diff --git a/modules/spark-wallet.nix b/modules/spark-wallet.nix index d727e16..e2d6226 100644 --- a/modules/spark-wallet.nix +++ b/modules/spark-wallet.nix @@ -3,6 +3,7 @@ with lib; let + nix-bitcoin-services = pkgs.callPackage ./nix-bitcoin-services.nix { }; cfg = config.services.spark-wallet; dataDir = "/var/lib/spark-wallet/"; onion-chef-service = (if cfg.onion-service then [ "onion-chef.service" ] else []); @@ -63,11 +64,9 @@ in { User = "clightning"; Restart = "on-failure"; RestartSec = "10s"; - PrivateTmp = "true"; - ProtectSystem = "full"; - NoNewPrivileges = "true"; - PrivateDevices = "true"; - }; + } // nix-bitcoin-services.defaultHardening + // nix-bitcoin-services.nodejs + // nix-bitcoin-services.allowTor; }; }; }