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