diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 82eec40ecf1..4d29662e61a 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -696,6 +696,7 @@ ./services/monitoring/arbtt.nix ./services/monitoring/bosun.nix ./services/monitoring/cadvisor.nix + ./services/monitoring/cockpit.nix ./services/monitoring/collectd.nix ./services/monitoring/das_watchdog.nix ./services/monitoring/datadog-agent.nix diff --git a/nixos/modules/services/monitoring/cockpit.nix b/nixos/modules/services/monitoring/cockpit.nix new file mode 100644 index 00000000000..2947b4d8012 --- /dev/null +++ b/nixos/modules/services/monitoring/cockpit.nix @@ -0,0 +1,231 @@ +{ pkgs, config, lib, ... }: + +let + cfg = config.services.cockpit; + inherit (lib) types mkEnableOption mkOption mkIf mdDoc literalMD mkPackageOptionMD; + settingsFormat = pkgs.formats.ini {}; +in { + options = { + services.cockpit = { + enable = mkEnableOption (mdDoc "Cockpit"); + + package = mkPackageOptionMD pkgs "Cockpit" { + default = [ "cockpit" ]; + }; + + settings = lib.mkOption { + type = settingsFormat.type; + + default = {}; + + description = mdDoc '' + Settings for cockpit that will be saved in /etc/cockpit/cockpit.conf. + + See the [documentation](https://cockpit-project.org/guide/latest/cockpit.conf.5.html), that is also available with `man cockpit.conf.5` for details. + ''; + }; + + port = mkOption { + description = mdDoc "Port where cockpit will listen."; + type = types.port; + default = 9090; + }; + + openFirewall = mkOption { + description = mdDoc "Open port for cockpit."; + type = types.bool; + default = false; + }; + }; + }; + config = mkIf cfg.enable { + + # expose cockpit-bridge system-wide + environment.systemPackages = [ cfg.package ]; + + # allow cockpit to find its plugins + environment.pathsToLink = [ "/share/cockpit" ]; + + # generate cockpit settings + environment.etc."cockpit/cockpit.conf".source = settingsFormat.generate "cockpit.conf" cfg.settings; + + security.pam.services.cockpit = {}; + + networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.port ]; + + # units are in reverse sort order if you ls $out/lib/systemd/system + # all these units are basically verbatim translated from upstream + + # Translation from $out/lib/systemd/system/systemd-cockpithttps.slice + systemd.slices.system-cockpithttps = { + description = "Resource limits for all cockpit-ws-https@.service instances"; + sliceConfig = { + TasksMax = 200; + MemoryHigh = "75%"; + MemoryMax = "90%"; + }; + }; + + # Translation from $out/lib/systemd/system/cockpit-wsinstance-https@.socket + systemd.sockets."cockpit-wsinstance-https@" = { + unitConfig = { + Description = "Socket for Cockpit Web Service https instance %I"; + BindsTo = [ "cockpit.service" "cockpit-wsinstance-https@%i.service" ]; + # clean up the socket after the service exits, to prevent fd leak + # this also effectively prevents a DoS by starting arbitrarily many sockets, as + # the services are resource-limited by system-cockpithttps.slice + Documentation = "man:cockpit-ws(8)"; + }; + socketConfig = { + ListenStream = "/run/cockpit/wsinstance/https@%i.sock"; + SocketUser = "root"; + SocketMode = "0600"; + }; + }; + + # Translation from $out/lib/systemd/system/cockpit-wsinstance-https@.service + systemd.services."cockpit-wsinstance-https@" = { + description = "Cockpit Web Service https instance %I"; + bindsTo = [ "cockpit.service"]; + path = [ cfg.package ]; + documentation = [ "man:cockpit-ws(8)" ]; + serviceConfig = { + Slice = "system-cockpithttps.slice"; + ExecStart = "${cfg.package}/libexec/cockpit-ws --for-tls-proxy --port=0"; + User = "root"; + Group = ""; + }; + }; + + # Translation from $out/lib/systemd/system/cockpit-wsinstance-http.socket + systemd.sockets.cockpit-wsinstance-http = { + unitConfig = { + Description = "Socket for Cockpit Web Service http instance"; + BindsTo = "cockpit.service"; + Documentation = "man:cockpit-ws(8)"; + }; + socketConfig = { + ListenStream = "/run/cockpit/wsinstance/http.sock"; + SocketUser = "root"; + SocketMode = "0600"; + }; + }; + + # Translation from $out/lib/systemd/system/cockpit-wsinstance-https-factory.socket + systemd.sockets.cockpit-wsinstance-https-factory = { + unitConfig = { + Description = "Socket for Cockpit Web Service https instance factory"; + BindsTo = "cockpit.service"; + Documentation = "man:cockpit-ws(8)"; + }; + socketConfig = { + ListenStream = "/run/cockpit/wsinstance/https-factory.sock"; + Accept = true; + SocketUser = "root"; + SocketMode = "0600"; + }; + }; + + # Translation from $out/lib/systemd/system/cockpit-wsinstance-https-factory@.service + systemd.services."cockpit-wsinstance-https-factory@" = { + description = "Cockpit Web Service https instance factory"; + documentation = [ "man:cockpit-ws(8)" ]; + path = [ cfg.package ]; + serviceConfig = { + ExecStart = "${cfg.package}/libexec/cockpit-wsinstance-factory"; + User = "root"; + }; + }; + + # Translation from $out/lib/systemd/system/cockpit-wsinstance-http.service + systemd.services."cockpit-wsinstance-http" = { + description = "Cockpit Web Service http instance"; + bindsTo = [ "cockpit.service" ]; + path = [ cfg.package ]; + documentation = [ "man:cockpit-ws(8)" ]; + serviceConfig = { + ExecStart = "${cfg.package}/libexec/cockpit-ws --no-tls --port=0"; + User = "root"; + Group = ""; + }; + }; + + # Translation from $out/lib/systemd/system/cockpit.socket + systemd.sockets."cockpit" = { + unitConfig = { + Description = "Cockpit Web Service Socket"; + Documentation = "man:cockpit-ws(8)"; + Wants = "cockpit-motd.service"; + }; + socketConfig = { + ListenStream = cfg.port; + ExecStartPost = [ + "-${cfg.package}/share/cockpit/motd/update-motd \"\" localhost" + "-${pkgs.coreutils}/bin/ln -snf active.motd /run/cockpit/motd" + ]; + ExecStopPost = "-${pkgs.coreutils}/bin/ln -snf inactive.motd /run/cockpit/motd"; + }; + wantedBy = [ "sockets.target" ]; + }; + + # Translation from $out/lib/systemd/system/cockpit.service + systemd.services."cockpit" = { + description = "Cockpit Web Service"; + documentation = [ "man:cockpit-ws(8)" ]; + restartIfChanged = true; + path = with pkgs; [ coreutils cfg.package ]; + requires = [ "cockpit.socket" "cockpit-wsinstance-http.socket" "cockpit-wsinstance-https-factory.socket" ]; + after = [ "cockpit-wsinstance-http.socket" "cockpit-wsinstance-https-factory.socket" ]; + environment = { + G_MESSAGES_DEBUG = "cockpit-ws,cockpit-bridge"; + }; + serviceConfig = { + RuntimeDirectory="cockpit/tls"; + ExecStartPre = [ + # cockpit-tls runs in a more constrained environment, these + means that these commands + # will run with full privilege instead of inside that constrained environment + # See https://www.freedesktop.org/software/systemd/man/systemd.service.html#ExecStart= for details + "+${cfg.package}/libexec/cockpit-certificate-ensure --for-cockpit-tls" + ]; + ExecStart = "${cfg.package}/libexec/cockpit-tls"; + User = "root"; + Group = ""; + NoNewPrivileges = true; + ProtectSystem = "strict"; + ProtectHome = true; + PrivateTmp = true; + PrivateDevices = true; + ProtectKernelTunables = true; + RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ]; + MemoryDenyWriteExecute = true; + }; + }; + + # Translation from $out/lib/systemd/system/cockpit-motd.service + # This part basically implements a motd state machine: + # - If cockpit.socket is enabled then /run/cockpit/motd points to /run/cockpit/active.motd + # - If cockpit.socket is disabled then /run/cockpit/motd points to /run/cockpit/inactive.motd + # - As cockpit.socket is disabled by default, /run/cockpit/motd points to /run/cockpit/inactive.motd + # /run/cockpit/active.motd is generated dynamically by cockpit-motd.service + systemd.services."cockpit-motd" = { + path = with pkgs; [ nettools ]; + serviceConfig = { + Type = "oneshot"; + ExecStart = "${cfg.package}/share/cockpit/motd/update-motd"; + }; + description = "Cockpit motd updater service"; + documentation = [ "man:cockpit-ws(8)" ]; + wants = [ "network.target" ]; + after = [ "network.target" "cockpit.socket" ]; + }; + + systemd.tmpfiles.rules = [ # From $out/lib/tmpfiles.d/cockpit-tmpfiles.conf + "C /run/cockpit/inactive.motd 0640 root root - ${cfg.package}/share/cockpit/motd/inactive.motd" + "f /run/cockpit/active.motd 0640 root root -" + "L+ /run/cockpit/motd - - - - inactive.motd" + "d /etc/cockpit/ws-certs.d 0600 root root 0" + ]; + }; + + meta.maintainers = pkgs.cockpit.meta.maintainers; +}