Some Loki deployments (e.g., behind a reverse proxy or Grafana Cloud) require HTTP Basic Authentication. This adds optional --loki-username and --loki-password flags (and corresponding env vars) to the lab-monitoring server, along with NixOS module options for secure credential management via systemd LoadCredential. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
174 lines
5.2 KiB
Nix
174 lines
5.2 KiB
Nix
{ config, lib, pkgs, ... }:
|
|
|
|
let
|
|
cfg = config.services.lab-monitoring;
|
|
|
|
mkHttpFlags = httpCfg: lib.concatStringsSep " " ([
|
|
"--transport http"
|
|
"--http-address '${httpCfg.address}'"
|
|
"--http-endpoint '${httpCfg.endpoint}'"
|
|
"--session-ttl '${httpCfg.sessionTTL}'"
|
|
] ++ lib.optionals (httpCfg.allowedOrigins != []) (
|
|
map (origin: "--allowed-origins '${origin}'") httpCfg.allowedOrigins
|
|
) ++ lib.optionals httpCfg.tls.enable [
|
|
"--tls-cert '${httpCfg.tls.certFile}'"
|
|
"--tls-key '${httpCfg.tls.keyFile}'"
|
|
]);
|
|
in
|
|
{
|
|
options.services.lab-monitoring = {
|
|
enable = lib.mkEnableOption "Lab Monitoring MCP server";
|
|
|
|
package = lib.mkPackageOption pkgs "lab-monitoring" { };
|
|
|
|
prometheusUrl = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "http://localhost:9090";
|
|
description = "Prometheus base URL.";
|
|
};
|
|
|
|
alertmanagerUrl = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "http://localhost:9093";
|
|
description = "Alertmanager base URL.";
|
|
};
|
|
|
|
lokiUrl = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
default = null;
|
|
description = "Loki base URL. When set, enables log query tools (query_logs, list_labels, list_label_values).";
|
|
};
|
|
|
|
lokiUsername = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
default = null;
|
|
description = "Username for Loki basic authentication.";
|
|
};
|
|
|
|
lokiPasswordFile = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.path;
|
|
default = null;
|
|
description = "Path to a file containing the password for Loki basic authentication. Recommended over storing secrets in the Nix store.";
|
|
};
|
|
|
|
enableSilences = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = false;
|
|
description = "Enable the create_silence tool (write operation, disabled by default).";
|
|
};
|
|
|
|
http = {
|
|
address = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "127.0.0.1:8084";
|
|
description = "HTTP listen address for the MCP server.";
|
|
};
|
|
|
|
endpoint = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "/mcp";
|
|
description = "HTTP endpoint path for MCP requests.";
|
|
};
|
|
|
|
allowedOrigins = lib.mkOption {
|
|
type = lib.types.listOf lib.types.str;
|
|
default = [ ];
|
|
description = "Allowed Origin headers for CORS.";
|
|
};
|
|
|
|
sessionTTL = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "30m";
|
|
description = "Session TTL for HTTP transport.";
|
|
};
|
|
|
|
tls = {
|
|
enable = lib.mkEnableOption "TLS for HTTP transport";
|
|
|
|
certFile = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.path;
|
|
default = null;
|
|
description = "Path to TLS certificate file.";
|
|
};
|
|
|
|
keyFile = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.path;
|
|
default = null;
|
|
description = "Path to TLS private key file.";
|
|
};
|
|
};
|
|
};
|
|
|
|
openFirewall = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = false;
|
|
description = "Whether to open the firewall for the MCP HTTP server.";
|
|
};
|
|
};
|
|
|
|
config = lib.mkIf cfg.enable {
|
|
assertions = [
|
|
{
|
|
assertion = !cfg.http.tls.enable || (cfg.http.tls.certFile != null && cfg.http.tls.keyFile != null);
|
|
message = "services.lab-monitoring.http.tls: both certFile and keyFile must be set when TLS is enabled";
|
|
}
|
|
];
|
|
|
|
systemd.services.lab-monitoring = {
|
|
description = "Lab Monitoring MCP Server";
|
|
wantedBy = [ "multi-user.target" ];
|
|
after = [ "network.target" ];
|
|
|
|
environment = {
|
|
PROMETHEUS_URL = cfg.prometheusUrl;
|
|
ALERTMANAGER_URL = cfg.alertmanagerUrl;
|
|
} // lib.optionalAttrs (cfg.lokiUrl != null) {
|
|
LOKI_URL = cfg.lokiUrl;
|
|
} // lib.optionalAttrs (cfg.lokiUsername != null) {
|
|
LOKI_USERNAME = cfg.lokiUsername;
|
|
};
|
|
|
|
script = let
|
|
httpFlags = mkHttpFlags cfg.http;
|
|
silenceFlag = lib.optionalString cfg.enableSilences "--enable-silences";
|
|
in ''
|
|
${lib.optionalString (cfg.lokiPasswordFile != null) ''
|
|
export LOKI_PASSWORD="$(< "$CREDENTIALS_DIRECTORY/loki-password")"
|
|
''}
|
|
exec ${cfg.package}/bin/lab-monitoring serve ${httpFlags} ${silenceFlag}
|
|
'';
|
|
|
|
serviceConfig = {
|
|
Type = "simple";
|
|
DynamicUser = true;
|
|
Restart = "on-failure";
|
|
RestartSec = "5s";
|
|
} // lib.optionalAttrs (cfg.lokiPasswordFile != null) {
|
|
LoadCredential = [ "loki-password:${cfg.lokiPasswordFile}" ];
|
|
} // {
|
|
# Hardening
|
|
NoNewPrivileges = true;
|
|
ProtectSystem = "strict";
|
|
ProtectHome = true;
|
|
PrivateTmp = true;
|
|
PrivateDevices = true;
|
|
ProtectKernelTunables = true;
|
|
ProtectKernelModules = true;
|
|
ProtectControlGroups = true;
|
|
RestrictNamespaces = true;
|
|
RestrictRealtime = true;
|
|
RestrictSUIDSGID = true;
|
|
MemoryDenyWriteExecute = true;
|
|
LockPersonality = true;
|
|
};
|
|
};
|
|
|
|
networking.firewall = lib.mkIf cfg.openFirewall (let
|
|
addressParts = lib.splitString ":" cfg.http.address;
|
|
port = lib.toInt (lib.last addressParts);
|
|
in {
|
|
allowedTCPPorts = [ port ];
|
|
});
|
|
};
|
|
}
|