1{
2 system ? builtins.currentSystem,
3 config ? { },
4 pkgs ? import ../.. { inherit system config; },
5}:
6
7with import ../lib/testing-python.nix { inherit system pkgs; };
8with pkgs.lib;
9
10let
11 stunnelCommon = {
12 services.stunnel = {
13 enable = true;
14 user = "stunnel";
15 };
16 users.groups.stunnel = { };
17 users.users.stunnel = {
18 isSystemUser = true;
19 group = "stunnel";
20 };
21 };
22 makeCert =
23 { config, pkgs, ... }:
24 {
25 systemd.services.create-test-cert = {
26 wantedBy = [ "sysinit.target" ];
27 before = [
28 "sysinit.target"
29 "shutdown.target"
30 ];
31 conflicts = [ "shutdown.target" ];
32 unitConfig.DefaultDependencies = false;
33 serviceConfig.Type = "oneshot";
34 script = ''
35 ${pkgs.openssl}/bin/openssl req -batch -x509 -newkey rsa -nodes -out /test-cert.pem -keyout /test-key.pem -subj /CN=${config.networking.hostName}
36 ( umask 077; cat /test-key.pem /test-cert.pem > /test-key-and-cert.pem )
37 chown stunnel /test-key.pem /test-key-and-cert.pem
38 '';
39 };
40 };
41 serverCommon =
42 { pkgs, ... }:
43 {
44 networking.firewall.allowedTCPPorts = [ 443 ];
45 services.stunnel.servers.https = {
46 accept = "443";
47 connect = 80;
48 cert = "/test-key-and-cert.pem";
49 };
50 systemd.services.simple-webserver = {
51 wantedBy = [ "multi-user.target" ];
52 script = ''
53 cd /etc/webroot
54 ${pkgs.python3}/bin/python -m http.server 80
55 '';
56 };
57 };
58 copyCert = src: dest: filename: ''
59 from shlex import quote
60 ${src}.wait_for_file("/test-key-and-cert.pem")
61 server_cert = ${src}.succeed("cat /test-cert.pem")
62 ${dest}.succeed("echo %s > ${filename}" % quote(server_cert))
63 '';
64
65in
66{
67 basicServer = makeTest {
68 name = "basicServer";
69
70 nodes = {
71 client = { };
72 server = {
73 imports = [
74 makeCert
75 serverCommon
76 stunnelCommon
77 ];
78 environment.etc."webroot/index.html".text = "well met";
79 };
80 };
81
82 testScript = ''
83 start_all()
84
85 ${copyCert "server" "client" "/authorized-server-cert.crt"}
86
87 server.wait_for_unit("simple-webserver")
88 server.wait_for_unit("stunnel")
89
90 client.succeed("curl --fail --cacert /authorized-server-cert.crt https://server/ > out")
91 client.succeed('[[ "$(< out)" == "well met" ]]')
92 '';
93 };
94
95 serverAndClient = makeTest {
96 name = "serverAndClient";
97
98 nodes = {
99 client = {
100 imports = [ stunnelCommon ];
101 services.stunnel.clients = {
102 httpsClient = {
103 accept = "80";
104 connect = "server:443";
105 CAFile = "/authorized-server-cert.crt";
106 };
107 httpsClientWithHostVerify = {
108 accept = "81";
109 connect = "server:443";
110 CAFile = "/authorized-server-cert.crt";
111 verifyHostname = "server";
112 };
113 httpsClientWithHostVerifyFail = {
114 accept = "82";
115 connect = "server:443";
116 CAFile = "/authorized-server-cert.crt";
117 verifyHostname = "wronghostname";
118 };
119 };
120 };
121 server = {
122 imports = [
123 makeCert
124 serverCommon
125 stunnelCommon
126 ];
127 environment.etc."webroot/index.html".text = "hello there";
128 };
129 };
130
131 testScript = ''
132 start_all()
133
134 ${copyCert "server" "client" "/authorized-server-cert.crt"}
135
136 server.wait_for_unit("simple-webserver")
137 server.wait_for_unit("stunnel")
138
139 # In case stunnel came up before we got the server's cert copied over
140 client.succeed("systemctl reload-or-restart stunnel")
141
142 client.succeed("curl --fail http://localhost/ > out")
143 client.succeed('[[ "$(< out)" == "hello there" ]]')
144
145 client.succeed("curl --fail http://localhost:81/ > out")
146 client.succeed('[[ "$(< out)" == "hello there" ]]')
147
148 client.fail("curl --fail http://localhost:82/ > out")
149 client.succeed('[[ "$(< out)" == "" ]]')
150 '';
151 };
152
153 mutualAuth = makeTest {
154 name = "mutualAuth";
155
156 nodes = rec {
157 client = {
158 imports = [
159 makeCert
160 stunnelCommon
161 ];
162 services.stunnel.clients.authenticated-https = {
163 accept = "80";
164 connect = "server:443";
165 verifyPeer = true;
166 CAFile = "/authorized-server-cert.crt";
167 cert = "/test-cert.pem";
168 key = "/test-key.pem";
169 };
170 };
171 wrongclient = client;
172 server = {
173 imports = [
174 makeCert
175 serverCommon
176 stunnelCommon
177 ];
178 services.stunnel.servers.https = {
179 CAFile = "/authorized-client-certs.crt";
180 verifyPeer = true;
181 };
182 environment.etc."webroot/index.html".text = "secret handshake";
183 };
184 };
185
186 testScript = ''
187 start_all()
188
189 ${copyCert "server" "client" "/authorized-server-cert.crt"}
190 ${copyCert "client" "server" "/authorized-client-certs.crt"}
191 ${copyCert "server" "wrongclient" "/authorized-server-cert.crt"}
192
193 # In case stunnel came up before we got the cross-certs in place
194 client.succeed("systemctl reload-or-restart stunnel")
195 server.succeed("systemctl reload-or-restart stunnel")
196 wrongclient.succeed("systemctl reload-or-restart stunnel")
197
198 server.wait_for_unit("simple-webserver")
199 client.fail("curl --fail --insecure https://server/ > out")
200 client.succeed('[[ "$(< out)" == "" ]]')
201 client.succeed("curl --fail http://localhost/ > out")
202 client.succeed('[[ "$(< out)" == "secret handshake" ]]')
203 wrongclient.fail("curl --fail http://localhost/ > out")
204 wrongclient.succeed('[[ "$(< out)" == "" ]]')
205 '';
206 };
207}