diff options
Diffstat (limited to 'nixos/modules/services/web-servers/pomerium.nix')
-rw-r--r-- | nixos/modules/services/web-servers/pomerium.nix | 131 |
1 files changed, 131 insertions, 0 deletions
diff --git a/nixos/modules/services/web-servers/pomerium.nix b/nixos/modules/services/web-servers/pomerium.nix new file mode 100644 index 00000000000..2bc7d01c7c2 --- /dev/null +++ b/nixos/modules/services/web-servers/pomerium.nix @@ -0,0 +1,131 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + format = pkgs.formats.yaml {}; +in +{ + options.services.pomerium = { + enable = mkEnableOption "the Pomerium authenticating reverse proxy"; + + configFile = mkOption { + type = with types; nullOr path; + default = null; + description = "Path to Pomerium config YAML. If set, overrides services.pomerium.settings."; + }; + + useACMEHost = mkOption { + type = with types; nullOr str; + default = null; + description = '' + If set, use a NixOS-generated ACME certificate with the specified name. + + Note that this will require you to use a non-HTTP-based challenge, or + disable Pomerium's in-built HTTP redirect server by setting + http_redirect_addr to null and use a different HTTP server for serving + the challenge response. + + If you're using an HTTP-based challenge, you should use the + Pomerium-native autocert option instead. + ''; + }; + + settings = mkOption { + description = '' + The contents of Pomerium's config.yaml, in Nix expressions. + + Specifying configFile will override this in its entirety. + + See <link xlink:href="https://pomerium.io/reference/">the Pomerium + configuration reference</link> for more information about what to put + here. + ''; + default = {}; + type = format.type; + }; + + secretsFile = mkOption { + type = with types; nullOr path; + default = null; + description = '' + Path to file containing secrets for Pomerium, in systemd + EnvironmentFile format. See the systemd.exec(5) man page. + ''; + }; + }; + + config = let + cfg = config.services.pomerium; + cfgFile = if cfg.configFile != null then cfg.configFile else (format.generate "pomerium.yaml" cfg.settings); + in mkIf cfg.enable ({ + systemd.services.pomerium = { + description = "Pomerium authenticating reverse proxy"; + wants = [ "network.target" ] ++ (optional (cfg.useACMEHost != null) "acme-finished-${cfg.useACMEHost}.target"); + after = [ "network.target" ] ++ (optional (cfg.useACMEHost != null) "acme-finished-${cfg.useACMEHost}.target"); + wantedBy = [ "multi-user.target" ]; + environment = optionalAttrs (cfg.useACMEHost != null) { + CERTIFICATE_FILE = "fullchain.pem"; + CERTIFICATE_KEY_FILE = "key.pem"; + }; + startLimitIntervalSec = 60; + + serviceConfig = { + DynamicUser = true; + StateDirectory = [ "pomerium" ]; + ExecStart = "${pkgs.pomerium}/bin/pomerium -config ${cfgFile}"; + + PrivateUsers = false; # breaks CAP_NET_BIND_SERVICE + MemoryDenyWriteExecute = false; # breaks LuaJIT + + NoNewPrivileges = true; + PrivateTmp = true; + PrivateDevices = true; + DevicePolicy = "closed"; + ProtectSystem = "strict"; + ProtectHome = true; + ProtectControlGroups = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectKernelLogs = true; + RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK"; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + LockPersonality = true; + SystemCallArchitectures = "native"; + + EnvironmentFile = cfg.secretsFile; + AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ]; + CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ]; + + WorkingDirectory = mkIf (cfg.useACMEHost != null) "$CREDENTIALS_DIRECTORY"; + LoadCredential = optionals (cfg.useACMEHost != null) [ + "fullchain.pem:/var/lib/acme/${cfg.useACMEHost}/fullchain.pem" + "key.pem:/var/lib/acme/${cfg.useACMEHost}/key.pem" + ]; + }; + }; + + # postRun hooks on cert renew can't be used to restart Nginx since renewal + # runs as the unprivileged acme user. sslTargets are added to wantedBy + before + # which allows the acme-finished-$cert.target to signify the successful updating + # of certs end-to-end. + systemd.services.pomerium-config-reload = mkIf (cfg.useACMEHost != null) { + # TODO(lukegb): figure out how to make config reloading work with credentials. + + wantedBy = [ "acme-finished-${cfg.useACMEHost}.target" "multi-user.target" ]; + # Before the finished targets, after the renew services. + before = [ "acme-finished-${cfg.useACMEHost}.target" ]; + after = [ "acme-${cfg.useACMEHost}.service" ]; + # Block reloading if not all certs exist yet. + unitConfig.ConditionPathExists = [ "${config.security.acme.certs.${cfg.useACMEHost}.directory}/fullchain.pem" ]; + serviceConfig = { + Type = "oneshot"; + TimeoutSec = 60; + ExecCondition = "/run/current-system/systemd/bin/systemctl -q is-active pomerium.service"; + ExecStart = "/run/current-system/systemd/bin/systemctl restart pomerium.service"; + }; + }; + }); +} |