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