1import ./make-test-python.nix (
2 { pkgs, ... }:
3
4 let
5 inherit (import ./ssh-keys.nix pkgs)
6 snakeOilPrivateKey
7 snakeOilPublicKey
8 snakeOilEd25519PrivateKey
9 snakeOilEd25519PublicKey
10 ;
11 in
12 {
13 name = "openssh";
14 meta = with pkgs.lib.maintainers; {
15 maintainers = [ aszlig ];
16 };
17
18 nodes = {
19
20 server =
21 { ... }:
22
23 {
24 services.openssh.enable = true;
25 security.pam.services.sshd.limits = [
26 {
27 domain = "*";
28 item = "memlock";
29 type = "-";
30 value = 1024;
31 }
32 ];
33 users.users.root.openssh.authorizedKeys.keys = [
34 snakeOilPublicKey
35 ];
36 };
37
38 server-allowed-users =
39 { ... }:
40
41 {
42 services.openssh = {
43 enable = true;
44 settings.AllowUsers = [
45 "alice"
46 "bob"
47 ];
48 };
49 users.groups = {
50 alice = { };
51 bob = { };
52 carol = { };
53 };
54 users.users = {
55 alice = {
56 isNormalUser = true;
57 group = "alice";
58 openssh.authorizedKeys.keys = [ snakeOilPublicKey ];
59 };
60 bob = {
61 isNormalUser = true;
62 group = "bob";
63 openssh.authorizedKeys.keys = [ snakeOilPublicKey ];
64 };
65 carol = {
66 isNormalUser = true;
67 group = "carol";
68 openssh.authorizedKeys.keys = [ snakeOilPublicKey ];
69 };
70 };
71 };
72
73 server-lazy =
74 { ... }:
75
76 {
77 services.openssh = {
78 enable = true;
79 startWhenNeeded = true;
80 };
81 security.pam.services.sshd.limits = [
82 {
83 domain = "*";
84 item = "memlock";
85 type = "-";
86 value = 1024;
87 }
88 ];
89 users.users.root.openssh.authorizedKeys.keys = [
90 snakeOilPublicKey
91 ];
92 };
93
94 server-lazy-socket = {
95 virtualisation.vlans = [
96 1
97 2
98 ];
99 services.openssh = {
100 enable = true;
101 startWhenNeeded = true;
102 ports = [ 2222 ];
103 listenAddresses = [ { addr = "0.0.0.0"; } ];
104 };
105 users.users.root.openssh.authorizedKeys.keys = [
106 snakeOilPublicKey
107 ];
108 };
109
110 server-localhost-only =
111 { ... }:
112
113 {
114 services.openssh = {
115 enable = true;
116 listenAddresses = [
117 {
118 addr = "127.0.0.1";
119 port = 22;
120 }
121 ];
122 };
123 };
124
125 server-localhost-only-lazy =
126 { ... }:
127
128 {
129 services.openssh = {
130 enable = true;
131 startWhenNeeded = true;
132 listenAddresses = [
133 {
134 addr = "127.0.0.1";
135 port = 22;
136 }
137 ];
138 };
139 };
140
141 server-match-rule =
142 { ... }:
143
144 {
145 services.openssh = {
146 enable = true;
147 listenAddresses = [
148 {
149 addr = "127.0.0.1";
150 port = 22;
151 }
152 {
153 addr = "[::]";
154 port = 22;
155 }
156 ];
157 extraConfig = ''
158 # Combined test for two (predictable) Match criterias
159 Match LocalAddress 127.0.0.1 LocalPort 22
160 PermitRootLogin yes
161
162 # Separate tests for Match criterias
163 Match User root
164 PermitRootLogin yes
165 Match Group root
166 PermitRootLogin yes
167 Match Host nohost.example
168 PermitRootLogin yes
169 Match LocalAddress 127.0.0.1
170 PermitRootLogin yes
171 Match LocalPort 22
172 PermitRootLogin yes
173 Match RDomain nohost.example
174 PermitRootLogin yes
175 Match Address 127.0.0.1
176 PermitRootLogin yes
177 '';
178 };
179 };
180
181 server-no-openssl =
182 { ... }:
183 {
184 services.openssh = {
185 enable = true;
186 package = pkgs.opensshPackages.openssh.override {
187 linkOpenssl = false;
188 };
189 hostKeys = [
190 {
191 type = "ed25519";
192 path = "/etc/ssh/ssh_host_ed25519_key";
193 }
194 ];
195 settings = {
196 # Since this test is against an OpenSSH-without-OpenSSL,
197 # we have to override NixOS's defaults ciphers (which require OpenSSL)
198 # and instead set these to null, which will mean OpenSSH uses its defaults.
199 # Expectedly, OpenSSH's defaults don't require OpenSSL when it's compiled
200 # without OpenSSL.
201 Ciphers = null;
202 KexAlgorithms = null;
203 Macs = null;
204 };
205 };
206 users.users.root.openssh.authorizedKeys.keys = [
207 snakeOilEd25519PublicKey
208 ];
209 };
210
211 server-no-pam =
212 { pkgs, ... }:
213 {
214 services.openssh = {
215 enable = true;
216 package = pkgs.opensshPackages.openssh.override {
217 withPAM = false;
218 };
219 settings = {
220 UsePAM = false;
221 };
222 };
223 users.users.root.openssh.authorizedKeys.keys = [
224 snakeOilPublicKey
225 ];
226 };
227
228 client =
229 { ... }:
230 {
231 virtualisation.vlans = [
232 1
233 2
234 ];
235 };
236
237 };
238
239 testScript = ''
240 start_all()
241
242 server.wait_for_unit("sshd", timeout=30)
243 server_allowed_users.wait_for_unit("sshd", timeout=30)
244 server_localhost_only.wait_for_unit("sshd", timeout=30)
245 server_match_rule.wait_for_unit("sshd", timeout=30)
246 server_no_openssl.wait_for_unit("sshd", timeout=30)
247 server_no_pam.wait_for_unit("sshd", timeout=30)
248
249 server_lazy.wait_for_unit("sshd.socket", timeout=30)
250 server_localhost_only_lazy.wait_for_unit("sshd.socket", timeout=30)
251 server_lazy_socket.wait_for_unit("sshd.socket", timeout=30)
252
253 with subtest("manual-authkey"):
254 client.succeed(
255 '${pkgs.openssh}/bin/ssh-keygen -t ed25519 -f /root/.ssh/id_ed25519 -N ""'
256 )
257 public_key = client.succeed(
258 "${pkgs.openssh}/bin/ssh-keygen -y -f /root/.ssh/id_ed25519"
259 )
260 public_key = public_key.strip()
261 client.succeed("chmod 600 /root/.ssh/id_ed25519")
262
263 server.succeed("echo '{}' > /root/.ssh/authorized_keys".format(public_key))
264 server_lazy.succeed("echo '{}' > /root/.ssh/authorized_keys".format(public_key))
265
266 client.wait_for_unit("network.target")
267 client.succeed(
268 "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server 'echo hello world' >&2",
269 timeout=30
270 )
271 client.succeed(
272 "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server 'ulimit -l' | grep 1024",
273 timeout=30
274 )
275
276 client.succeed(
277 "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server-lazy 'echo hello world' >&2",
278 timeout=30
279 )
280 client.succeed(
281 "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server-lazy 'ulimit -l' | grep 1024",
282 timeout=30
283 )
284
285 with subtest("socket activation on a non-standard port"):
286 client.succeed(
287 "cat ${snakeOilPrivateKey} > privkey.snakeoil"
288 )
289 client.succeed("chmod 600 privkey.snakeoil")
290 # The final segment in this IP is allocated according to the alphabetical order of machines in this test.
291 client.succeed(
292 "ssh -p 2222 -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil root@192.168.2.5 true",
293 timeout=30
294 )
295
296 with subtest("configured-authkey"):
297 client.succeed(
298 "cat ${snakeOilPrivateKey} > privkey.snakeoil"
299 )
300 client.succeed("chmod 600 privkey.snakeoil")
301 client.succeed(
302 "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil server true",
303 timeout=30
304 )
305 client.succeed(
306 "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil server-lazy true",
307 timeout=30
308 )
309
310 with subtest("localhost-only"):
311 server_localhost_only.succeed("ss -nlt | grep '127.0.0.1:22'")
312 server_localhost_only_lazy.succeed("ss -nlt | grep '127.0.0.1:22'")
313
314 with subtest("match-rules"):
315 server_match_rule.succeed("ss -nlt | grep '127.0.0.1:22'")
316
317 with subtest("allowed-users"):
318 client.succeed(
319 "cat ${snakeOilPrivateKey} > privkey.snakeoil"
320 )
321 client.succeed("chmod 600 privkey.snakeoil")
322 client.succeed(
323 "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil alice@server-allowed-users true",
324 timeout=30
325 )
326 client.succeed(
327 "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil bob@server-allowed-users true",
328 timeout=30
329 )
330 client.fail(
331 "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil carol@server-allowed-users true",
332 timeout=30
333 )
334
335 with subtest("no-openssl"):
336 client.succeed(
337 "cat ${snakeOilEd25519PrivateKey} > privkey.snakeoil"
338 )
339 client.succeed("chmod 600 privkey.snakeoil")
340 client.succeed(
341 "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil server-no-openssl true",
342 timeout=30
343 )
344
345 with subtest("no-pam"):
346 client.succeed(
347 "cat ${snakeOilPrivateKey} > privkey.snakeoil"
348 )
349 client.succeed("chmod 600 privkey.snakeoil")
350 client.succeed(
351 "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil server-no-pam true",
352 timeout=30
353 )
354
355 # None of the per-connection units should have failed.
356 server_lazy.fail("systemctl is-failed 'sshd@*.service'")
357 '';
358 }
359)