Merge branch 'master' into staging
[jackhill/guix/guix.git] / gnu / tests / ssh.scm
1 ;;; GNU Guix --- Functional package management for GNU
2 ;;; Copyright © 2016, 2017, 2018, 2019 Ludovic Courtès <ludo@gnu.org>
3 ;;; Copyright © 2017, 2018 Clément Lassieur <clement@lassieur.org>
4 ;;; Copyright © 2017 Marius Bakke <mbakke@fastmail.com>
5 ;;;
6 ;;; This file is part of GNU Guix.
7 ;;;
8 ;;; GNU Guix is free software; you can redistribute it and/or modify it
9 ;;; under the terms of the GNU General Public License as published by
10 ;;; the Free Software Foundation; either version 3 of the License, or (at
11 ;;; your option) any later version.
12 ;;;
13 ;;; GNU Guix is distributed in the hope that it will be useful, but
14 ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
15 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 ;;; GNU General Public License for more details.
17 ;;;
18 ;;; You should have received a copy of the GNU General Public License
19 ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
20
21 (define-module (gnu tests ssh)
22 #:use-module (gnu tests)
23 #:use-module (gnu system)
24 #:use-module (gnu system vm)
25 #:use-module (gnu services)
26 #:use-module (gnu services ssh)
27 #:use-module (gnu services networking)
28 #:use-module (gnu packages ssh)
29 #:use-module (guix gexp)
30 #:use-module (guix store)
31 #:export (%test-openssh
32 %test-dropbear))
33
34 (define* (run-ssh-test name ssh-service pid-file
35 #:key (sftp? #f) (test-getlogin? #t))
36 "Run a test of an OS running SSH-SERVICE, which writes its PID to PID-FILE.
37 SSH-SERVICE must be configured to listen on port 22 and to allow for root and
38 empty-password logins.
39
40 When SFTP? is true, run an SFTP server test."
41 (define os
42 (marionette-operating-system
43 (simple-operating-system (service dhcp-client-service-type) ssh-service)
44 #:imported-modules '((gnu services herd)
45 (guix combinators))))
46 (define vm
47 (virtual-machine
48 (operating-system os)
49 (port-forwardings '((2222 . 22)))))
50
51 (define test
52 (with-imported-modules '((gnu build marionette))
53 (with-extensions (list guile-ssh)
54 #~(begin
55 (use-modules (gnu build marionette)
56 (srfi srfi-26)
57 (srfi srfi-64)
58 (ice-9 textual-ports)
59 (ice-9 match)
60 (ssh session)
61 (ssh auth)
62 (ssh channel)
63 (ssh popen)
64 (ssh sftp))
65
66 (define marionette
67 ;; Enable TCP forwarding of the guest's port 22.
68 (make-marionette (list #$vm)))
69
70 (define (make-session-for-test)
71 "Make a session with predefined parameters for a test."
72 (make-session #:user "root"
73 #:port 2222
74 #:host "localhost"
75 #:log-verbosity 'protocol))
76
77 (define (call-with-connected-session proc)
78 "Call the one-argument procedure PROC with a freshly created and
79 connected SSH session object, return the result of the procedure call. The
80 session is disconnected when the PROC is finished."
81 (let ((session (make-session-for-test)))
82 (dynamic-wind
83 (lambda ()
84 (let ((result (connect! session)))
85 (unless (equal? result 'ok)
86 (error "Could not connect to a server"
87 session result))))
88 (lambda () (proc session))
89 (lambda () (disconnect! session)))))
90
91 (define (call-with-connected-session/auth proc)
92 "Make an authenticated session. We should be able to connect as
93 root with an empty password."
94 (call-with-connected-session
95 (lambda (session)
96 ;; Try the simple authentication methods. Dropbear requires
97 ;; 'none' when there are no passwords, whereas OpenSSH accepts
98 ;; 'password' with an empty password.
99 (let loop ((methods (list (cut userauth-password! <> "")
100 (cut userauth-none! <>))))
101 (match methods
102 (()
103 (error "all the authentication methods failed"))
104 ((auth rest ...)
105 (match (pk 'auth (auth session))
106 ('success
107 (proc session))
108 ('denied
109 (loop rest)))))))))
110
111 (mkdir #$output)
112 (chdir #$output)
113
114 (test-begin "ssh-daemon")
115
116 ;; Wait for sshd to be up and running.
117 (test-assert "service running"
118 (marionette-eval
119 '(begin
120 (use-modules (gnu services herd))
121 (start-service 'ssh-daemon))
122 marionette))
123
124 ;; Check sshd's PID file.
125 (test-equal "sshd PID"
126 (wait-for-file #$pid-file marionette)
127 (marionette-eval
128 '(begin
129 (use-modules (gnu services herd)
130 (srfi srfi-1))
131
132 (live-service-running
133 (find (lambda (live)
134 (memq 'ssh-daemon
135 (live-service-provision live)))
136 (current-services))))
137 marionette))
138
139 ;; Connect to the guest over SSH. Make sure we can run a shell
140 ;; command there.
141 (test-equal "shell command"
142 'hello
143 (call-with-connected-session/auth
144 (lambda (session)
145 ;; FIXME: 'get-server-public-key' segfaults.
146 ;; (get-server-public-key session)
147 (let ((channel (make-channel session)))
148 (channel-open-session channel)
149 (channel-request-exec channel "echo hello > /root/witness")
150 (and (zero? (channel-get-exit-status channel))
151 (wait-for-file "/root/witness" marionette))))))
152
153 ;; Check whether the 'getlogin' procedure returns the right thing.
154 (unless #$test-getlogin?
155 (test-skip 1))
156 (test-equal "getlogin"
157 '(0 "root")
158 (call-with-connected-session/auth
159 (lambda (session)
160 (let* ((pipe (open-remote-input-pipe
161 session
162 "guile -c '(display (getlogin))'"))
163 (output (get-string-all pipe))
164 (status (channel-get-exit-status pipe)))
165 (list status output)))))
166
167 ;; Connect to the guest over SFTP. Make sure we can write and
168 ;; read a file there.
169 (unless #$sftp?
170 (test-skip 1))
171 (test-equal "SFTP file writing and reading"
172 'hello
173 (call-with-connected-session/auth
174 (lambda (session)
175 (let ((sftp-session (make-sftp-session session))
176 (witness "/root/sftp-witness"))
177 (call-with-remote-output-file sftp-session witness
178 (cut display "hello" <>))
179 (call-with-remote-input-file sftp-session witness
180 read)))))
181
182 ;; Connect to the guest over SSH. Make sure we can run commands
183 ;; from the system profile.
184 (test-equal "run executables from system profile"
185 #t
186 (call-with-connected-session/auth
187 (lambda (session)
188 (let ((channel (make-channel session)))
189 (channel-open-session channel)
190 (channel-request-exec
191 channel
192 (string-append
193 "mkdir -p /root/.guix-profile/bin && "
194 "touch /root/.guix-profile/bin/path-witness && "
195 "chmod 755 /root/.guix-profile/bin/path-witness"))
196 (zero? (channel-get-exit-status channel))))))
197
198 ;; Connect to the guest over SSH. Make sure we can run commands
199 ;; from the user profile.
200 (test-equal "run executable from user profile"
201 #t
202 (call-with-connected-session/auth
203 (lambda (session)
204 (let ((channel (make-channel session)))
205 (channel-open-session channel)
206 (channel-request-exec channel "path-witness")
207 (zero? (channel-get-exit-status channel))))))
208
209 (test-end)
210 (exit (= (test-runner-fail-count (test-runner-current)) 0))))))
211
212 (gexp->derivation name test))
213
214 (define %test-openssh
215 (system-test
216 (name "openssh")
217 (description "Connect to a running OpenSSH daemon.")
218 (value (run-ssh-test name
219 ;; Allow root logins with an empty password to
220 ;; simplify testing.
221 (service openssh-service-type
222 (openssh-configuration
223 (permit-root-login #t)
224 (allow-empty-passwords? #t)))
225 "/var/run/sshd.pid"
226 #:sftp? #t))))
227
228 (define %test-dropbear
229 (system-test
230 (name "dropbear")
231 (description "Connect to a running Dropbear SSH daemon.")
232 (value (run-ssh-test name
233 (service dropbear-service-type
234 (dropbear-configuration
235 (root-login? #t)
236 (allow-empty-passwords? #t)))
237 "/var/run/dropbear.pid"
238
239 ;; XXX: Our Dropbear is not built with PAM support.
240 ;; Even when it is, it seems to ignore the PAM
241 ;; 'session' requirements.
242 #:test-getlogin? #f))))