1{
2 system ? builtins.currentSystem,
3 pkgs ? import ../.. { inherit system; },
4 lib ? pkgs.lib,
5}:
6
7let
8 inherit (import ../lib/testing-python.nix { inherit system pkgs; }) makeTest;
9 nodeIP = n: n.networking.primaryIPAddress;
10 dnsZone =
11 nodes:
12 pkgs.writeText "agnos.test.zone" ''
13 $TTL 604800
14 @ IN SOA ns1.agnos.test. root.agnos.test. (
15 3 ; Serial
16 604800 ; Refresh
17 86400 ; Retry
18 2419200 ; Expire
19 604800 ) ; Negative Cache TTL
20 ;
21 ; name servers - NS records
22 IN NS ns1.agnos.test.
23
24 ; name servers - A records
25 ns1.agnos.test. IN A ${nodeIP nodes.dnsserver}
26
27 agnos-ns.agnos.test. IN A ${nodeIP nodes.server}
28 _acme-challenge.a.agnos.test. IN NS agnos-ns.agnos.test.
29 _acme-challenge.b.agnos.test. IN NS agnos-ns.agnos.test.
30 _acme-challenge.c.agnos.test. IN NS agnos-ns.agnos.test.
31 _acme-challenge.d.agnos.test. IN NS agnos-ns.agnos.test.
32 '';
33
34 mkTest =
35 {
36 name,
37 extraServerConfig ? { },
38 checkFirewallClosed ? true,
39 }:
40 makeTest {
41 inherit name;
42 meta = {
43 maintainers = with lib.maintainers; [ justinas ];
44 };
45
46 nodes = {
47 # The fake ACME server which will respond to client requests
48 acme =
49 { nodes, pkgs, ... }:
50 {
51 imports = [ ./common/acme/server ];
52 environment.systemPackages = [ pkgs.netcat ];
53 networking.nameservers = lib.mkForce [ (nodeIP nodes.dnsserver) ];
54 };
55
56 # A fake DNS server which points _acme-challenge subdomains to "server"
57 dnsserver =
58 { nodes, ... }:
59 {
60 networking.firewall.allowedTCPPorts = [ 53 ];
61 networking.firewall.allowedUDPPorts = [ 53 ];
62 services.bind = {
63 cacheNetworks = [ "192.168.1.0/24" ];
64 enable = true;
65 extraOptions = ''
66 dnssec-validation no;
67 '';
68 zones."agnos.test" = {
69 file = dnsZone nodes;
70 master = true;
71 };
72 };
73 };
74
75 # The server using agnos to request certificates
76 server =
77 { nodes, ... }:
78 {
79 imports = [ extraServerConfig ];
80
81 networking.extraHosts = ''
82 ${nodeIP nodes.acme} acme.test
83 '';
84 security.agnos = {
85 enable = true;
86 generateKeys.enable = true;
87 persistent = false;
88 server = "https://acme.test/dir";
89 serverCa = ./common/acme/server/ca.cert.pem;
90 temporarilyOpenFirewall = true;
91
92 settings.accounts = [
93 {
94 email = "webmaster@agnos.test";
95 # account with an existing private key
96 private_key_path = "${./common/acme/server/acme.test.key.pem}";
97
98 certificates = [
99 {
100 domains = [ "a.agnos.test" ];
101 # Absolute paths
102 fullchain_output_file = "/tmp/a.agnos.test.crt";
103 key_output_file = "/tmp/a.agnos.test.key";
104 }
105
106 {
107 domains = [
108 "b.agnos.test"
109 "*.b.agnos.test"
110 ];
111 # Relative paths
112 fullchain_output_file = "b.agnos.test.crt";
113 key_output_file = "b.agnos.test.key";
114 }
115 ];
116 }
117
118 {
119 email = "webmaster2@agnos.test";
120 # account with a missing private key, should get generated
121 private_key_path = "webmaster2.key";
122
123 certificates = [
124 {
125 domains = [ "c.agnos.test" ];
126 # Absolute paths
127 fullchain_output_file = "/tmp/c.agnos.test.crt";
128 key_output_file = "/tmp/c.agnos.test.key";
129 }
130
131 {
132 domains = [
133 "d.agnos.test"
134 "*.d.agnos.test"
135 ];
136 # Relative paths
137 fullchain_output_file = "d.agnos.test.crt";
138 key_output_file = "d.agnos.test.key";
139 }
140 ];
141 }
142 ];
143 };
144 };
145 };
146
147 testScript = ''
148 def check_firewall_closed(caller):
149 """
150 Check that TCP port 53 is closed again.
151
152 Since we do not set `networking.firewall.rejectPackets`,
153 "timed out" indicates a closed port,
154 while "connection refused" (after agnos has shut down) indicates an open port.
155 """
156
157 out = caller.fail("nc -v -z -w 1 server 53 2>&1")
158 assert "Connection timed out" in out
159
160 start_all()
161 acme.wait_for_unit('pebble.service')
162 server.wait_for_unit('default.target')
163
164 # Test that agnos.timer is scheduled
165 server.succeed("systemctl status agnos.timer")
166 server.succeed('systemctl start agnos.service')
167
168 expected_perms = "640 agnos agnos"
169 outputs = [
170 "/tmp/a.agnos.test.crt",
171 "/tmp/a.agnos.test.key",
172 "/var/lib/agnos/b.agnos.test.crt",
173 "/var/lib/agnos/b.agnos.test.key",
174 "/var/lib/agnos/webmaster2.key",
175 "/tmp/c.agnos.test.crt",
176 "/tmp/c.agnos.test.key",
177 "/var/lib/agnos/d.agnos.test.crt",
178 "/var/lib/agnos/d.agnos.test.key",
179 ]
180 for o in outputs:
181 out = server.succeed(f"stat -c '%a %U %G' {o}").strip()
182 assert out == expected_perms, \
183 f"Expected mode/owner/group to be '{expected_perms}', but it was '{out}'"
184
185 ${lib.optionalString checkFirewallClosed "check_firewall_closed(acme)"}
186 '';
187 };
188in
189{
190 iptables = mkTest {
191 name = "iptables";
192 };
193
194 nftables = mkTest {
195 name = "nftables";
196 extraServerConfig = {
197 networking.nftables.enable = true;
198 };
199 };
200
201 no-firewall = mkTest {
202 name = "no-firewall";
203 extraServerConfig = {
204 networking.firewall.enable = lib.mkForce false;
205 security.agnos.temporarilyOpenFirewall = lib.mkForce false;
206 };
207 checkFirewallClosed = false;
208 };
209}