Skip to content
Snippets Groups Projects
test_privatestorage.py 9.52 KiB
Newer Older
  • Learn to ignore specific revisions
  • import hmac
    from shlex import quote
    from time import time
    
    def runOnNode(node, argvs):
    
        """
        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.
        """
    
        for argv in argvs:
            try:
                node.succeed('set -eo pipefail; {} | systemd-cat'.format(" ".join(map(quote, argv))))
            except Exception as e:
                code, output = node.execute('cat /tmp/stdout /tmp/stderr')
                node.log(output)
                raise
    
    Florian Sesser's avatar
    Florian Sesser committed
    def ssh(username, sshPrivateKeyFile, hostname):
    
        """
        Generate a command which can be used with runOnNode to ssh to the given
        host.
        """
        return [
    
            ["cp", sshPrivateKeyFile, "/tmp/ssh_key"],
            ["chmod", "0400", "/tmp/ssh_key"],
            ["ssh", "-oStrictHostKeyChecking=no", "-i", "/tmp/ssh_key",
             "{username}@{hostname}".format(username=username, hostname=hostname), ":"],
        ]
    
    def checkout_session_completed(voucher: str) -> str:
        """
        Return a request body string which represents the payment completed event
        for the given voucher.
        """
        return """\
    {
      "id": "evt_1LxcsdBHXBAMm9bPSq6UWAZe",
      "object": "event",
      "api_version": "2019-11-05",
      "created": 1666903247,
      "data": {
        "object": {
          "id": "cs_test_a1kWLWGoXZPa6ywyVnuib8DPA3BqXCWZX5UEjLfKh7gLjdZy2LD3F5mEp3",
          "object": "checkout.session",
          "after_expiration": null,
          "allow_promotion_codes": null,
          "amount_subtotal": 3000,
          "amount_total": 3000,
          "automatic_tax": {
            "enabled": false,
            "status": null
          },
          "billing_address_collection": null,
          "cancel_url": "https://httpbin.org/post",
          "client_reference_id": "%(voucher)s",
          "consent": null,
          "consent_collection": null,
          "created": 1666903243,
          "currency": "usd",
          "customer": "cus_Mh0u62xtelUehD",
          "customer_creation": "always",
          "customer_details": {
            "address": {
              "city": null,
              "country": null,
              "line1": null,
              "line2": null,
              "postal_code": null,
              "state": null
            },
            "email": "stripe@example.com",
            "name": null,
            "phone": null,
            "tax_exempt": "none",
            "tax_ids": [
    
            ]
          },
          "customer_email": null,
          "display_items": [
            {
              "amount": 1500,
              "currency": "usd",
              "custom": {
                "description": "comfortable cotton t-shirt",
                "images": null,
                "name": "t-shirt"
              },
              "quantity": 2,
              "type": "custom"
            }
          ],
          "expires_at": 1666989643,
          "livemode": false,
          "locale": null,
          "metadata": {
          },
          "mode": "payment",
          "payment_intent": "pi_3LxcsZBHXBAMm9bP1daBGoPV",
          "payment_link": null,
          "payment_method_collection": "always",
          "payment_method_options": {
          },
          "payment_method_types": [
            "card"
          ],
          "payment_status": "paid",
          "phone_number_collection": {
            "enabled": false
          },
          "recovered_from": null,
          "setup_intent": null,
          "shipping": null,
          "shipping_address_collection": null,
          "shipping_options": [
    
          ],
          "shipping_rate": null,
          "status": "complete",
          "submit_type": null,
          "subscription": null,
          "success_url": "https://httpbin.org/post",
          "total_details": {
            "amount_discount": 0,
            "amount_shipping": 0,
            "amount_tax": 0
          },
          "url": null
        }
      },
      "livemode": false,
      "pending_webhooks": 2,
      "request": {
        "id": null,
        "idempotency_key": null
      },
      "type": "checkout.session.completed"
    }
    """ % dict(voucher=voucher)
    
    def stripe_signature(key: str, body: str) -> str:
        """
        Construct a valid value for the ``Stripe-Signature`` header item.
        """
        timestamp = int(time())
        v1 = hmac.new(key.encode("utf-8"), f"{timestamp}.{body}".encode("utf-8"), "sha256").hexdigest()
        return f"t={timestamp},v1={v1}"
    
    def pay_for_voucher(url: str, webhook_secret, voucher: str) -> list[str]:
        """
        Return a command to run to report to the issuer that payment for the given
        voucher has been received.
        """
        body = checkout_session_completed(voucher)
        return [
            "curl",
            "-X", "POST",
            "--header", "content-type: application/json; charset=utf-8",
            "--header", f"stripe-signature: {stripe_signature(webhook_secret, body)}",
            "--data-binary", body,
            url + "v1/stripe/webhook",
    
    Florian Sesser's avatar
    Florian Sesser committed
            sshPrivateKeyFile,
    
            pemFile,
            run_introducer,
            run_client,
            get_passes,
            exercise_storage,
            introducerPort,
            introducerFURL,
            issuerURL,
            ristrettoPublicKey,
    
    ):
        """
        """
        # 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)
    
    Florian Sesser's avatar
    Florian Sesser committed
        runOnNode(issuer, ssh("probeuser", sshPrivateKeyFile, "storage"))
        runOnNode(issuer, ssh("root", sshPrivateKeyFile, "storage"))
    
    Florian Sesser's avatar
    Florian Sesser committed
        runOnNode(storage, ssh("probeuser", sshPrivateKeyFile, "issuer"))
        runOnNode(storage, ssh("root", sshPrivateKeyFile, "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 ]')
    
    
        # Make sure the issuer is ready to accept connections.
        issuer.wait_for_open_port(80)
    
    
        # Pretend to be Stripe and report that our voucher has been paid for.
        runOnNode(issuer, [pay_for_voucher("http://localhost/", stripeWebhookSecretKey, voucher)])
    
    
        #
        # Storage appears to be working so try to get a client to speak with it.
        #
    
        runOnNode(client, [[run_client, "/tmp/client", introducerFURL, issuerURL, ristrettoPublicKey, str(tokenCount)]])
    
        client.wait_for_open_port(3456)
    
        # Make sure the fake Stripe API server is ready for requests.
        try:
    
            api_stripe_com.wait_for_open_port(80)
    
        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:
    
                get_passes,
                "http://127.0.0.1:3456",
                "/tmp/client/private/api_auth_token",
                voucher,
    
        except:
            # Dump the fake Stripe API server logs, too, since the error may arise
            # from a PaymentServer/Stripe interaction.
    
            for node, unit in [(api_stripe_com, "api.stripe.com"), (issuer, "zkapissuer")]:
                code, output = node.execute(f'journalctl -u {unit}')
                node.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-v2.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')