From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from eggs.gnu.org ([2001:470:142:3::10]:34587) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1hyAlo-0008Lw-67 for guix-patches@gnu.org; Thu, 15 Aug 2019 04:06:05 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1hyAlm-0004JJ-HA for guix-patches@gnu.org; Thu, 15 Aug 2019 04:06:04 -0400 Received: from debbugs.gnu.org ([209.51.188.43]:41747) by eggs.gnu.org with esmtps (TLS1.0:RSA_AES_128_CBC_SHA1:16) (Exim 4.71) (envelope-from ) id 1hyAlm-0004J9-D4 for guix-patches@gnu.org; Thu, 15 Aug 2019 04:06:02 -0400 Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1hyAlm-0004XN-6d for guix-patches@gnu.org; Thu, 15 Aug 2019 04:06:02 -0400 Subject: [bug#36957] [PATCH 1/5] machine: Allow non-root users to deploy. Resent-Message-ID: From: zerodaysfordays@sdf.lonestar.org (Jakob L. Kreuze) References: <87a7cl3zyy.fsf@sdf.lonestar.org> <87sgqcobds.fsf@dustycloud.org> <87pnlgjymv.fsf_-_@sdf.lonestar.org> <87y304vyyo.fsf@elephly.net> <87tvarjtgw.fsf@sdf.lonestar.org> <87h86jxyea.fsf@dustycloud.org> <875zmy26u6.fsf_-_@sdf.lonestar.org> Date: Thu, 15 Aug 2019 04:05:04 -0400 In-Reply-To: <875zmy26u6.fsf_-_@sdf.lonestar.org> (Jakob L. Kreuze's message of "Thu, 15 Aug 2019 04:03:45 -0400") Message-ID: <871rxm26rz.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: Christopher Lemmer Webber Cc: 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.=20=20 (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 | 8 +++++++ guix/remote.scm | 57 ++++++++++++++++++++++++++++----------------- guix/ssh.scm | 25 ++++++++++++++------ 4 files changed, 72 insertions(+), 28 deletions(-) diff --git a/doc/guix.texi b/doc/guix.texi index a7facf4701..e5cec7ad25 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 @@ -25546,6 +25547,15 @@ accepts store items it receives from the coordinat= or: # guix archive --authorize < coordinator-public-key.txt @end example =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 @code{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 670990a633..fb15d39e61 100644 =2D-- a/gnu/machine/ssh.scm +++ b/gnu/machine/ssh.scm @@ -101,6 +101,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." diff --git a/guix/remote.scm b/guix/remote.scm index bcac64ea7a..d8124e41ab 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)) @@ -92,7 +106,8 @@ result to the current output port using the (guix repl) = protocol." (build-locally? #t) (system (%current-system)) (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 @@ -119,7 +134,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 +143,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 9b5ca68894..90311127a1 100644 =2D-- a/guix/ssh.scm +++ b/guix/ssh.scm @@ -98,16 +98,27 @@ specifies; otherwise use them. Throw an error on failu= re." (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 () =2D-=20 2.22.0 --=-=-= Content-Type: application/pgp-signature; name="signature.asc" -----BEGIN PGP SIGNATURE----- iQIzBAEBCAAdFiEEa1VJLOiXAjQ2BGSm9Qb9Fp2P2VoFAl1VEjIACgkQ9Qb9Fp2P 2VopbQ/+JecFi3jpCgfba9ce2Y8/2HfCUsSDv4Ru7nnk42XjcQ0/5grnPHfTnAEn XC4DbDCmTjxRZ2UFJjZgxuz+OwP3lAFswUb8JuoMpCw3Wr+HOlDjmQiwhYRO09OX xDPURpC3aa/+Xr9Wv4AnIzcKbp3rqqxtv098yg9nb5L5LnHrkFGPxjApcIPpZveh KYDCAeynAbfrOqxNpMDzS0JmccT7DpA3btqYNq2HZ/knTlQQPJQgWPZoUu3kqoxw MPivk+lf2UE7W8jeQfOzViMW6zD73Uul6lzii0LtkRMbAAKx8SpPGp2z0oFBC5ps Hd9TKEL/FsvwvNxGK6V94Idb13nOg0RrUH/MIrMNl81D1hkv5PkIwtyB4SXs4DQU RXTuMs19hDLFviYAPZFMQNx5w0wniS8DUNP51RIuNvn13cpT3vvq4zUj2xOTRnKn VhlwL9eVIgYMrI0CjpK0fxtl28v0vL43/w6Y2IgAtswDn7SLouGekhtu276eZKvi /ivWYr8vWdUMRwO35MCI1RPfDrqkj8hlIuALvRIc757kc2djIlECYZ68QEbhLxiv SsVjRHF+/gF56vdR+kprI50VHcdYA+ty5Mlm002S1KZrlqmOkDfAJkbpb7Lt1v4f 0BwGpibUxaAQD83CzUtUHF+sv1LsPb40PLnIc3Xa+hG4bkWlPJw= =/356 -----END PGP SIGNATURE----- --=-=-=--