1# The certificate for the ACME service is exported as:
2#
3# config.test-support.acme.caCert
4#
5# This value can be used inside the configuration of other test nodes to inject
6# the test certificate into security.pki.certificateFiles or into package
7# overlays.
8#
9# The hosts file of this node will be populated with a mapping of certificate
10# domains (including extraDomainNames) to their parent nodes in the test suite.
11# This negates the need for a DNS server for most testing. You can still specify
12# a custom nameserver/resolver if necessary for other reasons.
13{
14 config,
15 pkgs,
16 lib,
17 nodes ? { },
18 ...
19}:
20let
21 testCerts = import ./snakeoil-certs.nix;
22 domain = testCerts.domain;
23
24 pebbleConf.pebble = {
25 listenAddress = "0.0.0.0:443";
26 managementListenAddress = "0.0.0.0:15000";
27 # These certs and keys are used for the Web Front End (WFE)
28 certificate = testCerts.${domain}.cert;
29 privateKey = testCerts.${domain}.key;
30 httpPort = 80;
31 tlsPort = 443;
32 ocspResponderURL = "http://${domain}:4002";
33 strict = true;
34 };
35
36 pebbleConfFile = pkgs.writeText "pebble.conf" (builtins.toJSON pebbleConf);
37
38in
39{
40 options.test-support.acme = {
41 caDomain = lib.mkOption {
42 type = lib.types.str;
43 readOnly = true;
44 default = domain;
45 description = ''
46 A domain name to use with the `nodes` attribute to
47 identify the CA server.
48 '';
49 };
50 caCert = lib.mkOption {
51 type = lib.types.path;
52 readOnly = true;
53 default = testCerts.ca.cert;
54 description = ''
55 A certificate file to use with the `nodes` attribute to
56 inject the test CA certificate used in the ACME server into
57 {option}`security.pki.certificateFiles`.
58 '';
59 };
60 };
61
62 config = {
63 networking = {
64 firewall.allowedTCPPorts = [
65 80
66 443
67 15000
68 4002
69 ];
70
71 # Match the caDomain - nixos/lib/testing/network.nix will then add a record for us to
72 # all nodes in /etc/hosts
73 hostName = "acme";
74 domain = "test";
75
76 # Extend /etc/hosts to resolve all configured certificates to their hosts.
77 # This way, no DNS server will be needed to validate HTTP-01 certs.
78 hosts = lib.attrsets.concatMapAttrs (
79 _: node:
80 let
81 inherit (node.networking) primaryIPAddress primaryIPv6Address;
82 ips = builtins.filter (ip: ip != "") [
83 primaryIPAddress
84 primaryIPv6Address
85 ];
86 names = lib.lists.unique (
87 lib.lists.flatten (
88 lib.lists.concatMap
89 (
90 cfg:
91 lib.attrsets.mapAttrsToList (
92 domain: cfg:
93 builtins.map (builtins.replaceStrings [ "*." ] [ "" ]) ([ domain ] ++ cfg.extraDomainNames)
94 ) cfg.configuration.security.acme.certs
95 )
96 # A specialisation's config is nested under its configuration attribute.
97 # For ease of use, nest the root node's configuration similarly.
98 ([ { configuration = node; } ] ++ (builtins.attrValues node.specialisation))
99 )
100 );
101 in
102 builtins.listToAttrs (builtins.map (ip: lib.attrsets.nameValuePair ip names) ips)
103 ) nodes;
104 };
105
106 systemd.services = {
107 pebble = {
108 enable = true;
109 description = "Pebble ACME server";
110 wantedBy = [ "network.target" ];
111 environment = {
112 # We're not testing lego, we're just testing our configuration.
113 # No need to sleep or randomly fail nonces.
114 PEBBLE_VA_NOSLEEP = "1";
115 PEBBLE_WFE_NONCEREJECT = "0";
116 };
117
118 serviceConfig = {
119 RuntimeDirectory = "pebble";
120 WorkingDirectory = "/run/pebble";
121
122 # Required to bind on privileged ports.
123 AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
124
125 ExecStart = "${pkgs.pebble}/bin/pebble -config ${pebbleConfFile}";
126 };
127 };
128 };
129 };
130}