From 8a9190bee780284aae83171422fbe8a2d0a49f60 Mon Sep 17 00:00:00 2001 From: Luflosi Date: Fri, 14 Apr 2023 16:28:35 +0200 Subject: [PATCH 1/7] nixos/doc/rl-2305: clean up Kubo notes - Link to the `services.kubo.settings` option - Fix an incorrect quote --- nixos/doc/manual/release-notes/rl-2305.section.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nixos/doc/manual/release-notes/rl-2305.section.md b/nixos/doc/manual/release-notes/rl-2305.section.md index 99440c000c25..c94eddc1e5f7 100644 --- a/nixos/doc/manual/release-notes/rl-2305.section.md +++ b/nixos/doc/manual/release-notes/rl-2305.section.md @@ -135,7 +135,7 @@ In addition to numerous new and upgraded packages, this release has the followin - `keepassx` and `keepassx2` have been removed, due to upstream [stopping development](https://www.keepassx.org/index.html%3Fp=636.html). Consider [KeePassXC](https://keepassxc.org) as a maintained alternative. -- The `services.kubo.settings` option is now no longer stateful. If you changed any of the options in `services.kubo.settings` in the past and then removed them from your NixOS configuration again, those changes are still in your Kubo configuration file but will now be reset to the default. If you're unsure, you may want to make a backup of your configuration file (probably /var/lib/ipfs/config) and compare after the update. +- The [services.kubo.settings](#opt-services.kubo.settings) option is now no longer stateful. If you changed any of the options in [services.kubo.settings](#opt-services.kubo.settings) in the past and then removed them from your NixOS configuration again, those changes are still in your Kubo configuration file but will now be reset to the default. If you're unsure, you may want to make a backup of your configuration file (probably /var/lib/ipfs/config) and compare after the update. - The EC2 image module no longer fetches instance metadata in stage-1. This results in a significantly smaller initramfs, since network drivers no longer need to be included, and faster boots, since metadata fetching can happen in parallel with startup of other services. This breaks services which rely on metadata being present by the time stage-2 is entered. Anything which reads EC2 metadata from `/etc/ec2-metadata` should now have an `after` dependency on `fetch-ec2-metadata.service` @@ -338,7 +338,7 @@ In addition to numerous new and upgraded packages, this release has the followin [headscale's example configuration](https://github.com/juanfont/headscale/blob/main/config-example.yaml) can be directly written as attribute-set in Nix within this option. -- `services.kubo` now unmounts `ipfsMountDir` and `ipnsMountDir` even if it is killed unexpectedly when 'autoMount` is enabled. +- `services.kubo` now unmounts `ipfsMountDir` and `ipnsMountDir` even if it is killed unexpectedly when `autoMount` is enabled. - `nixos/lib/make-disk-image.nix` can now mutate EFI variables, run user-provided EFI firmware or variable templates. This is now extensively documented in the NixOS manual. From 62bff9ab15f7c23f7db90664c53d13b080bf34b1 Mon Sep 17 00:00:00 2001 From: Luflosi Date: Fri, 14 Apr 2023 01:49:48 +0200 Subject: [PATCH 2/7] nixos/tests/kubo: add Luflosi as maintainer --- nixos/tests/kubo.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nixos/tests/kubo.nix b/nixos/tests/kubo.nix index 3ea7c894ab3a..4f6bc0ef1cfa 100644 --- a/nixos/tests/kubo.nix +++ b/nixos/tests/kubo.nix @@ -1,7 +1,7 @@ import ./make-test-python.nix ({ pkgs, ...} : { name = "kubo"; meta = with pkgs.lib.maintainers; { - maintainers = [ mguentner ]; + maintainers = [ mguentner Luflosi ]; }; nodes.machine = { ... }: { From bf97703f0e160307cc9318029b05a028e95f6ce3 Mon Sep 17 00:00:00 2001 From: Luflosi Date: Fri, 14 Apr 2023 02:43:41 +0200 Subject: [PATCH 3/7] nixos/tests/kubo: clean up code - Use `runTest` instead of `handleTest`, which simplifies the code a little - Use `lib.maintainers` instead of `pkgs.lib.maintainers` - Use `ipfs add --quieter` instead of `ipfs add | awk '{ print $2 }'` - Whitespace and comment changes --- nixos/tests/all-tests.nix | 2 +- nixos/tests/kubo.nix | 17 +++++++---------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 2ad1ec35022a..201dc3d72b50 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -359,7 +359,7 @@ in { ksm = handleTest ./ksm.nix {}; kthxbye = handleTest ./kthxbye.nix {}; kubernetes = handleTestOn ["x86_64-linux"] ./kubernetes {}; - kubo = handleTest ./kubo.nix {}; + kubo = runTest ./kubo.nix; ladybird = handleTest ./ladybird.nix {}; languagetool = handleTest ./languagetool.nix {}; latestKernel.login = handleTest ./login.nix { latestKernel = true; }; diff --git a/nixos/tests/kubo.nix b/nixos/tests/kubo.nix index 4f6bc0ef1cfa..08468e31bf77 100644 --- a/nixos/tests/kubo.nix +++ b/nixos/tests/kubo.nix @@ -1,6 +1,6 @@ -import ./make-test-python.nix ({ pkgs, ...} : { +{ lib, ...} : { name = "kubo"; - meta = with pkgs.lib.maintainers; { + meta = with lib.maintainers; { maintainers = [ mguentner Luflosi ]; }; @@ -25,21 +25,18 @@ import ./make-test-python.nix ({ pkgs, ...} : { testScript = '' start_all() - # IPv4 activation - + # IPv4 socket activation machine.succeed("ipfs --api /ip4/127.0.0.1/tcp/2324 id") ipfs_hash = machine.succeed( - "echo fnord | ipfs --api /ip4/127.0.0.1/tcp/2324 add | awk '{ print $2 }'" + "echo fnord | ipfs --api /ip4/127.0.0.1/tcp/2324 add --quieter" ) - machine.succeed(f"ipfs cat /ipfs/{ipfs_hash.strip()} | grep fnord") - # Unix domain socket activation - machine.stop_job("ipfs") + # Unix domain socket activation ipfs_hash = machine.succeed( - "echo fnord2 | ipfs --api /unix/run/ipfs.sock add | awk '{ print $2 }'" + "echo fnord2 | ipfs --api /unix/run/ipfs.sock add --quieter" ) machine.succeed( f"ipfs --api /unix/run/ipfs.sock cat /ipfs/{ipfs_hash.strip()} | grep fnord2" @@ -66,4 +63,4 @@ import ./make-test-python.nix ({ pkgs, ...} : { fuse.succeed(f"cat /ipfs/{ipfs_hash} | grep fnord3") ''; -}) +} From 409df93c01854b70606720a50da9bca283e8164e Mon Sep 17 00:00:00 2001 From: Luflosi Date: Fri, 14 Apr 2023 02:46:22 +0200 Subject: [PATCH 4/7] nixos/tests/kubo: use subtests instead of comments Specify the intent of parts of the tests by using `with subtest("...")` instead of a comment for more clarity. --- nixos/tests/kubo.nix | 58 ++++++++++++++++++++++---------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/nixos/tests/kubo.nix b/nixos/tests/kubo.nix index 08468e31bf77..e0bd918150ac 100644 --- a/nixos/tests/kubo.nix +++ b/nixos/tests/kubo.nix @@ -25,42 +25,42 @@ testScript = '' start_all() - # IPv4 socket activation - machine.succeed("ipfs --api /ip4/127.0.0.1/tcp/2324 id") - ipfs_hash = machine.succeed( - "echo fnord | ipfs --api /ip4/127.0.0.1/tcp/2324 add --quieter" - ) - machine.succeed(f"ipfs cat /ipfs/{ipfs_hash.strip()} | grep fnord") + with subtest("IPv4 socket activation"): + machine.succeed("ipfs --api /ip4/127.0.0.1/tcp/2324 id") + ipfs_hash = machine.succeed( + "echo fnord | ipfs --api /ip4/127.0.0.1/tcp/2324 add --quieter" + ) + machine.succeed(f"ipfs cat /ipfs/{ipfs_hash.strip()} | grep fnord") machine.stop_job("ipfs") - # Unix domain socket activation - ipfs_hash = machine.succeed( - "echo fnord2 | ipfs --api /unix/run/ipfs.sock add --quieter" - ) - machine.succeed( - f"ipfs --api /unix/run/ipfs.sock cat /ipfs/{ipfs_hash.strip()} | grep fnord2" - ) + with subtest("Unix domain socket activation"): + ipfs_hash = machine.succeed( + "echo fnord2 | ipfs --api /unix/run/ipfs.sock add --quieter" + ) + machine.succeed( + f"ipfs --api /unix/run/ipfs.sock cat /ipfs/{ipfs_hash.strip()} | grep fnord2" + ) - # Test if setting dataDir works properly with the hardened systemd unit - machine.succeed("test -e /mnt/ipfs/config") - machine.succeed("test ! -e /var/lib/ipfs/") + with subtest("Setting dataDir works properly with the hardened systemd unit"): + machine.succeed("test -e /mnt/ipfs/config") + machine.succeed("test ! -e /var/lib/ipfs/") - # Test FUSE mountpoint - # The FUSE mount functionality is broken as of v0.13.0 and v0.17.0. - # See https://github.com/ipfs/kubo/issues/9044. - # Workaround: using CID Version 1 avoids that. - ipfs_hash = fuse.succeed( - "echo fnord3 | ipfs --api /ip4/127.0.0.1/tcp/2324 add --quieter --cid-version=1" - ).strip() + with subtest("FUSE mountpoint"): + # The FUSE mount functionality is broken as of v0.13.0 and v0.17.0. + # See https://github.com/ipfs/kubo/issues/9044. + # Workaround: using CID Version 1 avoids that. + ipfs_hash = fuse.succeed( + "echo fnord3 | ipfs --api /ip4/127.0.0.1/tcp/2324 add --quieter --cid-version=1" + ).strip() - fuse.succeed(f"cat /ipfs/{ipfs_hash} | grep fnord3") + fuse.succeed(f"cat /ipfs/{ipfs_hash} | grep fnord3") - # Force Kubo to crash and wait for it to restart - # Tests the unmounting of /ipns and /ipfs - fuse.systemctl("kill --signal=SIGKILL ipfs.service") - fuse.wait_for_unit("ipfs.service", timeout = 30) + with subtest("Unmounting of /ipns and /ipfs"): + # Force Kubo to crash and wait for it to restart + fuse.systemctl("kill --signal=SIGKILL ipfs.service") + fuse.wait_for_unit("ipfs.service", timeout = 30) - fuse.succeed(f"cat /ipfs/{ipfs_hash} | grep fnord3") + fuse.succeed(f"cat /ipfs/{ipfs_hash} | grep fnord3") ''; } From 929a00bd84acbf35447d3df1066b1c8afd7ac82d Mon Sep 17 00:00:00 2001 From: Luflosi Date: Fri, 14 Apr 2023 02:26:32 +0200 Subject: [PATCH 5/7] nixos/kubo: give normal users access to the daemon by default Fixes https://github.com/NixOS/nixpkgs/issues/223289. This doesn't reduce the security in any way since it was already possible for normal users to do what I do here and create such a fake repo for themselves and set their $IPFS_PATH variable to it. It was and still is also possible to just use the --api CLI option. This change just removes the manual setup that would otherwise be required. We wouldn't need this workaround if https://github.com/ipfs/kubo/pull/9366 was merged but the fix seems to have been ignored upstream. Patching it ourselves seems like a bad idea since the patch has security implications. --- .../modules/services/network-filesystems/kubo.nix | 14 +++++++++++++- nixos/tests/kubo.nix | 13 ++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/nixos/modules/services/network-filesystems/kubo.nix b/nixos/modules/services/network-filesystems/kubo.nix index 468e47d749b7..716be4fd59a9 100644 --- a/nixos/modules/services/network-filesystems/kubo.nix +++ b/nixos/modules/services/network-filesystems/kubo.nix @@ -22,6 +22,18 @@ let configFile = settingsFormat.generate "kubo-config.json" customizedConfig; + # Create a fake repo containing only the file "api". + # $IPFS_PATH will point to this directory instead of the real one. + # For some reason the Kubo CLI tools insist on reading the + # config file when it exists. But the Kubo daemon sets the file + # permissions such that only the ipfs user is allowed to read + # this file. This prevents normal users from talking to the daemon. + # To work around this terrible design, create a fake repo with no + # config file, only an api file and everything should work as expected. + fakeKuboRepo = pkgs.writeTextDir "api" '' + /unix/run/ipfs.sock + ''; + kuboFlags = utils.escapeSystemdExecArgs ( optional cfg.autoMount "--mount" ++ optional cfg.enableGC "--enable-gc" ++ @@ -248,7 +260,7 @@ in ]; environment.systemPackages = [ cfg.package ]; - environment.variables.IPFS_PATH = cfg.dataDir; + environment.variables.IPFS_PATH = fakeKuboRepo; # https://github.com/lucas-clemente/quic-go/wiki/UDP-Receive-Buffer-Size boot.kernel.sysctl."net.core.rmem_max" = mkDefault 2500000; diff --git a/nixos/tests/kubo.nix b/nixos/tests/kubo.nix index e0bd918150ac..dfe653b5d271 100644 --- a/nixos/tests/kubo.nix +++ b/nixos/tests/kubo.nix @@ -12,6 +12,9 @@ settings.Addresses.API = "/ip4/127.0.0.1/tcp/2324"; dataDir = "/mnt/ipfs"; }; + users.users.alice = { + isNormalUser = true; + }; }; nodes.fuse = { ... }: { @@ -25,6 +28,14 @@ testScript = '' start_all() + with subtest("Automatic socket activation"): + ipfs_hash = machine.succeed( + "echo fnord0 | su alice -l -c 'ipfs add --quieter'" + ) + machine.succeed(f"ipfs cat /ipfs/{ipfs_hash.strip()} | grep fnord0") + + machine.stop_job("ipfs") + with subtest("IPv4 socket activation"): machine.succeed("ipfs --api /ip4/127.0.0.1/tcp/2324 id") ipfs_hash = machine.succeed( @@ -51,7 +62,7 @@ # See https://github.com/ipfs/kubo/issues/9044. # Workaround: using CID Version 1 avoids that. ipfs_hash = fuse.succeed( - "echo fnord3 | ipfs --api /ip4/127.0.0.1/tcp/2324 add --quieter --cid-version=1" + "echo fnord3 | ipfs add --quieter --cid-version=1" ).strip() fuse.succeed(f"cat /ipfs/{ipfs_hash} | grep fnord3") From 7ceebbb35bfebd887f41418588d1ccc32d3a977a Mon Sep 17 00:00:00 2001 From: Luflosi Date: Fri, 14 Apr 2023 15:56:10 +0200 Subject: [PATCH 6/7] nixos/kubo: allow multiple API and Gateway addresses The daemon allows specifying an array of multiaddrs for Addresses.API and Addresses.Gateway, so the NixOS module should allow that as well. --- .../services/network-filesystems/kubo.nix | 40 ++++++++++++------- nixos/tests/kubo.nix | 3 +- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/nixos/modules/services/network-filesystems/kubo.nix b/nixos/modules/services/network-filesystems/kubo.nix index 716be4fd59a9..f496cfb4338f 100644 --- a/nixos/modules/services/network-filesystems/kubo.nix +++ b/nixos/modules/services/network-filesystems/kubo.nix @@ -50,6 +50,22 @@ let splitMulitaddr = addrRaw: lib.tail (lib.splitString "/" addrRaw); + multiaddrsToListenStreams = addrIn: + let + addrs = if builtins.typeOf addrIn == "list" + then addrIn else [ addrIn ]; + unfilteredResult = map multiaddrToListenStream addrs; + in + builtins.filter (addr: addr != null) unfilteredResult; + + multiaddrsToListenDatagrams = addrIn: + let + addrs = if builtins.typeOf addrIn == "list" + then addrIn else [ addrIn ]; + unfilteredResult = map multiaddrToListenDatagram addrs; + in + builtins.filter (addr: addr != null) unfilteredResult; + multiaddrToListenStream = addrRaw: let addr = splitMulitaddr addrRaw; @@ -166,13 +182,16 @@ in options = { Addresses.API = mkOption { - type = types.str; + type = types.oneOf [ types.str (types.listOf types.str) ]; default = "/ip4/127.0.0.1/tcp/5001"; - description = lib.mdDoc "Where Kubo exposes its API to"; + description = lib.mdDoc '' + Multiaddr or array of multiaddrs describing the address to serve the local HTTP API on. + In addition to the multiaddrs listed here, the daemon will also listen on a Unix domain socket. + ''; }; Addresses.Gateway = mkOption { - type = types.str; + type = types.oneOf [ types.str (types.listOf types.str) ]; default = "/ip4/127.0.0.1/tcp/8080"; description = lib.mdDoc "Where the IPFS Gateway can be reached"; }; @@ -350,15 +369,9 @@ in wantedBy = [ "sockets.target" ]; socketConfig = { ListenStream = - let - fromCfg = multiaddrToListenStream cfg.settings.Addresses.Gateway; - in - [ "" ] ++ lib.optional (fromCfg != null) fromCfg; + [ "" ] ++ (multiaddrsToListenStreams cfg.settings.Addresses.Gateway); ListenDatagram = - let - fromCfg = multiaddrToListenDatagram cfg.settings.Addresses.Gateway; - in - [ "" ] ++ lib.optional (fromCfg != null) fromCfg; + [ "" ] ++ (multiaddrsToListenDatagrams cfg.settings.Addresses.Gateway); }; }; @@ -367,10 +380,7 @@ in # We also include "%t/ipfs.sock" because there is no way to put the "%t" # in the multiaddr. socketConfig.ListenStream = - let - fromCfg = multiaddrToListenStream cfg.settings.Addresses.API; - in - [ "" "%t/ipfs.sock" ] ++ lib.optional (fromCfg != null) fromCfg; + [ "" "%t/ipfs.sock" ] ++ multiaddrsToListenStreams cfg.settings.Addresses.API; }; }; diff --git a/nixos/tests/kubo.nix b/nixos/tests/kubo.nix index dfe653b5d271..53642ee3fc8d 100644 --- a/nixos/tests/kubo.nix +++ b/nixos/tests/kubo.nix @@ -20,7 +20,8 @@ nodes.fuse = { ... }: { services.kubo = { enable = true; - settings.Addresses.API = "/ip4/127.0.0.1/tcp/2324"; + # Only allow API access through the Unix domain socket + settings.Addresses.API = [ ]; autoMount = true; }; }; From f2be3ae30d960b2f7f1c816d9cab43000556fd0a Mon Sep 17 00:00:00 2001 From: Luflosi Date: Fri, 14 Apr 2023 16:37:32 +0200 Subject: [PATCH 7/7] nixos/kubo: restrict access to the API to users in a group by default --- .../doc/manual/release-notes/rl-2305.section.md | 2 ++ .../services/network-filesystems/kubo.nix | 17 ++++++++++++----- nixos/tests/kubo.nix | 17 ++++++++++++----- 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/nixos/doc/manual/release-notes/rl-2305.section.md b/nixos/doc/manual/release-notes/rl-2305.section.md index c94eddc1e5f7..0d91e780371e 100644 --- a/nixos/doc/manual/release-notes/rl-2305.section.md +++ b/nixos/doc/manual/release-notes/rl-2305.section.md @@ -137,6 +137,8 @@ In addition to numerous new and upgraded packages, this release has the followin - The [services.kubo.settings](#opt-services.kubo.settings) option is now no longer stateful. If you changed any of the options in [services.kubo.settings](#opt-services.kubo.settings) in the past and then removed them from your NixOS configuration again, those changes are still in your Kubo configuration file but will now be reset to the default. If you're unsure, you may want to make a backup of your configuration file (probably /var/lib/ipfs/config) and compare after the update. +- The Kubo HTTP API will no longer listen on localhost and will instead only listen on a Unix domain socket by default. Read the [services.kubo.settings.Addresses.API](#opt-services.kubo.settings.Addresses.API) option description for more information. + - The EC2 image module no longer fetches instance metadata in stage-1. This results in a significantly smaller initramfs, since network drivers no longer need to be included, and faster boots, since metadata fetching can happen in parallel with startup of other services. This breaks services which rely on metadata being present by the time stage-2 is entered. Anything which reads EC2 metadata from `/etc/ec2-metadata` should now have an `after` dependency on `fetch-ec2-metadata.service` diff --git a/nixos/modules/services/network-filesystems/kubo.nix b/nixos/modules/services/network-filesystems/kubo.nix index f496cfb4338f..2537bb1b8d80 100644 --- a/nixos/modules/services/network-filesystems/kubo.nix +++ b/nixos/modules/services/network-filesystems/kubo.nix @@ -183,10 +183,12 @@ in options = { Addresses.API = mkOption { type = types.oneOf [ types.str (types.listOf types.str) ]; - default = "/ip4/127.0.0.1/tcp/5001"; + default = [ ]; description = lib.mdDoc '' Multiaddr or array of multiaddrs describing the address to serve the local HTTP API on. In addition to the multiaddrs listed here, the daemon will also listen on a Unix domain socket. + To allow the ipfs CLI tools to communicate with the daemon over that socket, + add your user to the correct group, e.g. `users.users.alice.extraGroups = [ config.services.kubo.group ];` ''; }; @@ -377,10 +379,15 @@ in systemd.sockets.ipfs-api = { wantedBy = [ "sockets.target" ]; - # We also include "%t/ipfs.sock" because there is no way to put the "%t" - # in the multiaddr. - socketConfig.ListenStream = - [ "" "%t/ipfs.sock" ] ++ multiaddrsToListenStreams cfg.settings.Addresses.API; + socketConfig = { + # We also include "%t/ipfs.sock" because there is no way to put the "%t" + # in the multiaddr. + ListenStream = + [ "" "%t/ipfs.sock" ] ++ (multiaddrsToListenStreams cfg.settings.Addresses.API); + SocketMode = "0660"; + SocketUser = cfg.user; + SocketGroup = cfg.group; + }; }; }; diff --git a/nixos/tests/kubo.nix b/nixos/tests/kubo.nix index 53642ee3fc8d..496f409a40a9 100644 --- a/nixos/tests/kubo.nix +++ b/nixos/tests/kubo.nix @@ -4,7 +4,7 @@ maintainers = [ mguentner Luflosi ]; }; - nodes.machine = { ... }: { + nodes.machine = { config, ... }: { services.kubo = { enable = true; # Also will add a unix domain socket socket API address, see module. @@ -14,16 +14,22 @@ }; users.users.alice = { isNormalUser = true; + extraGroups = [ config.services.kubo.group ]; }; }; - nodes.fuse = { ... }: { + nodes.fuse = { config, ... }: { services.kubo = { enable = true; - # Only allow API access through the Unix domain socket - settings.Addresses.API = [ ]; autoMount = true; }; + users.users.alice = { + isNormalUser = true; + extraGroups = [ config.services.kubo.group ]; + }; + users.users.bob = { + isNormalUser = true; + }; }; testScript = '' @@ -59,11 +65,12 @@ machine.succeed("test ! -e /var/lib/ipfs/") with subtest("FUSE mountpoint"): + fuse.fail("echo a | su bob -l -c 'ipfs add --quieter'") # The FUSE mount functionality is broken as of v0.13.0 and v0.17.0. # See https://github.com/ipfs/kubo/issues/9044. # Workaround: using CID Version 1 avoids that. ipfs_hash = fuse.succeed( - "echo fnord3 | ipfs add --quieter --cid-version=1" + "echo fnord3 | su alice -l -c 'ipfs add --quieter --cid-version=1'" ).strip() fuse.succeed(f"cat /ipfs/{ipfs_hash} | grep fnord3")