diff --git a/DEPLOYMENT-NOTES.rst b/DEPLOYMENT-NOTES.rst index 0a7ea52e0bfb20e77f86797ead1778d614a2a720..5f1b15e933e9c2e98c364bf5969350a494987f61 100644 --- a/DEPLOYMENT-NOTES.rst +++ b/DEPLOYMENT-NOTES.rst @@ -1,6 +1,8 @@ Deployment notes ================ +- 2021-10-12 The secret in ``private-keys/grafana-slack-url`` needs to be changed to remove the ``SLACKURL=`` prefix. + - 2021-09-30 `Enable alerting <https://whetstone.privatestorage.io/privatestorage/PrivateStorageio/-/merge_requests/185>`_ needs a secret in ``private-keys/grafana-slack-url`` looking like the template in ``morph/grid/local/private-keys/grafana-slack-url`` and pointing to the secret API endpoint URL saved in `this 1Password entry <https://privatestorage.1password.com/vaults/7flqasy5hhhmlbtp5qozd3j4ga/allitems/cgznskz2oix2tyx5xyntwaos5i>`_ (or create a new secret URL at https://www.slack.com/apps/A0F7XDUAZ). - 2021-09-07 `Manage access to payment metrics <https://whetstone.privatestorage.io/privatestorage/PrivateStorageio/-/merge_requests/146>`_ requires moving and chown'ing the PaymentServer database on the ``payments`` host:: diff --git a/ci-tools/vulnerability-scan b/ci-tools/vulnerability-scan index 48bf51e071a398f37565717a22b2066d3f905fbe..67e1a21263fa65843b34d185884ea6df2596220a 100755 --- a/ci-tools/vulnerability-scan +++ b/ci-tools/vulnerability-scan @@ -32,6 +32,12 @@ else fi ' +# The version (1.9.6) of vulnix in nixos-21.05 incorrectly collapses +# derivations with the same name+version, but different sets of patches +# applied. Therefore, we use a recent nixos-unstable version that has a newer +# version of vulnix included. +export NIX_PATH=nixpkgs=https://api.github.com/repos/NixOS/nixpkgs/tarball/ee084c02040e864eeeb4cf4f8538d92f7c675671 + # vulnix exits with an error status if there are vulnerabilities. We told # GitLab to allow this by setting `allow_failure` to true in the GitLab CI # config. vulnix exit status indicates what vulnix thinks happened. If we diff --git a/morph/grid/local/private-keys/README.rst b/morph/grid/local/private-keys/README.rst index 91670ac1a0ea6ee2c68df71ff196d010bdba8637..8ecd2dd261b02dd757862703944ad970688d3e7e 100644 --- a/morph/grid/local/private-keys/README.rst +++ b/morph/grid/local/private-keys/README.rst @@ -23,7 +23,7 @@ grafana-slack-url ----------------- This file is read by Grafana's systemd service to set an environment variable with a secret Slack WebHook URL to post alerts to. -The only line in the file should be ``SLACKURL=`` with the secret URL. +The only line in the file should be the secret URL. Use the url from `this 1Password entry <https://privatestorage.1password.com/vaults/7flqasy5hhhmlbtp5qozd3j4ga/allitems/cgznskz2oix2tyx5xyntwaos5i>`_ or get a new secret URL for your Slack channel at https://www.slack.com/apps/A0F7XDUAZ. stripe.secret diff --git a/morph/grid/local/private-keys/grafana-slack-url b/morph/grid/local/private-keys/grafana-slack-url index cb7dd1aec785a557fef6082a7570bc8c56728f14..0885b7bfe1786d19f845c45d749bafaf12756cb4 100644 --- a/morph/grid/local/private-keys/grafana-slack-url +++ b/morph/grid/local/private-keys/grafana-slack-url @@ -1,2 +1,2 @@ -SLACKURL=https://hooks.slack.com/services/x/y/z +https://hooks.slack.com/services/x/y/z diff --git a/nixos/modules/issuer.nix b/nixos/modules/issuer.nix index 5cec1c4a9dc07f297abab049790dbf970388c91b..da3eed73e59349b4faaf64ebb32c067e952917ae 100644 --- a/nixos/modules/issuer.nix +++ b/nixos/modules/issuer.nix @@ -189,7 +189,7 @@ in { extraGroups = [ "keys" ]; }; - # Open 80 and 443 for the certbot HTTP server and the PaymentServer HTTPS server. + # Open 80 and 443 for nginx networking.firewall.allowedTCPPorts = [ 80 443 diff --git a/nixos/modules/monitoring/server/grafana-dashboards/meta-monitoring.json b/nixos/modules/monitoring/server/grafana-dashboards/meta-monitoring.json index c1d27b28965eae82939d2dfefdb7f9a709bfd486..17564492ffc163c2c98a1a5e6ed35bc52d63e6c0 100644 --- a/nixos/modules/monitoring/server/grafana-dashboards/meta-monitoring.json +++ b/nixos/modules/monitoring/server/grafana-dashboards/meta-monitoring.json @@ -42,7 +42,7 @@ }, "reducer": { "params": [], - "type": "avg" + "type": "count" }, "type": "query" } @@ -52,7 +52,7 @@ "frequency": "1m", "handler": 1, "name": "Scraping down", - "noDataState": "no_data", + "noDataState": "ok", "notifications": [] }, "aliasColors": {}, @@ -86,7 +86,7 @@ }, "lines": false, "linewidth": 1, - "nullPointMode": "null", + "nullPointMode": "null as zero", "options": { "alertThreshold": false }, diff --git a/nixos/modules/monitoring/server/grafana-dashboards/resources-overview.json b/nixos/modules/monitoring/server/grafana-dashboards/resources-overview.json index 5ecbcb9b709f7093592e54f368166da064b1ae73..0b22728e178ddd5295afd43a17cd0b9c20c530fd 100644 --- a/nixos/modules/monitoring/server/grafana-dashboards/resources-overview.json +++ b/nixos/modules/monitoring/server/grafana-dashboards/resources-overview.json @@ -49,7 +49,7 @@ "fillGradient": 0, "gridPos": { "h": 7, - "w": 8, + "w": 6, "x": 0, "y": 1 }, @@ -193,8 +193,8 @@ "fillGradient": 0, "gridPos": { "h": 7, - "w": 8, - "x": 8, + "w": 6, + "x": 6, "y": 1 }, "hiddenSeries": false, @@ -334,8 +334,8 @@ "fillGradient": 0, "gridPos": { "h": 7, - "w": 8, - "x": 16, + "w": 6, + "x": 12, "y": 1 }, "hiddenSeries": false, @@ -425,6 +425,152 @@ "alignLevel": null } }, + { + "alert": { + "alertRuleTags": {}, + "conditions": [ + { + "evaluator": { + "params": [ + 0.1 + ], + "type": "gt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [ + "A", + "5m", + "now" + ] + }, + "reducer": { + "params": [], + "type": "avg" + }, + "type": "query" + } + ], + "executionErrorState": "alerting", + "for": "5m", + "frequency": "1m", + "handler": 1, + "name": "Swap usage alert", + "noDataState": "no_data", + "notifications": [] + }, + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "description": "How much Swap is in use? Relative to available swap.", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 6, + "x": 18, + "y": 1 + }, + "hiddenSeries": false, + "id": 30, + "legend": { + "alignAsTable": false, + "avg": false, + "current": false, + "hideEmpty": false, + "hideZero": false, + "max": false, + "min": false, + "rightSide": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.10", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "1 - node_memory_SwapFree_bytes / node_memory_SwapTotal_bytes", + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{instance}}", + "refId": "A" + } + ], + "thresholds": [ + { + "colorMode": "critical", + "fill": true, + "line": true, + "op": "gt", + "value": 0.1, + "visible": true + } + ], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Swap used %", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:98", + "format": "percentunit", + "label": null, + "logBase": 1, + "max": "1", + "min": "0", + "show": true + }, + { + "$$hashKey": "object:99", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": false + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, { "collapsed": false, "datasource": null, diff --git a/nixos/modules/monitoring/server/grafana.nix b/nixos/modules/monitoring/server/grafana.nix index 1783782ce7e395f9201dd93e2386f4eed4bf003e..1b51abd4b795a7d6dd8c4c4319beecae4162bb53 100644 --- a/nixos/modules/monitoring/server/grafana.nix +++ b/nixos/modules/monitoring/server/grafana.nix @@ -67,17 +67,14 @@ in { default = false; description = '' Enables the slack alerter. Expects a file that contains - the definition of an environment variable named SLACKURL - pointing to the secret Slack Web Hook URL in - grafanaSlackUrlFile (see below). + the secret Slack Web Hook URL in grafanaSlackUrlFile (see below). ''; }; grafanaSlackUrlFile = lib.mkOption { type = lib.types.path; default = /run/keys/grafana-slack-url; description = '' - Where to find the Grafana Systemd EnvironmentFile that - sets the secret SLACKURL environment variable. + Where to find the file that containts the slack URL. ''; }; }; @@ -86,12 +83,6 @@ in { # Port 80 for ACME ssl retrieval only. 443 for nginx -> grafana. networking.firewall.allowedTCPPorts = [ 80 443 ]; - # We pass the secret Slack URL using an environment variable. - systemd.services.grafana.serviceConfig.EnvironmentFile = - if cfg.enableSlackAlert - then [ cfg.grafanaSlackUrlFile ] - else [ ]; - services.grafana = { enable = true; domain = cfg.domain; @@ -157,7 +148,9 @@ in { uploadImage = true; }; secure_settings = { - url = "$SLACKURL"; + # `$__file{}` reads the value from the named file. + # See https://grafana.com/docs/grafana/latest/administration/configuration/#file-provider + url = "$__file{${toString cfg.grafanaSlackUrlFile}}"; }; }]); }; diff --git a/nixos/modules/spending.nix b/nixos/modules/spending.nix index 238fbe8f939c4ddb0c78b9a34e106dbea8e39921..325dd147012b7844a8cb0b4b7071c4cd2cd88f28 100644 --- a/nixos/modules/spending.nix +++ b/nixos/modules/spending.nix @@ -127,6 +127,16 @@ in # Want a regex instead? try locations."~ /v\d+/" proxyPass = "http://unix:${cfg.unixSocket}"; }; + locations."/metrics" = { + proxyPass = "http://unix:${cfg.unixSocket}"; + # Only allow our monitoringvpn subnet + extraConfig = '' + allow 172.23.23.0/24; + allow 127.0.0.1; + allow ::1; + deny all; + ''; + }; locations."/" = { # Return a 404 error for any paths not specified above. extraConfig = '' @@ -135,5 +145,11 @@ in }; }; }; + + # Open 80 and 443 for nginx + networking.firewall.allowedTCPPorts = [ + 80 + 443 + ]; }; } diff --git a/nixos/pkgs/zkap-spending-service/repo.json b/nixos/pkgs/zkap-spending-service/repo.json index 39aeb8404c890e4781ee77f2a93d85d68acee5c3..69f7a30053de661f2c7829384e9496e49077cfd9 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": "e0d63b79213d16f2de6629167ea8f1236ba22e14", + "rev": "cbf7509f429ffd6e6cf37a73e4ff84a9c5ce1141", "branch": "main", "domain": "whetstone.privatestorage.io", - "outputHash": "30abb0g9xxn4lp493kj5wmz8kj5q2iqvw40m8llqvb3zamx60gd8cy451ii7z15qbrbx9xmjdfw0k4gviij46fkx1s8nbich5c8qx57", + "outputHash": "04g7pcykc2525cg3z7wg5834s7vqn82xaqjvf52l6dnxv3mb9xr93kk505dvxcwhgfbqpim5i479s9kqd8gi7q3lq5wn5fq7rf7lkrj", "outputHashAlgo": "sha512" } diff --git a/nixos/pkgs/zkapissuer/default.nix b/nixos/pkgs/zkapissuer/default.nix index b4f90d3582cd686fbdf62a6267cb1070c05e9c57..efa55ff108e72fb7d78d95c6db46bddcdca1116f 100644 --- a/nixos/pkgs/zkapissuer/default.nix +++ b/nixos/pkgs/zkapissuer/default.nix @@ -1,6 +1,7 @@ -{ callPackage }: +{ callPackage, fetchFromGitHub, lib }: let - repo = callPackage ./repo.nix { }; + repo-data = lib.importJSON ./repo.json; + repo = fetchFromGitHub (builtins.removeAttrs repo-data [ "branch" ]); PaymentServer = (import "${repo}/nix").PaymentServer; in PaymentServer.components.exes."PaymentServer-exe" diff --git a/nixos/pkgs/zkapissuer/repo.json b/nixos/pkgs/zkapissuer/repo.json new file mode 100644 index 0000000000000000000000000000000000000000..0a003dc61620fd92b1a618e9845763e276c9693a --- /dev/null +++ b/nixos/pkgs/zkapissuer/repo.json @@ -0,0 +1,8 @@ +{ + "owner": "PrivateStorageio", + "repo": "PaymentServer", + "rev": "e080beb14ec58ffe8e55c35e6dddd46c5082887f", + "branch": "main", + "outputHashAlgo": "sha256", + "outputHash": "1zck9kawbs2lkr3qjipira9gawa4gxlqijqqjrmlvvyp9mr0fgxm" +} diff --git a/nixos/pkgs/zkapissuer/repo.nix b/nixos/pkgs/zkapissuer/repo.nix deleted file mode 100644 index 6646a2e32eb8e5a747e4491ce43f706fee65724c..0000000000000000000000000000000000000000 --- a/nixos/pkgs/zkapissuer/repo.nix +++ /dev/null @@ -1,7 +0,0 @@ -{ fetchFromGitHub }: -fetchFromGitHub { - owner = "PrivateStorageio"; - repo = "PaymentServer"; - rev = "ff30e85c231a3b5ad76426bbf8801f8f76884367"; - sha256 = "1spz19f5z96shmfpazj0rv6877xvchf3gl49a4xahjbbsz39x34x"; -} diff --git a/nixos/tests/spending.nix b/nixos/tests/spending.nix index c970157b9375e0d99e2be8d4f782992163a6c948..8500471a58ff3f447e03ec1bf9005ff626169113 100644 --- a/nixos/tests/spending.nix +++ b/nixos/tests/spending.nix @@ -11,10 +11,14 @@ services.private-storage-spending.enable = true; services.private-storage-spending.domain = "localhost"; }; + external = { ... }: { + # A node that has no particular configuration, for testing access rules + # for external hosts. + }; }; testScript = { nodes }: let revision = nodes.spending.config.passthru.ourpkgs.zkap-spending-service.meta.rev; - curl = "${pkgs.curl}/bin/curl -sSf"; + curl = "${pkgs.curl}/bin/curl -sSf --max-time 5"; in '' import json @@ -25,8 +29,17 @@ with subtest("Ensure we can ping the spending service"): output = spending.succeed("${curl} http://localhost/v1/_ping") assert json.loads(output)["status"] == "ok", "Could not ping spending service." + with subtest("Ensure external hosts can ping the spending service"): + output = external.succeed("${curl} http://spending/v1/_ping") + assert json.loads(output)["status"] == "ok", "Could not ping spending service." with subtest("Ensure that the spending service version matches the expected version"): output = spending.succeed("${curl} http://localhost/v1/_version") assert json.loads(output)["revision"] == "${revision}", "Spending service revision does not match." + with subtest("Ensure that the spending service generates metrics"): + # TODO: We should pass "-H 'accept: application/openmetrics-text'" here. + # See https://github.com/prometheus/prometheus/issues/8932 + output = spending.succeed("${curl} http://localhost/metrics | ${pkgs.prometheus}/bin/promtool check metrics") + with subtest("Ensure that the metrics are not accesible from other machines"): + output = external.fail("${curl} http://spending/metrics") ''; } diff --git a/nixpkgs-2105.json b/nixpkgs-2105.json index bfd07e9db63256d7ff1efdfc77f23c18a06fcff3..55b7cf2d1617db02e5bdcc6944f32549ee92d695 100644 --- a/nixpkgs-2105.json +++ b/nixpkgs-2105.json @@ -1,5 +1,5 @@ { "name": "release2105", - "url": "https://releases.nixos.org/nixos/21.05/nixos-21.05.3468.92609f3d9bc/nixexprs.tar.xz", - "sha256": "154bws59vasydyqb8kfi32fyhawqpwpn85y0sn93hz9ch0r6pgvh" + "url": "https://releases.nixos.org/nixos/21.05/nixos-21.05.3740.ce7a1190a0f/nixexprs.tar.xz", + "sha256": "112drvixj81vscj8cncmks311rk2ik5gydpd03d3r0yc939zjskg" } diff --git a/tools/default.nix b/tools/default.nix index fb44c660e7d4a4a62cec6bb58a008a1bf00429dc..b10bb5f209c44c3ccba5cf509655e6d25fbb88da 100644 --- a/tools/default.nix +++ b/tools/default.nix @@ -16,6 +16,7 @@ let python-commands = [ ./update-nixpkgs ./update-gitlab-repo + ./update-github-repo ]; in # This derivation creates a package that wraps our tools to setup an environment diff --git a/tools/update-github-repo b/tools/update-github-repo new file mode 100644 index 0000000000000000000000000000000000000000..0e7e1511fc017c360660dc9fb752ff03f315f9bb --- /dev/null +++ b/tools/update-github-repo @@ -0,0 +1,89 @@ +#!/usr/bin/env python + +""" +Update a pinned github repository. + +Pass this path to a JSON file and it will update it to the latest +version of the branch it specifies. You can also pass a different +branch or repository owner, which will update the file to point at +the new branch/repository, and update to the latest version. +""" + +import argparse +import json +from pathlib import Path + +import httpx +from ps_tools import get_url_hash + +HASH_TYPE = "sha512" + +ARCHIVE_TEMPLATE = "https://api.github.com/repos/{owner}/{repo}/tarball/{rev}" +BRANCH_TEMPLATE = ( + "https://api.github.com/repos/{owner}/{repo}/commits/{branch}" +) + + +def get_github_commit(config): + response = httpx.get(BRANCH_TEMPLATE.format(**config)) + response.raise_for_status() + return response.json()["sha"] + + +def get_github_archive_url(config): + return ARCHIVE_TEMPLATE.format(**config) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "repo_file", + metavar="repo-file", + type=Path, + help="JSON file with pinned configuration.", + ) + parser.add_argument( + "--branch", + type=str, + help="Branch to update to.", + ) + parser.add_argument( + "--owner", + type=str, + help="Repository owner to update to.", + ) + parser.add_argument( + "--rev", + type=str, + help="Revision to pin.", + ) + parser.add_argument( + "--dry-run", + action="store_true", + ) + args = parser.parse_args() + + repo_file = args.repo_file + config = json.loads(repo_file.read_text()) + + for key in ["owner", "branch"]: + if getattr(args, key) is not None: + config[key] = getattr(args, key) + + if args.rev is not None: + config["rev"] = args.rev + else: + config["rev"] = get_github_commit(config) + + archive_url = get_github_archive_url(config) + config.update(get_url_hash(HASH_TYPE, "source", archive_url)) + + output = json.dumps(config, indent=2) + if args.dry_run: + print(output) + else: + repo_file.write_text(output) + + +if __name__ == "__main__": + main()