From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mp2 ([2001:41d0:2:4a6f::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by ms11 with LMTPS id QA6yCWytS1/kSwAA0tVLHw (envelope-from ) for ; Sun, 30 Aug 2020 13:45:16 +0000 Received: from aspmx1.migadu.com ([2001:41d0:2:4a6f::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by mp2 with LMTPS id XkF8BWytS1++LAAAB5/wlQ (envelope-from ) for ; Sun, 30 Aug 2020 13:45:16 +0000 Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by aspmx1.migadu.com (Postfix) with ESMTPS id AB3B894053E for ; Sun, 30 Aug 2020 13:45:15 +0000 (UTC) Received: from localhost ([::1]:55114 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1kCNdy-00054H-Di for larch@yhetil.org; Sun, 30 Aug 2020 09:45:14 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:40970) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1kCNdm-00053z-3d for guix-patches@gnu.org; Sun, 30 Aug 2020 09:45:02 -0400 Received: from debbugs.gnu.org ([209.51.188.43]:38110) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1kCNdl-0002x3-Pe for guix-patches@gnu.org; Sun, 30 Aug 2020 09:45:01 -0400 Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1kCNdl-00075S-NV for guix-patches@gnu.org; Sun, 30 Aug 2020 09:45:01 -0400 X-Loop: help-debbugs@gnu.org Subject: [bug#43106] [PATCH] DRAFT services: childhurd: Support for setting secrets. Resent-From: Ludovic =?UTF-8?Q?Court=C3=A8s?= Original-Sender: "Debbugs-submit" Resent-CC: guix-patches@gnu.org Resent-Date: Sun, 30 Aug 2020 13:45:01 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: followup 43106 X-GNU-PR-Package: guix-patches X-GNU-PR-Keywords: patch To: "Jan \(janneke\) Nieuwenhuizen" Cc: 43106@debbugs.gnu.org Received: via spool by 43106-submit@debbugs.gnu.org id=B43106.159879506027173 (code B ref 43106); Sun, 30 Aug 2020 13:45:01 +0000 Received: (at 43106) by debbugs.gnu.org; 30 Aug 2020 13:44:20 +0000 Received: from localhost ([127.0.0.1]:49656 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1kCNd2-000748-64 for submit@debbugs.gnu.org; Sun, 30 Aug 2020 09:44:20 -0400 Received: from eggs.gnu.org ([209.51.188.92]:58960) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1kCNcx-00073s-Et for 43106@debbugs.gnu.org; Sun, 30 Aug 2020 09:44:15 -0400 Received: from fencepost.gnu.org ([2001:470:142:3::e]:39965) by eggs.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1kCNcs-0002sp-4P; Sun, 30 Aug 2020 09:44:06 -0400 Received: from [2a01:e0a:1d:7270:af76:b9b:ca24:c465] (port=41822 helo=ribbon) by fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_256_CBC_SHA1:256) (Exim 4.82) (envelope-from ) id 1kCNcr-0002tY-0L; Sun, 30 Aug 2020 09:44:05 -0400 From: Ludovic =?UTF-8?Q?Court=C3=A8s?= References: <20200829215726.3910-1-janneke@gnu.org> Date: Sun, 30 Aug 2020 15:44:03 +0200 In-Reply-To: <20200829215726.3910-1-janneke@gnu.org> (Jan Nieuwenhuizen's message of "Sat, 29 Aug 2020 23:57:26 +0200") Message-ID: <87h7ski6zg.fsf@gnu.org> User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/26.3 (gnu/linux) MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-Spam-Score: -2.3 (--) X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list X-Spam-Score: -3.3 (---) X-BeenThere: guix-patches@gnu.org List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: guix-patches-bounces+larch=yhetil.org@gnu.org Sender: "Guix-patches" X-Scanner: scn0 Authentication-Results: aspmx1.migadu.com; dkim=none; dmarc=none; spf=pass (aspmx1.migadu.com: domain of guix-patches-bounces@gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=guix-patches-bounces@gnu.org X-Spam-Score: 0.49 X-TUID: dFMVoyU/Cnap --=-=-= Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable Hi! "Jan (janneke) Nieuwenhuizen" skribis: > TODO: This seems to work...but it can keep the shepherd from finishing for > quite some time (half a minute)...not sure what to do here, WDYT? > > A great way to play with it is by doing something like > > sudo -E ./pre-inst-env guile -c '(use-modules (gnu build childhurd)) (hur= d-vm-copy-secrets 10022 "/etc/childhurd")' > > * gnu/build/childhurd.scm: New file. > * gnu/local.mk (GNU_SYSTEM_MODULES): Add it. > * gnu/services/virtualization.scm (hurd-vm-shepherd-service): Use it to s= et > secrets. > (hurd-vm-port): New function. > (hurd-vm-net-options): Use it. > * doc/guix.texi (The Hurd in a Virtual Machine): Document it. Nice, thanks for working on it! > +@item @code{secret-root} (default: @code{#f}) > +If set, the root directory with out-of-band secrets to be injected into > +the childhurd once it runs. Childhurds are volatile which means that on > +every startup, secrets such as the SSH host keys and Guix signing key > +are recreated. > + > +Typical use is setting @code{secret-root} to @code{"/etc/childhurd"} > +pointing at a tree of non-volatile secrets like so > + > +@example > +/etc/childhurd/etc/guix/signing-key.pub > +/etc/childhurd/etc/guix/signing-key.sec > +/etc/childhurd/etc/ssh/ssh_host_ed25519_key > +/etc/childhurd/etc/ssh/ssh_host_ecdsa_key > +/etc/childhurd/etc/ssh/ssh_host_ed25519_key.pub > +/etc/childhurd/etc/ssh/ssh_host_ecdsa_key.pub > +@end example Would it make sense to have a list of source/target pairs instead of a directory: (("/etc/childhurd/pubkey" . "/etc/guix/signing-key.pub") =E2=80=A6) ? [...] > +(define-module (gnu build childhurd) > + #:use-module (ice-9 rdelim) > + #:use-module (guix build utils) > + > + ;; #:use-module (ssh auth) > + ;; #:use-module (ssh channel) > + ;; #:use-module (ssh session) > + ;; #:use-module (ssh sftp) > + > + #:autoload (ssh auth) (userauth-password!) You could add the file to MODULES_NOT_COMPILED in gnu/local.mk to avoid the autoload dance. > +(define* (hurd-vm-copy-secrets port secret-root #:key (retry 20)) > + "Copy all files under SECRET-ROOT using ssh to childhurd at local PORT= ." > + (format (current-error-port) "hurd-vm-copy-secrets\n") > + (let ((session (make-session #:host "127.0.0.1" #:port port > + #:user "root"))) I just realized that we have a bootstrapping issue here: we have to explicitly skip SSH host authentication because we haven=E2=80=99t installe= d the host keys yet. The boot sequence of the guest is actually: generate SSH host keys, start sshd, receive host keys over SFTP. [...] > - (start #~(make-forkexec-constructor #$vm-command)) > + (requirement '(loopback networking user-processes)) > + (start > + (with-imported-modules (source-module-closure '((gnu build childh= urd) > + (guix build utils= ))) > + (with-extensions (list guile-ssh) > + #~(let ((spawn (make-forkexec-constructor #$vm-command))) > + (use-modules (gnu build childhurd)) We should use the =E2=80=98modules=E2=80=99 field of ins= tead of a non-top-level =E2=80=98use-modules=E2=80=99. > + (lambda _ > + (let ((pid (spawn)) > + (port #$(hurd-vm-port config %hurd-vm-ssh-port)) > + (root #$(hurd-vm-configuration-secret-root config)= )) > + (when (and root (directory-exists? root)) > + (catch #t > + (lambda _ > + (hurd-vm-copy-secrets port root)) > + (lambda (key . args) > + (format (current-error-port) "childhurd: ~a ~s\n= " key args)))) To avoid race conditions, we probably have to wait until PORT becomes available, no? Also, the VM boots even if we=E2=80=99ve failed to inject t= he secrets, right? As discussed on IRC, attached is my attempt at addressing this problem: the guest would run an activation snippet early on to receive secret files over raw unauthenticated TCP, blocking until it has received them. What=E2=80=99s missing from this patch is the host side that actually conne= cts to the guest and sends this file. I think it has the advantage of failing in case the secrets haven=E2=80=99t= been installed and it avoids the SSH host key bootstrapping issue. (It has at least the disadvantage of not being fully implemented. :-)) Also, longer term, it would allow us to not force password-less root authentication in the VM. I=E2=80=99m tempted to go the raw TCP way; WDYT? We can pair-hack on it if= you feel like it! Thanks, Ludo=E2=80=99. --=-=-= Content-Type: text/x-patch; charset=utf-8 Content-Disposition: inline Content-Transfer-Encoding: quoted-printable modified gnu/services/virtualization.scm @@ -1,6 +1,6 @@ ;;; GNU Guix --- Functional package management for GNU ;;; Copyright =C2=A9 2017 Ryan Moe -;;; Copyright =C2=A9 2018 Ludovic Court=C3=A8s +;;; Copyright =C2=A9 2018, 2020 Ludovic Court=C3=A8s ;;; Copyright =C2=A9 2020 Jan (janneke) Nieuwenhuizen ;;; ;;; This file is part of GNU Guix. @@ -804,6 +804,93 @@ given QEMU package." compiled for other architectures using QEMU and the @code{binfmt_misc} functionality of the kernel Linux."))) =20 + +;;; +;;; Secrets for guest VMs. +;;; + +(define (secret-service-activation port) + "Return an activation snippet that fetches sensitive material at PORT, o= ver +TCP." + (with-imported-modules '((guix build utils)) + #~(begin + (use-modules (guix build utils) + (rnrs io ports) + (rnrs bytevectors) + (ice-9 match)) + + (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) + (format (current-error-port) + "waiting for secrets on port ~a...~%" + port) + (match (accept sock) + ((client . address) + (format (current-error-port) "client connection from ~a~%" + (inet-ntop (sockaddr:fam address) + (sockaddr:addr address))) + (close-port sock) + client)))) + + ;; 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 (<=3D 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) + (format (current-error-port) + "installing file '~a' (~a bytes)...~%" + file size) + (mkdir-p (dirname file)) + (call-with-output-file file + (lambda (output) + (dump port output size) + (chmod file mode)))) + files sizes modes)) + (_ + (format (current-error-port) + "invalid secrets received~%") + (sleep 3) + (reboot)))) + + (let ((port (wait-for-client #$port))) + (read-secrets port) + (close-port port)))) + + (computed-file "secret-service-client" install-secrets)) + +(define secret-service-type + (service-type + (name 'secret-service) + (extensions (list (service-extension activation-service-type + secret-service-activation))) + (description + "This service fetches secret key and other sensitive material over TCP= at +boot time. This service is meant to be used by virtual machines (VMs) that +can only be accessed by their host."))) + ;;; ;;; The Hurd in VM service: a Childhurd. @@ -819,6 +906,8 @@ functionality of the kernel Linux."))) (target "/dev/vda") (timeout 0))) (services (cons* + ;; Receive secret keys on port 5900, TCP. + (service secret-service-type 5900) (service openssh-service-type (openssh-configuration (openssh openssh-sans-x) modified gnu/system/examples/bare-hurd.tmpl @@ -41,14 +41,16 @@ (host-name "guixygnu") (timezone "Europe/Amsterdam") (packages (cons openssh-sans-x %base-packages/hurd)) - (services (cons (service openssh-service-type - (openssh-configuration - (openssh openssh-sans-x) - (use-pam? #f) - (port-number 2222) - (permit-root-login #t) - (allow-empty-passwords? #t) - (password-authentication? #t))) - %base-services/hurd)))) + (services (append (list (service openssh-service-type + (openssh-configuration + (openssh openssh-sans-x) + (use-pam? #f) + (port-number 2222) + (permit-root-login #t) + (allow-empty-passwords? #t) + (password-authentication? #t))) + (service (@@ (gnu services virtualization) + secret-service-type))) + %base-services/hurd)))) =20 %hurd-os --=-=-=--