aboutsummaryrefslogtreecommitdiffstats
path: root/modules/server.nix
diff options
context:
space:
mode:
Diffstat (limited to 'modules/server.nix')
-rw-r--r--modules/server.nix89
1 files changed, 78 insertions, 11 deletions
diff --git a/modules/server.nix b/modules/server.nix
index 1996f5e..d9449a8 100644
--- a/modules/server.nix
+++ b/modules/server.nix
@@ -34,6 +34,13 @@ let
replacement = "$1";
}];
+ # Where Grafana's secret key actually comes from: a file you supply, or one this
+ # module generates into Grafana's data directory on first start.
+ secretKeyPath =
+ if cfg.grafana.generateSecretKey
+ then "${config.services.grafana.dataDir}/secret-key"
+ else cfg.grafana.secretKeyFile;
+
builtinDashboards = pkgs.callPackage ./dashboards.nix { inherit dsUid; };
selected = filterAttrs (name: _: cfg.grafana.dashboards.${name} or false) builtinDashboards;
@@ -185,6 +192,31 @@ in
description = "Run Grafana, provisioned with datasources and dashboards.";
};
+ generateSecretKey = mkOption {
+ type = types.bool;
+ default = cfg.grafana.secretKeyFile == null;
+ defaultText = lib.literalMD "`true` unless {option}`secretKeyFile` is set";
+ description = ''
+ Generate a random `security.secret_key` on first start and keep it in
+ Grafana's data directory, instead of supplying one yourself.
+
+ Grafana needs this key to be *stable* -- it encrypts credentials stored in
+ Grafana's database, and they become undecryptable if it changes. It has an
+ upstream default, but that default is a constant compiled into every Grafana
+ release, so leaving it unset means encrypting with a publicly known key;
+ nixpkgs asserts rather than let that happen silently.
+
+ The generated file is written by the grafana user with mode 0600, so only
+ Grafana and root can read it. Note it then lives beside the database it
+ protects: anyone who can read `grafana.db` can read the key too. That is a
+ real limitation, and it is why {option}`secretKeyFile` exists -- but the key
+ is still per-install and secret from anyone without filesystem access, which
+ is the property that actually matters here. Prefer `secretKeyFile` once
+ Grafana's database holds credentials worth protecting, such as a datasource
+ password or a contact-point API token.
+ '';
+ };
+
secretKeyFile = mkOption {
type = types.nullOr types.path;
default = null;
@@ -245,14 +277,24 @@ in
# Grafana uses this key to encrypt datasource credentials in its database, and
# nixpkgs stopped shipping a default in 26.05. Caught here rather than left to
# Grafana's own assertion, which names an option this module manages for you.
- assertions = [{
- assertion = !cfg.grafana.enable || cfg.grafana.secretKeyFile != null;
- message = ''
- services.telemetry.server.grafana.secretKeyFile must be set when Grafana is
- enabled. Generate a key (`openssl rand -base64 32`), store it with whatever
- secret manager you use, and point this option at the resulting file.
- '';
- }];
+ assertions = [
+ {
+ assertion = !(cfg.grafana.generateSecretKey && cfg.grafana.secretKeyFile != null);
+ message = ''
+ services.telemetry.server.grafana.generateSecretKey and .secretKeyFile are
+ mutually exclusive. Setting secretKeyFile turns generation off by default, so
+ this only fires if you asked for both explicitly.
+ '';
+ }
+ {
+ assertion = !cfg.grafana.enable || secretKeyPath != null;
+ message = ''
+ Grafana needs a secret key. Either leave
+ services.telemetry.server.grafana.generateSecretKey at its default so one is
+ generated on first start, or point .secretKeyFile at a file you manage.
+ '';
+ }
+ ];
# --------------------------------------------------------------------------------
# Loki
@@ -338,7 +380,7 @@ in
}
# A file provider rather than a literal, so the key is never written into a
# world-readable store path. The credential is mounted by LoadCredential below.
- // lib.optionalAttrs (cfg.grafana.secretKeyFile != null) {
+ // lib.optionalAttrs (secretKeyPath != null) {
security.secret_key = "$__file{/run/credentials/grafana.service/secret_key}";
};
@@ -389,10 +431,35 @@ in
};
};
- systemd.services.grafana = mkIf (cfg.grafana.enable && cfg.grafana.secretKeyFile != null) {
- serviceConfig.LoadCredential = [ "secret_key:${cfg.grafana.secretKeyFile}" ];
+ systemd.services.grafana = mkIf (cfg.grafana.enable && secretKeyPath != null) {
+ serviceConfig.LoadCredential = [ "secret_key:${secretKeyPath}" ];
};
+ # Runs as the grafana user, into a directory users.users.grafana.createHome already
+ # made and owns -- so there is no chown, and no fighting with Grafana over the
+ # directory's ownership. UMask makes the file 0600 on creation; the explicit chmod
+ # also repairs a file left too permissive by an earlier version or a manual copy.
+ systemd.services.grafana-secret-key =
+ mkIf (cfg.grafana.enable && cfg.grafana.generateSecretKey) {
+ description = "Generate Grafana's secret key if it does not exist yet";
+ wantedBy = [ "grafana.service" ];
+ before = [ "grafana.service" ];
+ serviceConfig = {
+ Type = "oneshot";
+ RemainAfterExit = true;
+ User = "grafana";
+ Group = "grafana";
+ UMask = "0077";
+ };
+ script = ''
+ key=${lib.escapeShellArg secretKeyPath}
+ if [ ! -s "$key" ]; then
+ ${pkgs.openssl}/bin/openssl rand -base64 32 > "$key"
+ fi
+ chmod 0600 "$key"
+ '';
+ };
+
environment.etc = mkIf cfg.grafana.enable (
mapAttrs' (name: src: nameValuePair "grafana-dashboards/${name}.json" { source = src; })
dashboards