;;; GNU Guix --- Functional package management for GNU ;;; Copyright © 2020-2022 Ludovic Courtès ;;; Copyright © 2020 Jan (janneke) Nieuwenhuizen ;;; ;;; This file is part of GNU Guix. ;;; ;;; GNU Guix is free software; you can redistribute it and/or modify it ;;; under the terms of the GNU General Public License as published by ;;; the Free Software Foundation; either version 3 of the License, or (at ;;; your option) any later version. ;;; ;;; GNU Guix is distributed in the hope that it will be useful, but ;;; WITHOUT ANY WARRANTY; without even the implied warranty of ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;;; GNU General Public License for more details. ;;; ;;; You should have received a copy of the GNU General Public License ;;; along with GNU Guix. If not, see . (define-module (gnu build secret-service) #:use-module (guix build utils) #:use-module (srfi srfi-26) #:use-module (rnrs bytevectors) #:use-module (ice-9 binary-ports) #:use-module (ice-9 match) #:use-module (ice-9 rdelim) #:export (secret-service-receive-secrets secret-service-send-secrets)) ;;; Commentary: ;;; ;;; Utility procedures for copying secrets into a VM. ;;; ;;; Code: (define-syntax log (lambda (s) "Log the given message." (syntax-case s () ((_ fmt args ...) (with-syntax ((fmt (string-append "secret service: " (syntax->datum #'fmt)))) ;; Log to the current output port. That way, when ;; 'secret-service-send-secrets' is called from shepherd, output goes ;; to syslog. #'(format (current-output-port) fmt args ...)))))) (define-syntax with-modules (syntax-rules () "Dynamically load the given MODULEs at run time, making the chosen bindings available within the lexical scope of BODY." ((_ ((module #:select (bindings ...)) rest ...) body ...)
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2014, 2015, 2016, 2017, 2018 Ludovic Courtès <ludo@gnu.org>
;;;
;;; This file is part of GNU Guix.
;;;
;;; GNU Guix is free software; you can redistribute it and/or modify it
;;; under the terms of the GNU General Public License as published by
;;; the Free Software Foundation; either version 3 of the License, or (at
;;; your option) any later version.
;;;
;;; GNU Guix is distributed in the hope that it will be useful, but
;;; WITHOUT ANY WARRANTY; without even the implied warranty of
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;;; GNU General Public License for more details.
;;;
;;; You should have received a copy of the GNU General Public License
;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.

;;;
;;; Check whether important binaries are available.
;;;

(use-modules (guix store)
             (guix grafts)
             (guix packages)
             (guix derivations)
             (guix ui)
             (gnu packages commencement)
             (ice-9 match)
             (srfi srfi-1)
             (srfi srfi-26))

(define (final-inputs store system)
  "Return the list of outputs directories of the final inputs for SYSTEM."
  (append-map (match-lambda
                ((or (name package) (name package _))
                 (let ((drv (package-derivation store package system)))
                   ;; Libc's 'debug' output refers to gcc-cross-boot0, but it's
                   ;; hard to avoid, so we tolerate it.  This should be the
                   ;; only exception.  Likewise, 'bash:include' depends on
                   ;; bootstrap-binaries via its 'Makefile.inc' (FIXME).
                   (filter-map (match-lambda
                                 (("debug" . directory)
                                  (if (string=? "glibc" (package-name package))
                                      #f
                                      directory))
                                 (("include" . directory)
                                  (if (string=? "bash" (package-name package))
                                      #f
                                      directory))
                                 ((_ . directory) directory))
                               (derivation->output-paths drv)))))
              %final-inputs))

(define (assert-valid-substitute substitute)
  "Make sure SUBSTITUTE does not refer to any bootstrap inputs, and bail out
if it does."
  (let ((references (substitutable-references substitute)))
    (when (any (cut string-contains <> "boot") references)
      (leave (G_ "'~a' refers to bootstrap inputs: ~s~%")
             (substitutable-path substitute) references))))

(define (test-final-inputs store system)
  "Check whether the final inputs for SYSTEM are clean---i.e., they don't
refer to the bootstrap tools."
  (format #t "checking final inputs for '~a'...~%" system)
  (let* ((inputs    (final-inputs store system))
         (available (substitutable-path-info store inputs)))
    (for-each (lambda (dir)
                (unless (find (lambda (substitute)
                                (string=? (substitutable-path substitute)
                                          dir))
                              available)
                  (leave (G_ "~a (system: ~a) has no substitute~%")
                         dir system)))
              inputs)

    (for-each assert-valid-substitute available)))

;; Entry point.
(with-store store
  (parameterize ((%graft? #f))
    (set-build-options store #:use-substitutes? #t)

    (for-each (cut test-final-inputs store <>)
              %cuirass-supported-systems)))
o ~a~%" port) (close-port sock) #f)))) (define (delete-file* file) "Ensure FILE does not exist." (catch 'system-error (lambda () (delete-file file)) (lambda args (unless (= ENOENT (system-error-errno args)) (apply throw args))))) (define (secret-service-receive-secrets port) "Listen to local PORT and wait for a secret service client to send secrets. Write them to the file system. Return the list of files installed on success, and #f otherwise." (define (wait-for-client port) ;; Wait for a TCP connection on PORT. Note: We cannot use the ;; virtio-serial ports, which would be safer, because they are ;; (presumably) unsupported on GNU/Hurd. (let ((sock (socket AF_INET SOCK_STREAM 0))) (bind sock AF_INET INADDR_ANY port) (listen sock 1) (log "waiting for secrets on port ~a...~%" port) (match (select (list sock) '() '() 60) (((_) () ()) (match (accept sock) ((client . address) (log "client connection from ~a~%" (inet-ntop (sockaddr:fam address) (sockaddr:addr address))) ;; Send a "hello" message. This allows the client running on the ;; host to know that it's now actually connected to server running ;; in the guest. (write '(secret-service-server (version 0)) client) (force-output client) (close-port sock) client))) ((() () ()) (log "did not receive any secrets; time out~%") (close-port sock) #f)))) ;; TODO: Remove when (@ (guix build utils) dump-port) has a 'size' ;; parameter. (define (dump in out size) ;; Copy SIZE bytes from IN to OUT. (define buf-size 65536) (define buf (make-bytevector buf-size)) (let loop ((left size)) (if (<= left 0) 0 (let ((read (get-bytevector-n! in buf 0 (min left buf-size)))) (if (eof-object? read) left (begin (put-bytevector out buf 0 read) (loop (- left read)))))))) (define (read-secrets port) ;; Read secret files from PORT and install them. (match (false-if-exception (read port)) (('secrets ('version 0) ('files ((files sizes modes) ...))) (for-each (lambda (file size mode) (log "installing file '~a' (~a bytes)...~%" file size) (mkdir-p (dirname file)) ;; It could be that FILE already exists, for instance ;; because it has been created by a service's activation ;; snippet (e.g., SSH host keys). Delete it. (delete-file* file) (call-with-output-file file (lambda (output) (dump port output size) (chmod file mode)))) files sizes modes) (log "received ~a secret files~%" (length files)) files) (_ (log "invalid secrets received~%") #f))) (let* ((port (wait-for-client port)) (result (and=> port read-secrets))) (when port (close-port port)) result)) ;;; Local Variables: ;;; eval: (put 'with-modules 'scheme-indent-function 1) ;;; End: ;;; secret-service.scm ends here