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