diff --git a/ci-tools/known_hosts.staging b/ci-tools/known_hosts.staging index 2a015656e4c21498f2fe152723888046d9341c1a..1a8e6245e569fdda6daa81cc67c4c8cc33b8e8f8 100644 --- a/ci-tools/known_hosts.staging +++ b/ci-tools/known_hosts.staging @@ -1,3 +1,3 @@ monitoring.privatestorage-staging.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINI9kvEBaOMvpWqcFH+6nFvRriBECKB4RFShdPiIMkk9 payments.privatestorage-staging.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK0eO/01VFwdoZzpclrmu656eaMkE19BaxtDdkkFHMa8 -storage001.privatestorage-staging.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFP8L6OHCxq9XFd8ME8ZrCbmO5dGZDPH8I5dm0AwSGiN +storage001.privatestorage-staging.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA6iWHO9/4s3h9VIpaxgD+rgj/OQh8+jupxBoOmie3St diff --git a/default.nix b/default.nix index 6441675a243e22e6154267c656652c8d8575940e..8b605281715be3f74c375a6b5532a4a87a6b4993 100644 --- a/default.nix +++ b/default.nix @@ -1,4 +1,4 @@ -{ pkgs ? import ./nixpkgs-2105.nix { } }: +{ pkgs ? import ./nixpkgs.nix { } }: { # Render the project documentation source to some presentation format (ie, # html) with Sphinx. diff --git a/morph/grid/local/grid.nix b/morph/grid/local/grid.nix index e672f3d14ef5f7389511a818921a9a75c6e948fe..da8a83812ceba910280bfc61210487b2f217113f 100644 --- a/morph/grid/local/grid.nix +++ b/morph/grid/local/grid.nix @@ -75,7 +75,7 @@ let ]; config = { grid.monitoringvpnIPv4 = "172.23.23.11"; - grid.publicIPv4 = "192.168.67.21"; + grid.publicIPv4 = "192.168.56.21"; grid.issuer = { inherit (grid-config) issuerDomains allowedChargeOrigins; }; @@ -89,7 +89,7 @@ let ]; config = { grid.monitoringvpnIPv4 = "172.23.23.12"; - grid.publicIPv4 = "192.168.67.22"; + grid.publicIPv4 = "192.168.56.22"; grid.storage = { inherit (grid-config) passValue publicStoragePort; }; @@ -104,7 +104,7 @@ let ]; config = { grid.monitoringvpnIPv4 = "172.23.23.13"; - grid.publicIPv4 = "192.168.67.23"; + grid.publicIPv4 = "192.168.56.23"; grid.storage = { inherit (grid-config) passValue publicStoragePort; }; @@ -119,7 +119,7 @@ let ]; config = { grid.monitoringvpnIPv4 = "172.23.23.1"; - grid.publicIPv4 = "192.168.67.24"; + grid.publicIPv4 = "192.168.56.24"; grid.monitoring = { inherit paymentExporterTargets blackboxExporterHttpsTargets; inherit (grid-config) monitoringDomains; diff --git a/morph/lib/default.nix b/morph/lib/default.nix index 88c83bc2211da2e00e69f95d7d2110d3ee636cc3..c43fa6ea1ebc6b3159f3f2f6872c23f0da62b776 100644 --- a/morph/lib/default.nix +++ b/morph/lib/default.nix @@ -17,7 +17,7 @@ # installed, as well as the NixOS module set that is used. # This is intended to be used in a grid definition like: # network = { ... ; inherit (gridlib) pkgs; ... } - pkgs = import ../../nixpkgs-2105.nix { + pkgs = import ../../nixpkgs.nix { # Ensure that configuration of the system where this runs # doesn't leak into what we build. # See https://github.com/NixOS/nixpkgs/issues/62513 diff --git a/morph/lib/monitoring.nix b/morph/lib/monitoring.nix index 64f26c92c4e0e8f7cc961b98972ec6c3ce8f2c89..3cf1680e1e1d0d1399b3e5741cc8b3850f68c2e5 100644 --- a/morph/lib/monitoring.nix +++ b/morph/lib/monitoring.nix @@ -111,19 +111,19 @@ in { permissions = "0400"; action = ["sudo" "systemctl" "restart" "wireguard-monitoringvpn.service"]; }; - } - (lib.mkIf (cfg.googleOAuthClientID != "") { - "grafana-google-sso-secret" = { - source = "${privateKeyPath}/grafana-google-sso.secret"; - destination = "/run/keys/grafana-google-sso.secret"; + "grafana-admin-password" = { + source = "${privateKeyPath}/grafana-admin.password"; + destination = "/run/keys/grafana-admin.password"; owner.user = config.systemd.services.grafana.serviceConfig.User; owner.group = config.users.users.grafana.group; permissions = "0400"; action = ["sudo" "systemctl" "restart" "grafana.service"]; }; - "grafana-admin-password" = { - source = "${privateKeyPath}/grafana-admin.password"; - destination = "/run/keys/grafana-admin.password"; + } + (lib.mkIf (cfg.googleOAuthClientID != "") { + "grafana-google-sso-secret" = { + source = "${privateKeyPath}/grafana-google-sso.secret"; + destination = "/run/keys/grafana-google-sso.secret"; owner.user = config.systemd.services.grafana.serviceConfig.User; owner.group = config.users.users.grafana.group; permissions = "0400"; diff --git a/nixos/lib/default.nix b/nixos/lib/default.nix new file mode 100644 index 0000000000000000000000000000000000000000..3ebaf60b7d536589d46a745d2945cbef96b2b554 --- /dev/null +++ b/nixos/lib/default.nix @@ -0,0 +1,6 @@ +{ callPackage }: +{ + /* A library of tools useful for writing tests with Nix. + */ + testing = callPackage ./testing.nix { }; +} diff --git a/nixos/lib/testing.nix b/nixos/lib/testing.nix new file mode 100644 index 0000000000000000000000000000000000000000..d89717a0a76f93bb6062ad63c6cfdbb91c12c746 --- /dev/null +++ b/nixos/lib/testing.nix @@ -0,0 +1,23 @@ +{ ...}: +{ + /* Returns a string that runs tests from the Python code at the given path. + + The Python code is loaded using *execfile* and the *test* global it + defines is called with the given keyword arguments. + + Type: makeTestScript :: Path -> AttrSet -> String + + Example: + testScript = (makeTestScript ./test_foo.py { x = "y"; }); + */ + makeTestScript = { testpath, kwargs ? {} }: + '' + # The driver runs pyflakes on this script before letting it + # run... Convince pyflakes that there is a `test` name. + test = None + with open("${testpath}") as testfile: + exec(testfile.read(), globals()) + # For simple types, JSON is compatible with Python syntax! + test(**${builtins.toJSON kwargs}) + ''; +} diff --git a/nixos/modules/monitoring/exporters/megacli2prom.nix b/nixos/modules/monitoring/exporters/megacli2prom.nix index a38f1ccc18b59073ff835e50babeb565f79a20b8..c1f10aef7821343e2f0a572d006782858dcf7ef4 100644 --- a/nixos/modules/monitoring/exporters/megacli2prom.nix +++ b/nixos/modules/monitoring/exporters/megacli2prom.nix @@ -35,11 +35,10 @@ in { config = lib.mkIf cfg.enable { - environment.systemPackages = [ ourpkgs.megacli2prom ]; + environment.systemPackages = [ ourpkgs.megacli2prom pkgs.megacli ]; systemd.services.megacli2prom = { enable = true; description = "MegaCli2Prom metrics gathering service"; - wantedBy = [ "multi-user.target" ]; startAt = cfg.interval; path = [ pkgs.megacli ]; # Save to a temp file and then move atomically so the diff --git a/nixos/modules/tahoe.nix b/nixos/modules/tahoe.nix index e0b6eb4d8be3c5359de1d391c42b2ba83f7a1ba4..44c381e6b6dfa6039d1dd6a49d44f1afaf51ab10 100644 --- a/nixos/modules/tahoe.nix +++ b/nixos/modules/tahoe.nix @@ -156,6 +156,10 @@ in nameValuePair "tahoe.introducer-${node}" { description = "Tahoe node user for introducer ${node}"; isSystemUser = true; + group = "tahoe.introducer-${node}"; + }); + users.groups = flip mapAttrs' cfg.introducers (node: _: + nameValuePair "tahoe.introducer-${node}" { }); }) (mkIf (cfg.nodes != {}) { @@ -287,6 +291,10 @@ in nameValuePair "tahoe.${node}" { description = "Tahoe node user for node ${node}"; isSystemUser = true; + group = "tahoe.${node}"; + }); + users.groups = flip mapAttrs' cfg.introducers (node: _: + nameValuePair "tahoe.${node}" { }); }) ]; diff --git a/nixos/modules/update-deployment b/nixos/modules/update-deployment index 1c8960588f418e57eeaadb7ad29db4285369cbdd..a8efffa062ad8f8dc6b6dc22827e4f0087b4d618 100755 --- a/nixos/modules/update-deployment +++ b/nixos/modules/update-deployment @@ -72,14 +72,14 @@ EOF ssh -o StrictHostKeyChecking=no "$(hostname).$(domainname)" ":" # Set nixpkgs to our preferred version for the morph build. Annoyingly, we -# can't just use nixpkgs-2105.nix as our nixpkgs because some code (in morph, +# can't just use nixpkgs.nix as our nixpkgs because some code (in morph, # at least) wants <nixpkgs> to be a fully-resolved path to a nixpkgs tree. # For example, morph evaluated `import <nixpkgs/lib>` which would turn into -# something like `import nixpkgs-2105.nix/lib` which is nonsense. +# something like `import nixpkgs.nix/lib` which is nonsense. # # So instead, import our nixpkgs which forces it to be instantiated in the # store, then ask for its path, then set NIX_PATH to that. -export NIX_PATH="nixpkgs=$(nix eval "(import ${CHECKOUT}/nixpkgs-2105.nix { }).path")" +export NIX_PATH="nixpkgs=$(nix eval "(import ${CHECKOUT}/nixpkgs.nix { }).path")" # Attempt to update just this host. Choose the morph grid definition matching # the grid we belong to and limit the morph deployment update to the host diff --git a/nixos/pkgs/default.nix b/nixos/pkgs/default.nix index bfc30b36101c220434606832127a7e8ca0a70490..435095f7890b7ac41afaebe050a756c4b4887641 100644 --- a/nixos/pkgs/default.nix +++ b/nixos/pkgs/default.nix @@ -5,6 +5,8 @@ # pkgs.callPackage ./nixos/pkgs {buildPlatform, hostPlatform, callPackage}: { + lib = callPackage ../lib {}; + leasereport = callPackage ./leasereport {}; # `privatestorage` is a derivation with a good Tahoe+ZKAP environment # that is exposed by ZKAPAuthorizer. diff --git a/nixos/pkgs/megacli2prom/repo.json b/nixos/pkgs/megacli2prom/repo.json index 3c8cd0af95adf95e22def4e727b8c2c5d12044aa..daa4c7b8a0a7dba8ca382c9e1913e2c7ff2d36cd 100644 --- a/nixos/pkgs/megacli2prom/repo.json +++ b/nixos/pkgs/megacli2prom/repo.json @@ -2,7 +2,7 @@ "owner": "PrivateStorageio", "repo": "megacli2prom", "branch": "main", - "rev": "9536933d325c843b2662f80486660bf81d73941e", + "rev": "e76300ca0a723bf0ed105d805f166976162d58d3", "outputHashAlgo": "sha512", - "outputHash": "1xrsv0bkmazbhqarx84lhvmrzzdv1bm04xvr0hw1yrw1f4xb450f4pwgapnkjczy0l4c6rp3pmh64cblgbs3ki30wacbv1bqzv5745g" + "outputHash": "256di1f4bw5a0kqm37wr5dk9yg0cxhgqaflrhk0p3azimml3pd1gr4rh54mj4vsrw17iyziajmilx98fsvc9w70y14rh7kgxcam9vwp" } \ No newline at end of file diff --git a/nixos/pkgs/privatestorage/default.nix b/nixos/pkgs/privatestorage/default.nix index bd487af32941f6db920ea2d43ec89e9eded38201..3bbbd3dbcf0b974e6e1997e20773cddbd9ea59c0 100644 --- a/nixos/pkgs/privatestorage/default.nix +++ b/nixos/pkgs/privatestorage/default.nix @@ -2,7 +2,7 @@ let repo-data = lib.importJSON ./repo.json; repo = fetchFromGitHub (builtins.removeAttrs repo-data [ "branch" ]); - privatestorage = callPackage repo {}; + privatestorage = callPackage repo { python = "python39"; }; in privatestorage.privatestorage diff --git a/nixos/pkgs/privatestorage/repo.json b/nixos/pkgs/privatestorage/repo.json index 81f6e18ba4bbec657a5a5ba543ef05408bf472ad..20fb749ffdbdcdb86e2af21c63ac1c756c6157a1 100644 --- a/nixos/pkgs/privatestorage/repo.json +++ b/nixos/pkgs/privatestorage/repo.json @@ -2,7 +2,7 @@ "owner": "PrivateStorageio", "branch": "main", "repo": "ZKAPAuthorizer", - "rev": "b61f3d4a3f5eb72cb600dd83796a1aaca2931e07", + "rev": "a263d171aa20d6b34926a6af51b849cd127b7190", "outputHashAlgo": "sha512", - "outputHash": "2d7a9m34jx1k38fmiwskgwd1ryyhrb56m9nam12fd66shl8qzmlfcr1lwf063qi1wqdzb2g7998vxbv3c2bmvw7g6iqwzjmsck2czpn" -} \ No newline at end of file + "outputHash": "0sxxhmrfbag5ksis0abvwnlqzfiqlwdyfnhav2h7hpn62r81l7k2gk4jhs3blw8r6bv6di978lx6rv56r1vmjnpmlyx2l33afzf9bf3" +} diff --git a/nixos/pkgs/zkap-spending-service/repo.json b/nixos/pkgs/zkap-spending-service/repo.json index 69f7a30053de661f2c7829384e9496e49077cfd9..eafc2e2e96592926a40535ee370db9bda9f10cc4 100644 --- a/nixos/pkgs/zkap-spending-service/repo.json +++ b/nixos/pkgs/zkap-spending-service/repo.json @@ -1,9 +1,9 @@ -{ - "owner": "privatestorage", - "repo": "zkap-spending-service", - "rev": "cbf7509f429ffd6e6cf37a73e4ff84a9c5ce1141", - "branch": "main", - "domain": "whetstone.privatestorage.io", - "outputHash": "04g7pcykc2525cg3z7wg5834s7vqn82xaqjvf52l6dnxv3mb9xr93kk505dvxcwhgfbqpim5i479s9kqd8gi7q3lq5wn5fq7rf7lkrj", - "outputHashAlgo": "sha512" -} +{ + "owner": "privatestorage", + "repo": "zkap-spending-service", + "rev": "66fd395268b466d4c7bb0a740fb758a5acccd1c4", + "branch": "main", + "domain": "whetstone.privatestorage.io", + "outputHash": "1nryvsccncrka25kzrwqkif4x68ib0cs2vbw1ngfmzw86gjgqx01a7acgspmrpfs62p4q8zw0f2ynl8jr3ygyypjrl8v7w8g49y0y0y", + "outputHashAlgo": "sha512" +} diff --git a/nixos/tests/private-storage.nix b/nixos/tests/private-storage.nix index a208ce249f1f1227f966e38a1c62ab6166d187f8..eaff1ed5320607e6aabc94226804aea4b7186b0a 100644 --- a/nixos/tests/private-storage.nix +++ b/nixos/tests/private-storage.nix @@ -1,25 +1,14 @@ { pkgs }: let + ourpkgs = pkgs.callPackage ../pkgs { }; + sshPrivateKey = ./probeuser_ed25519; sshPublicKey = ./probeuser_ed25519.pub; + sshUsers = { root = (builtins.readFile sshPublicKey); probeuser = (builtins.readFile sshPublicKey); }; - # Generate a command which can be used with runOnNode to ssh to the given - # host. - ssh = username: hostname: [ - "cp" sshPrivateKey "/tmp/ssh_key" ";" - "chmod" "0400" "/tmp/ssh_key" ";" - "ssh" "-oStrictHostKeyChecking=no" "-i" "/tmp/ssh_key" "${username}@${hostname}" ":" - ]; - - # Separate helper programs so we can write as little python inside a string - # inside a nix expression as possible. - run-introducer = ./run-introducer.py; - run-client = ./run-client.py; - get-passes = ./get-passes.py; - exercise-storage = ./exercise-storage.py; # This is a test double of the Stripe API server. It is extremely simple. # It barely knows how to respond to exactly the API endpoints we use, @@ -72,18 +61,6 @@ let networking.firewall.enable = false; networking.dhcpcd.enable = false; }; - - # Return a python program fragment to run a shell command on one of the nodes. - # The first argument is the name of the node. The second is a list of the - # argv to run. - # - # The program's output is piped to systemd-cat and the python fragment - # evaluates to success if the command exits with a success status. - runOnNode = node: argv: - let - command = builtins.concatStringsSep " " argv; - in - "${node}.succeed('set -eo pipefail; ${command} | systemd-cat')"; in { # https://nixos.org/nixos/manual/index.html#sec-nixos-tests # https://nixos.mayflower.consulting/blog/2019/07/11/leveraging-nixos-tests-in-your-project/ @@ -177,134 +154,16 @@ in { }; # Test the machines with a Python program. - testScript = '' - # Boot the VMs. We used to do them all in parallel but the boot - # sequence got flaky at some point for some reason I don't - # understand. :/ It might be related to this: - # - # https://discourse.nixos.org/t/nixos-ppc64le-vm-does-not-have-dev-vda-device/11548/9 - # - # See <nixpkgs/nixos/modules/virtualisation/qemu-vm.nix> for the Nix - # that constructs the QEMU command that gets run. - # - # Boot them one at a time for now. - issuer.connect() - introducer.connect() - storage.connect() - client.connect() - api_stripe_com.connect() - - # The issuer and the storage server should accept SSH connections. This - # doesn't prove it is so but if it fails it's a pretty good indication - # it isn't so. - storage.wait_for_open_port(22) - ${runOnNode "issuer" (ssh "probeuser" "storage")} - ${runOnNode "issuer" (ssh "root" "storage")} - issuer.wait_for_open_port(22) - ${runOnNode "storage" (ssh "probeuser" "issuer")} - ${runOnNode "storage" (ssh "root" "issuer")} - - # Set up a Tahoe-LAFS introducer. - introducer.copy_from_host('${pemFile}', '/tmp/node.pem') - - try: - ${runOnNode "introducer" [ run-introducer "/tmp/node.pem" (toString introducerPort) introducerFURL ]} - except: - code, output = introducer.execute('cat /tmp/stdout /tmp/stderr') - introducer.log(output) - raise - - # - # Get a Tahoe-LAFS storage server up. - # - code, version = storage.execute('tahoe --version') - storage.log(version) - - # The systemd unit should reach the running state. - storage.wait_for_unit('tahoe.storage.service') - - # Some while after that the Tahoe-LAFS node should listen on the web API - # port. The port number here has to agree with the port number set in - # the private-storage.nix module. - storage.wait_for_open_port(3456) - - # Once the web API is listening it should be possible to scrape some - # status from the node if it is really working. - storage.succeed('tahoe -d /var/db/tahoe-lafs/storage status') - - # It should have Eliot logging turned on as well. - storage.succeed('[ -e /var/db/tahoe-lafs/storage/logs/eliot.json ]') - - # - # Storage appears to be working so try to get a client to speak with it. - # - ${runOnNode "client" [ run-client "/tmp/client" introducerFURL issuerURL ristrettoPublicKey ]} - client.wait_for_open_port(3456) - - # Make sure the fake Stripe API server is ready for requests. - try: - api_stripe_com.wait_for_unit("api.stripe.com") - except: - code, output = api_stripe_com.execute('journalctl -u api.stripe.com') - api_stripe_com.log(output) - raise - - # Get some ZKAPs from the issuer. - try: - ${runOnNode "client" [ - get-passes - "http://127.0.0.1:3456" - "/tmp/client/private/api_auth_token" - issuerURL - voucher - ]} - except: - code, output = client.execute('cat /tmp/stdout /tmp/stderr'); - client.log(output) - - # Dump the fake Stripe API server logs, too, since the error may arise - # from a PaymentServer/Stripe interaction. - code, output = api_stripe_com.execute('journalctl -u api.stripe.com') - api_stripe_com.log(output) - raise - - # The client should be prepped now. Make it try to use some storage. - try: - ${runOnNode "client" [ exercise-storage "/tmp/client" ]} - except: - code, output = client.execute('cat /tmp/stdout /tmp/stderr') - client.log(output) - raise - - # It should be possible to restart the storage service without the - # storage node fURL changing. - try: - furlfile = '/var/db/tahoe-lafs/storage/private/storage-plugin.privatestorageio-zkapauthz-v1.furl' - before = storage.execute('cat ' + furlfile) - ${runOnNode "storage" [ "systemctl" "restart" "tahoe.storage" ]} - after = storage.execute('cat ' + furlfile) - if (before != after): - raise Exception('fURL changes after storage node restart') - except: - code, output = storage.execute('cat /tmp/stdout /tmp/stderr') - storage.log(output) - raise - - # The client should actually still work, too. - try: - ${runOnNode "client" [ exercise-storage "/tmp/client" ]} - except: - code, output = client.execute('cat /tmp/stdout /tmp/stderr') - client.log(output) - raise - - # The issuer metrics should be accessible from the monitoring network. - issuer.execute('ifconfig lo:fauxvpn 172.23.23.2/24') - issuer.wait_until_succeeds("nc -z 172.23.23.2 80") - issuer.succeed('curl --silent --insecure --fail --output /dev/null http://172.23.23.2/metrics') - # The issuer metrics should NOT be accessible from any other network. - issuer.fail('curl --silent --insecure --fail --output /dev/null http://localhost/metrics') - client.fail('curl --silent --insecure --fail --output /dev/null http://issuer/metrics') - issuer.execute('ifconfig lo:fauxvpn down') - ''; + testScript = ourpkgs.lib.testing.makeTestScript { + testpath = ./test_privatestorage.py; + kwargs = { + inherit sshPrivateKey pemFile introducerPort introducerFURL issuerURL ristrettoPublicKey voucher; + + # Supply some helper programs to help the tests stay a bit higher level. + run_introducer = ./run-introducer.py; + run_client = ./run-client.py; + get_passes = ./get-passes.py; + exercise_storage = ./exercise-storage.py; + }; + }; } diff --git a/nixos/tests/tahoe.nix b/nixos/tests/tahoe.nix index e39fd6d3fcb776e8e5215bb1264e08e2b7306c1f..a007e65efd2d6bee8ab4adba9df3cb2901f53526 100644 --- a/nixos/tests/tahoe.nix +++ b/nixos/tests/tahoe.nix @@ -1,5 +1,8 @@ -{ ... }: - { +{ pkgs }: +let + ourpkgs = pkgs.callPackage ../pkgs { }; +in +{ nodes = { storage = { config, pkgs, ourpkgs, ... }: { imports = [ @@ -23,50 +26,7 @@ }; }; }; - testScript = '' - start_all() - - # After the service starts, destroy the "created" marker to force it to - # re-create its internal state. - storage.wait_for_open_port(4001) - storage.succeed("systemctl stop tahoe.storage") - storage.succeed("rm /var/db/tahoe-lafs/storage.created") - storage.succeed("systemctl start tahoe.storage") - - # After it starts up again, verify it has consistent internal state and a - # backup of the prior state. - storage.wait_for_open_port(4001) - storage.succeed("[ -e /var/db/tahoe-lafs/storage ]") - storage.succeed("[ -e /var/db/tahoe-lafs/storage.created ]") - storage.succeed("[ -e /var/db/tahoe-lafs/storage.1 ]") - storage.succeed("[ -e /var/db/tahoe-lafs/storage.1/private/node.privkey ]") - storage.succeed("[ -e /var/db/tahoe-lafs/storage.1/private/node.pem ]") - storage.succeed("[ ! -e /var/db/tahoe-lafs/storage.2 ]") - - # Stop it again, once again destroy the "created" marker, and this time also - # jam some partial state in the way that will need cleanup. - storage.succeed("systemctl stop tahoe.storage") - storage.succeed("rm /var/db/tahoe-lafs/storage.created") - storage.succeed("mkdir -p /var/db/tahoe-lafs/storage.atomic/partial") - try: - storage.succeed("systemctl start tahoe.storage") - except: - x, y = storage.execute("journalctl -u tahoe.storage") - storage.log(y) - raise - - # After it starts up again, verify it has consistent internal state and - # backups of the prior two states. It also has no copy of the inconsistent - # state because it could never have been used. - storage.wait_for_open_port(4001) - storage.succeed("[ -e /var/db/tahoe-lafs/storage ]") - storage.succeed("[ -e /var/db/tahoe-lafs/storage.created ]") - storage.succeed("[ -e /var/db/tahoe-lafs/storage.1 ]") - storage.succeed("[ -e /var/db/tahoe-lafs/storage.2 ]") - storage.succeed("[ -e /var/db/tahoe-lafs/storage.2/private/node.privkey ]") - storage.succeed("[ -e /var/db/tahoe-lafs/storage.2/private/node.pem ]") - storage.succeed("[ ! -e /var/db/tahoe-lafs/storage.atomic ]") - storage.succeed("[ ! -e /var/db/tahoe-lafs/storage/partial ]") - storage.succeed("[ ! -e /var/db/tahoe-lafs/storage.3 ]") - ''; + testScript = ourpkgs.lib.testing.makeTestScript { + testpath = ./test_tahoe.py; + }; } diff --git a/nixos/tests/test_privatestorage.py b/nixos/tests/test_privatestorage.py new file mode 100644 index 0000000000000000000000000000000000000000..dc060d51f1815f549485d3415b3b3af97d5c79af --- /dev/null +++ b/nixos/tests/test_privatestorage.py @@ -0,0 +1,148 @@ +def runOnNode(node, argv): + """ + Run a shell command on one of the nodes. The first argument is the name + of the node. The second is a list of the argv to run. + + The program's output is piped to systemd-cat and the python fragment + evaluates to success if the command exits with a success status. + """ + try: + node.succeed('set -eo pipefail; {} | systemd-cat'.format(" ".join(argv))) + except Exception as e: + code, output = node.execute('cat /tmp/stdout /tmp/stderr') + introducer.log(output) + raise + +def ssh(username, sshPrivateKey, hostname): + """ + Generate a command which can be used with runOnNode to ssh to the given + host. + """ + return [ + "cp", sshPrivateKey, "/tmp/ssh_key", ";", + "chmod", "0400", "/tmp/ssh_key", ";", + "ssh", "-oStrictHostKeyChecking=no", "-i", "/tmp/ssh_key", + "{username}@{hostname}".format(username=username, hostname=hostname), ":", + ] + +def test( + sshPrivateKey, + pemFile, + run_introducer, + run_client, + get_passes, + exercise_storage, + introducerPort, + introducerFURL, + issuerURL, + ristrettoPublicKey, + voucher, +): + """ + """ + # Boot the VMs. We used to do them all in parallel but the boot + # sequence got flaky at some point for some reason I don't + # understand. :/ It might be related to this: + # + # https://discourse.nixos.org/t/nixos-ppc64le-vm-does-not-have-dev-vda-device/11548/9 + # + # See <nixpkgs/nixos/modules/virtualisation/qemu-vm.nix> for the Nix + # that constructs the QEMU command that gets run. + # + # Boot them one at a time for now. + issuer.connect() + introducer.connect() + storage.connect() + client.connect() + api_stripe_com.connect() + + # The issuer and the storage server should accept SSH connections. This + # doesn't prove it is so but if it fails it's a pretty good indication + # it isn't so. + storage.wait_for_open_port(22) + runOnNode(issuer, ssh("probeuser", sshPrivateKey, "storage")) + runOnNode(issuer, ssh("root", sshPrivateKey, "storage")) + issuer.wait_for_open_port(22) + runOnNode(storage, ssh("probeuser", sshPrivateKey, "issuer")) + runOnNode(storage, ssh("root", sshPrivateKey, "issuer")) + + # Set up a Tahoe-LAFS introducer. + introducer.copy_from_host(pemFile, '/tmp/node.pem') + + runOnNode(introducer, [run_introducer, "/tmp/node.pem", str(introducerPort), introducerFURL]) + + # + # Get a Tahoe-LAFS storage server up. + # + code, version = storage.execute('tahoe --version') + storage.log(version) + + # The systemd unit should reach the running state. + storage.wait_for_unit('tahoe.storage.service') + + # Some while after that the Tahoe-LAFS node should listen on the web API + # port. The port number here has to agree with the port number set in + # the private-storage.nix module. + storage.wait_for_open_port(3456) + + # Once the web API is listening it should be possible to scrape some + # status from the node if it is really working. + storage.succeed('tahoe -d /var/db/tahoe-lafs/storage status') + + # It should have Eliot logging turned on as well. + storage.succeed('[ -e /var/db/tahoe-lafs/storage/logs/eliot.json ]') + + # + # Storage appears to be working so try to get a client to speak with it. + # + runOnNode(client, [run_client, "/tmp/client", introducerFURL, issuerURL, ristrettoPublicKey]) + client.wait_for_open_port(3456) + + # Make sure the fake Stripe API server is ready for requests. + try: + api_stripe_com.wait_for_unit("api.stripe.com") + except: + code, output = api_stripe_com.execute('journalctl -u api.stripe.com') + api_stripe_com.log(output) + raise + + # Get some ZKAPs from the issuer. + try: + runOnNode(client, [ + get_passes, + "http://127.0.0.1:3456", + "/tmp/client/private/api_auth_token", + issuerURL, + voucher, + ]) + except: + # Dump the fake Stripe API server logs, too, since the error may arise + # from a PaymentServer/Stripe interaction. + code, output = api_stripe_com.execute('journalctl -u api.stripe.com') + api_stripe_com.log(output) + + raise + + # The client should be prepped now. Make it try to use some storage. + runOnNode(client, [exercise_storage, "/tmp/client"]) + + # It should be possible to restart the storage service without the + # storage node fURL changing. + furlfile = '/var/db/tahoe-lafs/storage/private/storage-plugin.privatestorageio-zkapauthz-v1.furl' + before = storage.execute('cat ' + furlfile) + runOnNode(storage, ["systemctl", "restart", "tahoe.storage"]) + after = storage.execute('cat ' + furlfile) + if (before != after): + raise Exception('fURL changes after storage node restart') + + # The client should actually still work, too. + runOnNode(client, [exercise_storage, "/tmp/client"]) + + # The issuer metrics should be accessible from the monitoring network. + issuer.execute('ifconfig lo:fauxvpn 172.23.23.2/24') + issuer.wait_until_succeeds("nc -z 172.23.23.2 80") + issuer.succeed('curl --silent --insecure --fail --output /dev/null http://172.23.23.2/metrics') + # The issuer metrics should NOT be accessible from any other network. + issuer.fail('curl --silent --insecure --fail --output /dev/null http://localhost/metrics') + client.fail('curl --silent --insecure --fail --output /dev/null http://issuer/metrics') + issuer.execute('ifconfig lo:fauxvpn down') diff --git a/nixos/tests/test_tahoe.py b/nixos/tests/test_tahoe.py new file mode 100644 index 0000000000000000000000000000000000000000..c5190c78b04cb2cc59b5275e45869dd53e0e81c3 --- /dev/null +++ b/nixos/tests/test_tahoe.py @@ -0,0 +1,45 @@ +def test(): + start_all() + + # After the service starts, destroy the "created" marker to force it to + # re-create its internal state. + storage.wait_for_open_port(4001) + storage.succeed("systemctl stop tahoe.storage") + storage.succeed("rm /var/db/tahoe-lafs/storage.created") + storage.succeed("systemctl start tahoe.storage") + + # After it starts up again, verify it has consistent internal state and a + # backup of the prior state. + storage.wait_for_open_port(4001) + storage.succeed("[ -e /var/db/tahoe-lafs/storage ]") + storage.succeed("[ -e /var/db/tahoe-lafs/storage.created ]") + storage.succeed("[ -e /var/db/tahoe-lafs/storage.1 ]") + storage.succeed("[ -e /var/db/tahoe-lafs/storage.1/private/node.privkey ]") + storage.succeed("[ -e /var/db/tahoe-lafs/storage.1/private/node.pem ]") + storage.succeed("[ ! -e /var/db/tahoe-lafs/storage.2 ]") + + # Stop it again, once again destroy the "created" marker, and this time also + # jam some partial state in the way that will need cleanup. + storage.succeed("systemctl stop tahoe.storage") + storage.succeed("rm /var/db/tahoe-lafs/storage.created") + storage.succeed("mkdir -p /var/db/tahoe-lafs/storage.atomic/partial") + try: + storage.succeed("systemctl start tahoe.storage") + except: + x, y = storage.execute("journalctl -u tahoe.storage") + storage.log(y) + raise + + # After it starts up again, verify it has consistent internal state and + # backups of the prior two states. It also has no copy of the inconsistent + # state because it could never have been used. + storage.wait_for_open_port(4001) + storage.succeed("[ -e /var/db/tahoe-lafs/storage ]") + storage.succeed("[ -e /var/db/tahoe-lafs/storage.created ]") + storage.succeed("[ -e /var/db/tahoe-lafs/storage.1 ]") + storage.succeed("[ -e /var/db/tahoe-lafs/storage.2 ]") + storage.succeed("[ -e /var/db/tahoe-lafs/storage.2/private/node.privkey ]") + storage.succeed("[ -e /var/db/tahoe-lafs/storage.2/private/node.pem ]") + storage.succeed("[ ! -e /var/db/tahoe-lafs/storage.atomic ]") + storage.succeed("[ ! -e /var/db/tahoe-lafs/storage/partial ]") + storage.succeed("[ ! -e /var/db/tahoe-lafs/storage.3 ]") diff --git a/nixpkgs-2105.json b/nixpkgs-2105.json deleted file mode 100644 index 523c1468f35019c6685f3a8486603d0936732dbe..0000000000000000000000000000000000000000 --- a/nixpkgs-2105.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "release2105", - "url": "https://releases.nixos.org/nixos/21.05/nixos-21.05.4547.2949ed36539/nixexprs.tar.xz", - "sha256": "0nm5znl7lh3qws29ppzpzsqscyw3hk7q0128xqmga2g86qcmy38x" -} \ No newline at end of file diff --git a/nixpkgs-2105.nix b/nixpkgs-2105.nix index 536d913b89ba6a57d8d683381ea1c8f40e026b4f..fbd7ca592bfd4e9d1b941f437758f8da1b8bcb5a 100644 --- a/nixpkgs-2105.nix +++ b/nixpkgs-2105.nix @@ -1 +1,6 @@ -import (builtins.fetchTarball (builtins.fromJSON (builtins.readFile ./nixpkgs-2105.json))) +# This actually imports nixos-21.11 but we need to keep this file around so that +# upgrades work, as the on-node deployment script expects this file in the checkout. +# See https://whetstone.privatestorage.io/privatestorage/PrivateStorageio/-/merge_requests/222#note_18600 +# This file can be removed once all nodes have been updated to point to the new file. + +import ./nixpkgs.nix diff --git a/nixpkgs.json b/nixpkgs.json new file mode 100644 index 0000000000000000000000000000000000000000..3066c2260d204d86ef3b50ef3deac3619cd14145 --- /dev/null +++ b/nixpkgs.json @@ -0,0 +1,5 @@ +{ + "name": "source", + "url": "https://releases.nixos.org/nixos/21.11/nixos-21.11.335130.386234e2a61/nixexprs.tar.xz", + "sha256": "05lw8w4mbpzxsam09s22q7fm21ayhzh9w8g74vhhhmigr18ggxc7" +} diff --git a/nixpkgs.nix b/nixpkgs.nix new file mode 100644 index 0000000000000000000000000000000000000000..a49c447874e45bea0804185636468568f5bd5035 --- /dev/null +++ b/nixpkgs.nix @@ -0,0 +1 @@ +import (builtins.fetchTarball (builtins.fromJSON (builtins.readFile ./nixpkgs.json))) diff --git a/shell.nix b/shell.nix index a5741377eec5ebd4b8862a0ea47e15edfdac2731..b8be3a3a6088a987468329ad29919a6957313c6a 100644 --- a/shell.nix +++ b/shell.nix @@ -1,7 +1,7 @@ let - release2105 = import ./nixpkgs-2105.nix { }; + pinned-pkgs = import ./nixpkgs.nix { }; in -{ pkgs ? release2105, lib ? pkgs.lib, python ? pkgs.python3 }: +{ pkgs ? pinned-pkgs, lib ? pkgs.lib, python ? pkgs.python3 }: let tools = pkgs.callPackage ./tools {}; in @@ -10,7 +10,7 @@ pkgs.mkShell { # first adds that path to the store, and then interpolates the store path # into the string. We use `builtins.toString` to convert the path to a # string without copying it to the store before interpolating. Either the - # path is already in the store (e.g. when `pkgs` is `release2105`) so we + # path is already in the store (e.g. when `pkgs` is `pinned-pkgs`) so we # avoid making a second copy with a longer name, or the user passed in local # path (e.g. a checkout of nixpkgs) and we point at it directly, rather than # a snapshot of it. diff --git a/tools/update-nixpkgs b/tools/update-nixpkgs index 09c823b0a419b5937d4953337b94a26c4b502e32..3c6832c95cee09632318f7a4ef4efc7099e317e1 100755 --- a/tools/update-nixpkgs +++ b/tools/update-nixpkgs @@ -10,7 +10,7 @@ from ps_tools import get_url_hash # We pass this to builtins.fetchTarball which only supports sha256 HASH_TYPE = "sha256" -DEFAULT_CHANNEL = "nixos-21.05" +DEFAULT_CHANNEL = "nixos-21.11" CHANNEL_URL_TEMPLATE = "https://channels.nixos.org/{channel}/nixexprs.tar.xz" @@ -37,7 +37,7 @@ def main(): "repo_file", metavar="repo-file", nargs="?", - default=Path(__file__).parent.with_name("nixpkgs-2105.json"), + default=Path(__file__).parent.with_name("nixpkgs.json"), type=Path, help="JSON file with pinned configuration.", )