1 ;;; GNU Guix --- Functional package management for GNU
2 ;;; Copyright © 2020 Ludovic Courtès <ludo@gnu.org>
3 ;;; Copyright © 2020 Jan (janneke) Nieuwenhuizen <janneke@gnu.org>
5 ;;; This file is part of GNU Guix.
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.
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.
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/>.
20 (define-module (gnu build secret-service)
21 #:use-module (guix build utils)
23 #:use-module (srfi srfi-26)
24 #:use-module (rnrs bytevectors)
25 #:use-module (ice-9 binary-ports)
26 #:use-module (ice-9 match)
27 #:use-module (ice-9 rdelim)
29 #:export (secret-service-receive-secrets
30 secret-service-send-secrets))
34 ;;; Utility procedures for copying secrets into a VM.
38 (define* (secret-service-send-secrets port secret-root #:key (retry 60))
39 "Copy all files under SECRET-ROOT using TCP to secret-service listening at
40 local PORT. If connect fails, sleep 1s and retry RETRY times."
42 (define (file->file+size+mode file-name)
43 (let ((stat (stat file-name))
44 (target (substring file-name (string-length secret-root))))
45 (list target (stat:size stat) (stat:mode stat))))
47 (format (current-error-port) "sending secrets to ~a~%" port)
48 (let ((sock (socket AF_INET SOCK_STREAM 0))
49 (addr (make-socket-address AF_INET INADDR_LOOPBACK port)))
50 ;; connect to wait for port
51 (let loop ((retry retry))
53 (cute connect sock addr)
56 (apply throw key args))
57 (format (current-error-port) "retrying connection~%")
61 (format (current-error-port) "connected! sending files in ~s %~"
63 (let* ((files (if secret-root (find-files secret-root) '()))
64 (files-sizes-modes (map file->file+size+mode files))
67 (files ,files-sizes-modes))))
69 (for-each (compose (cute dump-port <> sock)
70 (cute open-input-file <>))
73 (define (secret-service-receive-secrets port)
74 "Listen to local PORT and wait for a secret service client to send secrets.
75 Write them to the file system."
77 (define (wait-for-client port)
78 ;; Wait for a TCP connection on PORT. Note: We cannot use the
79 ;; virtio-serial ports, which would be safer, because they are
80 ;; (presumably) unsupported on GNU/Hurd.
81 (let ((sock (socket AF_INET SOCK_STREAM 0)))
82 (bind sock AF_INET INADDR_ANY port)
84 (format (current-error-port)
85 "waiting for secrets on port ~a...~%"
89 (format (current-error-port) "client connection from ~a~%"
90 (inet-ntop (sockaddr:fam address)
91 (sockaddr:addr address)))
95 ;; TODO: Remove when (@ (guix build utils) dump-port) has a 'size'
97 (define (dump in out size)
98 ;; Copy SIZE bytes from IN to OUT.
99 (define buf-size 65536)
100 (define buf (make-bytevector buf-size))
102 (let loop ((left size))
105 (let ((read (get-bytevector-n! in buf 0 (min left buf-size))))
106 (if (eof-object? read)
109 (put-bytevector out buf 0 read)
110 (loop (- left read))))))))
112 (define (read-secrets port)
113 ;; Read secret files from PORT and install them.
114 (match (false-if-exception (read port))
115 (('secrets ('version 0)
116 ('files ((files sizes modes) ...)))
117 (for-each (lambda (file size mode)
118 (format (current-error-port)
119 "installing file '~a' (~a bytes)...~%"
121 (mkdir-p (dirname file))
122 (call-with-output-file file
124 (dump port output size)
128 (format (current-error-port)
129 "invalid secrets received~%")
132 (let* ((port (wait-for-client port))
133 (result (read-secrets port)))
137 ;;; secret-service.scm ends here