1{ config, options, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.neo4j;
7 opt = options.services.neo4j;
8 certDirOpt = options.services.neo4j.directories.certificates;
9 isDefaultPathOption = opt: isOption opt && opt.type == types.path && opt.highestPrio >= 1500;
10
11 sslPolicies = mapAttrsToList (
12 name: conf: ''
13 dbms.ssl.policy.${name}.allow_key_generation=${boolToString conf.allowKeyGeneration}
14 dbms.ssl.policy.${name}.base_directory=${conf.baseDirectory}
15 ${optionalString (conf.ciphers != null) ''
16 dbms.ssl.policy.${name}.ciphers=${concatStringsSep "," conf.ciphers}
17 ''}
18 dbms.ssl.policy.${name}.client_auth=${conf.clientAuth}
19 ${if length (splitString "/" conf.privateKey) > 1 then
20 "dbms.ssl.policy.${name}.private_key=${conf.privateKey}"
21 else
22 "dbms.ssl.policy.${name}.private_key=${conf.baseDirectory}/${conf.privateKey}"
23 }
24 ${if length (splitString "/" conf.privateKey) > 1 then
25 "dbms.ssl.policy.${name}.public_certificate=${conf.publicCertificate}"
26 else
27 "dbms.ssl.policy.${name}.public_certificate=${conf.baseDirectory}/${conf.publicCertificate}"
28 }
29 dbms.ssl.policy.${name}.revoked_dir=${conf.revokedDir}
30 dbms.ssl.policy.${name}.tls_versions=${concatStringsSep "," conf.tlsVersions}
31 dbms.ssl.policy.${name}.trust_all=${boolToString conf.trustAll}
32 dbms.ssl.policy.${name}.trusted_dir=${conf.trustedDir}
33 ''
34 ) cfg.ssl.policies;
35
36 serverConfig = pkgs.writeText "neo4j.conf" ''
37 # General
38 server.default_listen_address=${cfg.defaultListenAddress}
39 server.databases.default_to_read_only=${boolToString cfg.readOnly}
40 ${optionalString (cfg.workerCount > 0) ''
41 dbms.threads.worker_count=${toString cfg.workerCount}
42 ''}
43
44 # Directories (readonly)
45 # dbms.directories.certificates=${cfg.directories.certificates}
46 server.directories.plugins=${cfg.directories.plugins}
47 server.directories.lib=${cfg.package}/share/neo4j/lib
48 ${optionalString (cfg.constrainLoadCsv) ''
49 server.directories.import=${cfg.directories.imports}
50 ''}
51
52 # Directories (read and write)
53 server.directories.data=${cfg.directories.data}
54 server.directories.logs=${cfg.directories.home}/logs
55 server.directories.run=${cfg.directories.home}/run
56
57 # HTTP Connector
58 ${optionalString (cfg.http.enable) ''
59 server.http.enabled=${boolToString cfg.http.enable}
60 server.http.listen_address=${cfg.http.listenAddress}
61 server.http.advertised_address=${cfg.http.listenAddress}
62 ''}
63
64 # HTTPS Connector
65 server.https.enabled=${boolToString cfg.https.enable}
66 server.https.listen_address=${cfg.https.listenAddress}
67 server.https.advertised_address=${cfg.https.listenAddress}
68
69 # BOLT Connector
70 server.bolt.enabled=${boolToString cfg.bolt.enable}
71 server.bolt.listen_address=${cfg.bolt.listenAddress}
72 server.bolt.advertised_address=${cfg.bolt.listenAddress}
73 server.bolt.tls_level=${cfg.bolt.tlsLevel}
74
75 # SSL Policies
76 ${concatStringsSep "\n" sslPolicies}
77
78 # Default retention policy from neo4j.conf
79 db.tx_log.rotation.retention_policy=1 days
80
81 # Default JVM parameters from neo4j.conf
82 server.jvm.additional=-XX:+UseG1GC
83 server.jvm.additional=-XX:-OmitStackTraceInFastThrow
84 server.jvm.additional=-XX:+AlwaysPreTouch
85 server.jvm.additional=-XX:+UnlockExperimentalVMOptions
86 server.jvm.additional=-XX:+TrustFinalNonStaticFields
87 server.jvm.additional=-XX:+DisableExplicitGC
88 server.jvm.additional=-Djdk.tls.ephemeralDHKeySize=2048
89 server.jvm.additional=-Djdk.tls.rejectClientInitiatedRenegotiation=true
90 server.jvm.additional=-Dunsupported.dbms.udc.source=tarball
91
92 #server.memory.off_heap.transaction_max_size=12000m
93 #server.memory.heap.max_size=12000m
94 #server.memory.pagecache.size=4g
95 #server.tx_state.max_off_heap_memory=8000m
96
97 # Extra Configuration
98 ${cfg.extraServerConfig}
99 '';
100
101in {
102
103 imports = [
104 (mkRenamedOptionModule [ "services" "neo4j" "host" ] [ "services" "neo4j" "defaultListenAddress" ])
105 (mkRenamedOptionModule [ "services" "neo4j" "listenAddress" ] [ "services" "neo4j" "defaultListenAddress" ])
106 (mkRenamedOptionModule [ "services" "neo4j" "enableBolt" ] [ "services" "neo4j" "bolt" "enable" ])
107 (mkRenamedOptionModule [ "services" "neo4j" "enableHttps" ] [ "services" "neo4j" "https" "enable" ])
108 (mkRenamedOptionModule [ "services" "neo4j" "certDir" ] [ "services" "neo4j" "directories" "certificates" ])
109 (mkRenamedOptionModule [ "services" "neo4j" "dataDir" ] [ "services" "neo4j" "directories" "home" ])
110 (mkRemovedOptionModule [ "services" "neo4j" "port" ] "Use services.neo4j.http.listenAddress instead.")
111 (mkRemovedOptionModule [ "services" "neo4j" "boltPort" ] "Use services.neo4j.bolt.listenAddress instead.")
112 (mkRemovedOptionModule [ "services" "neo4j" "httpsPort" ] "Use services.neo4j.https.listenAddress instead.")
113 (mkRemovedOptionModule [ "services" "neo4j" "shell" "enabled" ] "shell.enabled was removed upstream")
114 (mkRemovedOptionModule [ "services" "neo4j" "udc" "enabled" ] "udc.enabled was removed upstream")
115 ];
116
117 ###### interface
118
119 options.services.neo4j = {
120
121 enable = mkOption {
122 type = types.bool;
123 default = false;
124 description = ''
125 Whether to enable Neo4j Community Edition.
126 '';
127 };
128
129 constrainLoadCsv = mkOption {
130 type = types.bool;
131 default = true;
132 description = ''
133 Sets the root directory for file URLs used with the Cypher
134 `LOAD CSV` clause to be that defined by
135 {option}`directories.imports`. It restricts
136 access to only those files within that directory and its
137 subdirectories.
138
139 Setting this option to `false` introduces
140 possible security problems.
141 '';
142 };
143
144 defaultListenAddress = mkOption {
145 type = types.str;
146 default = "127.0.0.1";
147 description = ''
148 Default network interface to listen for incoming connections. To
149 listen for connections on all interfaces, use "0.0.0.0".
150
151 Specifies the default IP address and address part of connector
152 specific {option}`listenAddress` options. To bind specific
153 connectors to a specific network interfaces, specify the entire
154 {option}`listenAddress` option for that connector.
155 '';
156 };
157
158 extraServerConfig = mkOption {
159 type = types.lines;
160 default = "";
161 description = ''
162 Extra configuration for Neo4j Community server. Refer to the
163 [complete reference](https://neo4j.com/docs/operations-manual/current/reference/configuration-settings/)
164 of Neo4j configuration settings.
165 '';
166 };
167
168 package = mkPackageOption pkgs "neo4j" { };
169
170 readOnly = mkOption {
171 type = types.bool;
172 default = false;
173 description = ''
174 Only allow read operations from this Neo4j instance.
175 '';
176 };
177
178 workerCount = mkOption {
179 type = types.ints.between 0 44738;
180 default = 0;
181 description = ''
182 Number of Neo4j worker threads, where the default of
183 `0` indicates a worker count equal to the number of
184 available processors.
185 '';
186 };
187
188 bolt = {
189 enable = mkOption {
190 type = types.bool;
191 default = true;
192 description = ''
193 Enable the BOLT connector for Neo4j. Setting this option to
194 `false` will stop Neo4j from listening for incoming
195 connections on the BOLT port (7687 by default).
196 '';
197 };
198
199 listenAddress = mkOption {
200 type = types.str;
201 default = ":7687";
202 description = ''
203 Neo4j listen address for BOLT traffic. The listen address is
204 expressed in the format `<ip-address>:<port-number>`.
205 '';
206 };
207
208 sslPolicy = mkOption {
209 type = types.str;
210 default = "legacy";
211 description = ''
212 Neo4j SSL policy for BOLT traffic.
213
214 The legacy policy is a special policy which is not defined in
215 the policy configuration section, but rather derives from
216 {option}`directories.certificates` and
217 associated files (by default: {file}`neo4j.key` and
218 {file}`neo4j.cert`). Its use will be deprecated.
219
220 Note: This connector must be configured to support/require
221 SSL/TLS for the legacy policy to actually be utilized. See
222 {option}`bolt.tlsLevel`.
223 '';
224 };
225
226 tlsLevel = mkOption {
227 type = types.enum [ "REQUIRED" "OPTIONAL" "DISABLED" ];
228 default = "OPTIONAL";
229 description = ''
230 SSL/TSL requirement level for BOLT traffic.
231 '';
232 };
233 };
234
235 directories = {
236 certificates = mkOption {
237 type = types.path;
238 default = "${cfg.directories.home}/certificates";
239 defaultText = literalExpression ''"''${config.${opt.directories.home}}/certificates"'';
240 description = ''
241 Directory for storing certificates to be used by Neo4j for
242 TLS connections.
243
244 When setting this directory to something other than its default,
245 ensure the directory's existence, and that read/write permissions are
246 given to the Neo4j daemon user `neo4j`.
247
248 Note that changing this directory from its default will prevent
249 the directory structure required for each SSL policy from being
250 automatically generated. A policy's directory structure as defined by
251 its {option}`baseDirectory`,{option}`revokedDir` and
252 {option}`trustedDir` must then be setup manually. The
253 existence of these directories is mandatory, as well as the presence
254 of the certificate file and the private key. Ensure the correct
255 permissions are set on these directories and files.
256 '';
257 };
258
259 data = mkOption {
260 type = types.path;
261 default = "${cfg.directories.home}/data";
262 defaultText = literalExpression ''"''${config.${opt.directories.home}}/data"'';
263 description = ''
264 Path of the data directory. You must not configure more than one
265 Neo4j installation to use the same data directory.
266
267 When setting this directory to something other than its default,
268 ensure the directory's existence, and that read/write permissions are
269 given to the Neo4j daemon user `neo4j`.
270 '';
271 };
272
273 home = mkOption {
274 type = types.path;
275 default = "/var/lib/neo4j";
276 description = ''
277 Path of the Neo4j home directory. Other default directories are
278 subdirectories of this path. This directory will be created if
279 non-existent, and its ownership will be {command}`chown` to
280 the Neo4j daemon user `neo4j`.
281 '';
282 };
283
284 imports = mkOption {
285 type = types.path;
286 default = "${cfg.directories.home}/import";
287 defaultText = literalExpression ''"''${config.${opt.directories.home}}/import"'';
288 description = ''
289 The root directory for file URLs used with the Cypher
290 `LOAD CSV` clause. Only meaningful when
291 {option}`constrainLoadCvs` is set to
292 `true`.
293
294 When setting this directory to something other than its default,
295 ensure the directory's existence, and that read permission is
296 given to the Neo4j daemon user `neo4j`.
297 '';
298 };
299
300 plugins = mkOption {
301 type = types.path;
302 default = "${cfg.directories.home}/plugins";
303 defaultText = literalExpression ''"''${config.${opt.directories.home}}/plugins"'';
304 description = ''
305 Path of the database plugin directory. Compiled Java JAR files that
306 contain database procedures will be loaded if they are placed in
307 this directory.
308
309 When setting this directory to something other than its default,
310 ensure the directory's existence, and that read permission is
311 given to the Neo4j daemon user `neo4j`.
312 '';
313 };
314 };
315
316 http = {
317 enable = mkOption {
318 type = types.bool;
319 default = true;
320 description = ''
321 Enable the HTTP connector for Neo4j. Setting this option to
322 `false` will stop Neo4j from listening for incoming
323 connections on the HTTPS port (7474 by default).
324 '';
325 };
326
327 listenAddress = mkOption {
328 type = types.str;
329 default = ":7474";
330 description = ''
331 Neo4j listen address for HTTP traffic. The listen address is
332 expressed in the format `<ip-address>:<port-number>`.
333 '';
334 };
335 };
336
337 https = {
338 enable = mkOption {
339 type = types.bool;
340 default = true;
341 description = ''
342 Enable the HTTPS connector for Neo4j. Setting this option to
343 `false` will stop Neo4j from listening for incoming
344 connections on the HTTPS port (7473 by default).
345 '';
346 };
347
348 listenAddress = mkOption {
349 type = types.str;
350 default = ":7473";
351 description = ''
352 Neo4j listen address for HTTPS traffic. The listen address is
353 expressed in the format `<ip-address>:<port-number>`.
354 '';
355 };
356
357 sslPolicy = mkOption {
358 type = types.str;
359 default = "legacy";
360 description = ''
361 Neo4j SSL policy for HTTPS traffic.
362
363 The legacy policy is a special policy which is not defined in the
364 policy configuration section, but rather derives from
365 {option}`directories.certificates` and
366 associated files (by default: {file}`neo4j.key` and
367 {file}`neo4j.cert`). Its use will be deprecated.
368 '';
369 };
370 };
371
372 shell = {
373 enable = mkOption {
374 type = types.bool;
375 default = false;
376 description = ''
377 Enable a remote shell server which Neo4j Shell clients can log in to.
378 Only applicable to {command}`neo4j-shell`.
379 '';
380 };
381 };
382
383 ssl.policies = mkOption {
384 type = with types; attrsOf (submodule ({ name, config, options, ... }: {
385 options = {
386
387 allowKeyGeneration = mkOption {
388 type = types.bool;
389 default = false;
390 description = ''
391 Allows the generation of a private key and associated self-signed
392 certificate. Only performed when both objects cannot be found for
393 this policy. It is recommended to turn this off again after keys
394 have been generated.
395
396 The public certificate is required to be duplicated to the
397 directory holding trusted certificates as defined by the
398 {option}`trustedDir` option.
399
400 Keys should in general be generated and distributed offline by a
401 trusted certificate authority and not by utilizing this mode.
402 '';
403 };
404
405 baseDirectory = mkOption {
406 type = types.path;
407 default = "${cfg.directories.certificates}/${name}";
408 defaultText = literalExpression ''"''${config.${opt.directories.certificates}}/''${name}"'';
409 description = ''
410 The mandatory base directory for cryptographic objects of this
411 policy. This path is only automatically generated when this
412 option as well as {option}`directories.certificates` are
413 left at their default. Ensure read/write permissions are given
414 to the Neo4j daemon user `neo4j`.
415
416 It is also possible to override each individual
417 configuration with absolute paths. See the
418 {option}`privateKey` and {option}`publicCertificate`
419 policy options.
420 '';
421 };
422
423 ciphers = mkOption {
424 type = types.nullOr (types.listOf types.str);
425 default = null;
426 description = ''
427 Restrict the allowed ciphers of this policy to those defined
428 here. The default ciphers are those of the JVM platform.
429 '';
430 };
431
432 clientAuth = mkOption {
433 type = types.enum [ "NONE" "OPTIONAL" "REQUIRE" ];
434 default = "REQUIRE";
435 description = ''
436 The client authentication stance for this policy.
437 '';
438 };
439
440 privateKey = mkOption {
441 type = types.str;
442 default = "private.key";
443 description = ''
444 The name of private PKCS #8 key file for this policy to be found
445 in the {option}`baseDirectory`, or the absolute path to
446 the key file. It is mandatory that a key can be found or generated.
447 '';
448 };
449
450 publicCertificate = mkOption {
451 type = types.str;
452 default = "public.crt";
453 description = ''
454 The name of public X.509 certificate (chain) file in PEM format
455 for this policy to be found in the {option}`baseDirectory`,
456 or the absolute path to the certificate file. It is mandatory
457 that a certificate can be found or generated.
458
459 The public certificate is required to be duplicated to the
460 directory holding trusted certificates as defined by the
461 {option}`trustedDir` option.
462 '';
463 };
464
465 revokedDir = mkOption {
466 type = types.path;
467 default = "${config.baseDirectory}/revoked";
468 defaultText = literalExpression ''"''${config.${options.baseDirectory}}/revoked"'';
469 description = ''
470 Path to directory of CRLs (Certificate Revocation Lists) in
471 PEM format. Must be an absolute path. The existence of this
472 directory is mandatory and will need to be created manually when:
473 setting this option to something other than its default; setting
474 either this policy's {option}`baseDirectory` or
475 {option}`directories.certificates` to something other than
476 their default. Ensure read/write permissions are given to the
477 Neo4j daemon user `neo4j`.
478 '';
479 };
480
481 tlsVersions = mkOption {
482 type = types.listOf types.str;
483 default = [ "TLSv1.2" ];
484 description = ''
485 Restrict the TLS protocol versions of this policy to those
486 defined here.
487 '';
488 };
489
490 trustAll = mkOption {
491 type = types.bool;
492 default = false;
493 description = ''
494 Makes this policy trust all remote parties. Enabling this is not
495 recommended and the policy's trusted directory will be ignored.
496 Use of this mode is discouraged. It would offer encryption but
497 no security.
498 '';
499 };
500
501 trustedDir = mkOption {
502 type = types.path;
503 default = "${config.baseDirectory}/trusted";
504 defaultText = literalExpression ''"''${config.${options.baseDirectory}}/trusted"'';
505 description = ''
506 Path to directory of X.509 certificates in PEM format for
507 trusted parties. Must be an absolute path. The existence of this
508 directory is mandatory and will need to be created manually when:
509 setting this option to something other than its default; setting
510 either this policy's {option}`baseDirectory` or
511 {option}`directories.certificates` to something other than
512 their default. Ensure read/write permissions are given to the
513 Neo4j daemon user `neo4j`.
514
515 The public certificate as defined by
516 {option}`publicCertificate` is required to be duplicated
517 to this directory.
518 '';
519 };
520
521 directoriesToCreate = mkOption {
522 type = types.listOf types.path;
523 internal = true;
524 readOnly = true;
525 description = ''
526 Directories of this policy that will be created automatically
527 when the certificates directory is left at its default value.
528 This includes all options of type path that are left at their
529 default value.
530 '';
531 };
532
533 };
534
535 config.directoriesToCreate = optionals
536 (certDirOpt.highestPrio >= 1500 && options.baseDirectory.highestPrio >= 1500)
537 (map (opt: opt.value) (filter isDefaultPathOption (attrValues options)));
538
539 }));
540 default = {};
541 description = ''
542 Defines the SSL policies for use with Neo4j connectors. Each attribute
543 of this set defines a policy, with the attribute name defining the name
544 of the policy and its namespace. Refer to the operations manual section
545 on Neo4j's
546 [SSL Framework](https://neo4j.com/docs/operations-manual/current/security/ssl-framework/)
547 for further details.
548 '';
549 };
550
551 };
552
553 ###### implementation
554
555 config =
556 let
557 # Assertion helpers
558 policyNameList = attrNames cfg.ssl.policies;
559 validPolicyNameList = [ "legacy" ] ++ policyNameList;
560 validPolicyNameString = concatStringsSep ", " validPolicyNameList;
561
562 # Capture various directories left at their default so they can be created.
563 defaultDirectoriesToCreate = map (opt: opt.value) (filter isDefaultPathOption (attrValues options.services.neo4j.directories));
564 policyDirectoriesToCreate = concatMap (pol: pol.directoriesToCreate) (attrValues cfg.ssl.policies);
565 in
566
567 mkIf cfg.enable {
568 assertions = [
569 { assertion = !elem "legacy" policyNameList;
570 message = "The policy 'legacy' is special to Neo4j, and its name is reserved."; }
571 { assertion = elem cfg.bolt.sslPolicy validPolicyNameList;
572 message = "Invalid policy assigned: `services.neo4j.bolt.sslPolicy = \"${cfg.bolt.sslPolicy}\"`, defined policies are: ${validPolicyNameString}"; }
573 { assertion = elem cfg.https.sslPolicy validPolicyNameList;
574 message = "Invalid policy assigned: `services.neo4j.https.sslPolicy = \"${cfg.https.sslPolicy}\"`, defined policies are: ${validPolicyNameString}"; }
575 ];
576
577 systemd.services.neo4j = {
578 description = "Neo4j Daemon";
579 wantedBy = [ "multi-user.target" ];
580 after = [ "network.target" ];
581 environment = {
582 NEO4J_HOME = "${cfg.directories.home}";
583 NEO4J_CONF = "${cfg.directories.home}/conf";
584 };
585 serviceConfig = {
586 ExecStart = "${cfg.package}/bin/neo4j console";
587 User = "neo4j";
588 PermissionsStartOnly = true;
589 LimitNOFILE = 40000;
590 };
591
592 preStart = ''
593 # Directories Setup
594 # Always ensure home exists with nested conf, logs directories.
595 mkdir -m 0700 -p ${cfg.directories.home}/{conf,logs}
596
597 # Create other sub-directories and policy directories that have been left at their default.
598 ${concatMapStringsSep "\n" (
599 dir: ''
600 mkdir -m 0700 -p ${dir}
601 '') (defaultDirectoriesToCreate ++ policyDirectoriesToCreate)}
602
603 # Place the configuration where Neo4j can find it.
604 ln -fs ${serverConfig} ${cfg.directories.home}/conf/neo4j.conf
605
606 # Ensure neo4j user ownership
607 chown -R neo4j ${cfg.directories.home}
608 '';
609 };
610
611 environment.systemPackages = [ cfg.package ];
612
613 users.users.neo4j = {
614 isSystemUser = true;
615 group = "neo4j";
616 description = "Neo4j daemon user";
617 home = cfg.directories.home;
618 };
619 users.groups.neo4j = {};
620 };
621
622 meta = {
623 maintainers = with lib.maintainers; [ patternspandemic jonringer ];
624 };
625}