From patchwork Wed Aug 7 12:46:29 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Jakob L. Kreuze" X-Patchwork-Id: 14888 Return-Path: X-Original-To: patchwork@mira.cbaines.net Delivered-To: patchwork@mira.cbaines.net Received: by mira.cbaines.net (Postfix, from userid 113) id EEB5217274; Wed, 7 Aug 2019 13:50:26 +0100 (BST) X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on mira.cbaines.net X-Spam-Level: X-Spam-Status: No, score=-1.9 required=5.0 tests=BAYES_00 autolearn=unavailable autolearn_force=no version=3.4.0 Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mira.cbaines.net (Postfix) with ESMTP id 72FC517252 for ; Wed, 7 Aug 2019 13:50:26 +0100 (BST) Received: from localhost ([::1]:40678 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.86_2) (envelope-from ) id 1hvLOc-0002Zp-4n for patchwork@mira.cbaines.net; Wed, 07 Aug 2019 08:50:26 -0400 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 X-Loop: help-debbugs@gnu.org Subject: [bug#36957] [PATCH] machine: Allow non-root users to deploy. Resent-From: zerodaysfordays@sdf.lonestar.org (Jakob L. Kreuze) Original-Sender: "Debbugs-submit" Resent-CC: guix-patches@gnu.org Resent-Date: Wed, 07 Aug 2019 12:50:02 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: report 36957 X-GNU-PR-Package: guix-patches X-GNU-PR-Keywords: patch To: 36957@debbugs.gnu.org X-Debbugs-Original-To: guix-patches@gnu.org Received: via spool by submit@debbugs.gnu.org id=B.15651821891821 (code B ref -1); Wed, 07 Aug 2019 12:50:02 +0000 Received: (at submit) by debbugs.gnu.org; 7 Aug 2019 12:49:49 +0000 Received: from localhost ([127.0.0.1]:38181 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1hvLO0-0000TJ-MV for submit@debbugs.gnu.org; Wed, 07 Aug 2019 08:49:49 -0400 Received: from lists.gnu.org ([209.51.188.17]:57507) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1hvLNy-0000TB-El for submit@debbugs.gnu.org; Wed, 07 Aug 2019 08:49:46 -0400 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> User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/26.2 (gnu/linux) MIME-Version: 1.0 X-detected-operating-system: by eggs.gnu.org: Genre and OS details not recognized. X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] X-Received-From: 209.51.188.43 X-BeenThere: guix-patches@gnu.org List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: guix-patches-bounces+patchwork=mira.cbaines.net@gnu.org Sender: "Guix-patches" X-getmail-retrieved-from-mailbox: Patches * 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. --- 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 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -25514,6 +25514,7 @@ evaluates to. As an example, @var{file} might contain 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, starting virtual machines through a Virtual Private Server (VPS) provider. In such a case, a different @var{environment} type would be used. +@code{user}, in this example, specifies the name of the user account to log 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 --- 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. ;;; +(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= "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? - (machine-configuration machine)))) + (machine-configuration machine)) + #:become-command + (machine-become-command machine))) ;;; @@ -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-all port)))) - (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 --- 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)) @@ -41,29 +43,41 @@ ;;; ;;; Code: -(define (remote-pipe-for-gexp lowered session) - "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)) - (apply open-remote-pipe* session OPEN_READ - (string-append (derivation-input-output-path - (lowered-gexp-guile lowered)) - "/bin/guile") - "--no-auto-compile" - (append (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)))))) + (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))))) -(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 -prerequisites of EXP are already available on the host at SESSION." - (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) - (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 that all the elements EXP refers to are built and deployed to SESSION beforehand. When BUILD-LOCALLY? is true, said dependencies are built locally and sent to the remote store afterwards; otherwise, dependencies are built directly on the -remote store." +remote store. If BECOME-COMMAND is given, use that to invoke the remote Guile +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)) - (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)) - (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 --- a/guix/ssh.scm +++ b/guix/ssh.scm @@ -100,16 +100,27 @@ specifies; otherwise use them. Throw an error on failure." (message (format #f (G_ "SSH connection to '~a' failed: ~a~%") host (get-error session)))))))))) -(define (remote-inferior session) - "Return a remote inferior for the given SESSION." - (let ((pipe (open-remote-pipe* session OPEN_BOTH - "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 the + ;; 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))) -(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 inferior -right away." - (let ((inferior (remote-inferior session))) +right away. If BECOME-COMMAND is given, use that to invoke the remote Guile +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-system)) session)) -(define (remote-authorize-signing-key key session) +(define* (remote-authorize-signing-key key session #:optional become-command) "Send KEY, a canonical sexp containing a public key, over SESSION and add 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 authorized." (mkdir-p (dirname %acl-file)) (with-atomic-file-output %acl-file (cut write-acl acl <>))))) - session)) + session + become-command)) (define* (send-files local files remote #:key