diff options
author | Sandro <sandro.jaeckel@gmail.com> | 2022-08-17 15:20:45 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-08-17 15:20:45 +0200 |
commit | a9f3c22db5d0e73b3b3e65689bf879e8e8e46bc0 (patch) | |
tree | 9ee7040c4710c7c69ba4243b4bcc048b63be7586 | |
parent | 61a3fc5f308f130f15a8eb340be1a24d79f5008c (diff) | |
parent | 49da90755b3c9d9d94246c0cabefc5d5fbac9550 (diff) | |
download | nixlib-a9f3c22db5d0e73b3b3e65689bf879e8e8e46bc0.tar nixlib-a9f3c22db5d0e73b3b3e65689bf879e8e8e46bc0.tar.gz nixlib-a9f3c22db5d0e73b3b3e65689bf879e8e8e46bc0.tar.bz2 nixlib-a9f3c22db5d0e73b3b3e65689bf879e8e8e46bc0.tar.lz nixlib-a9f3c22db5d0e73b3b3e65689bf879e8e8e46bc0.tar.xz nixlib-a9f3c22db5d0e73b3b3e65689bf879e8e8e46bc0.tar.zst nixlib-a9f3c22db5d0e73b3b3e65689bf879e8e8e46bc0.zip |
Merge pull request #182382 from SuperSandro2000/portunus
-rw-r--r-- | maintainers/maintainer-list.nix | 6 | ||||
-rw-r--r-- | nixos/modules/module-list.nix | 1 | ||||
-rw-r--r-- | nixos/modules/services/misc/portunus.nix | 288 | ||||
-rw-r--r-- | nixos/modules/services/web-apps/dex.nix | 28 | ||||
-rw-r--r-- | pkgs/servers/portunus/default.nix | 31 | ||||
-rw-r--r-- | pkgs/top-level/all-packages.nix | 2 |
6 files changed, 350 insertions, 6 deletions
diff --git a/maintainers/maintainer-list.nix b/maintainers/maintainer-list.nix index a8c47dbdbfdf..f95592557a95 100644 --- a/maintainers/maintainer-list.nix +++ b/maintainers/maintainer-list.nix @@ -7956,6 +7956,12 @@ githubId = 31056089; name = "Tom Ho"; }; + majewsky = { + email = "majewsky@gmx.net"; + github = "majewsky"; + githubId = 24696; + name = "Stefan Majewsky"; + }; majiir = { email = "majiir@nabaal.net"; github = "Majiir"; diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 8a8df700330e..82c4d69a7880 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -620,6 +620,7 @@ ./services/misc/plikd.nix ./services/misc/podgrab.nix ./services/misc/polaris.nix + ./services/misc/portunus.nix ./services/misc/prowlarr.nix ./services/misc/tautulli.nix ./services/misc/pinnwand.nix diff --git a/nixos/modules/services/misc/portunus.nix b/nixos/modules/services/misc/portunus.nix new file mode 100644 index 000000000000..a2247272fa26 --- /dev/null +++ b/nixos/modules/services/misc/portunus.nix @@ -0,0 +1,288 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.portunus; + +in +{ + options.services.portunus = { + enable = mkEnableOption "Portunus, a self-contained user/group management and authentication service for LDAP"; + + domain = mkOption { + type = types.str; + example = "sso.example.com"; + description = "Subdomain which gets reverse proxied to Portunus webserver."; + }; + + port = mkOption { + type = types.port; + default = 8080; + description = '' + Port where the Portunus webserver should listen on. + + This must be put behind a TLS-capable reverse proxy because Portunus only listens on localhost. + ''; + }; + + package = mkOption { + type = types.package; + default = pkgs.portunus; + defaultText = "pkgs.portunus"; + description = "The Portunus package to use."; + }; + + seedPath = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + Path to a portunus seed file in json format. + See <link xlink:href="https://github.com/majewsky/portunus#seeding-users-and-groups-from-static-configuration"/> for available options. + ''; + }; + + stateDir = mkOption { + type = types.path; + default = "/var/lib/portunus"; + description = "Path where Portunus stores its state."; + }; + + user = mkOption { + type = types.str; + default = "portunus"; + description = "User account under which Portunus runs its webserver."; + }; + + group = mkOption { + type = types.str; + default = "portunus"; + description = "Group account under which Portunus runs its webserver."; + }; + + dex = { + enable = mkEnableOption '' + Dex ldap connector. + + To activate dex, first a search user must be created in the Portunus web ui + and then the password must to be set as the <literal>DEX_SEARCH_USER_PASSWORD</literal> environment variable + in the <xref linkend="opt-services.dex.environmentFile"/> setting. + ''; + + oidcClients = mkOption { + type = types.listOf (types.submodule { + options = { + callbackURL = mkOption { + type = types.str; + description = "URL where the OIDC client should redirect"; + }; + id = mkOption { + type = types.str; + description = "ID of the OIDC client"; + }; + }; + }); + default = [ ]; + example = [ + { + callbackURL = "https://example.com/client/oidc/callback"; + id = "service"; + } + ]; + description = '' + List of OIDC clients. + + The OIDC secret must be set as the <literal>DEX_CLIENT_''${id}</literal> environment variable + in the <xref linkend="opt-services.dex.environmentFile"/> setting. + ''; + }; + + port = mkOption { + type = types.port; + default = 5556; + description = "Port where dex should listen on."; + }; + }; + + ldap = { + package = mkOption { + type = types.package; + default = pkgs.openldap; + defaultText = "pkgs.openldap"; + description = "The OpenLDAP package to use."; + }; + + searchUserName = mkOption { + type = types.str; + default = ""; + example = "admin"; + description = '' + The login name of the search user. + This user account must be configured in Portunus either manually or via seeding. + ''; + }; + + suffix = mkOption { + type = types.str; + example = "dc=example,dc=org"; + description = '' + The DN of the topmost entry in your LDAP directory. + Please refer to the Portunus documentation for more information on how this impacts the structure of the LDAP directory. + ''; + }; + + tls = mkOption { + type = types.bool; + default = false; + description = '' + Wether to enable LDAPS protocol. + This also adds two entries to the <literal>/etc/hosts</literal> file to point <xref linkend="opt-services.portunus.domain"/> to localhost, + so that CLIs and programs can use ldaps protocol and verify the certificate without opening the firewall port for the protocol. + + This requires a TLS certificate for <xref linkend="opt-services.portunus.domain"/> to be configured via <xref linkend="opt-security.acme.certs"/>. + ''; + }; + + user = mkOption { + type = types.str; + default = "openldap"; + description = "User account under which Portunus runs its LDAP server."; + }; + + group = mkOption { + type = types.str; + default = "openldap"; + description = "Group account under which Portunus runs its LDAP server."; + }; + }; + }; + + config = mkIf cfg.enable { + assertions = [ + { + assertion = cfg.dex.enable -> cfg.ldap.searchUserName != ""; + message = "services.portunus.dex.enable requires services.portunus.ldap.searchUserName to be set."; + } + ]; + + # add ldapsearch(1) etc. to interactive shells + environment.systemPackages = [ cfg.ldap.package ]; + + # allow connecting via ldaps /w certificate without opening ports + networking.hosts = mkIf cfg.ldap.tls { + "::1" = [ cfg.domain ]; + "127.0.0.1" = [ cfg.domain ]; + }; + + services.dex = mkIf cfg.dex.enable { + enable = true; + settings = { + issuer = "https://${cfg.domain}/dex"; + web.http = "127.0.0.1:${toString cfg.dex.port}"; + storage = { + type = "sqlite3"; + config.file = "/var/lib/dex/dex.db"; + }; + enablePasswordDB = false; + connectors = [{ + type = "ldap"; + id = "ldap"; + name = "LDAP"; + config = { + host = "${cfg.domain}:636"; + bindDN = "uid=${cfg.ldap.searchUserName},ou=users,${cfg.ldap.suffix}"; + bindPW = "$DEX_SEARCH_USER_PASSWORD"; + userSearch = { + baseDN = "ou=users,${cfg.ldap.suffix}"; + filter = "(objectclass=person)"; + username = "uid"; + idAttr = "uid"; + emailAttr = "mail"; + nameAttr = "cn"; + preferredUsernameAttr = "uid"; + }; + groupSearch = { + baseDN = "ou=groups,${cfg.ldap.suffix}"; + filter = "(objectclass=groupOfNames)"; + nameAttr = "cn"; + userMatchers = [{ userAttr = "DN"; groupAttr = "member"; }]; + }; + }; + }]; + + staticClients = forEach cfg.dex.oidcClients (client: { + inherit (client) id; + redirectURIs = [ client.callbackURI ]; + name = "OIDC for ${client.id}"; + secret = "$DEX_CLIENT_${client.id}"; + }); + }; + }; + + systemd.services = { + dex.serviceConfig = mkIf cfg.dex.enable { + # `dex.service` is super locked down out of the box, but we need some + # place to write the SQLite database. This creates $STATE_DIRECTORY below + # /var/lib/private because DynamicUser=true, but it gets symlinked into + # /var/lib/dex inside the unit + StateDirectory = "dex"; + }; + + portunus = { + description = "Self-contained authentication service"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + serviceConfig.ExecStart = "${cfg.package.out}/bin/portunus-orchestrator"; + environment = { + PORTUNUS_LDAP_SUFFIX = cfg.ldap.suffix; + PORTUNUS_SERVER_BINARY = "${cfg.package}/bin/portunus-server"; + PORTUNUS_SERVER_GROUP = cfg.group; + PORTUNUS_SERVER_USER = cfg.user; + PORTUNUS_SERVER_HTTP_LISTEN = "[::]:${toString cfg.port}"; + PORTUNUS_SERVER_STATE_DIR = cfg.stateDir; + PORTUNUS_SLAPD_BINARY = "${cfg.ldap.package}/libexec/slapd"; + PORTUNUS_SLAPD_GROUP = cfg.ldap.group; + PORTUNUS_SLAPD_USER = cfg.ldap.user; + PORTUNUS_SLAPD_SCHEMA_DIR = "${cfg.ldap.package}/etc/schema"; + } // (optionalAttrs (cfg.seedPath != null) ({ + PORTUNUS_SEED_PATH = cfg.seedPath; + })) // (optionalAttrs cfg.ldap.tls ( + let + acmeDirectory = config.security.acme.certs."${cfg.domain}".directory; + in + { + PORTUNUS_SLAPD_TLS_CA_CERTIFICATE = "/etc/ssl/certs/ca-certificates.crt"; + PORTUNUS_SLAPD_TLS_CERTIFICATE = "${acmeDirectory}/cert.pem"; + PORTUNUS_SLAPD_TLS_DOMAIN_NAME = cfg.domain; + PORTUNUS_SLAPD_TLS_PRIVATE_KEY = "${acmeDirectory}/key.pem"; + })); + }; + }; + + users.users = mkMerge [ + (mkIf (cfg.ldap.user == "openldap") { + openldap = { + group = cfg.ldap.group; + isSystemUser = true; + }; + }) + (mkIf (cfg.user == "portunus") { + portunus = { + group = cfg.group; + isSystemUser = true; + }; + }) + ]; + + users.groups = mkMerge [ + (mkIf (cfg.ldap.user == "openldap") { + openldap = { }; + }) + (mkIf (cfg.user == "portunus") { + portunus = { }; + }) + ]; + }; + + meta.maintainers = [ majewsky ] ++ teams.c3d2.members; +} diff --git a/nixos/modules/services/web-apps/dex.nix b/nixos/modules/services/web-apps/dex.nix index eebf4b740c77..82fdcd212f96 100644 --- a/nixos/modules/services/web-apps/dex.nix +++ b/nixos/modules/services/web-apps/dex.nix @@ -11,15 +11,26 @@ let settingsFormat = pkgs.formats.yaml {}; configFile = settingsFormat.generate "config.yaml" filteredSettings; - startPreScript = pkgs.writeShellScript "dex-start-pre" ('' - '' + (concatStringsSep "\n" (builtins.map (file: '' - ${pkgs.replace-secret}/bin/replace-secret '${file}' '${file}' /run/dex/config.yaml - '') secretFiles))); + startPreScript = pkgs.writeShellScript "dex-start-pre" + (concatStringsSep "\n" (map (file: '' + replace-secret '${file}' '${file}' /run/dex/config.yaml + '') + secretFiles)); in { options.services.dex = { enable = mkEnableOption "the OpenID Connect and OAuth2 identity provider"; + environmentFile = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + Environment file (see <literal>systemd.exec(5)</literal> + "EnvironmentFile=" section for the syntax) to define variables for dex. + This option can be used to safely include secret keys into the dex configuration. + ''; + }; + settings = mkOption { type = settingsFormat.type; default = {}; @@ -48,6 +59,9 @@ in description = lib.mdDoc '' The available options can be found in [the example configuration](https://github.com/dexidp/dex/blob/v${pkgs.dex.version}/config.yaml.dist). + + It's also possible to refer to environment variables (defined in [services.dex.environmentFile](#opt-services.dex.environmentFile)) + using the syntax `$VARIABLE_NAME`. ''; }; }; @@ -57,15 +71,15 @@ in description = "dex identity provider"; wantedBy = [ "multi-user.target" ]; after = [ "networking.target" ] ++ (optional (cfg.settings.storage.type == "postgres") "postgresql.service"); - + path = with pkgs; [ replace-secret ]; serviceConfig = { ExecStart = "${pkgs.dex-oidc}/bin/dex serve /run/dex/config.yaml"; ExecStartPre = [ "${pkgs.coreutils}/bin/install -m 600 ${configFile} /run/dex/config.yaml" "+${startPreScript}" ]; - RuntimeDirectory = "dex"; + RuntimeDirectory = "dex"; AmbientCapabilities = "CAP_NET_BIND_SERVICE"; BindReadOnlyPaths = [ "/nix/store" @@ -109,6 +123,8 @@ in TemporaryFileSystem = "/:ro"; # Does not work well with the temporary root #UMask = "0066"; + } // optionalAttrs (cfg.environmentFile != null) { + EnvironmentFile = cfg.environmentFile; }; }; }; diff --git a/pkgs/servers/portunus/default.nix b/pkgs/servers/portunus/default.nix new file mode 100644 index 000000000000..c0ee915c7bbe --- /dev/null +++ b/pkgs/servers/portunus/default.nix @@ -0,0 +1,31 @@ +{ lib +, buildGoModule +, fetchFromGitHub +}: + +buildGoModule rec { + pname = "portunus"; + version = "1.1.0-beta.2"; + + src = fetchFromGitHub { + owner = "majewsky"; + repo = "portunus"; + rev = "v${version}"; + sha256 = "sha256-hGOMbaEWecgQvpk/2E8mcJZ9QMjllIhS3RBr7PKnbjQ="; + }; + + vendorSha256 = null; + + postInstall = '' + mv $out/bin/{,portunus-}orchestrator + mv $out/bin/{,portunus-}server + ''; + + meta = with lib; { + description = "Self-contained user/group management and authentication service"; + homepage = "https://github.com/majewsky/portunus"; + license = licenses.gpl3Plus; + platforms = platforms.linux; + maintainers = with maintainers; [ majewsky ] ++ teams.c3d2.members; + }; +} diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index fda2ba998a15..c8851d98c396 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -22437,6 +22437,8 @@ with pkgs; podgrab = callPackage ../servers/misc/podgrab { }; + portunus = callPackage ../servers/portunus { }; + prosody = callPackage ../servers/xmpp/prosody { withExtraLibs = []; withExtraLuaPackages = _: []; |