From 0eadf55fd51ffdb3dc4bb633f1fcadac1f24a5db Mon Sep 17 00:00:00 2001
From: Gabriel Arazas <foodogsquared@foodogsquared.one>
Date: Sun, 8 Oct 2023 03:27:47 +0800
Subject: [PATCH] hosts/plover: init Grafana server

---
 hosts/plover/default.nix                  |   3 +
 hosts/plover/modules/services/grafana.nix | 127 ++++++++++++++++++++++
 2 files changed, 130 insertions(+)
 create mode 100644 hosts/plover/modules/services/grafana.nix

diff --git a/hosts/plover/default.nix b/hosts/plover/default.nix
index 034ea0a2..4f1b6bd3 100644
--- a/hosts/plover/default.nix
+++ b/hosts/plover/default.nix
@@ -28,6 +28,9 @@ in
     # The reverse proxy of choice.
     ./modules/services/nginx.nix
 
+    # The monitoring stack.
+    ./modules/services/grafana.nix
+
     # The database of choice which is used by most self-managed services on
     # this server.
     ./modules/services/postgresql.nix
diff --git a/hosts/plover/modules/services/grafana.nix b/hosts/plover/modules/services/grafana.nix
new file mode 100644
index 00000000..aece15a1
--- /dev/null
+++ b/hosts/plover/modules/services/grafana.nix
@@ -0,0 +1,127 @@
+{ config, lib, pkgs, ... }:
+
+let
+  monitoringDomain = "monitoring.${config.networking.domain}";
+  authDomain = "auth.${config.networking.domain}";
+  authSubpath = path: "${authDomain}/${path}";
+
+  vouchDomain = "vouch.${config.networking.domain}";
+  vouchSettings = config.services.vouch-proxy.settings;
+
+  certDir = file: "${config.security.acme.certs."${monitoringDomain}".directory}/${file}";
+  inherit (config.services.grafana) settings;
+in
+{
+  services.grafana = {
+    enable = true;
+
+    settings = {
+      auth = {
+        disable_login_form = true;
+        login_maximum_inactive_lifetime_duration = "3d";
+        login_maximum_lifetime_duration = "14d";
+      };
+
+      "auth.generic_oauth" = {
+        api_url = authSubpath "oauth2/authorise";
+        client_id = "grafana";
+        client_secret = "$_file{${config.sops.secrets."vouch-proxy/client/secret".path}";
+        enabled = true;
+        name = "Kanidm";
+        oauth_url = authSubpath "ui/oauth2";
+        scopes = lib.concatStringsSep " " [ "openid" "email" "profile" ];
+        token_url = authSubpath "oauth2/token";
+      };
+
+      database = {
+        host = "127.0.0.1:${builtins.toString config.services.postgresql.port}";
+        password = "$_file{${config.sops.secrets."grafana/database/password".path}}";
+        type = "postgres";
+        user = config.services.grafana.database.name;
+      };
+
+      log = {
+        level = "warn";
+        mode = "syslog";
+      };
+
+      security = {
+        admin_email = config.security.acme.defaults.email;
+        admin_password = "$_file{${config.sops.secrets."grafana/users/admin/password".path}}";
+        cookie_secure = true;
+        csrf_trusted_origins = [
+          vouchDomain
+          "auth.${config.networking.domain}"
+        ];
+        strict_transport_security = true;
+        strict_transport_security_subdomains = true;
+      };
+
+      users = {
+        default_theme = "system";
+        default_language = "detect";
+      };
+
+      server = {
+        enable_gzip = true;
+        enforce_domain = true;
+        http_addr = "127.0.0.1";
+        http_port = 3000;
+        root_url = "${monitoringDomain}/grafana";
+        serve_from_sub_path = true;
+      };
+    };
+  };
+
+  services.nginx.virtualHosts."${monitoringDomain}" = {
+    forceSSL = true;
+    enableACME = true;
+    acmeRoot = null;
+
+    extraConfig = ''
+      auth_request /validate;
+
+      # If the user is not logged in, redirect them to Vouch's login URL
+      error_page 401 = @error401;
+      location @error401 {
+        return 302 http://${vouchDomain}/login?url=$scheme://$http_host$request_uri&vouch-failcount=$auth_resp_failcount&X-Vouch-Token=$auth_resp_jwt&error=$auth_resp_err;
+      }
+    '';
+
+    locations = {
+      "= /validate" = {
+        proxyPass = "http://${vouchSettings.vouch.listen}:${builtins.toString vouchSettings.vouch.port}";
+        extraConfig = ''
+          proxy_pass_request_body off;
+
+          # These will be passed to @error_401 call.
+          auth_request_set $auth_resp_x_vouch_user $upstream_http_x_vouch_user;
+          auth_request_set $auth_resp_jwt $upstream_http_x_vouch_jwt;
+          auth_request_set $auth_resp_err $upstream_http_x_vouch_err;
+          auth_request_set $auth_resp_failcount $upstream_http_x_vouch_failcount;
+        '';
+      };
+
+      # Serving Grafana with a subpath.
+      "/grafana".proxyPass = "http://${settings.server.http_addr}:${builtins.toString settings.server.http_port}";
+    };
+  };
+
+  # Setting up with secure schema usage pattern.
+  systemd.services.grafana = {
+    preStart = let
+        grafanaDatabaseUser = config.services.grafana.settings.database.user;
+        psql = lib.getExe' config.services.postgresql.package "psql";
+      in
+      lib.mkBefore ''
+        # Setting up the appropriate schema for PostgreSQL secure schema usage.
+        ${psql} -tAc "SELECT 1 FROM information_schema.schemata WHERE schema_name='${grafanaDatabaseUser}';" \
+          grep -q 1 || ${psql} -tAc "CREATE SCHEMA IF NOT EXISTS AUTHORIZATION ${grafanaDatabaseUser};"
+    '';
+  };
+
+  sops.secrets = lib.getSecrets ../../secrets/secrets.yaml {
+    "grafana/database/password" = { };
+    "grafana/users/admin/password" = { };
+  };
+}