3
0
Fork 0
forked from mirrors/nixpkgs

Merge pull request #182382 from SuperSandro2000/portunus

This commit is contained in:
Sandro 2022-08-17 15:20:45 +02:00 committed by GitHub
commit a9f3c22db5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 350 additions and 6 deletions

View file

@ -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";

View file

@ -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

View file

@ -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;
}

View file

@ -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;
};
};
};

View file

@ -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;
};
}

View file

@ -22437,6 +22437,8 @@ with pkgs;
podgrab = callPackage ../servers/misc/podgrab { };
portunus = callPackage ../servers/portunus { };
prosody = callPackage ../servers/xmpp/prosody {
withExtraLibs = [];
withExtraLuaPackages = _: [];