From 2f68d8cb10e1b9b5a695cfff175d81630850624e Mon Sep 17 00:00:00 2001 From: Joscha Date: Sat, 17 Jun 2023 23:04:29 +0200 Subject: [PATCH 1/2] nixos/grafana: reformat --- nixos/modules/services/monitoring/grafana.nix | 176 ++++++++++-------- 1 file changed, 96 insertions(+), 80 deletions(-) diff --git a/nixos/modules/services/monitoring/grafana.nix b/nixos/modules/services/monitoring/grafana.nix index adffadf3fc47..6706035cd5a5 100644 --- a/nixos/modules/services/monitoring/grafana.nix +++ b/nixos/modules/services/monitoring/grafana.nix @@ -5,25 +5,25 @@ with lib; let cfg = config.services.grafana; opt = options.services.grafana; - provisioningSettingsFormat = pkgs.formats.yaml {}; + provisioningSettingsFormat = pkgs.formats.yaml { }; declarativePlugins = pkgs.linkFarm "grafana-plugins" (builtins.map (pkg: { name = pkg.pname; path = pkg; }) cfg.declarativePlugins); useMysql = cfg.settings.database.type == "mysql"; usePostgresql = cfg.settings.database.type == "postgres"; - settingsFormatIni = pkgs.formats.ini {}; + settingsFormatIni = pkgs.formats.ini { }; configFile = settingsFormatIni.generate "config.ini" cfg.settings; mkProvisionCfg = name: attr: provisionCfg: if provisionCfg.path != null - then provisionCfg.path + then provisionCfg.path else provisioningSettingsFormat.generate "${name}.yaml" (if provisionCfg.settings != null - then provisionCfg.settings - else { - apiVersion = 1; - ${attr} = []; - }); + then provisionCfg.settings + else { + apiVersion = 1; + ${attr} = [ ]; + }); datasourceFileOrDir = mkProvisionCfg "datasource" "datasources" cfg.provision.datasources; dashboardFileOrDir = mkProvisionCfg "dashboard" "providers" cfg.provision.dashboards; @@ -35,9 +35,10 @@ let notifierFileOrDir = pkgs.writeText "notifier.yaml" (builtins.toJSON notifierConfiguration); - generateAlertingProvisioningYaml = x: if (cfg.provision.alerting."${x}".path == null) - then provisioningSettingsFormat.generate "${x}.yaml" cfg.provision.alerting."${x}".settings - else cfg.provision.alerting."${x}".path; + generateAlertingProvisioningYaml = x: + if (cfg.provision.alerting."${x}".path == null) + then provisioningSettingsFormat.generate "${x}.yaml" cfg.provision.alerting."${x}".settings + else cfg.provision.alerting."${x}".path; rulesFileOrDir = generateAlertingProvisioningYaml "rules"; contactPointsFileOrDir = generateAlertingProvisioningYaml "contactPoints"; policiesFileOrDir = generateAlertingProvisioningYaml "policies"; @@ -102,7 +103,7 @@ let description = lib.mdDoc "Datasource type. Required."; }; access = mkOption { - type = types.enum ["proxy" "direct"]; + type = types.enum [ "proxy" "direct" ]; default = "proxy"; description = lib.mdDoc "Access mode. proxy or direct (Server or Browser in the UI). Required."; }; @@ -170,7 +171,7 @@ let description = lib.mdDoc "Notifier name."; }; type = mkOption { - type = types.enum ["dingding" "discord" "email" "googlechat" "hipchat" "kafka" "line" "teams" "opsgenie" "pagerduty" "prometheus-alertmanager" "pushover" "sensu" "sensugo" "slack" "telegram" "threema" "victorops" "webhook"]; + type = types.enum [ "dingding" "discord" "email" "googlechat" "hipchat" "kafka" "line" "teams" "opsgenie" "pagerduty" "prometheus-alertmanager" "pushover" "sensu" "sensugo" "slack" "telegram" "threema" "victorops" "webhook" ]; description = lib.mdDoc "Notifier type."; }; uid = mkOption { @@ -225,7 +226,8 @@ let }; }; }; -in { +in +{ imports = [ (mkRenamedOptionModule [ "services" "grafana" "protocol" ] [ "services" "grafana" "settings" "server" "protocol" ]) (mkRenamedOptionModule [ "services" "grafana" "addr" ] [ "services" "grafana" "settings" "server" "http_addr" ]) @@ -354,7 +356,7 @@ in { protocol = mkOption { description = lib.mdDoc "Which protocol to listen."; default = "http"; - type = types.enum ["http" "https" "h2" "socket"]; + type = types.enum [ "http" "https" "h2" "socket" ]; }; http_addr = mkOption { @@ -426,7 +428,7 @@ in { type = mkOption { description = lib.mdDoc "Database type."; default = "sqlite3"; - type = types.enum ["mysql" "sqlite3" "postgres"]; + type = types.enum [ "mysql" "sqlite3" "postgres" ]; }; host = mkOption { @@ -555,7 +557,7 @@ in { auto_assign_org_role = mkOption { description = lib.mdDoc "Default role new users will be auto assigned."; default = "Viewer"; - type = types.enum ["Viewer" "Editor" "Admin"]; + type = types.enum [ "Viewer" "Editor" "Admin" ]; }; }; @@ -575,7 +577,7 @@ in { description = lib.mdDoc '' Declaratively provision Grafana's datasources. ''; - default = {}; + default = { }; type = submodule' { options.settings = mkOption { description = lib.mdDoc '' @@ -595,13 +597,13 @@ in { datasources = mkOption { description = lib.mdDoc "List of datasources to insert/update."; - default = []; + default = [ ]; type = types.listOf grafanaTypes.datasourceConfig; }; deleteDatasources = mkOption { description = lib.mdDoc "List of datasources that should be deleted from the database."; - default = []; + default = [ ]; type = types.listOf (types.submodule { options.name = mkOption { description = lib.mdDoc "Name of the datasource to delete."; @@ -650,7 +652,7 @@ in { description = lib.mdDoc '' Declaratively provision Grafana's dashboards. ''; - default = {}; + default = { }; type = submodule' { options.settings = mkOption { description = lib.mdDoc '' @@ -669,7 +671,7 @@ in { options.providers = mkOption { description = lib.mdDoc "List of dashboards to insert/update."; - default = []; + default = [ ]; type = types.listOf grafanaTypes.dashboardConfig; }; }); @@ -700,7 +702,7 @@ in { notifiers = mkOption { description = lib.mdDoc "Grafana notifier configuration."; - default = []; + default = [ ]; type = types.listOf grafanaTypes.notifierConfig; apply = x: map _filter x; }; @@ -736,7 +738,7 @@ in { groups = mkOption { description = lib.mdDoc "List of rule groups to import or update."; - default = []; + default = [ ]; type = types.listOf (types.submodule { freeformType = provisioningSettingsFormat.type; @@ -759,7 +761,7 @@ in { deleteRules = mkOption { description = lib.mdDoc "List of alert rule UIDs that should be deleted."; - default = []; + default = [ ]; type = types.listOf (types.submodule { options.orgId = mkOption { description = lib.mdDoc "Organization ID, default = 1"; @@ -860,7 +862,7 @@ in { contactPoints = mkOption { description = lib.mdDoc "List of contact points to import or update."; - default = []; + default = [ ]; type = types.listOf (types.submodule { freeformType = provisioningSettingsFormat.type; @@ -873,7 +875,7 @@ in { deleteContactPoints = mkOption { description = lib.mdDoc "List of receivers that should be deleted."; - default = []; + default = [ ]; type = types.listOf (types.submodule { options.orgId = mkOption { description = lib.mdDoc "Organization ID, default = 1."; @@ -941,7 +943,7 @@ in { policies = mkOption { description = lib.mdDoc "List of contact points to import or update."; - default = []; + default = [ ]; type = types.listOf (types.submodule { freeformType = provisioningSettingsFormat.type; }); @@ -949,7 +951,7 @@ in { resetPolicies = mkOption { description = lib.mdDoc "List of orgIds that should be reset to the default policy."; - default = []; + default = [ ]; type = types.listOf types.int; }; }; @@ -1011,7 +1013,7 @@ in { templates = mkOption { description = lib.mdDoc "List of templates to import or update."; - default = []; + default = [ ]; type = types.listOf (types.submodule { freeformType = provisioningSettingsFormat.type; @@ -1029,7 +1031,7 @@ in { deleteTemplates = mkOption { description = lib.mdDoc "List of alert rule UIDs that should be deleted."; - default = []; + default = [ ]; type = types.listOf (types.submodule { options.orgId = mkOption { description = lib.mdDoc "Organization ID, default = 1."; @@ -1093,7 +1095,7 @@ in { muteTimes = mkOption { description = lib.mdDoc "List of mute time intervals to import or update."; - default = []; + default = [ ]; type = types.listOf (types.submodule { freeformType = provisioningSettingsFormat.type; @@ -1106,7 +1108,7 @@ in { deleteMuteTimes = mkOption { description = lib.mdDoc "List of mute time intervals that should be deleted."; - default = []; + default = [ ]; type = types.listOf (types.submodule { options.orgId = mkOption { description = lib.mdDoc "Organization ID, default = 1."; @@ -1168,45 +1170,58 @@ in { }; config = mkIf cfg.enable { - warnings = let - doesntUseFileProvider = opt: defaultValue: - let - regex = "${optionalString (defaultValue != null) "^${defaultValue}$|"}^\\$__(file|env)\\{.*}$|^\\$[^_\\$][^ ]+$"; - in builtins.match regex opt == null; - in - # Ensure that no custom credentials are leaked into the Nix store. Unless the default value - # is specified, this can be achieved by using the file/env provider: - # https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#variable-expansion - (optional ( - doesntUseFileProvider cfg.settings.database.password "" || - doesntUseFileProvider cfg.settings.security.admin_password "admin" - ) '' - Grafana passwords will be stored as plaintext in the Nix store! - Use file provider or an env-var instead. - '') - # Warn about deprecated notifiers. - ++ (optional (cfg.provision.notifiers != []) '' - Notifiers are deprecated upstream and will be removed in Grafana 10. - Use `services.grafana.provision.alerting.contactPoints` instead. - '') - # Ensure that `secureJsonData` of datasources provisioned via `datasources.settings` - # only uses file/env providers. - ++ (optional ( - let - datasourcesToCheck = optionals - (cfg.provision.datasources.settings != null) - cfg.provision.datasources.settings.datasources; - declarationUnsafe = { secureJsonData, ... }: - secureJsonData != null - && any (flip doesntUseFileProvider null) (attrValues secureJsonData); - in any declarationUnsafe datasourcesToCheck - ) '' - Declarations in the `secureJsonData`-block of a datasource will be leaked to the - Nix store unless a file-provider or an env-var is used! - '') - ++ (optional ( - any (x: x.secure_settings != null) cfg.provision.notifiers - ) "Notifier secure settings will be stored as plaintext in the Nix store! Use file provider instead."); + warnings = + let + doesntUseFileProvider = opt: defaultValue: + let regex = "${optionalString (defaultValue != null) "^${defaultValue}$|"}^\\$__(file|env)\\{.*}$|^\\$[^_\\$][^ ]+$"; + in builtins.match regex opt == null; + + # Ensure that no custom credentials are leaked into the Nix store. Unless the default value + # is specified, this can be achieved by using the file/env provider: + # https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#variable-expansion + passwordWithoutFileProvider = optional + ( + doesntUseFileProvider cfg.settings.database.password "" || + doesntUseFileProvider cfg.settings.security.admin_password "admin" + ) + '' + Grafana passwords will be stored as plaintext in the Nix store! + Use file provider or an env-var instead. + ''; + + # Warn about deprecated notifiers. + deprecatedNotifiers = optional (cfg.provision.notifiers != [ ]) '' + Notifiers are deprecated upstream and will be removed in Grafana 10. + Use `services.grafana.provision.alerting.contactPoints` instead. + ''; + + # Ensure that `secureJsonData` of datasources provisioned via `datasources.settings` + # only uses file/env providers. + secureJsonDataWithoutFileProvider = optional + ( + let + datasourcesToCheck = optionals + (cfg.provision.datasources.settings != null) + cfg.provision.datasources.settings.datasources; + declarationUnsafe = { secureJsonData, ... }: + secureJsonData != null + && any (flip doesntUseFileProvider null) (attrValues secureJsonData); + in + any declarationUnsafe datasourcesToCheck + ) + '' + Declarations in the `secureJsonData`-block of a datasource will be leaked to the + Nix store unless a file-provider or an env-var is used! + ''; + + notifierSecureSettingsWithoutFileProvider = optional + (any (x: x.secure_settings != null) cfg.provision.notifiers) + "Notifier secure settings will be stored as plaintext in the Nix store! Use file provider instead."; + in + passwordWithoutFileProvider + ++ deprecatedNotifiers + ++ secureJsonDataWithoutFileProvider + ++ notifierSecureSettingsWithoutFileProvider; environment.systemPackages = [ cfg.package ]; @@ -1216,11 +1231,12 @@ in { message = "Cannot set both datasources settings and datasources path"; } { - assertion = let - prometheusIsNotDirect = opt: all - ({ type, access, ... }: type == "prometheus" -> access != "direct") - opt; - in + assertion = + let + prometheusIsNotDirect = opt: all + ({ type, access, ... }: type == "prometheus" -> access != "direct") + opt; + in cfg.provision.datasources.settings == null || prometheusIsNotDirect cfg.provision.datasources.settings.datasources; message = "For datasources of type `prometheus`, the `direct` access mode is not supported anymore (since Grafana 9.2.0)"; } @@ -1252,8 +1268,8 @@ in { systemd.services.grafana = { description = "Grafana Service Daemon"; - wantedBy = ["multi-user.target"]; - after = ["networking.target"] ++ lib.optional usePostgresql "postgresql.service" ++ lib.optional useMysql "mysql.service"; + wantedBy = [ "multi-user.target" ]; + after = [ "networking.target" ] ++ lib.optional usePostgresql "postgresql.service" ++ lib.optional useMysql "mysql.service"; script = '' set -o errexit -o pipefail -o nounset -o errtrace shopt -s inherit_errexit @@ -1309,6 +1325,6 @@ in { createHome = true; group = "grafana"; }; - users.groups.grafana = {}; + users.groups.grafana = { }; }; } From 5e4ec14596fc081a844947f4e0ce5bc7ade5e591 Mon Sep 17 00:00:00 2001 From: Joscha Date: Sat, 17 Jun 2023 23:52:23 +0200 Subject: [PATCH 2/2] nixos/grafana: update and add settings I went through all categories that were already present in the grafana module and added most options from the official docs at https://grafana.com/docs/grafana/v9.5/setup-grafana/configure-grafana/ I also modified the descriptions of some existing options to match the official docs more closely. --- nixos/modules/services/monitoring/grafana.nix | 577 +++++++++++++++++- 1 file changed, 556 insertions(+), 21 deletions(-) diff --git a/nixos/modules/services/monitoring/grafana.nix b/nixos/modules/services/monitoring/grafana.nix index 6706035cd5a5..1786b09e28af 100644 --- a/nixos/modules/services/monitoring/grafana.nix +++ b/nixos/modules/services/monitoring/grafana.nix @@ -378,17 +378,61 @@ in }; domain = mkOption { - description = lib.mdDoc "The public facing domain name used to access grafana from a browser."; + description = lib.mdDoc '' + The public facing domain name used to access grafana from a browser. + + This setting is only used in the default value of the `root_url` setting. + If you set the latter manually, this option does not have to be specified. + ''; default = "localhost"; type = types.str; }; + enforce_domain = mkOption { + description = lib.mdDoc '' + Redirect to correct domain if the host header does not match the domain. + Prevents DNS rebinding attacks. + ''; + default = false; + type = types.bool; + }; + root_url = mkOption { - description = lib.mdDoc "Full public facing url."; + description = lib.mdDoc '' + This is the full URL used to access Grafana from a web browser. + This is important if you use Google or GitHub OAuth authentication (for the callback URL to be correct). + + This setting is also important if you have a reverse proxy in front of Grafana that exposes it through a subpath. + In that case add the subpath to the end of this URL setting. + ''; + # https://github.com/grafana/grafana/blob/cb7e18938b8eb6860a64b91aaba13a7eb31bc95b/conf/defaults.ini#L54 default = "%(protocol)s://%(domain)s:%(http_port)s/"; type = types.str; }; + serve_from_sub_path = mkOption { + description = lib.mdDoc '' + Serve Grafana from subpath specified in the `root_url` setting. + By default it is set to `false` for compatibility reasons. + + By enabling this setting and using a subpath in `root_url` above, + e.g. `root_url = "http://localhost:3000/grafana"`, + Grafana is accessible on `http://localhost:3000/grafana`. + If accessed without subpath, Grafana will redirect to an URL with the subpath. + ''; + default = false; + type = types.bool; + }; + + router_logging = mkOption { + description = lib.mdDoc '' + Set to `true` for Grafana to log all HTTP requests (not just errors). + These are logged as Info level events to the Grafana log. + ''; + default = false; + type = types.bool; + }; + static_root_path = mkOption { description = lib.mdDoc "Root path for static assets."; default = "${cfg.package}/share/grafana/public"; @@ -398,30 +442,82 @@ in enable_gzip = mkOption { description = lib.mdDoc '' - Set this option to true to enable HTTP compression, this can improve transfer speed and bandwidth utilization. - It is recommended that most users set it to true. By default it is set to false for compatibility reasons. + Set this option to `true` to enable HTTP compression, this can improve transfer speed and bandwidth utilization. + It is recommended that most users set it to `true`. By default it is set to `false` for compatibility reasons. ''; default = false; type = types.bool; }; cert_file = mkOption { - description = lib.mdDoc "Cert file for ssl."; + description = lib.mdDoc '' + Path to the certificate file (if `protocol` is set to `https` or `h2`). + ''; default = ""; type = types.str; }; cert_key = mkOption { - description = lib.mdDoc "Cert key for ssl."; + description = lib.mdDoc '' + Path to the certificate key file (if `protocol` is set to `https` or `h2`). + ''; default = ""; type = types.str; }; + socket_gid = mkOption { + description = lib.mdDoc '' + GID where the socket should be set when `protocol=socket`. + Make sure that the target group is in the group of Grafana process and that Grafana process is the file owner before you change this setting. + It is recommended to set the gid as http server user gid. + Not set when the value is -1. + ''; + default = -1; + type = types.int; + }; + + socket_mode = mkOption { + description = lib.mdDoc '' + Mode where the socket should be set when `protocol=socket`. + Make sure that Grafana process is the file owner before you change this setting. + ''; + # I assume this value is interpreted as octal literal by grafana. + # If this was an int, people following tutorials or porting their + # old config could stumble across nix not having octal literals. + default = "0660"; + type = types.str; + }; + socket = mkOption { - description = lib.mdDoc "Path where the socket should be created when protocol=socket. Make sure that Grafana has appropriate permissions before you change this setting."; + description = lib.mdDoc '' + Path where the socket should be created when `protocol=socket`. + Make sure that Grafana has appropriate permissions before you change this setting. + ''; default = "/run/grafana/grafana.sock"; type = types.str; }; + + cdn_url = mkOption { + description = lib.mdDoc '' + Specify a full HTTP URL address to the root of your Grafana CDN assets. + Grafana will add edition and version paths. + + For example, given a cdn url like `https://cdn.myserver.com` + grafana will try to load a javascript file from `http://cdn.myserver.com/grafana-oss/7.4.0/public/build/app..js`. + ''; + default = ""; + type = types.str; + }; + + read_timeout = mkOption { + description = lib.mdDoc '' + Sets the maximum time using a duration format (5s/5m/5ms) + before timing out read of an incoming request and closing idle connections. + 0 means there is no timeout for reading the request. + ''; + default = "0"; + type = types.str; + }; }; database = { @@ -432,26 +528,33 @@ in }; host = mkOption { - description = lib.mdDoc "Database host."; + description = lib.mdDoc '' + Only applicable to MySQL or Postgres. + Includes IP or hostname and port or in case of Unix sockets the path to it. + For example, for MySQL running on the same host as Grafana: `host = "127.0.0.1:3306"` + or with Unix sockets: `host = "/var/run/mysqld/mysqld.sock"` + ''; default = "127.0.0.1:3306"; type = types.str; }; name = mkOption { - description = lib.mdDoc "Database name."; + description = lib.mdDoc "The name of the Grafana database."; default = "grafana"; type = types.str; }; user = mkOption { - description = lib.mdDoc "Database user."; + description = lib.mdDoc "The database user (not applicable for `sqlite3`)."; default = "root"; type = types.str; }; password = mkOption { description = lib.mdDoc '' - Database password. Please note that the contents of this option + The database user's password (not applicable for `sqlite3`). + + Please note that the contents of this option will end up in a world-readable Nix store. Use the file provider pointing at a reasonably secured file in the local filesystem to work around that. Look at the documentation for details: @@ -461,15 +564,144 @@ in type = types.str; }; + max_idle_conn = mkOption { + description = lib.mdDoc "The maximum number of connections in the idle connection pool."; + default = 2; + type = types.int; + }; + + max_open_conn = mkOption { + description = lib.mdDoc "The maximum number of open connections to the database."; + default = 0; # https://github.com/grafana/grafana/blob/cb7e18938b8eb6860a64b91aaba13a7eb31bc95b/conf/defaults.ini#L123-L124 + type = types.int; + }; + + conn_max_lifetime = mkOption { + description = lib.mdDoc '' + Sets the maximum amount of time a connection may be reused. + The default is 14400 (which means 14400 seconds or 4 hours). + For MySQL, this setting should be shorter than the `wait_timeout` variable. + ''; + default = 14400; + type = types.int; + }; + + locking_attempt_timeout_sec = mkOption { + description = lib.mdDoc '' + For `mysql`, if the `migrationLocking` feature toggle is set, + specify the time (in seconds) to wait before failing to lock the database for the migrations. + ''; + default = 0; + type = types.int; + }; + + log_queries = mkOption { + description = lib.mdDoc "Set to `true` to log the sql calls and execution times"; + default = false; + type = types.bool; + }; + + ssl_mode = mkOption { + description = lib.mdDoc '' + For Postgres, use either `disable`, `require` or `verify-full`. + For MySQL, use either `true`, `false`, or `skip-verify`. + ''; + default = "disable"; # https://github.com/grafana/grafana/blob/cb7e18938b8eb6860a64b91aaba13a7eb31bc95b/conf/defaults.ini#L134 + type = types.enum [ "disable" "require" "verify-full" "true" "false" "skip-verify" ]; + }; + + isolation_level = mkOption { + description = lib.mdDoc '' + Only the MySQL driver supports isolation levels in Grafana. + In case the value is empty, the driver's default isolation level is applied. + ''; + default = null; + type = types.nullOr (types.enum [ "READ-UNCOMMITTED" "READ-COMMITTED" "REPEATABLE-READ" "SERIALIZABLE" ]); + }; + + ca_cert_path = mkOption { + description = lib.mdDoc "The path to the CA certificate to use."; + default = ""; + type = types.str; + }; + + client_key_path = mkOption { + description = lib.mdDoc "The path to the client key. Only if server requires client authentication."; + default = ""; + type = types.str; + }; + + client_cert_path = mkOption { + description = lib.mdDoc "The path to the client cert. Only if server requires client authentication."; + default = ""; + type = types.str; + }; + + server_cert_name = mkOption { + description = lib.mdDoc '' + The common name field of the certificate used by the `mysql` or `postgres` server. + Not necessary if `ssl_mode` is set to `skip-verify`. + ''; + default = ""; + type = types.str; + }; + path = mkOption { - description = lib.mdDoc "Only applicable to sqlite3 database. The file path where the database will be stored."; + description = lib.mdDoc "Only applicable to `sqlite3` database. The file path where the database will be stored."; default = "${cfg.dataDir}/data/grafana.db"; defaultText = literalExpression ''"''${config.${opt.dataDir}}/data/grafana.db"''; type = types.path; }; + + cache_mode = mkOption { + description = lib.mdDoc '' + For `sqlite3` only. + [Shared cache](https://www.sqlite.org/sharedcache.html) setting used for connecting to the database. + ''; + default = "private"; + type = types.enum [ "private" "shared" ]; + }; + + wal = mkOption { + description = lib.mdDoc '' + For `sqlite3` only. + Setting to enable/disable [Write-Ahead Logging](https://sqlite.org/wal.html). + ''; + default = false; + type = types.bool; + }; + + query_retries = mkOption { + description = lib.mdDoc '' + This setting applies to `sqlite3` only and controls the number of times the system retries a query when the database is locked. + ''; + default = 0; + type = types.int; + }; + + transaction_retries = mkOption { + description = lib.mdDoc '' + This setting applies to `sqlite3` only and controls the number of times the system retries a transaction when the database is locked. + ''; + default = 5; + type = types.int; + }; + + # TODO Add "instrument_queries" option when upgrading to grafana 10.0 + # instrument_queries = mkOption { + # description = lib.mdDoc "Set to `true` to add metrics and tracing for database queries."; + # default = false; + # type = types.bool; + # }; }; security = { + disable_initial_admin_creation = mkOption { + description = lib.mdDoc "Disable creation of admin user on first start of Grafana."; + default = false; + type = types.bool; + }; + admin_user = mkOption { description = lib.mdDoc "Default admin username."; default = "admin"; @@ -488,6 +720,12 @@ in type = types.str; }; + admin_email = mkOption { + description = lib.mdDoc "The email of the default Grafana Admin, created on startup."; + default = "admin@localhost"; + type = types.str; + }; + secret_key = mkOption { description = lib.mdDoc '' Secret key used for signing. Please note that the contents of this option @@ -499,6 +737,139 @@ in default = "SW2YcwTIb9zpOOhoPsMm"; type = types.str; }; + + disable_gravatar = mkOption { + description = lib.mdDoc "Set to `true` to disable the use of Gravatar for user profile images."; + default = false; + type = types.bool; + }; + + data_source_proxy_whitelist = mkOption { + description = lib.mdDoc '' + Define a whitelist of allowed IP addresses or domains, with ports, + to be used in data source URLs with the Grafana data source proxy. + Format: `ip_or_domain:port` separated by spaces. + PostgreSQL, MySQL, and MSSQL data sources do not use the proxy and are therefore unaffected by this setting. + ''; + default = ""; + type = types.str; + }; + + disable_brute_force_login_protection = mkOption { + description = lib.mdDoc "Set to `true` to disable [brute force login protection](https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html#account-lockout)."; + default = false; + type = types.bool; + }; + + cookie_secure = mkOption { + description = lib.mdDoc "Set to `true` if you host Grafana behind HTTPS."; + default = false; + type = types.bool; + }; + + cookie_samesite = mkOption { + description = lib.mdDoc '' + Sets the `SameSite` cookie attribute and prevents the browser from sending this cookie along with cross-site requests. + The main goal is to mitigate the risk of cross-origin information leakage. + This setting also provides some protection against cross-site request forgery attacks (CSRF), + [read more about SameSite here](https://owasp.org/www-community/SameSite). + Using value `disabled` does not add any `SameSite` attribute to cookies. + ''; + default = "lax"; + type = types.enum [ "lax" "strict" "none" "disabled" ]; + }; + + allow_embedding = mkOption { + description = lib.mdDoc '' + When `false`, the HTTP header `X-Frame-Options: deny` will be set in Grafana HTTP responses + which will instruct browsers to not allow rendering Grafana in a ``, `