-
Notifications
You must be signed in to change notification settings - Fork 0
/
ldap-server.nix
191 lines (162 loc) · 6.43 KB
/
ldap-server.nix
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# This module deploys an LDAP server using Portunus.
{ config, pkgs, lib, options, ... }:
with lib;
let
cfg = config.my.services.portunus;
internalListenPorts = { portunus = 18693; dex = 18694; };
oidcClients = [
{
id = "matrix-synapse";
callbackURI = "https://${config.services.matrix-synapse.settings.server_name}/_synapse/client/oidc/callback";
}
];
dstRootCA_X3 = pkgs.writeText "dst-root-ca-x3.pem" ''
-----BEGIN CERTIFICATE-----
MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/
MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow
PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD
Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O
rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq
OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b
xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw
7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD
aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV
HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG
SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69
ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr
AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz
R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5
JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo
Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ
-----END CERTIFICATE-----
'';
in {
imports = [
/nix/my/unpacked/generated-oidc-config.nix
];
options.my.services.portunus = {
domainName = mkOption {
default = null;
description = "domain name for Portunus (must be given to enable the service)";
example = "sso.example.org";
type = types.nullOr types.str;
};
ldapDomainName = mkOption {
description = "domain name for the LDAP server";
example = "example.org";
type = types.str;
};
ldapSuffix = mkOption {
description = "DN of the topmost entry in the LDAP directory";
example = "dc=example,dc=org";
type = types.str;
};
};
options.my.services.oidc.clientSecrets = let
mkPair = client: (nameValuePair client.id (mkOption {
description = "client secret for OIDC client ${client.id}";
type = types.str;
}));
in listToAttrs (map mkPair oidcClients);
config = mkIf (cfg.domainName != null) {
############################################################################
# Portunus
services.portunus = {
enable = true;
port = internalListenPorts.portunus;
ldap.suffix = cfg.ldapSuffix;
};
systemd.services.portunus.after = [
# in my setup, Portunus will fail to start up unless the WireGuard network
# is already up, because ldap-client.nix overrides the LDAP server's domain
# name to refer to an address on the WireGuard network
"wireguard-wg-monitoring.service"
];
services.nginx.virtualHosts.${cfg.domainName} = {
forceSSL = true;
enableACME = true;
locations."/".proxyPass = "http://127.0.0.1:${toString internalListenPorts.portunus}";
locations."/dex/".proxyPass = "http://127.0.0.1:${toString internalListenPorts.dex}/dex/";
};
############################################################################
# LDAPS
# to get an ACME cert, we need to add a dummy vhost to the nginx config
services.nginx.virtualHosts.${cfg.ldapDomainName} = mkDefault {
forceSSL = true;
enableACME = true;
locations."/".extraConfig = ''
deny all;
return 404;
'';
};
security.acme.certs.${cfg.ldapDomainName}.postRun = ''
cat ${dstRootCA_X3} chain.pem > complete-chain.pem
systemctl restart portunus.service
'';
systemd.services.portunus.environment = let
acmeDirectory = "/var/lib/acme/${cfg.ldapDomainName}";
in {
PORTUNUS_SLAPD_TLS_CA_CERTIFICATE = "${acmeDirectory}/complete-chain.pem";
PORTUNUS_SLAPD_TLS_CERTIFICATE = "${acmeDirectory}/cert.pem";
PORTUNUS_SLAPD_TLS_DOMAIN_NAME = cfg.ldapDomainName;
PORTUNUS_SLAPD_TLS_PRIVATE_KEY = "${acmeDirectory}/key.pem";
};
# allow access to LDAPS port
networking.firewall.interfaces.wg-monitoring.allowedTCPPorts = [ 636 ];
############################################################################
# OIDC with Dex
services.dex.enable = true;
services.dex.settings = {
# HTTP config
issuer = "https://${cfg.domainName}/dex";
web.http = "127.0.0.1:${toString internalListenPorts.dex}";
# storage backend
storage = {
type = "sqlite3";
config.file = "/var/lib/dex/dex.db";
};
# connectors
enablePasswordDB = false;
connectors = [{
type = "ldap";
id = "ldap";
name = "LDAP";
config = let clientCfg = config.my.ldap; in {
host = "${clientCfg.domainName}:636";
bindDN = "uid=${clientCfg.searchUserName},ou=users,${clientCfg.suffix}";
bindPW = config.my.ldap.searchUserPassword;
userSearch = {
baseDN = "ou=users,${clientCfg.suffix}";
filter = "(objectclass=person)";
username = "uid";
idAttr = "uid";
emailAttr = "mail";
nameAttr = "cn";
preferredUsernameAttr = "uid";
};
groupSearch = {
baseDN = "ou=groups,${clientCfg.suffix}";
filter = "(objectclass=groupOfNames)";
nameAttr = "cn";
userMatchers = [{ userAttr = "DN"; groupAttr = "member"; }];
};
};
}];
staticClients = forEach oidcClients (client: {
id = client.id;
redirectURIs = [ client.callbackURI ];
name = "OIDC for ${client.id}";
secret = config.my.services.oidc.clientSecrets.${client.id};
});
};
systemd.services.dex.serviceConfig = {
# `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, so the config as above works.
StateDirectory = "dex";
};
};
}