secret-service.scm 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2020-2022 Ludovic Courtès <ludo@gnu.org>
  3. ;;; Copyright © 2020 Jan (janneke) Nieuwenhuizen <janneke@gnu.org>
  4. ;;;
  5. ;;; This file is part of GNU Guix.
  6. ;;;
  7. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  8. ;;; under the terms of the GNU General Public License as published by
  9. ;;; the Free Software Foundation; either version 3 of the License, or (at
  10. ;;; your option) any later version.
  11. ;;;
  12. ;;; GNU Guix is distributed in the hope that it will be useful, but
  13. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  14. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. ;;; GNU General Public License for more details.
  16. ;;;
  17. ;;; You should have received a copy of the GNU General Public License
  18. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  19. (define-module (gnu build secret-service)
  20. #:use-module (guix build utils)
  21. #:use-module (srfi srfi-26)
  22. #:use-module (rnrs bytevectors)
  23. #:use-module (ice-9 binary-ports)
  24. #:use-module (ice-9 match)
  25. #:use-module (ice-9 rdelim)
  26. #:export (secret-service-receive-secrets
  27. secret-service-send-secrets))
  28. ;;; Commentary:
  29. ;;;
  30. ;;; Utility procedures for copying secrets into a VM.
  31. ;;;
  32. ;;; Code:
  33. (define-syntax log
  34. (lambda (s)
  35. "Log the given message."
  36. (syntax-case s ()
  37. ((_ fmt args ...)
  38. (with-syntax ((fmt (string-append "secret service: "
  39. (syntax->datum #'fmt))))
  40. ;; Log to the current output port. That way, when
  41. ;; 'secret-service-send-secrets' is called from shepherd, output goes
  42. ;; to syslog.
  43. #'(format (current-output-port) fmt args ...))))))
  44. (define-syntax with-modules
  45. (syntax-rules ()
  46. "Dynamically load the given MODULEs at run time, making the chosen
  47. bindings available within the lexical scope of BODY."
  48. ((_ ((module #:select (bindings ...)) rest ...) body ...)
  49. (let* ((iface (resolve-interface 'module))
  50. (bindings (module-ref iface 'bindings))
  51. ...)
  52. (with-modules (rest ...) body ...)))
  53. ((_ () body ...)
  54. (begin body ...))))
  55. (define (wait-for-readable-fd port timeout)
  56. "Wait until PORT has data available for reading or TIMEOUT has expired.
  57. Return #t in the former case and #f in the latter case."
  58. (match (resolve-module '(fibers) #f) ;using Fibers?
  59. (#f
  60. (log "blocking on socket...~%")
  61. (match (select (list port) '() '() timeout)
  62. (((_) () ()) #t)
  63. ((() () ()) #f)))
  64. (fibers
  65. ;; We're running on the Shepherd 0.9+ with Fibers. Arrange to make a
  66. ;; non-blocking wait so that other fibers can be scheduled in while we
  67. ;; wait for PORT.
  68. (with-modules (((fibers) #:select (spawn-fiber sleep))
  69. ((fibers channels)
  70. #:select (make-channel put-message get-message)))
  71. ;; Make PORT non-blocking.
  72. (let ((flags (fcntl port F_GETFL)))
  73. (fcntl port F_SETFL (logior O_NONBLOCK flags)))
  74. (let ((channel (make-channel)))
  75. (spawn-fiber
  76. (lambda ()
  77. (sleep timeout) ;suspends the fiber
  78. (put-message channel 'timeout)))
  79. (spawn-fiber
  80. (lambda ()
  81. (lookahead-u8 port) ;suspends the fiber
  82. (put-message channel 'readable)))
  83. (log "suspending fiber on socket...~%")
  84. (match (get-message channel)
  85. ('readable #t)
  86. ('timeout #f)))))))
  87. (define* (secret-service-send-secrets port secret-root
  88. #:key (retry 60)
  89. (handshake-timeout 120))
  90. "Copy all files under SECRET-ROOT using TCP to secret-service listening at
  91. local PORT. If connect fails, sleep 1s and retry RETRY times; once connected,
  92. wait for at most HANDSHAKE-TIMEOUT seconds for handshake to complete. Return
  93. #f on failure."
  94. (define (file->file+size+mode file-name)
  95. (let ((stat (stat file-name))
  96. (target (substring file-name (string-length secret-root))))
  97. (list target (stat:size stat) (stat:mode stat))))
  98. (define (send-files sock)
  99. (let* ((files (if secret-root (find-files secret-root) '()))
  100. (files-sizes-modes (map file->file+size+mode files))
  101. (secrets `(secrets
  102. (version 0)
  103. (files ,files-sizes-modes))))
  104. (write secrets sock)
  105. (for-each (lambda (file)
  106. (call-with-input-file file
  107. (lambda (input)
  108. (dump-port input sock))))
  109. files)))
  110. (log "sending secrets to ~a~%" port)
  111. (let ((sock (socket AF_INET (logior SOCK_CLOEXEC SOCK_STREAM) 0))
  112. (addr (make-socket-address AF_INET INADDR_LOOPBACK port))
  113. (sleep (if (resolve-module '(fibers) #f)
  114. (module-ref (resolve-interface '(fibers)) 'sleep)
  115. sleep)))
  116. ;; Connect to QEMU on the forwarded port. The 'connect' call succeeds as
  117. ;; soon as QEMU is ready, even if there's no server listening on the
  118. ;; forward port inside the guest.
  119. (let loop ((retry retry))
  120. (catch 'system-error
  121. (cute connect sock addr)
  122. (lambda (key . args)
  123. (when (zero? retry)
  124. (apply throw key args))
  125. (log "retrying connection [~a attempts left]~%"
  126. (- retry 1))
  127. (sleep 1)
  128. (loop (1- retry)))))
  129. (log "connected; waiting for handshake...~%")
  130. ;; Wait for "hello" message from the server. This is the only way to know
  131. ;; that we're really connected to the server inside the guest.
  132. (if (wait-for-readable-fd sock handshake-timeout)
  133. (match (read sock)
  134. (('secret-service-server ('version version ...))
  135. (log "sending files from ~s...~%" secret-root)
  136. (send-files sock)
  137. (log "done sending files to port ~a~%" port)
  138. (close-port sock)
  139. secret-root)
  140. (x
  141. (log "invalid handshake ~s~%" x)
  142. (close-port sock)
  143. #f))
  144. (begin ;timeout
  145. (log "timeout while sending files to ~a~%" port)
  146. (close-port sock)
  147. #f))))
  148. (define (delete-file* file)
  149. "Ensure FILE does not exist."
  150. (catch 'system-error
  151. (lambda ()
  152. (delete-file file))
  153. (lambda args
  154. (unless (= ENOENT (system-error-errno args))
  155. (apply throw args)))))
  156. (define (secret-service-receive-secrets port)
  157. "Listen to local PORT and wait for a secret service client to send secrets.
  158. Write them to the file system. Return the list of files installed on success,
  159. and #f otherwise."
  160. (define (wait-for-client port)
  161. ;; Wait for a TCP connection on PORT. Note: We cannot use the
  162. ;; virtio-serial ports, which would be safer, because they are
  163. ;; (presumably) unsupported on GNU/Hurd.
  164. (let ((sock (socket AF_INET (logior SOCK_CLOEXEC SOCK_STREAM) 0)))
  165. (bind sock AF_INET INADDR_ANY port)
  166. (listen sock 1)
  167. (log "waiting for secrets on port ~a...~%" port)
  168. (match (select (list sock) '() '() 60)
  169. (((_) () ())
  170. (match (accept sock)
  171. ((client . address)
  172. (log "client connection from ~a~%"
  173. (inet-ntop (sockaddr:fam address)
  174. (sockaddr:addr address)))
  175. ;; Send a "hello" message. This allows the client running on the
  176. ;; host to know that it's now actually connected to server running
  177. ;; in the guest.
  178. (write '(secret-service-server (version 0)) client)
  179. (force-output client)
  180. (close-port sock)
  181. client)))
  182. ((() () ())
  183. (log "did not receive any secrets; time out~%")
  184. (close-port sock)
  185. #f))))
  186. ;; TODO: Remove when (@ (guix build utils) dump-port) has a 'size'
  187. ;; parameter.
  188. (define (dump in out size)
  189. ;; Copy SIZE bytes from IN to OUT.
  190. (define buf-size 65536)
  191. (define buf (make-bytevector buf-size))
  192. (let loop ((left size))
  193. (if (<= left 0)
  194. 0
  195. (let ((read (get-bytevector-n! in buf 0 (min left buf-size))))
  196. (if (eof-object? read)
  197. left
  198. (begin
  199. (put-bytevector out buf 0 read)
  200. (loop (- left read))))))))
  201. (define (read-secrets port)
  202. ;; Read secret files from PORT and install them.
  203. (match (false-if-exception (read port))
  204. (('secrets ('version 0)
  205. ('files ((files sizes modes) ...)))
  206. (for-each (lambda (file size mode)
  207. (log "installing file '~a' (~a bytes)...~%"
  208. file size)
  209. (mkdir-p (dirname file))
  210. ;; It could be that FILE already exists, for instance
  211. ;; because it has been created by a service's activation
  212. ;; snippet (e.g., SSH host keys). Delete it.
  213. (delete-file* file)
  214. (call-with-output-file file
  215. (lambda (output)
  216. (dump port output size)
  217. (chmod file mode))))
  218. files sizes modes)
  219. (log "received ~a secret files~%" (length files))
  220. files)
  221. (_
  222. (log "invalid secrets received~%")
  223. #f)))
  224. (let* ((port (wait-for-client port))
  225. (result (and=> port read-secrets)))
  226. (when port
  227. (close-port port))
  228. result))
  229. ;;; Local Variables:
  230. ;;; eval: (put 'with-modules 'scheme-indent-function 1)
  231. ;;; End:
  232. ;;; secret-service.scm ends here