From patchwork Thu Aug 8 20:26:27 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: 14900 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 A23711728A; Thu, 8 Aug 2019 21:27:52 +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 2DFE317283 for ; Thu, 8 Aug 2019 21:27:52 +0100 (BST) Received: from localhost ([::1]:54978 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.86_2) (envelope-from ) id 1hvp0p-0002dr-RN for patchwork@mira.cbaines.net; Thu, 08 Aug 2019 16:27:51 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:60061) by lists.gnu.org with esmtp (Exim 4.86_2) (envelope-from ) id 1hvp05-0000RS-Oa for guix-patches@gnu.org; Thu, 08 Aug 2019 16:27:07 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1hvp02-0001rW-OP for guix-patches@gnu.org; Thu, 08 Aug 2019 16:27:04 -0400 Received: from debbugs.gnu.org ([209.51.188.43]:32954) by eggs.gnu.org with esmtps (TLS1.0:RSA_AES_128_CBC_SHA1:16) (Exim 4.71) (envelope-from ) id 1hvp02-0001rO-Kl for guix-patches@gnu.org; Thu, 08 Aug 2019 16:27:02 -0400 Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1hvp02-00047o-Fc for guix-patches@gnu.org; Thu, 08 Aug 2019 16:27:02 -0400 X-Loop: help-debbugs@gnu.org Subject: [bug#36957] [PATCH v3] 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: Thu, 08 Aug 2019 20:27:02 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: followup 36957 X-GNU-PR-Package: guix-patches X-GNU-PR-Keywords: patch To: Ricardo Wurmus Received: via spool by 36957-submit@debbugs.gnu.org id=B36957.156529599315801 (code B ref 36957); Thu, 08 Aug 2019 20:27:02 +0000 Received: (at 36957) by debbugs.gnu.org; 8 Aug 2019 20:26:33 +0000 Received: from localhost ([127.0.0.1]:41772 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1hvozY-00046m-Ga for submit@debbugs.gnu.org; Thu, 08 Aug 2019 16:26:32 -0400 Received: from mx.sdf.org ([205.166.94.20]:59043) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1hvozW-00046e-HF for 36957@debbugs.gnu.org; Thu, 08 Aug 2019 16:26:31 -0400 Received: from Upsilon (mobile-107-107-58-85.mycingular.net [107.107.58.85]) (authenticated (0 bits)) by mx.sdf.org (8.15.2/8.14.5) with ESMTPSA id x78KQRTq015372 (using TLSv1.2 with cipher AES256-GCM-SHA384 (256 bits) verified NO); Thu, 8 Aug 2019 20:26:29 GMT 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> Date: Thu, 08 Aug 2019 16:26:27 -0400 In-Reply-To: <87y304vyyo.fsf@elephly.net> (Ricardo Wurmus's message of "Thu, 08 Aug 2019 10:33:03 +0200") Message-ID: <87pnlfjte4.fsf_-_@sdf.lonestar.org> User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/26.2 (gnu/linux) MIME-Version: 1.0 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: , Cc: 36957@debbugs.gnu.org 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 | 12 ++++++++- guix/remote.scm | 60 ++++++++++++++++++++++++++++----------------- guix/ssh.scm | 25 +++++++++++++------ 4 files changed, 77 insertions(+), 30 deletions(-) diff --git a/doc/guix.texi b/doc/guix.texi index 734206a4b2..1478749d7d 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 @@ -25546,6 +25547,15 @@ accepts store items it receives from the coordinator: # guix archive --authorize < coordinator-public-key.txt @end example +@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 @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 ba3e33c922..aba98f8de5 100644 --- a/gnu/machine/ssh.scm +++ b/gnu/machine/ssh.scm @@ -99,6 +99,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." @@ -106,7 +114,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))) ;;; diff --git a/guix/remote.scm b/guix/remote.scm index 5fecd954e9..b0b6afba93 100644 --- a/guix/remote.scm +++ b/guix/remote.scm @@ -26,6 +26,8 @@ #:use-module (guix derivations) #: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)) @@ -40,29 +42,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)) @@ -90,12 +104,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 ((lowered (lower-gexp (trampoline exp) #:module-path %load-path)) (remote -> (connect-to-remote-daemon session @@ -115,7 +131,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) @@ -124,4 +140,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 ede00133c8..0f65f9e65b 100644 --- a/guix/ssh.scm +++ b/guix/ssh.scm @@ -97,16 +97,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 ()