ssh.scm 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2014-2019, 2022, 2023 Ludovic Courtès <ludo@gnu.org>
  3. ;;; Copyright © 2016 David Craven <david@craven.ch>
  4. ;;; Copyright © 2016 Julien Lepiller <julien@lepiller.eu>
  5. ;;; Copyright © 2017 Clément Lassieur <clement@lassieur.org>
  6. ;;; Copyright © 2019 Ricardo Wurmus <rekado@elephly.net>
  7. ;;; Copyright © 2020 pinoaffe <pinoaffe@airmail.cc>
  8. ;;; Copyright © 2020 Oleg Pykhalov <go.wigust@gmail.com>
  9. ;;; Copyright © 2020 Brice Waegeneire <brice@waegenei.re>
  10. ;;; Copyright © 2021 Tobias Geerinckx-Rice <me@tobias.gr>
  11. ;;;
  12. ;;; This file is part of GNU Guix.
  13. ;;;
  14. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  15. ;;; under the terms of the GNU General Public License as published by
  16. ;;; the Free Software Foundation; either version 3 of the License, or (at
  17. ;;; your option) any later version.
  18. ;;;
  19. ;;; GNU Guix is distributed in the hope that it will be useful, but
  20. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  21. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  22. ;;; GNU General Public License for more details.
  23. ;;;
  24. ;;; You should have received a copy of the GNU General Public License
  25. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  26. (define-module (gnu services ssh)
  27. #:use-module (gnu packages ssh)
  28. #:use-module (gnu packages admin)
  29. #:use-module (gnu services)
  30. #:use-module (gnu services shepherd)
  31. #:use-module (gnu services web)
  32. #:use-module (gnu system pam)
  33. #:use-module (gnu system shadow)
  34. #:use-module (guix deprecation)
  35. #:use-module (guix gexp)
  36. #:use-module (guix records)
  37. #:use-module (guix modules)
  38. #:use-module (srfi srfi-1)
  39. #:use-module (srfi srfi-26)
  40. #:use-module (ice-9 match)
  41. #:use-module (ice-9 vlist)
  42. #:export (lsh-configuration
  43. lsh-configuration?
  44. lsh-service ; deprecated
  45. lsh-service-type
  46. openssh-configuration
  47. openssh-configuration?
  48. openssh-configuration-openssh
  49. openssh-configuration-pid-file
  50. openssh-configuration-port-number
  51. openssh-configuration-max-connections
  52. openssh-configuration-permit-root-login
  53. openssh-configuration-allow-empty-passwords?
  54. openssh-configuration-password-authentication?
  55. openssh-configuration-public-key-authentication?
  56. openssh-configuration-x11-forwarding?
  57. openssh-configuration-allow-agent-forwarding?
  58. openssh-configuration-allow-tcp-forwarding?
  59. openssh-configuration-gateway-ports?
  60. openssh-configuration-challenge-response-authentication?
  61. openssh-configuration-use-pam?
  62. openssh-configuration-print-last-log?
  63. openssh-configuration-subsystems
  64. openssh-configuration-accepted-environment
  65. openssh-configuration-log-level
  66. openssh-configuration-extra-content
  67. openssh-configuration-authorized-keys
  68. openssh-configuration-generate-host-keys?
  69. openssh-service-type
  70. dropbear-configuration
  71. dropbear-configuration?
  72. dropbear-service-type
  73. dropbear-service ; deprecated
  74. autossh-configuration
  75. autossh-configuration?
  76. autossh-service-type
  77. webssh-configuration
  78. webssh-configuration?
  79. webssh-service-type
  80. %webssh-configuration-nginx))
  81. ;;; Commentary:
  82. ;;;
  83. ;;; This module implements secure shell (SSH) services.
  84. ;;;
  85. ;;; Code:
  86. (define-record-type* <lsh-configuration>
  87. lsh-configuration make-lsh-configuration
  88. lsh-configuration?
  89. (lsh lsh-configuration-lsh
  90. (default lsh))
  91. (daemonic? lsh-configuration-daemonic?
  92. (default #t))
  93. (host-key lsh-configuration-host-key
  94. (default "/etc/lsh/host-key"))
  95. (interfaces lsh-configuration-interfaces
  96. (default '()))
  97. (port-number lsh-configuration-port-number
  98. (default 22))
  99. (allow-empty-passwords? lsh-configuration-allow-empty-passwords?
  100. (default #f))
  101. (root-login? lsh-configuration-root-login?
  102. (default #f))
  103. (syslog-output? lsh-configuration-syslog-output?
  104. (default #t))
  105. (pid-file? lsh-configuration-pid-file?
  106. (default #f))
  107. (pid-file lsh-configuration-pid-file
  108. (default "/var/run/lshd.pid"))
  109. (x11-forwarding? lsh-configuration-x11-forwarding?
  110. (default #t))
  111. (tcp/ip-forwarding? lsh-configuration-tcp/ip-forwarding?
  112. (default #t))
  113. (password-authentication? lsh-configuration-password-authentication?
  114. (default #t))
  115. (public-key-authentication? lsh-configuration-public-key-authentication?
  116. (default #t))
  117. (initialize? lsh-configuration-initialize?
  118. (default #t)))
  119. (define %yarrow-seed
  120. "/var/spool/lsh/yarrow-seed-file")
  121. (define (lsh-initialization lsh host-key)
  122. "Return the gexp to initialize the LSH service for HOST-KEY."
  123. #~(begin
  124. (unless (file-exists? #$%yarrow-seed)
  125. (system* (string-append #$lsh "/bin/lsh-make-seed")
  126. "--sloppy" "-o" #$%yarrow-seed))
  127. (unless (file-exists? #$host-key)
  128. (mkdir-p (dirname #$host-key))
  129. (format #t "creating SSH host key '~a'...~%" #$host-key)
  130. ;; FIXME: We're just doing a simple pipeline, but 'system' cannot be
  131. ;; used yet because /bin/sh might be dangling; factorize this somehow.
  132. (let* ((in+out (pipe))
  133. (keygen (primitive-fork)))
  134. (case keygen
  135. ((0)
  136. (close-port (car in+out))
  137. (close-fdes 1)
  138. (dup2 (fileno (cdr in+out)) 1)
  139. (execl (string-append #$lsh "/bin/lsh-keygen")
  140. "lsh-keygen" "--server"))
  141. (else
  142. (let ((write-key (primitive-fork)))
  143. (case write-key
  144. ((0)
  145. (close-port (cdr in+out))
  146. (close-fdes 0)
  147. (dup2 (fileno (car in+out)) 0)
  148. (execl (string-append #$lsh "/bin/lsh-writekey")
  149. "lsh-writekey" "--server" "-o" #$host-key))
  150. (else
  151. (close-port (car in+out))
  152. (close-port (cdr in+out))
  153. (waitpid keygen)
  154. (waitpid write-key))))))))))
  155. (define (lsh-activation config)
  156. "Return the activation gexp for CONFIG."
  157. #~(begin
  158. (use-modules (guix build utils))
  159. (mkdir-p "/var/spool/lsh")
  160. #$(if (lsh-configuration-initialize? config)
  161. (lsh-initialization (lsh-configuration-lsh config)
  162. (lsh-configuration-host-key config))
  163. #t)))
  164. (define (lsh-shepherd-service config)
  165. "Return a <shepherd-service> for lsh with CONFIG."
  166. (define lsh (lsh-configuration-lsh config))
  167. (define pid-file (lsh-configuration-pid-file config))
  168. (define pid-file? (lsh-configuration-pid-file? config))
  169. (define daemonic? (lsh-configuration-daemonic? config))
  170. (define interfaces (lsh-configuration-interfaces config))
  171. (define lsh-command
  172. (append
  173. (cons (file-append lsh "/sbin/lshd")
  174. (if daemonic?
  175. (let ((syslog (if (lsh-configuration-syslog-output? config)
  176. '()
  177. (list "--no-syslog"))))
  178. (cons "--daemonic"
  179. (if pid-file?
  180. (cons #~(string-append "--pid-file=" #$pid-file)
  181. syslog)
  182. (cons "--no-pid-file" syslog))))
  183. (if pid-file?
  184. (list #~(string-append "--pid-file=" #$pid-file))
  185. '())))
  186. (cons* #~(string-append "--host-key="
  187. #$(lsh-configuration-host-key config))
  188. #~(string-append "--password-helper=" #$lsh "/sbin/lsh-pam-checkpw")
  189. #~(string-append "--subsystems=sftp=" #$lsh "/sbin/sftp-server")
  190. "-p" (number->string (lsh-configuration-port-number config))
  191. (if (lsh-configuration-password-authentication? config)
  192. "--password" "--no-password")
  193. (if (lsh-configuration-public-key-authentication? config)
  194. "--publickey" "--no-publickey")
  195. (if (lsh-configuration-root-login? config)
  196. "--root-login" "--no-root-login")
  197. (if (lsh-configuration-x11-forwarding? config)
  198. "--x11-forward" "--no-x11-forward")
  199. (if (lsh-configuration-tcp/ip-forwarding? config)
  200. "--tcpip-forward" "--no-tcpip-forward")
  201. (if (null? interfaces)
  202. '()
  203. (map (cut string-append "--interface=" <>)
  204. interfaces)))))
  205. (define requires
  206. `(networking
  207. pam
  208. ,@(if (and daemonic? (lsh-configuration-syslog-output? config))
  209. '(syslogd)
  210. '())))
  211. (list (shepherd-service
  212. (documentation "GNU lsh SSH server")
  213. (provision '(ssh-daemon ssh sshd))
  214. (requirement requires)
  215. (start #~(make-forkexec-constructor (list #$@lsh-command)))
  216. (stop #~(make-kill-destructor)))))
  217. (define (lsh-pam-services config)
  218. "Return a list of <pam-services> for lshd with CONFIG."
  219. (list (unix-pam-service
  220. "lshd"
  221. #:login-uid? #t
  222. #:allow-empty-passwords?
  223. (lsh-configuration-allow-empty-passwords? config))))
  224. (define lsh-service-type
  225. (service-type
  226. (name 'lsh)
  227. (extensions
  228. (list (service-extension shepherd-root-service-type
  229. lsh-shepherd-service)
  230. (service-extension pam-root-service-type
  231. lsh-pam-services)
  232. (service-extension activation-service-type
  233. lsh-activation)))
  234. (description "Run the GNU@tie{}lsh secure shell (SSH) daemon,
  235. @command{lshd}.")
  236. (default-value (lsh-configuration))))
  237. (define-deprecated (lsh-service #:key
  238. (lsh lsh)
  239. (daemonic? #t)
  240. (host-key "/etc/lsh/host-key")
  241. (interfaces '())
  242. (port-number 22)
  243. (allow-empty-passwords? #f)
  244. (root-login? #f)
  245. (syslog-output? #t)
  246. (pid-file? #f)
  247. (pid-file "/var/run/lshd.pid")
  248. (x11-forwarding? #t)
  249. (tcp/ip-forwarding? #t)
  250. (password-authentication? #t)
  251. (public-key-authentication? #t)
  252. (initialize? #t))
  253. lsh-service-type
  254. "Run the @command{lshd} program from @var{lsh} to listen on port @var{port-number}.
  255. @var{host-key} must designate a file containing the host key, and readable
  256. only by root.
  257. When @var{daemonic?} is true, @command{lshd} will detach from the
  258. controlling terminal and log its output to syslogd, unless one sets
  259. @var{syslog-output?} to false. Obviously, it also makes lsh-service
  260. depend on existence of syslogd service. When @var{pid-file?} is true,
  261. @command{lshd} writes its PID to the file called @var{pid-file}.
  262. When @var{initialize?} is true, automatically create the seed and host key
  263. upon service activation if they do not exist yet. This may take long and
  264. require interaction.
  265. When @var{initialize?} is false, it is up to the user to initialize the
  266. randomness generator (@pxref{lsh-make-seed,,, lsh, LSH Manual}), and to create
  267. a key pair with the private key stored in file @var{host-key} (@pxref{lshd
  268. basics,,, lsh, LSH Manual}).
  269. When @var{interfaces} is empty, lshd listens for connections on all the
  270. network interfaces; otherwise, @var{interfaces} must be a list of host names
  271. or addresses.
  272. @var{allow-empty-passwords?} specifies whether to accept log-ins with empty
  273. passwords, and @var{root-login?} specifies whether to accept log-ins as
  274. root.
  275. The other options should be self-descriptive."
  276. (service lsh-service-type
  277. (lsh-configuration (lsh lsh) (daemonic? daemonic?)
  278. (host-key host-key) (interfaces interfaces)
  279. (port-number port-number)
  280. (allow-empty-passwords? allow-empty-passwords?)
  281. (root-login? root-login?)
  282. (syslog-output? syslog-output?)
  283. (pid-file? pid-file?) (pid-file pid-file)
  284. (x11-forwarding? x11-forwarding?)
  285. (tcp/ip-forwarding? tcp/ip-forwarding?)
  286. (password-authentication?
  287. password-authentication?)
  288. (public-key-authentication?
  289. public-key-authentication?)
  290. (initialize? initialize?))))
  291. ;;;
  292. ;;; OpenSSH.
  293. ;;;
  294. (define-record-type* <openssh-configuration>
  295. openssh-configuration make-openssh-configuration
  296. openssh-configuration?
  297. ;; file-like object
  298. (openssh openssh-configuration-openssh
  299. (default openssh))
  300. ;; string
  301. (pid-file openssh-configuration-pid-file
  302. (default "/var/run/sshd.pid"))
  303. ;; integer
  304. (port-number openssh-configuration-port-number
  305. (default 22))
  306. ;; integer
  307. (max-connections openssh-configuration-max-connections
  308. (default 200))
  309. ;; Boolean | 'prohibit-password
  310. (permit-root-login openssh-configuration-permit-root-login
  311. (default #f))
  312. ;; Boolean
  313. (allow-empty-passwords? openssh-configuration-allow-empty-passwords?
  314. (default #f))
  315. ;; Boolean
  316. (password-authentication? openssh-configuration-password-authentication?
  317. (default #t))
  318. ;; Boolean
  319. (public-key-authentication? openssh-configuration-public-key-authentication?
  320. (default #t))
  321. ;; Boolean
  322. (x11-forwarding? openssh-configuration-x11-forwarding?
  323. (default #f))
  324. ;; Boolean
  325. (allow-agent-forwarding? openssh-configuration-allow-agent-forwarding?
  326. (default #t))
  327. ;; Boolean
  328. (allow-tcp-forwarding? openssh-configuration-allow-tcp-forwarding?
  329. (default #t))
  330. ;; Boolean
  331. (gateway-ports? openssh-configuration-gateway-ports?
  332. (default #f))
  333. ;; Boolean
  334. (challenge-response-authentication?
  335. openssh-configuration-challenge-response-authentication?
  336. (default #f))
  337. ;; Boolean
  338. (use-pam? openssh-configuration-use-pam?
  339. (default #t))
  340. ;; Boolean
  341. (print-last-log? openssh-configuration-print-last-log?
  342. (default #t))
  343. ;; list of two-element lists
  344. (subsystems openssh-configuration-subsystems
  345. (default '(("sftp" "internal-sftp"))))
  346. ;; list of strings
  347. (accepted-environment openssh-configuration-accepted-environment
  348. (default '()))
  349. ;; symbol
  350. (log-level openssh-configuration-log-level
  351. (default 'info))
  352. ;; String
  353. ;; This is an "escape hatch" to provide configuration that isn't yet
  354. ;; supported by this configuration record.
  355. (extra-content openssh-configuration-extra-content
  356. (default ""))
  357. ;; list of user-name/file-like tuples
  358. (authorized-keys openssh-configuration-authorized-keys
  359. (default '()))
  360. ;; Boolean
  361. (generate-host-keys? openssh-configuration-generate-host-keys?
  362. (default #t))
  363. ;; Boolean
  364. ;; XXX: This should really be handled in an orthogonal way, for instance as
  365. ;; proposed in <https://bugs.gnu.org/27155>. Keep it internal/undocumented
  366. ;; for now.
  367. (%auto-start? openssh-auto-start?
  368. (default #t)))
  369. (define %openssh-accounts
  370. (list (user-group (name "sshd") (system? #t))
  371. (user-account
  372. (name "sshd")
  373. (group "sshd")
  374. (system? #t)
  375. (comment "sshd privilege separation user")
  376. (home-directory "/var/run/sshd")
  377. (shell (file-append shadow "/sbin/nologin")))))
  378. (define (openssh-activation config)
  379. "Return the activation GEXP for CONFIG."
  380. (with-imported-modules '((guix build utils))
  381. #~(begin
  382. (use-modules (guix build utils))
  383. (define (touch file-name)
  384. (call-with-output-file file-name (const #t)))
  385. ;; Make sure /etc/ssh can be read by the 'sshd' user.
  386. (mkdir-p "/etc/ssh")
  387. (chmod "/etc/ssh" #o755)
  388. (mkdir-p (dirname #$(openssh-configuration-pid-file config)))
  389. ;; 'sshd' complains if the authorized-key directory and its parents
  390. ;; are group-writable, which rules out /gnu/store. Thus we copy the
  391. ;; authorized-key directory to /etc.
  392. (catch 'system-error
  393. (lambda ()
  394. (delete-file-recursively "/etc/ssh/authorized_keys.d"))
  395. (lambda args
  396. (unless (= ENOENT (system-error-errno args))
  397. (apply throw args))))
  398. (copy-recursively #$(authorized-key-directory
  399. (openssh-configuration-authorized-keys config))
  400. "/etc/ssh/authorized_keys.d")
  401. (chmod "/etc/ssh/authorized_keys.d" #o555)
  402. (let ((lastlog "/var/log/lastlog"))
  403. (when #$(openssh-configuration-print-last-log? config)
  404. (unless (file-exists? lastlog)
  405. (touch lastlog))))
  406. (when #$(openssh-configuration-generate-host-keys? config)
  407. ;; Generate missing host keys.
  408. (system* (string-append #$(openssh-configuration-openssh config)
  409. "/bin/ssh-keygen") "-A")))))
  410. (define (authorized-key-directory keys)
  411. "Return a directory containing the authorized keys specified in KEYS, a list
  412. of user-name/file-like tuples."
  413. (define build
  414. (with-imported-modules (source-module-closure '((guix build utils)))
  415. #~(begin
  416. (use-modules (ice-9 match) (srfi srfi-26)
  417. (guix build utils))
  418. (mkdir #$output)
  419. (for-each (match-lambda
  420. ((user keys ...)
  421. (let ((file (string-append #$output "/" user)))
  422. (call-with-output-file file
  423. (lambda (port)
  424. (for-each (lambda (key)
  425. (call-with-input-file key
  426. (cut dump-port <> port)))
  427. keys))))))
  428. '#$keys))))
  429. (computed-file "openssh-authorized-keys" build))
  430. (define (openssh-config-file config)
  431. "Return the sshd configuration file corresponding to CONFIG."
  432. (computed-file
  433. "sshd_config"
  434. #~(begin
  435. (use-modules (ice-9 match))
  436. (call-with-output-file #$output
  437. (lambda (port)
  438. (display "# Generated by 'openssh-service'.\n" port)
  439. (format port "Port ~a\n"
  440. #$(number->string
  441. (openssh-configuration-port-number config)))
  442. (format port "PermitRootLogin ~a\n"
  443. #$(match (openssh-configuration-permit-root-login config)
  444. (#t "yes")
  445. (#f "no")
  446. ('without-password (warn-about-deprecation
  447. 'without-password #f
  448. #:replacement 'prohibit-password)
  449. "prohibit-password")
  450. ('prohibit-password "prohibit-password")))
  451. (format port "PermitEmptyPasswords ~a\n"
  452. #$(if (openssh-configuration-allow-empty-passwords? config)
  453. "yes" "no"))
  454. (format port "PasswordAuthentication ~a\n"
  455. #$(if (openssh-configuration-password-authentication? config)
  456. "yes" "no"))
  457. (format port "PubkeyAuthentication ~a\n"
  458. #$(if (openssh-configuration-public-key-authentication?
  459. config)
  460. "yes" "no"))
  461. (format port "X11Forwarding ~a\n"
  462. #$(if (openssh-configuration-x11-forwarding? config)
  463. "yes" "no"))
  464. (format port "AllowAgentForwarding ~a\n"
  465. #$(if (openssh-configuration-allow-agent-forwarding? config)
  466. "yes" "no"))
  467. (format port "AllowTcpForwarding ~a\n"
  468. #$(if (openssh-configuration-allow-tcp-forwarding? config)
  469. "yes" "no"))
  470. (format port "GatewayPorts ~a\n"
  471. #$(if (openssh-configuration-gateway-ports? config)
  472. "yes" "no"))
  473. (format port "PidFile ~a\n"
  474. #$(openssh-configuration-pid-file config))
  475. (format port "ChallengeResponseAuthentication ~a\n"
  476. #$(if (openssh-configuration-challenge-response-authentication?
  477. config)
  478. "yes" "no"))
  479. (format port "UsePAM ~a\n"
  480. #$(if (openssh-configuration-use-pam? config)
  481. "yes" "no"))
  482. (format port "PrintLastLog ~a\n"
  483. #$(if (openssh-configuration-print-last-log? config)
  484. "yes" "no"))
  485. (format port "LogLevel ~a\n"
  486. #$(string-upcase
  487. (symbol->string
  488. (openssh-configuration-log-level config))))
  489. ;; Add '/etc/authorized_keys.d/%u', which we populate.
  490. (format port "AuthorizedKeysFile \
  491. .ssh/authorized_keys .ssh/authorized_keys2 /etc/ssh/authorized_keys.d/%u\n")
  492. (for-each (lambda (s) (format port "AcceptEnv ~a\n" s))
  493. '#$(openssh-configuration-accepted-environment config))
  494. (for-each
  495. (match-lambda
  496. ((name command) (format port "Subsystem\t~a\t~a\n" name command)))
  497. '#$(openssh-configuration-subsystems config))
  498. (format port "~a\n"
  499. #$(openssh-configuration-extra-content config))
  500. #t)))))
  501. (define (openssh-shepherd-service config)
  502. "Return a <shepherd-service> for openssh with CONFIG."
  503. (define pid-file
  504. (openssh-configuration-pid-file config))
  505. (define port-number
  506. (openssh-configuration-port-number config))
  507. (define max-connections
  508. (openssh-configuration-max-connections config))
  509. (define config-file
  510. (openssh-config-file config))
  511. (define openssh-command
  512. #~(list (string-append #$(openssh-configuration-openssh config) "/sbin/sshd")
  513. "-D" "-f" #$config-file))
  514. (define inetd-style?
  515. ;; Whether to use 'make-inetd-constructor'. That procedure appeared in
  516. ;; Shepherd 0.9.0, but in 0.9.0, 'make-inetd-constructor' wouldn't let us
  517. ;; pass a list of endpoints, and it wouldn't let us define a service
  518. ;; listening on both IPv4 and IPv6, hence the conditional below.
  519. #~(and (defined? 'make-inetd-constructor)
  520. (not (string=? (@ (shepherd config) Version) "0.9.0"))))
  521. (define ipv6-support?
  522. ;; Expression that returns true if IPv6 support is available.
  523. #~(catch 'system-error
  524. (lambda ()
  525. (let ((sock (socket AF_INET6 SOCK_STREAM 0)))
  526. (close-port sock)
  527. #t))
  528. (const #f)))
  529. (list (shepherd-service
  530. (documentation "OpenSSH server.")
  531. (requirement '(pam syslogd loopback))
  532. (provision '(ssh-daemon ssh sshd))
  533. (start #~(if #$inetd-style?
  534. (make-inetd-constructor
  535. (append #$openssh-command '("-i"))
  536. (cons (endpoint
  537. (make-socket-address AF_INET INADDR_ANY
  538. #$port-number))
  539. (if #$ipv6-support?
  540. (list
  541. (endpoint
  542. (make-socket-address AF_INET6 IN6ADDR_ANY
  543. #$port-number)))
  544. '()))
  545. #:requirements '#$requirement
  546. #:max-connections #$max-connections)
  547. (make-forkexec-constructor #$openssh-command
  548. #:pid-file #$pid-file)))
  549. (stop #~(if #$inetd-style?
  550. (make-inetd-destructor)
  551. (make-kill-destructor)))
  552. (actions (list (shepherd-configuration-action config-file)))
  553. (auto-start? (openssh-auto-start? config)))))
  554. (define (openssh-pam-services config)
  555. "Return a list of <pam-services> for sshd with CONFIG."
  556. (list (unix-pam-service
  557. "sshd"
  558. #:login-uid? #t
  559. #:allow-empty-passwords?
  560. (openssh-configuration-allow-empty-passwords? config))))
  561. (define (extend-openssh-authorized-keys config keys)
  562. "Extend CONFIG with the extra authorized keys listed in KEYS."
  563. (openssh-configuration
  564. (inherit config)
  565. (authorized-keys
  566. (match (append (openssh-configuration-authorized-keys config) keys)
  567. ((and alist ((users _ ...) ...))
  568. ;; Build a user/key-list mapping.
  569. (let ((user-keys (alist->vhash alist)))
  570. ;; Coalesce the key lists associated with each user.
  571. (map (lambda (user)
  572. `(,user
  573. ,@(concatenate (vhash-fold* cons '() user user-keys))))
  574. users)))))))
  575. (define openssh-service-type
  576. (service-type (name 'openssh)
  577. (description
  578. "Run the OpenSSH secure shell (SSH) server, @command{sshd}.")
  579. (extensions
  580. (list (service-extension shepherd-root-service-type
  581. openssh-shepherd-service)
  582. (service-extension pam-root-service-type
  583. openssh-pam-services)
  584. (service-extension activation-service-type
  585. openssh-activation)
  586. (service-extension account-service-type
  587. (const %openssh-accounts))
  588. ;; Install OpenSSH in the system profile. That way,
  589. ;; 'scp' is found when someone tries to copy to or from
  590. ;; this machine.
  591. (service-extension profile-service-type
  592. (lambda (config)
  593. (list (openssh-configuration-openssh
  594. config))))))
  595. (compose concatenate)
  596. (extend extend-openssh-authorized-keys)
  597. (default-value (openssh-configuration))))
  598. ;;;
  599. ;;; Dropbear.
  600. ;;;
  601. (define-record-type* <dropbear-configuration>
  602. dropbear-configuration make-dropbear-configuration
  603. dropbear-configuration?
  604. (dropbear dropbear-configuration-dropbear
  605. (default dropbear))
  606. (port-number dropbear-configuration-port-number
  607. (default 22))
  608. (syslog-output? dropbear-configuration-syslog-output?
  609. (default #t))
  610. (pid-file dropbear-configuration-pid-file
  611. (default "/var/run/dropbear.pid"))
  612. (root-login? dropbear-configuration-root-login?
  613. (default #f))
  614. (allow-empty-passwords? dropbear-configuration-allow-empty-passwords?
  615. (default #f))
  616. (password-authentication? dropbear-configuration-password-authentication?
  617. (default #t)))
  618. (define (dropbear-activation config)
  619. "Return the activation gexp for CONFIG."
  620. #~(begin
  621. (use-modules (guix build utils))
  622. (mkdir-p "/etc/dropbear")))
  623. (define (dropbear-shepherd-service config)
  624. "Return a <shepherd-service> for dropbear with CONFIG."
  625. (define dropbear
  626. (dropbear-configuration-dropbear config))
  627. (define pid-file
  628. (dropbear-configuration-pid-file config))
  629. (define dropbear-command
  630. #~(list (string-append #$dropbear "/sbin/dropbear")
  631. ;; '-R' allows host keys to be automatically generated upon first
  632. ;; connection, at a time when /dev/urandom is more likely securely
  633. ;; seeded.
  634. "-F" "-R"
  635. "-p" #$(number->string (dropbear-configuration-port-number config))
  636. "-P" #$pid-file
  637. #$@(if (dropbear-configuration-syslog-output? config) '() '("-E"))
  638. #$@(if (dropbear-configuration-root-login? config) '() '("-w"))
  639. #$@(if (dropbear-configuration-password-authentication? config)
  640. '()
  641. '("-s" "-g"))
  642. #$@(if (dropbear-configuration-allow-empty-passwords? config)
  643. '("-B")
  644. '())))
  645. (define requires
  646. (if (dropbear-configuration-syslog-output? config)
  647. '(networking syslogd) '(networking)))
  648. (list (shepherd-service
  649. (documentation "Dropbear SSH server.")
  650. (requirement requires)
  651. (provision '(ssh-daemon ssh sshd))
  652. (start #~(make-forkexec-constructor #$dropbear-command
  653. #:pid-file #$pid-file))
  654. (stop #~(make-kill-destructor)))))
  655. (define dropbear-service-type
  656. (service-type (name 'dropbear)
  657. (description
  658. "Run the Dropbear secure shell (SSH) server.")
  659. (extensions
  660. (list (service-extension shepherd-root-service-type
  661. dropbear-shepherd-service)
  662. (service-extension activation-service-type
  663. dropbear-activation)))
  664. (default-value (dropbear-configuration))))
  665. (define-deprecated (dropbear-service #:optional
  666. (config (dropbear-configuration)))
  667. dropbear-service-type
  668. "Run the @uref{https://matt.ucc.asn.au/dropbear/dropbear.html,Dropbear SSH
  669. daemon} with the given @var{config}, a @code{<dropbear-configuration>}
  670. object."
  671. (service dropbear-service-type config))
  672. ;;;
  673. ;;; AutoSSH.
  674. ;;;
  675. (define-record-type* <autossh-configuration>
  676. autossh-configuration make-autossh-configuration
  677. autossh-configuration?
  678. (user autossh-configuration-user
  679. (default "autossh"))
  680. (poll autossh-configuration-poll
  681. (default 600))
  682. (first-poll autossh-configuration-first-poll
  683. (default #f))
  684. (gate-time autossh-configuration-gate-time
  685. (default 30))
  686. (log-level autossh-configuration-log-level
  687. (default 1))
  688. (max-start autossh-configuration-max-start
  689. (default #f))
  690. (message autossh-configuration-message
  691. (default ""))
  692. (port autossh-configuration-port
  693. (default "0"))
  694. (ssh-options autossh-configuration-ssh-options
  695. (default '())))
  696. (define (autossh-file-name config file)
  697. "Return a path in /var/run/autossh/ that is writable
  698. by @code{user} from @code{config}."
  699. (string-append "/var/run/autossh/"
  700. (autossh-configuration-user config)
  701. "/" file))
  702. (define (autossh-shepherd-service config)
  703. (shepherd-service
  704. (documentation "Automatically set up ssh connections (and keep them alive).")
  705. (provision '(autossh))
  706. (start #~(make-forkexec-constructor
  707. (list #$(file-append autossh "/bin/autossh")
  708. #$@(autossh-configuration-ssh-options config))
  709. #:user #$(autossh-configuration-user config)
  710. #:group (passwd:gid (getpw #$(autossh-configuration-user config)))
  711. #:pid-file #$(autossh-file-name config "pid")
  712. #:log-file #$(autossh-file-name config "log")
  713. #:environment-variables
  714. '(#$(string-append "AUTOSSH_PIDFILE="
  715. (autossh-file-name config "pid"))
  716. #$(string-append "AUTOSSH_LOGFILE="
  717. (autossh-file-name config "log"))
  718. #$(string-append "AUTOSSH_POLL="
  719. (number->string
  720. (autossh-configuration-poll config)))
  721. #$(string-append "AUTOSSH_FIRST_POLL="
  722. (number->string
  723. (or
  724. (autossh-configuration-first-poll config)
  725. (autossh-configuration-poll config))))
  726. #$(string-append "AUTOSSH_GATETIME="
  727. (number->string
  728. (autossh-configuration-gate-time config)))
  729. #$(string-append "AUTOSSH_LOGLEVEL="
  730. (number->string
  731. (autossh-configuration-log-level config)))
  732. #$(string-append "AUTOSSH_MAXSTART="
  733. (number->string
  734. (or (autossh-configuration-max-start config)
  735. -1)))
  736. #$(string-append "AUTOSSH_MESSAGE="
  737. (autossh-configuration-message config))
  738. #$(string-append "AUTOSSH_PORT="
  739. (autossh-configuration-port config)))))
  740. (stop #~(make-kill-destructor))))
  741. (define (autossh-service-activation config)
  742. (with-imported-modules '((guix build utils))
  743. #~(begin
  744. (use-modules (guix build utils))
  745. (define %user
  746. (getpw #$(autossh-configuration-user config)))
  747. (let* ((directory #$(autossh-file-name config ""))
  748. (log (string-append directory "/log")))
  749. (mkdir-p directory)
  750. (chown directory (passwd:uid %user) (passwd:gid %user))
  751. (call-with-output-file log (const #t))
  752. (chown log (passwd:uid %user) (passwd:gid %user))))))
  753. (define autossh-service-type
  754. (service-type
  755. (name 'autossh)
  756. (description "Automatically set up ssh connections (and keep them alive).")
  757. (extensions
  758. (list (service-extension shepherd-root-service-type
  759. (compose list autossh-shepherd-service))
  760. (service-extension activation-service-type
  761. autossh-service-activation)))
  762. (default-value (autossh-configuration))))
  763. ;;;
  764. ;;; WebSSH
  765. ;;;
  766. (define-record-type* <webssh-configuration>
  767. webssh-configuration make-webssh-configuration
  768. webssh-configuration?
  769. (package webssh-configuration-package ;file-like
  770. (default webssh))
  771. (user-name webssh-configuration-user-name ;string
  772. (default "webssh"))
  773. (group-name webssh-configuration-group-name ;string
  774. (default "webssh"))
  775. (policy webssh-configuration-policy ;symbol
  776. (default #f))
  777. (known-hosts webssh-configuration-known-hosts ;list of strings
  778. (default #f))
  779. (port webssh-configuration-port ;number
  780. (default #f))
  781. (address webssh-configuration-address ;string
  782. (default #f))
  783. (log-file webssh-configuration-log-file ;string
  784. (default "/var/log/webssh.log"))
  785. (log-level webssh-configuration-log-level ;symbol
  786. (default #f)))
  787. (define %webssh-configuration-nginx
  788. (nginx-server-configuration
  789. (listen '("80"))
  790. (locations
  791. (list (nginx-location-configuration
  792. (uri "/")
  793. (body '("proxy_pass http://127.0.0.1:8888;"
  794. "proxy_http_version 1.1;"
  795. "proxy_read_timeout 300;"
  796. "proxy_set_header Upgrade $http_upgrade;"
  797. "proxy_set_header Connection \"upgrade\";"
  798. "proxy_set_header Host $http_host;"
  799. "proxy_set_header X-Real-IP $remote_addr;"
  800. "proxy_set_header X-Real-PORT $remote_port;")))))))
  801. (define webssh-account
  802. ;; Return the user accounts and user groups for CONFIG.
  803. (match-lambda
  804. (($ <webssh-configuration> _ user-name group-name _ _ _ _ _ _)
  805. (list (user-group
  806. (name group-name))
  807. (user-account
  808. (name user-name)
  809. (group group-name)
  810. (comment "webssh privilege separation user")
  811. (home-directory (string-append "/var/run/" user-name))
  812. (shell #~(string-append #$shadow "/sbin/nologin")))))))
  813. (define webssh-activation
  814. ;; Return the activation GEXP for CONFIG.
  815. (match-lambda
  816. (($ <webssh-configuration> _ user-name group-name policy known-hosts _ _
  817. log-file _)
  818. (with-imported-modules '((guix build utils))
  819. #~(begin
  820. (let* ((home-dir (string-append "/var/run/" #$user-name))
  821. (ssh-dir (string-append home-dir "/.ssh"))
  822. (known-hosts-file (string-append ssh-dir "/known_hosts")))
  823. (call-with-output-file #$log-file (const #t))
  824. (mkdir-p ssh-dir)
  825. (case '#$policy
  826. ((reject)
  827. (if '#$known-hosts
  828. (call-with-output-file known-hosts-file
  829. (lambda (port)
  830. (for-each (lambda (host) (display host port) (newline port))
  831. '#$known-hosts)))
  832. (display-hint (G_ "webssh: reject policy requires `known-hosts'.")))))
  833. (for-each (lambda (file)
  834. (chown file
  835. (passwd:uid (getpw #$user-name))
  836. (group:gid (getpw #$group-name))))
  837. (list #$log-file ssh-dir known-hosts-file))
  838. (chmod ssh-dir #o700)))))))
  839. (define webssh-shepherd-service
  840. (match-lambda
  841. (($ <webssh-configuration> package user-name group-name policy _ port
  842. address log-file log-level)
  843. (list (shepherd-service
  844. (provision '(webssh))
  845. (documentation "Run webssh daemon.")
  846. (start #~(make-forkexec-constructor
  847. `(,(string-append #$webssh "/bin/wssh")
  848. ,(string-append "--log-file-prefix=" #$log-file)
  849. ,@(case '#$log-level
  850. ((debug) '("--logging=debug"))
  851. (else '()))
  852. ,@(case '#$policy
  853. ((reject) '("--policy=reject"))
  854. (else '()))
  855. ,@(if #$port
  856. (list (string-append "--port=" (number->string #$port)))
  857. '())
  858. ,@(if #$address
  859. (list (string-append "--address=" #$address))
  860. '()))
  861. #:user #$user-name
  862. #:group #$group-name))
  863. (stop #~(make-kill-destructor)))))))
  864. (define webssh-service-type
  865. (service-type
  866. (name 'webssh)
  867. (extensions
  868. (list (service-extension shepherd-root-service-type
  869. webssh-shepherd-service)
  870. (service-extension account-service-type
  871. webssh-account)
  872. (service-extension activation-service-type
  873. webssh-activation)))
  874. (default-value (webssh-configuration))
  875. (description
  876. "Run the webssh.")))
  877. ;;; ssh.scm ends here