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.
This commit is contained in:
		
							parent
							
								
									6ad2e17e8e
								
							
						
					
					
						commit
						5463fe512a
					
				
					 2 changed files with 61 additions and 12 deletions
				
			
		| 
						 | 
					@ -3657,7 +3657,8 @@ the @code{hydra.gnu.org} build farm.
 | 
				
			||||||
For security, each substitute is signed, allowing recipients to check
 | 
					For security, each substitute is signed, allowing recipients to check
 | 
				
			||||||
their authenticity and integrity (@pxref{Substitutes}).  Because
 | 
					their authenticity and integrity (@pxref{Substitutes}).  Because
 | 
				
			||||||
@command{guix publish} uses the system's signing key, which is only
 | 
					@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:
 | 
					The general syntax is:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3686,6 +3687,11 @@ The following options are available:
 | 
				
			||||||
@itemx -p @var{port}
 | 
					@itemx -p @var{port}
 | 
				
			||||||
Listen for HTTP requests on @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}]
 | 
					@item --repl[=@var{port}]
 | 
				
			||||||
@itemx -r [@var{port}]
 | 
					@itemx -r [@var{port}]
 | 
				
			||||||
Spawn a Guile REPL server (@pxref{REPL Servers,,, guile, GNU Guile
 | 
					Spawn a Guile REPL server (@pxref{REPL Servers,,, guile, GNU Guile
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -51,6 +51,8 @@ Publish ~a over HTTP.\n") %store-directory)
 | 
				
			||||||
  (display (_ "
 | 
					  (display (_ "
 | 
				
			||||||
  -p, --port=PORT        listen on PORT"))
 | 
					  -p, --port=PORT        listen on PORT"))
 | 
				
			||||||
  (display (_ "
 | 
					  (display (_ "
 | 
				
			||||||
 | 
					  -u, --user=USER        change privileges to USER as soon as possible"))
 | 
				
			||||||
 | 
					  (display (_ "
 | 
				
			||||||
  -r, --repl[=PORT]      spawn REPL server on PORT"))
 | 
					  -r, --repl[=PORT]      spawn REPL server on PORT"))
 | 
				
			||||||
  (newline)
 | 
					  (newline)
 | 
				
			||||||
  (display (_ "
 | 
					  (display (_ "
 | 
				
			||||||
| 
						 | 
					@ -68,6 +70,9 @@ Publish ~a over HTTP.\n") %store-directory)
 | 
				
			||||||
        (option '(#\V "version") #f #f
 | 
					        (option '(#\V "version") #f #f
 | 
				
			||||||
                (lambda _
 | 
					                (lambda _
 | 
				
			||||||
                  (show-version-and-exit "guix publish")))
 | 
					                  (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
 | 
					        (option '(#\p "port") #t #f
 | 
				
			||||||
                (lambda (opt name arg result)
 | 
					                (lambda (opt name arg result)
 | 
				
			||||||
                  (alist-cons 'port (string->number* 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)))
 | 
				
			||||||
        (not-found request))))
 | 
					        (not-found request))))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
(define (run-publish-server port store)
 | 
					(define (run-publish-server socket store)
 | 
				
			||||||
  (run-server (make-request-handler store)
 | 
					  (run-server (make-request-handler store)
 | 
				
			||||||
              'http
 | 
					              'http
 | 
				
			||||||
              `(#:addr ,INADDR_ANY
 | 
					              `(#:socket ,socket)))
 | 
				
			||||||
                #:port ,port)))
 | 
					
 | 
				
			||||||
 | 
					(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)
 | 
					(define (guix-publish . args)
 | 
				
			||||||
  (with-error-handling
 | 
					  (with-error-handling
 | 
				
			||||||
    (let* ((opts (args-fold* args %options
 | 
					    (let* ((opts   (args-fold* args %options
 | 
				
			||||||
                             (lambda (opt name arg result)
 | 
					                               (lambda (opt name arg result)
 | 
				
			||||||
                               (leave (_ "~A: unrecognized option~%") name))
 | 
					                                 (leave (_ "~A: unrecognized option~%") name))
 | 
				
			||||||
                             (lambda (arg result)
 | 
					                               (lambda (arg result)
 | 
				
			||||||
                               (leave (_ "~A: extraneuous argument~%") arg))
 | 
					                                 (leave (_ "~A: extraneuous argument~%") arg))
 | 
				
			||||||
                             %default-options))
 | 
					                               %default-options))
 | 
				
			||||||
           (port (assoc-ref opts 'port))
 | 
					           (port   (assoc-ref opts 'port))
 | 
				
			||||||
 | 
					           (user   (assoc-ref opts 'user))
 | 
				
			||||||
 | 
					           (socket (open-server-socket INADDR_ANY port))
 | 
				
			||||||
           (repl-port (assoc-ref opts 'repl)))
 | 
					           (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)
 | 
					      (format #t (_ "publishing ~a on port ~d~%") %store-directory port)
 | 
				
			||||||
      (when repl-port
 | 
					      (when repl-port
 | 
				
			||||||
        (repl:spawn-server (repl:make-tcp-server-socket #:port repl-port)))
 | 
					        (repl:spawn-server (repl:make-tcp-server-socket #:port repl-port)))
 | 
				
			||||||
      (with-store store
 | 
					      (with-store store
 | 
				
			||||||
        (run-publish-server (assoc-ref opts 'port) store)))))
 | 
					        (run-publish-server socket store)))))
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Reference in a new issue