Skip to content

Commit b50f45e

Browse files
committed
add declarative lldap using secrets contract
1 parent 94a5b77 commit b50f45e

8 files changed

+1260
-9
lines changed

docs/redirects.json

Lines changed: 104 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -311,8 +311,11 @@
311311
"blocks-lldap-global-setup": [
312312
"blocks-lldap.html#blocks-lldap-global-setup"
313313
],
314-
"blocks-lldap-manage-user-group": [
315-
"blocks-lldap.html#blocks-lldap-manage-user-group"
314+
"blocks-lldap-manage-groups": [
315+
"blocks-lldap.html#blocks-lldap-manage-groups"
316+
],
317+
"blocks-lldap-manage-users": [
318+
"blocks-lldap.html#blocks-lldap-manage-users"
316319
],
317320
"blocks-lldap-options": [
318321
"blocks-lldap.html#blocks-lldap-options"
@@ -362,6 +365,105 @@
362365
"blocks-lldap-options-shb.lldap.enable": [
363366
"blocks-lldap.html#blocks-lldap-options-shb.lldap.enable"
364367
],
368+
"blocks-lldap-options-shb.lldap.ensureGroupFields": [
369+
"blocks-lldap.html#blocks-lldap-options-shb.lldap.ensureGroupFields"
370+
],
371+
"blocks-lldap-options-shb.lldap.ensureGroupFields._name_.attributeType": [
372+
"blocks-lldap.html#blocks-lldap-options-shb.lldap.ensureGroupFields._name_.attributeType"
373+
],
374+
"blocks-lldap-options-shb.lldap.ensureGroupFields._name_.isEditable": [
375+
"blocks-lldap.html#blocks-lldap-options-shb.lldap.ensureGroupFields._name_.isEditable"
376+
],
377+
"blocks-lldap-options-shb.lldap.ensureGroupFields._name_.isList": [
378+
"blocks-lldap.html#blocks-lldap-options-shb.lldap.ensureGroupFields._name_.isList"
379+
],
380+
"blocks-lldap-options-shb.lldap.ensureGroupFields._name_.isVisible": [
381+
"blocks-lldap.html#blocks-lldap-options-shb.lldap.ensureGroupFields._name_.isVisible"
382+
],
383+
"blocks-lldap-options-shb.lldap.ensureGroupFields._name_.name": [
384+
"blocks-lldap.html#blocks-lldap-options-shb.lldap.ensureGroupFields._name_.name"
385+
],
386+
"blocks-lldap-options-shb.lldap.ensureGroups": [
387+
"blocks-lldap.html#blocks-lldap-options-shb.lldap.ensureGroups"
388+
],
389+
"blocks-lldap-options-shb.lldap.ensureGroups._name_.name": [
390+
"blocks-lldap.html#blocks-lldap-options-shb.lldap.ensureGroups._name_.name"
391+
],
392+
"blocks-lldap-options-shb.lldap.ensureUserFields": [
393+
"blocks-lldap.html#blocks-lldap-options-shb.lldap.ensureUserFields"
394+
],
395+
"blocks-lldap-options-shb.lldap.ensureUserFields._name_.attributeType": [
396+
"blocks-lldap.html#blocks-lldap-options-shb.lldap.ensureUserFields._name_.attributeType"
397+
],
398+
"blocks-lldap-options-shb.lldap.ensureUserFields._name_.isEditable": [
399+
"blocks-lldap.html#blocks-lldap-options-shb.lldap.ensureUserFields._name_.isEditable"
400+
],
401+
"blocks-lldap-options-shb.lldap.ensureUserFields._name_.isList": [
402+
"blocks-lldap.html#blocks-lldap-options-shb.lldap.ensureUserFields._name_.isList"
403+
],
404+
"blocks-lldap-options-shb.lldap.ensureUserFields._name_.isVisible": [
405+
"blocks-lldap.html#blocks-lldap-options-shb.lldap.ensureUserFields._name_.isVisible"
406+
],
407+
"blocks-lldap-options-shb.lldap.ensureUserFields._name_.name": [
408+
"blocks-lldap.html#blocks-lldap-options-shb.lldap.ensureUserFields._name_.name"
409+
],
410+
"blocks-lldap-options-shb.lldap.ensureUsers": [
411+
"blocks-lldap.html#blocks-lldap-options-shb.lldap.ensureUsers"
412+
],
413+
"blocks-lldap-options-shb.lldap.ensureUsers._name_.avatar_file": [
414+
"blocks-lldap.html#blocks-lldap-options-shb.lldap.ensureUsers._name_.avatar_file"
415+
],
416+
"blocks-lldap-options-shb.lldap.ensureUsers._name_.avatar_url": [
417+
"blocks-lldap.html#blocks-lldap-options-shb.lldap.ensureUsers._name_.avatar_url"
418+
],
419+
"blocks-lldap-options-shb.lldap.ensureUsers._name_.displayName": [
420+
"blocks-lldap.html#blocks-lldap-options-shb.lldap.ensureUsers._name_.displayName"
421+
],
422+
"blocks-lldap-options-shb.lldap.ensureUsers._name_.email": [
423+
"blocks-lldap.html#blocks-lldap-options-shb.lldap.ensureUsers._name_.email"
424+
],
425+
"blocks-lldap-options-shb.lldap.ensureUsers._name_.firstName": [
426+
"blocks-lldap.html#blocks-lldap-options-shb.lldap.ensureUsers._name_.firstName"
427+
],
428+
"blocks-lldap-options-shb.lldap.ensureUsers._name_.gravatar_avatar": [
429+
"blocks-lldap.html#blocks-lldap-options-shb.lldap.ensureUsers._name_.gravatar_avatar"
430+
],
431+
"blocks-lldap-options-shb.lldap.ensureUsers._name_.groups": [
432+
"blocks-lldap.html#blocks-lldap-options-shb.lldap.ensureUsers._name_.groups"
433+
],
434+
"blocks-lldap-options-shb.lldap.ensureUsers._name_.id": [
435+
"blocks-lldap.html#blocks-lldap-options-shb.lldap.ensureUsers._name_.id"
436+
],
437+
"blocks-lldap-options-shb.lldap.ensureUsers._name_.lastName": [
438+
"blocks-lldap.html#blocks-lldap-options-shb.lldap.ensureUsers._name_.lastName"
439+
],
440+
"blocks-lldap-options-shb.lldap.ensureUsers._name_.password": [
441+
"blocks-lldap.html#blocks-lldap-options-shb.lldap.ensureUsers._name_.password"
442+
],
443+
"blocks-lldap-options-shb.lldap.ensureUsers._name_.password.request": [
444+
"blocks-lldap.html#blocks-lldap-options-shb.lldap.ensureUsers._name_.password.request"
445+
],
446+
"blocks-lldap-options-shb.lldap.ensureUsers._name_.password.request.group": [
447+
"blocks-lldap.html#blocks-lldap-options-shb.lldap.ensureUsers._name_.password.request.group"
448+
],
449+
"blocks-lldap-options-shb.lldap.ensureUsers._name_.password.request.mode": [
450+
"blocks-lldap.html#blocks-lldap-options-shb.lldap.ensureUsers._name_.password.request.mode"
451+
],
452+
"blocks-lldap-options-shb.lldap.ensureUsers._name_.password.request.owner": [
453+
"blocks-lldap.html#blocks-lldap-options-shb.lldap.ensureUsers._name_.password.request.owner"
454+
],
455+
"blocks-lldap-options-shb.lldap.ensureUsers._name_.password.request.restartUnits": [
456+
"blocks-lldap.html#blocks-lldap-options-shb.lldap.ensureUsers._name_.password.request.restartUnits"
457+
],
458+
"blocks-lldap-options-shb.lldap.ensureUsers._name_.password.result": [
459+
"blocks-lldap.html#blocks-lldap-options-shb.lldap.ensureUsers._name_.password.result"
460+
],
461+
"blocks-lldap-options-shb.lldap.ensureUsers._name_.password.result.path": [
462+
"blocks-lldap.html#blocks-lldap-options-shb.lldap.ensureUsers._name_.password.result.path"
463+
],
464+
"blocks-lldap-options-shb.lldap.ensureUsers._name_.weser_avatar": [
465+
"blocks-lldap.html#blocks-lldap-options-shb.lldap.ensureUsers._name_.weser_avatar"
466+
],
365467
"blocks-lldap-options-shb.lldap.jwtSecret": [
366468
"blocks-lldap.html#blocks-lldap-options-shb.lldap.jwtSecret"
367469
],

flake.nix

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@
1515
let
1616
originPkgs = nixpkgs.legacyPackages.${system};
1717
shbPatches = originPkgs.lib.optionals (system == "x86_64-linux") [
18+
# Get rid of lldap patches when https://github.com/NixOS/nixpkgs/pull/425923 is merged.
19+
./patches/0001-lldap-lldap-0.6.1-unstable-2025-07-16.patch
20+
./patches/0002-lldap-add-options-to-set-important-secrets.patch
21+
./patches/0003-lldap-bootstrap-init-unstable-2025-07-16-lldap-add-e.patch
22+
1823
# Leaving commented out as an example.
1924
# (originPkgs.fetchpatch {
2025
# url = "https://github.com/NixOS/nixpkgs/pull/317107.patch";

modules/blocks/lldap.nix

Lines changed: 195 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,46 @@ let
66
contracts = pkgs.callPackage ../contracts {};
77

88
fqdn = "${cfg.subdomain}.${cfg.domain}";
9+
10+
inherit (lib) mkOption types;
11+
12+
ensureFormat = pkgs.formats.json { };
13+
14+
ensureFieldsOptions = name: {
15+
name = mkOption {
16+
type = types.str;
17+
description = "Name of the field.";
18+
default = name;
19+
};
20+
21+
attributeType = mkOption {
22+
type = types.enum [
23+
"STRING"
24+
"INTEGER"
25+
"JPEG"
26+
"DATE_TIME"
27+
];
28+
description = "Attribute type.";
29+
};
30+
31+
isEditable = mkOption {
32+
type = types.bool;
33+
description = "Is field editable.";
34+
default = true;
35+
};
36+
37+
isList = mkOption {
38+
type = types.bool;
39+
description = "Is field a list.";
40+
default = false;
41+
};
42+
43+
isVisible = mkOption {
44+
type = types.bool;
45+
description = "Is field visible in UI.";
46+
default = true;
47+
};
48+
};
949
in
1050
{
1151
options.shb.lldap = {
@@ -118,10 +158,155 @@ in
118158
};
119159
};
120160
};
161+
162+
ensureUsers = mkOption {
163+
description = ''
164+
Create the users defined here on service startup.
165+
166+
If `enforceEnsure` option is `true`, the groups
167+
users belong to must be present in the `ensureGroups` option.
168+
169+
Non-default options must be added to the `ensureGroupFields` option.
170+
'';
171+
default = { };
172+
type = types.attrsOf (
173+
types.submodule (
174+
{ name, ... }:
175+
{
176+
freeformType = ensureFormat.type;
177+
178+
options = {
179+
id = mkOption {
180+
type = types.str;
181+
description = "Username.";
182+
default = name;
183+
};
184+
185+
email = mkOption {
186+
type = types.str;
187+
description = "Email.";
188+
};
189+
190+
password = mkOption {
191+
description = "Password.";
192+
type = lib.types.submodule {
193+
options = contracts.secret.mkRequester {
194+
mode = "0440";
195+
owner = "lldap";
196+
group = "lldap";
197+
restartUnits = [ "lldap.service" ];
198+
};
199+
};
200+
};
201+
202+
displayName = mkOption {
203+
type = types.nullOr types.str;
204+
default = null;
205+
description = "Display name.";
206+
};
207+
208+
firstName = mkOption {
209+
type = types.nullOr types.str;
210+
default = null;
211+
description = "First name.";
212+
};
213+
214+
lastName = mkOption {
215+
type = types.nullOr types.str;
216+
default = null;
217+
description = "Last name.";
218+
};
219+
220+
avatar_file = mkOption {
221+
type = types.nullOr types.str;
222+
default = null;
223+
description = "Avatar file. Must be a valid path to jpeg file (ignored if avatar_url specified)";
224+
};
225+
226+
avatar_url = mkOption {
227+
type = types.nullOr types.str;
228+
default = null;
229+
description = "Avatar url. must be a valid URL to jpeg file (ignored if gravatar_avatar specified)";
230+
};
231+
232+
gravatar_avatar = mkOption {
233+
type = types.nullOr types.str;
234+
default = null;
235+
description = "Get avatar from Gravatar using the email.";
236+
};
237+
238+
weser_avatar = mkOption {
239+
type = types.nullOr types.str;
240+
default = null;
241+
description = "Convert avatar retrieved by gravatar or the URL.";
242+
};
243+
244+
groups = mkOption {
245+
type = types.listOf types.str;
246+
default = [ ];
247+
description = "Groups the user would be a member of (all the groups must be specified in group config files).";
248+
};
249+
};
250+
}
251+
)
252+
);
253+
};
254+
255+
ensureGroups = mkOption {
256+
description = ''
257+
Create the groups defined here on service startup.
258+
259+
Non-default options must be added to the `ensureGroupFields` option.
260+
'';
261+
default = { };
262+
type = types.attrsOf (
263+
types.submodule (
264+
{ name, ... }:
265+
{
266+
freeformType = ensureFormat.type;
267+
268+
options = {
269+
name = mkOption {
270+
type = types.str;
271+
description = "Name of the group.";
272+
default = name;
273+
};
274+
};
275+
}
276+
)
277+
);
278+
};
279+
280+
ensureUserFields = mkOption {
281+
description = "Extra fields for users";
282+
default = { };
283+
type = types.attrsOf (
284+
types.submodule (
285+
{ name, ... }:
286+
{
287+
options = ensureFieldsOptions name;
288+
}
289+
)
290+
);
291+
};
292+
293+
ensureGroupFields = mkOption {
294+
description = "Extra fields for groups";
295+
default = { };
296+
type = types.attrsOf (
297+
types.submodule (
298+
{ name, ... }:
299+
{
300+
options = ensureFieldsOptions name;
301+
}
302+
)
303+
);
304+
};
121305
};
122306

123307

124308
config = lib.mkIf cfg.enable {
309+
125310
services.nginx = {
126311
enable = true;
127312

@@ -151,10 +336,12 @@ in
151336
services.lldap = {
152337
enable = true;
153338

154-
environment = {
155-
LLDAP_JWT_SECRET_FILE = toString cfg.jwtSecret.result.path;
156-
LLDAP_LDAP_USER_PASS_FILE = toString cfg.ldapUserPassword.result.path;
339+
adminPasswordFile = toString cfg.ldapUserPassword.result.path;
340+
jwtSecretFile = toString cfg.jwtSecret.result.path;
341+
resetAdminPassword = "always";
342+
enforceEnsure = true;
157343

344+
environment = {
158345
RUST_LOG = lib.mkIf cfg.debug "debug";
159346
};
160347

@@ -170,6 +357,11 @@ in
170357

171358
verbose = cfg.debug;
172359
};
360+
361+
inherit (cfg) ensureGroups ensureUserFields ensureGroupFields;
362+
ensureUsers = lib.mapAttrs (n: v: (lib.removeAttrs v [ "password" ]) // {
363+
"password_file" = v.password.result.path;
364+
}) cfg.ensureUsers;
173365
};
174366
};
175367
}

0 commit comments

Comments
 (0)