ssh.scm 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2016-2022 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. (define-module (gnu tests ssh)
  21. #:use-module (gnu tests)
  22. #:use-module (gnu system)
  23. #:use-module (gnu system vm)
  24. #:use-module (gnu services)
  25. #:use-module (gnu services ssh)
  26. #:use-module (gnu services networking)
  27. #:use-module (gnu packages ssh)
  28. #:use-module (guix gexp)
  29. #:use-module (guix store)
  30. #:export (%test-openssh
  31. %test-dropbear))
  32. (define* (run-ssh-test name ssh-service pid-file
  33. #:key (sftp? #f) (test-getlogin? #t))
  34. "Run a test of an OS running SSH-SERVICE, which writes its PID to PID-FILE.
  35. SSH-SERVICE must be configured to listen on port 22 and to allow for root and
  36. empty-password logins.
  37. When SFTP? is true, run an SFTP server test."
  38. (define os
  39. (marionette-operating-system
  40. (simple-operating-system (service dhcp-client-service-type) ssh-service)
  41. #:imported-modules '((gnu services herd)
  42. (guix combinators))))
  43. (define vm
  44. (virtual-machine
  45. (operating-system os)
  46. (port-forwardings '((2222 . 22)))))
  47. (define test
  48. (with-imported-modules '((gnu build marionette))
  49. (with-extensions (list guile-ssh)
  50. #~(begin
  51. (use-modules (gnu build marionette)
  52. (srfi srfi-26)
  53. (srfi srfi-64)
  54. (ice-9 textual-ports)
  55. (ice-9 match)
  56. (ssh session)
  57. (ssh auth)
  58. (ssh channel)
  59. (ssh popen)
  60. (ssh sftp))
  61. (define marionette
  62. ;; Enable TCP forwarding of the guest's port 22.
  63. (make-marionette (list #$vm)))
  64. (define (make-session-for-test)
  65. "Make a session with predefined parameters for a test."
  66. (make-session #:user "root"
  67. #:port 2222
  68. #:host "localhost"
  69. #:log-verbosity 'protocol))
  70. (define (call-with-connected-session proc)
  71. "Call the one-argument procedure PROC with a freshly created and
  72. connected SSH session object, return the result of the procedure call. The
  73. session is disconnected when the PROC is finished."
  74. (let ((session (make-session-for-test)))
  75. (dynamic-wind
  76. (lambda ()
  77. (let ((result (connect! session)))
  78. (unless (equal? result 'ok)
  79. (error "Could not connect to a server"
  80. session result))))
  81. (lambda () (proc session))
  82. (lambda () (disconnect! session)))))
  83. (define (call-with-connected-session/auth proc)
  84. "Make an authenticated session. We should be able to connect as
  85. root with an empty password."
  86. (call-with-connected-session
  87. (lambda (session)
  88. ;; Try the simple authentication methods. Dropbear requires
  89. ;; 'none' when there are no passwords, whereas OpenSSH accepts
  90. ;; 'password' with an empty password.
  91. (let loop ((methods (list (cut userauth-password! <> "")
  92. (cut userauth-none! <>))))
  93. (match methods
  94. (()
  95. (error "all the authentication methods failed"))
  96. ((auth rest ...)
  97. (match (pk 'auth (auth session))
  98. ('success
  99. (proc session))
  100. ('denied
  101. (loop rest)))))))))
  102. (test-runner-current (system-test-runner #$output))
  103. (test-begin "ssh-daemon")
  104. ;; Wait for sshd to be up and running.
  105. (test-assert "service running"
  106. (marionette-eval
  107. '(begin
  108. (use-modules (gnu services herd))
  109. (start-service 'ssh-daemon))
  110. marionette))
  111. ;; Check sshd's PID file.
  112. (test-assert "sshd PID"
  113. (let ((pid (marionette-eval
  114. '(begin
  115. (use-modules (gnu services herd)
  116. (srfi srfi-1))
  117. (live-service-running
  118. (find (lambda (live)
  119. (memq 'ssh-daemon
  120. (live-service-provision live)))
  121. (current-services))))
  122. marionette)))
  123. (if #$pid-file
  124. (= pid (wait-for-file #$pid-file marionette))
  125. pid)))
  126. (test-assert "wait for port 22, IPv4"
  127. (wait-for-tcp-port 22 marionette))
  128. (test-assert "wait for port 22, IPv6"
  129. ;; Make sure it's also available as IPv6.
  130. ;; See <https://issues.guix.gnu.org/55335>.
  131. (wait-for-tcp-port 22 marionette
  132. #:address
  133. `(make-socket-address
  134. AF_INET6
  135. (inet-pton AF_INET6 "::1")
  136. 22)))
  137. ;; Connect to the guest over SSH. Make sure we can run a shell
  138. ;; command there.
  139. (test-equal "shell command"
  140. 'hello
  141. (call-with-connected-session/auth
  142. (lambda (session)
  143. ;; FIXME: 'get-server-public-key' segfaults.
  144. ;; (get-server-public-key session)
  145. (let ((channel (make-channel session)))
  146. (channel-open-session channel)
  147. (channel-request-exec channel "echo hello > /root/witness")
  148. (and (zero? (channel-get-exit-status channel))
  149. (wait-for-file "/root/witness" marionette))))))
  150. ;; Check whether the 'getlogin' procedure returns the right thing.
  151. (unless #$test-getlogin?
  152. (test-skip 1))
  153. (test-equal "getlogin"
  154. '(0 "root")
  155. (call-with-connected-session/auth
  156. (lambda (session)
  157. (let* ((pipe (open-remote-input-pipe
  158. session
  159. "guile -c '(display (getlogin))'"))
  160. (output (get-string-all pipe))
  161. (status (channel-get-exit-status pipe)))
  162. (list status output)))))
  163. ;; Connect to the guest over SFTP. Make sure we can write and
  164. ;; read a file there.
  165. (unless #$sftp?
  166. (test-skip 1))
  167. (test-equal "SFTP file writing and reading"
  168. 'hello
  169. (call-with-connected-session/auth
  170. (lambda (session)
  171. (let ((sftp-session (make-sftp-session session))
  172. (witness "/root/sftp-witness"))
  173. (call-with-remote-output-file sftp-session witness
  174. (cut display "hello" <>))
  175. (call-with-remote-input-file sftp-session witness
  176. read)))))
  177. ;; Connect to the guest over SSH. Make sure we can run commands
  178. ;; from the system profile.
  179. (test-equal "run executables from system profile"
  180. #t
  181. (call-with-connected-session/auth
  182. (lambda (session)
  183. (let ((channel (make-channel session)))
  184. (channel-open-session channel)
  185. (channel-request-exec
  186. channel
  187. (string-append
  188. "mkdir -p /root/.guix-profile/bin && "
  189. "touch /root/.guix-profile/bin/path-witness && "
  190. "chmod 755 /root/.guix-profile/bin/path-witness"))
  191. (zero? (channel-get-exit-status channel))))))
  192. ;; Connect to the guest over SSH. Make sure we can run commands
  193. ;; from the user profile.
  194. (test-equal "run executable from user profile"
  195. #t
  196. (call-with-connected-session/auth
  197. (lambda (session)
  198. (let ((channel (make-channel session)))
  199. (channel-open-session channel)
  200. (channel-request-exec channel "path-witness")
  201. (zero? (channel-get-exit-status channel))))))
  202. (test-end)))))
  203. (gexp->derivation name test))
  204. (define %test-openssh
  205. (system-test
  206. (name "openssh")
  207. (description "Connect to a running OpenSSH daemon.")
  208. (value (run-ssh-test name
  209. ;; Allow root logins with an empty password to
  210. ;; simplify testing.
  211. (service openssh-service-type
  212. (openssh-configuration
  213. (permit-root-login #t)
  214. (allow-empty-passwords? #t)))
  215. #f ;inetd-style, no PID file
  216. #:sftp? #t))))
  217. (define %test-dropbear
  218. (system-test
  219. (name "dropbear")
  220. (description "Connect to a running Dropbear SSH daemon.")
  221. (value (run-ssh-test name
  222. (service dropbear-service-type
  223. (dropbear-configuration
  224. (root-login? #t)
  225. (allow-empty-passwords? #t)))
  226. "/var/run/dropbear.pid"
  227. ;; XXX: Our Dropbear is not built with PAM support.
  228. ;; Even when it is, it seems to ignore the PAM
  229. ;; 'session' requirements.
  230. #:test-getlogin? #f))))