From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from eggs.gnu.org ([2001:470:142:3::10]:49500) by lists.gnu.org with esmtp (Exim 4.86_2) (envelope-from ) id 1hvLOG-0002Wk-Nb for guix-patches@gnu.org; Wed, 07 Aug 2019 08:50:06 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1hvLOE-0006vK-Tm for guix-patches@gnu.org; Wed, 07 Aug 2019 08:50:04 -0400 Received: from debbugs.gnu.org ([209.51.188.43]:57593) by eggs.gnu.org with esmtps (TLS1.0:RSA_AES_128_CBC_SHA1:16) (Exim 4.71) (envelope-from ) id 1hvLOE-0006vB-QX for guix-patches@gnu.org; Wed, 07 Aug 2019 08:50:02 -0400 Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1hvLOE-0000U0-Mb for guix-patches@gnu.org; Wed, 07 Aug 2019 08:50:02 -0400 Subject: [bug#36957] [PATCH] machine: Allow non-root users to deploy. Resent-Message-ID: Received: from eggs.gnu.org ([2001:470:142:3::10]:49430) by lists.gnu.org with esmtp (Exim 4.86_2) (envelope-from ) id 1hvLNw-0002Al-Ib for guix-patches@gnu.org; Wed, 07 Aug 2019 08:49:46 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1hvLNu-0006kw-ON for guix-patches@gnu.org; Wed, 07 Aug 2019 08:49:44 -0400 Received: from mx.sdf.org ([205.166.94.20]:64671) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1hvLNu-0006k9-CD for guix-patches@gnu.org; Wed, 07 Aug 2019 08:49:42 -0400 Received: from Epsilon (pool-173-76-53-40.bstnma.fios.verizon.net [173.76.53.40]) (authenticated (0 bits)) by mx.sdf.org (8.15.2/8.14.5) with ESMTPSA id x77CneGQ027546 (using TLSv1.2 with cipher AES256-GCM-SHA384 (256 bits) verified NO) for ; Wed, 7 Aug 2019 12:49:41 GMT From: zerodaysfordays@sdf.lonestar.org (Jakob L. Kreuze) Date: Wed, 07 Aug 2019 08:46:29 -0400 Message-ID: <87a7cl3zyy.fsf@sdf.lonestar.org> MIME-Version: 1.0 Content-Type: multipart/signed; boundary="=-=-="; micalg=pgp-sha256; protocol="application/pgp-signature" List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: guix-patches-bounces+kyle=kyleam.com@gnu.org Sender: "Guix-patches" To: 36957@debbugs.gnu.org --=-=-= Content-Type: text/plain Content-Transfer-Encoding: quoted-printable * doc/guix.texi (Invoking guix deploy): Add section describing prerequisites for deploying as a non-root user. * guix/remote.scm (remote-pipe-for-gexp): New optional 'become-command' argument. (%remote-eval): New optional 'become-command' argument. (remote-eval): New 'become-command' keyword argument. * guix/ssh.scm (remote-inferior): New optional 'become-command' argument. (inferior-remote-eval): New optional 'become-command' argument. (remote-authorize-signing-key): New optional 'become-command' argument. * gnu/machine/ssh.scm (machine-become-command): New variable. (managed-host-remote-eval): Invoke 'remote-eval' with the '#:become-command' keyword. (deploy-managed-host): Invoke 'remote-authorize-signing-key' with the '#:become-command' keyword. =2D-- doc/guix.texi | 10 ++++++++ gnu/machine/ssh.scm | 15 ++++++++++-- guix/remote.scm | 60 ++++++++++++++++++++++++++++----------------- guix/ssh.scm | 30 ++++++++++++++++------- 4 files changed, 82 insertions(+), 33 deletions(-) diff --git a/doc/guix.texi b/doc/guix.texi index 64ca44d494..144981af10 100644 =2D-- a/doc/guix.texi +++ b/doc/guix.texi @@ -25514,6 +25514,7 @@ evaluates to. As an example, @var{file} might cont= ain a definition like this: (environment managed-host-environment-type) (configuration (machine-ssh-configuration (host-name "localhost") + (user "alice") (identity "./id_rsa") (port 2222))))) @end example @@ -25530,6 +25531,15 @@ complex deployment may involve, for example, start= ing virtual machines through a Virtual Private Server (VPS) provider. In such a case, a different @var{environment} type would be used. =20 +@code{user}, in this example, specifies the name of the user account to lo= g in +as to perform the deployment. Its default value is @code{root}, but root +login over SSH may be forbidden in some cases. To work around this, +@command{guix deploy} can log in as an unprivileged user and employ +@code{sudo} to escalate privileges. This will only work if @code{sudo} is +currently installed on the remote and can be invoked non-interactively as +@code{user}. That is: the line in @code{sudoers} granting @code{user} the +ability to use @code{sudo} must contain the NOPASSWD tag. + @deftp {Data Type} machine This is the data type representing a single machine in a heterogeneous Guix deployment. diff --git a/gnu/machine/ssh.scm b/gnu/machine/ssh.scm index 90deff19a8..083e443a16 100644 =2D-- a/gnu/machine/ssh.scm +++ b/gnu/machine/ssh.scm @@ -105,6 +105,14 @@ one from the configuration's parameters if one was not= provided." ;;; Remote evaluation. ;;; =20 +(define (machine-become-command machine) + "Return as a list of strings the program and arguments necessary to run a +shell command with escalated privileges for MACHINE's configuration." + (if (string=3D "root" (machine-ssh-configuration-user + (machine-configuration machine))) + '() + '("/run/setuid-programs/sudo" "-n" "--"))) + (define (managed-host-remote-eval machine exp) "Internal implementation of 'machine-remote-eval' for MACHINE instances = with an environment type of 'managed-host." @@ -112,7 +120,9 @@ an environment type of 'managed-host." (remote-eval exp (machine-ssh-session machine) #:build-locally? (machine-ssh-configuration-build-locally? =2D (machine-configuration machine)))) + (machine-configuration machine)) + #:become-command + (machine-become-command machine))) =20 ;;; @@ -335,7 +345,8 @@ environment type of 'managed-host." (remote-authorize-signing-key (call-with-input-file %public-key-file (lambda (port) (string->canonical-sexp (get-string-al= l port)))) =2D (machine-ssh-session machine)) + (machine-ssh-session machine) + (machine-become-command machine)) (mlet %store-monad ((_ (check-deployment-sanity machine)) (boot-parameters (machine-boot-parameters machine))) (let* ((os (machine-operating-system machine)) diff --git a/guix/remote.scm b/guix/remote.scm index d5738ebbfa..d5992763b2 100644 =2D-- a/guix/remote.scm +++ b/guix/remote.scm @@ -27,6 +27,8 @@ #:use-module (guix utils) #:use-module (ssh popen) #:use-module (srfi srfi-1) + #:use-module (srfi srfi-34) + #:use-module (srfi srfi-35) #:use-module (ice-9 match) #:export (remote-eval)) =20 @@ -41,29 +43,41 @@ ;;; ;;; Code: =20 =2D(define (remote-pipe-for-gexp lowered session) =2D "Return a remote pipe for the given SESSION to evaluate LOWERED." +(define* (remote-pipe-for-gexp lowered session #:optional become-command) + "Return a remote pipe for the given SESSION to evaluate LOWERED. If +BECOME-COMMAND is given, use that to invoke the remote Guile REPL." (define shell-quote (compose object->string object->string)) =20 =2D (apply open-remote-pipe* session OPEN_READ =2D (string-append (derivation-input-output-path =2D (lowered-gexp-guile lowered)) =2D "/bin/guile") =2D "--no-auto-compile" =2D (append (append-map (lambda (directory) =2D `("-L" ,directory)) =2D (lowered-gexp-load-path lowered)) =2D (append-map (lambda (directory) =2D `("-C" ,directory)) =2D (lowered-gexp-load-path lowered)) =2D `("-c" =2D ,(shell-quote (lowered-gexp-sexp lowered)))))) + (define repl-command + (append (or become-command '()) + (list + (string-append (derivation-input-output-path + (lowered-gexp-guile lowered)) + "/bin/guile") + "--no-auto-compile") + (append-map (lambda (directory) + `("-L" ,directory)) + (lowered-gexp-load-path lowered)) + (append-map (lambda (directory) + `("-C" ,directory)) + (lowered-gexp-load-path lowered)) + `("-c" + ,(shell-quote (lowered-gexp-sexp lowered))))) =20 =2D(define (%remote-eval lowered session) + (let ((pipe (apply open-remote-pipe* session OPEN_READ repl-command))) + (when (eof-object? (peek-char pipe)) + (raise (condition + (&message + (message (format #f (G_ "failed to run '~{~a~^ ~}'") + repl-command)))))) + pipe)) + +(define* (%remote-eval lowered session #:optional become-command) "Evaluate LOWERED, a lowered gexp, in SESSION. This assumes that all the =2Dprerequisites of EXP are already available on the host at SESSION." =2D (let* ((pipe (remote-pipe-for-gexp lowered session)) +prerequisites of EXP are already available on the host at SESSION. If +BECOME-COMMAND is given, use that to invoke the remote Guile REPL." + (let* ((pipe (remote-pipe-for-gexp lowered session become-command)) (result (read-repl-response pipe))) (close-port pipe) result)) @@ -91,12 +105,14 @@ result to the current output port using the (guix repl= ) protocol." #:key (build-locally? #t) (module-path %load-path) =2D (socket-name "/var/guix/daemon-socket/socket")) + (socket-name "/var/guix/daemon-socket/socket") + (become-command #f)) "Evaluate EXP, a gexp, on the host at SESSION, an SSH session. Ensure t= hat all the elements EXP refers to are built and deployed to SESSION beforehan= d. When BUILD-LOCALLY? is true, said dependencies are built locally and sent = to the remote store afterwards; otherwise, dependencies are built directly on= the =2Dremote store." +remote store. If BECOME-COMMAND is given, use that to invoke the remote G= uile +REPL." (mlet* %store-monad ((system -> (remote-system session)) (lowered (lower-gexp (trampoline exp) #:system system @@ -119,7 +135,7 @@ remote store." (built-derivations inputs) ((store-lift send-files) to-send remote #:recursive? #t) (return (close-connection remote)) =2D (return (%remote-eval lowered session)))) + (return (%remote-eval lowered session become-command)))) (let ((to-send (append (map (compose derivation-file-name derivation-input-derivation) inputs) @@ -128,4 +144,4 @@ remote store." ((store-lift send-files) to-send remote #:recursive? #t) (return (build-derivations remote inputs)) (return (close-connection remote)) =2D (return (%remote-eval lowered session))))))) + (return (%remote-eval lowered session become-command))))))) diff --git a/guix/ssh.scm b/guix/ssh.scm index 5186c646ca..7bc499a2fe 100644 =2D-- a/guix/ssh.scm +++ b/guix/ssh.scm @@ -100,16 +100,27 @@ specifies; otherwise use them. Throw an error on fai= lure." (message (format #f (G_ "SSH connection to '~a' failed: ~a= ~%") host (get-error session)))))))))) =20 =2D(define (remote-inferior session) =2D "Return a remote inferior for the given SESSION." =2D (let ((pipe (open-remote-pipe* session OPEN_BOTH =2D "guix" "repl" "-t" "machine"))) +(define* (remote-inferior session #:optional become-command) + "Return a remote inferior for the given SESSION. If BECOME-COMMAND is +given, use that to invoke the remote Guile REPL." + (let* ((repl-command (append (or become-command '()) + '("guix" "repl" "-t" "machine"))) + (pipe (apply open-remote-pipe* session OPEN_BOTH repl-command))) + ;; XXX: 'channel-get-exit-status' would be better here, but hangs if t= he + ;; process does succeed. This doesn't reflect the documentation, so it= 's + ;; possible that it's a bug in guile-ssh. + (when (eof-object? (peek-char pipe)) + (raise (condition + (&message + (message (format #f (G_ "failed to run '~{~a~^ ~}'") + repl-command)))))) (port->inferior pipe))) =20 =2D(define (inferior-remote-eval exp session) +(define* (inferior-remote-eval exp session #:optional become-command) "Evaluate EXP in a new inferior running in SESSION, and close the inferi= or =2Dright away." =2D (let ((inferior (remote-inferior session))) +right away. If BECOME-COMMAND is given, use that to invoke the remote Gui= le +REPL." + (let ((inferior (remote-inferior session become-command))) (dynamic-wind (const #t) (lambda () @@ -291,7 +302,7 @@ the machine on the other end of SESSION." (inferior-remote-eval '(begin (use-modules (guix utils)) (%current-syste= m)) session)) =20 =2D(define (remote-authorize-signing-key key session) +(define* (remote-authorize-signing-key key session #:optional become-comma= nd) "Send KEY, a canonical sexp containing a public key, over SESSION and ad= d it to the system ACL file if it has not yet been authorized." (inferior-remote-eval @@ -310,7 +321,8 @@ to the system ACL file if it has not yet been authorize= d." (mkdir-p (dirname %acl-file)) (with-atomic-file-output %acl-file (cut write-acl acl <>))))) =2D session)) + session + become-command)) =20 (define* (send-files local files remote #:key =2D-=20 2.22.0 --=-=-= Content-Type: application/pgp-signature; name="signature.asc" -----BEGIN PGP SIGNATURE----- iQIzBAEBCAAdFiEEa1VJLOiXAjQ2BGSm9Qb9Fp2P2VoFAl1KyCUACgkQ9Qb9Fp2P 2VqPWA/+KiepqTPgj5D7GSE/zrbAfle0fFiITSS3bCfNPcwCLay4I63JeINzI7e4 4xcES7bp3YdmDJHpbhGXDw3sJsNqiJdaGbTy7QtjHACK/jVmTMydEYSSOBf8VOeu slHcfcC0BbZDlZEliq7kVNcFvf8pP/GS5HH4v8kfCQMXWEdY2E4Vy5DNTWbYtMBe kpdEz5+EkNJnXl7M/zax/Uil/X/GWMgYByDH+Tk4+8uxWIuye8FeqmE0crttSbmM 5165D7cT0ar+6Ka7gZhWnzyUxUjSSoKtt52cEMaJiB8b8NtQjusH+U+GM1raW48G dK49AK1iD3owbTXT4cYTe3zKlQKdD8W8itPlMWXg8Jy6Zs3fDnNkRQTuVP9HnnQ8 2j6lhlJB9I1CqKMRbmsA+7yxfmFixKBFj9HWBuEy206bLGoI2pOcnst/PhZ9Qjlc RsTvwAEcedXVYFl/c29JM//I3oe5qcd5mWm8TL9XZ64j7tjDT6dr6oMNuziaui3e v8gBuONNyVzTSsmToARg6mTSjPTUlvSfS1S5TFcs8Ve2FMiSMRVZl/TE4hSHpR0R AivWkFJ9wYUHst4ydc+j7WGKuZIyMukSLH5wzt7VPH0MZ9tM8Zc2N11y+upPNS7+ TrHWtKo8gq8VW6zuaYDoHXv2qmlSiD4uhUPaNGgVZQqPRZdCND8= =0BJ9 -----END PGP SIGNATURE----- --=-=-=--