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