~ruther/guix-local

5463fe512a02eb186ad95a1cae9d2682dbe2ccd0 — Ludovic Courtès 11 years ago 6ad2e17
publish: Add '--user' option.

* guix/scripts/publish.scm (show-help): Add --user.
  (%options): Likewise.
  (run-publish-server): Change 'port' parameter to 'socket'.  Pass
  #:socket instead of #:addr and #:port to 'run-server'.  Update caller
  accordingly.
  (open-server-socket, gather-user-privileges): New procedures.
  (guix-publish): Use them.  Force %PRIVATE-KEY and %PUBLIC-KEY early
  on.  Warn when running as root.
* doc/guix.texi (Invoking guix publish): Document --user.
2 files changed, 61 insertions(+), 12 deletions(-)

M doc/guix.texi
M guix/scripts/publish.scm
M doc/guix.texi => doc/guix.texi +7 -1
@@ 3657,7 3657,8 @@ the @code{hydra.gnu.org} build farm.
For security, each substitute is signed, allowing recipients to check
their authenticity and integrity (@pxref{Substitutes}).  Because
@command{guix publish} uses the system's signing key, which is only
readable by the system administrator, it must run as root.
readable by the system administrator, it must be started as root; the
@code{--user} option makes it drop root privileges early on.

The general syntax is:



@@ 3686,6 3687,11 @@ The following options are available:
@itemx -p @var{port}
Listen for HTTP requests on @var{port}.

@item --user=@var{user}
@itemx -u @var{user}
Change privileges to @var{user} as soon as possible---i.e., once the
server socket is open and the signing key has been read.

@item --repl[=@var{port}]
@itemx -r [@var{port}]
Spawn a Guile REPL server (@pxref{REPL Servers,,, guile, GNU Guile

M guix/scripts/publish.scm => guix/scripts/publish.scm +54 -11
@@ 51,6 51,8 @@ Publish ~a over HTTP.\n") %store-directory)
  (display (_ "
  -p, --port=PORT        listen on PORT"))
  (display (_ "
  -u, --user=USER        change privileges to USER as soon as possible"))
  (display (_ "
  -r, --repl[=PORT]      spawn REPL server on PORT"))
  (newline)
  (display (_ "


@@ 68,6 70,9 @@ Publish ~a over HTTP.\n") %store-directory)
        (option '(#\V "version") #f #f
                (lambda _
                  (show-version-and-exit "guix publish")))
        (option '(#\u "user") #t #f
                (lambda (opt name arg result)
                  (alist-cons 'user arg result)))
        (option '(#\p "port") #t #f
                (lambda (opt name arg result)
                  (alist-cons 'port (string->number* arg) result)))


@@ 220,24 225,62 @@ example: \"/foo/bar\" yields '(\"foo\" \"bar\")."
          (_ (not-found request)))
        (not-found request))))

(define (run-publish-server port store)
(define (run-publish-server socket store)
  (run-server (make-request-handler store)
              'http
              `(#:addr ,INADDR_ANY
                #:port ,port)))
              `(#:socket ,socket)))

(define (open-server-socket addr port)
  "Return a TCP socket bound to ADDR and PORT."
  (let ((sock (socket PF_INET SOCK_STREAM 0)))
    (setsockopt sock SOL_SOCKET SO_REUSEADDR 1)
    (bind sock AF_INET addr port)
    sock))

(define (gather-user-privileges user)
  "Switch to the identity of USER, a user name."
  (catch 'misc-error
    (lambda ()
      (let ((user (getpw user)))
        (setgroups #())
        (setgid (passwd:gid user))
        (setuid (passwd:uid user))))
    (lambda (key proc message args . rest)
      (leave (_ "user '~a' not found: ~a~%")
             user (apply format #f message args)))))


;;;
;;; Entry point.
;;;

(define (guix-publish . args)
  (with-error-handling
    (let* ((opts (args-fold* args %options
                             (lambda (opt name arg result)
                               (leave (_ "~A: unrecognized option~%") name))
                             (lambda (arg result)
                               (leave (_ "~A: extraneuous argument~%") arg))
                             %default-options))
           (port (assoc-ref opts 'port))
    (let* ((opts   (args-fold* args %options
                               (lambda (opt name arg result)
                                 (leave (_ "~A: unrecognized option~%") name))
                               (lambda (arg result)
                                 (leave (_ "~A: extraneuous argument~%") arg))
                               %default-options))
           (port   (assoc-ref opts 'port))
           (user   (assoc-ref opts 'user))
           (socket (open-server-socket INADDR_ANY port))
           (repl-port (assoc-ref opts 'repl)))
      ;; Read the key right away so that (1) we fail early on if we can't
      ;; access them, and (2) we can then drop privileges.
      (force %private-key)
      (force %public-key)

      (when user
        ;; Now that we've read the key material and opened the socket, we can
        ;; drop privileges.
        (gather-user-privileges user))

      (when (zero? (getuid))
        (warning (_ "server running as root; \
consider using the '--user' option!~%")))
      (format #t (_ "publishing ~a on port ~d~%") %store-directory port)
      (when repl-port
        (repl:spawn-server (repl:make-tcp-server-socket #:port repl-port)))
      (with-store store
        (run-publish-server (assoc-ref opts 'port) store)))))
        (run-publish-server socket store)))))