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