add oauth to grafana via keycloak

Change-Id: Ib35cb9ba01c1822f42dc068e95636aa571e1452a
diff --git a/modules/grafana.nix b/modules/grafana.nix
index 6aee70f..7c3b175 100644
--- a/modules/grafana.nix
+++ b/modules/grafana.nix
@@ -1,4 +1,6 @@
-{ lib, config, ... }: {
+{ lib, config, base, pkgs, ... }:
+lib.recursiveUpdate
+{
   services.grafana = {
     enable = true;
 
@@ -10,6 +12,29 @@
         enable_gzip = true;
       };
       analytics.reporting_enabled = false;
+      "auth.generic_oauth" = {
+        enabled = true;
+        name = "Clicks OAuth";
+        allow_sign_up = true;
+        client_id = "grafana";
+        client_secret = "!!client_secret!!";
+        scopes = "openid email profile offline_access roles";
+        email_attribute_path = "email";
+        login_attribute_path = "login";
+        name_attribute_path = "name";
+        auth_url =
+          "https://login.clicks.codes/realms/clicks/protocol/openid-connect/auth";
+        token_url =
+          "https://login.clicks.codes/realms/clicks/protocol/openid-connect/token";
+        api_url =
+          "https://login.clicks.codes/realms/clicks/protocol/openid-connect/userinfo";
+        role_attribute_path =
+        "contains(resource_access.grafana.roles[*], 'server_admin') && 'GrafanaAdmin' || contains(resource_access.grafana.roles[*], 'admin') && 'Admin' || contains(resource_access.grafana.roles[*], 'editor') && 'Editor' || 'Viewer'";
+        allow_assign_grafana_admin = true;
+        auto_login = true;
+      };
+      "auth.basic".enabled = false;
+      auth.disable_login_form = true;
     };
 
     provision.datasources.settings.datasources = [{
@@ -19,8 +44,55 @@
 
       url = "postgres://localhost:${toString config.services.postgresql.port}";
       user = "clicks_grafana";
-      password = "$__file{${config.sops.secrets.clicks_grafana_db_password.path}}";
+      password =
+        "$__file{${config.sops.secrets.clicks_grafana_db_password.path}}";
       # defined in postgres.nix
     }];
   };
+
+  sops.secrets.clicks_grafana_client_secret = {
+    mode = "0600";
+    owner = "root";
+    group = "nobody";
+    sopsFile = ../secrets/grafana.json;
+    format = "json";
+  };
 }
+  (
+    let isDerived = base != null;
+    in if isDerived then
+      let
+        generators = lib.generators;
+        cfg = config.services.grafana;
+        settingsFormatIni = pkgs.formats.ini {
+          listToValue =
+            lib.concatMapStringsSep " " (generators.mkValueStringDefault { });
+          mkKeyValue = generators.mkKeyValueDefault
+            {
+              mkValueString = v:
+                if v == null then "" else generators.mkValueStringDefault { } v;
+            } "=";
+        };
+        grafana_cfgfile = settingsFormatIni.generate "config.ini" cfg.settings;
+      in
+      {
+        scalpel.trafos."grafana.ini" = {
+          source = toString grafana_cfgfile;
+          matchers."client_secret".secret =
+            config.sops.secrets.clicks_grafana_client_secret.path;
+          owner = config.users.users.grafana.name;
+          group = "nobody";
+          mode = "0400";
+        };
+
+        systemd.services.grafana.serviceConfig.ExecStart = lib.mkForce (pkgs.writeShellScript "grafana-start" ''
+          set -o errexit -o pipefail -o nounset -o errtrace
+          shopt -s inherit_errexit
+
+          exec ${cfg.package}/bin/grafana-server -homepath ${cfg.dataDir} -config ${config.scalpel.trafos."grafana.ini".destination}
+        '');
+        systemd.services.grafana.restartTriggers = [ grafana_cfgfile ];
+      }
+    else
+      { }
+  )
diff --git a/secrets/grafana.json b/secrets/grafana.json
new file mode 100644
index 0000000..933ae48
--- /dev/null
+++ b/secrets/grafana.json
@@ -0,0 +1,28 @@
+{
+	"clicks_grafana_client_secret": "ENC[AES256_GCM,data:sLMtup1tpWomwyTxtfMQjMAHFxJuKO7URJ9at2JX8fo=,iv:11gakyak4ardVFKKGUyu4sl3j00SA4FIyTO4DpMsr/w=,tag:OkheD4Wr8qYeVRQCAD6rdg==,type:str]",
+	"sops": {
+		"kms": null,
+		"gcp_kms": null,
+		"azure_kv": null,
+		"hc_vault": null,
+		"age": [
+			{
+				"recipient": "age15mv77dpnh5762gk5rsw2u79uza4tg8cu6r3nlwjudlzmdqqck3ss6mg9dy",
+				"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBXVnJHeG94bDM1QjBnUEhB\nMXlRK01jUjk5VVFrWTZUbW5tcnRuRkJnNG5jCldwYmJYV0N0NnZjVGloTTVMQVB0\nVVljbVExb3FHc2pSMEYyRWlZM05kY0EKLS0tIG1aN0FKUmxEWHVqb09YOEpOSmY3\ncFJCT2hUbEEwRXRyN0ZYTXlXWElZdWcKXuHhvbzqqFCqaLxPt+ASnTh4zyrPjXvW\n6XZMazM9tfJHzpaYz4BgpYiqK1uGy1IkLjmVMS6DC8LfS3jfZ8Jb7g==\n-----END AGE ENCRYPTED FILE-----\n"
+			},
+			{
+				"recipient": "age1m7k864feyuezllp2hj4edkccn36rthrvfw969j6f0l3c0mhh5emsnfx6pd",
+				"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBFRVJkY1RITFJwQ2pzak1Q\nTG55RkFHUUt0dU9rV2FGUDJ3L3dpYXZhUnpBClpGRC9EMU5KZXNFZWhDY3I5dHN5\nQS9VeXJuS0QyYkhWU1RjNXNyMlVBS1UKLS0tIHd3Y1E0cERITnNRMkl2enB3Sm01\nOWlsMVhpUDh1R3VSMkd1cERZNkMreUEKcKeK2HQHsg06y7m44qGb39sILITZnp/8\nl39sUK2PtWB++GO4I8Cae+D6OVr1vMfseSQ5e87lXC3sH51mh32g1Q==\n-----END AGE ENCRYPTED FILE-----\n"
+			},
+			{
+				"recipient": "age1fxxnmkeuqhhct93c43pwkzhuzzq8857s5hye6pgfpku70kjn4ecqtamfqr",
+				"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA3anNtamtGRE1JSVM1TUY0\nVmZQSzhLK2w1M0lSckMyOG5uT0VmT1JyU2t3ClNtOWd5WitrSzI1ZzBjZktOOFUv\nTUp3NzhMT0JqUitRL3JETk90cExiMFUKLS0tIG9WdzRPWkpjcjVxN1NhcXorSG1j\nQkxaREp5S1I0K0t5NlhWL0xkK044OG8KE39CwjXPR+ydht9TaABKcihxR3d2XDX0\nxNfHWWasdoIcKG2NCAlhVBHmUoFU4OHaO0/NKRbt1RYxCOslYi05UA==\n-----END AGE ENCRYPTED FILE-----\n"
+			}
+		],
+		"lastmodified": "2023-10-11T21:44:46Z",
+		"mac": "ENC[AES256_GCM,data:Rtl/ITInTUqwhIkFZmEIBOqfQOaRIMV0biBOny9A4Z5yl0QlH9dubV4jvZNx+ZEDfeLm6qfjNGDIDlVHgP4+LqGVxUS+MOzlIgDl9cATmlnxSXNk4WURUmD459OVVqY0aAFXT6hhfyjXhmINrGFYa9qDGBAgWkaAgAE3dObMK0M=,iv:DFRkgo1qD9or/kOyIEaNp5NLZNGuZCMBpK9r3Vx+1L4=,tag:XYhPJhM0M2PdVN2IyZd6hA==,type:str]",
+		"pgp": null,
+		"unencrypted_suffix": "_unencrypted",
+		"version": "3.7.3"
+	}
+}
\ No newline at end of file