Add 'guix git authenticate'.
* guix/scripts/git.scm, guix/scripts/git/authenticate.scm, tests/guix-git-authenticate.sh: New files. * Makefile.am (MODULES): Add the *.scm files. (SH_TESTS): Add 'tests/guix-git-authenticate.sh'. * doc/guix.texi (Channels)[Specifying Channel Authorizations]: Mention 'guix git authenticate'. (Invoking guix git authenticate): New node. * po/guix/POTFILES.in: Add 'guix/scripts/git.scm' and 'guix/scripts/git/authenticate.scm'.
This commit is contained in:
		
							parent
							
								
									69db2993b5
								
							
						
					
					
						commit
						a98712785e
					
				
					 6 changed files with 383 additions and 3 deletions
				
			
		|  | @ -281,6 +281,8 @@ MODULES =					\ | |||
|   guix/scripts/publish.scm			\ | ||||
|   guix/scripts/edit.scm				\ | ||||
|   guix/scripts/size.scm				\ | ||||
|   guix/scripts/git.scm				\ | ||||
|   guix/scripts/git/authenticate.scm		\ | ||||
|   guix/scripts/graph.scm			\ | ||||
|   guix/scripts/weather.scm			\ | ||||
|   guix/scripts/container.scm			\ | ||||
|  | @ -463,6 +465,7 @@ SH_TESTS =					\ | |||
|   tests/guix-build-branch.sh			\ | ||||
|   tests/guix-download.sh			\ | ||||
|   tests/guix-gc.sh				\ | ||||
|   tests/guix-git-authenticate.sh		\ | ||||
|   tests/guix-hash.sh				\ | ||||
|   tests/guix-pack.sh				\ | ||||
|   tests/guix-pack-localstatedir.sh		\ | ||||
|  |  | |||
|  | @ -3981,6 +3981,7 @@ Before that, some security considerations. | |||
| 
 | ||||
| @subsection Channel Authentication | ||||
| 
 | ||||
| @anchor{channel-authentication} | ||||
| @cindex authentication, of channel code | ||||
| The @command{guix pull} and @command{guix time-machine} commands | ||||
| @dfn{authenticate} the code retrieved from channels: they make sure each | ||||
|  | @ -4200,6 +4201,7 @@ add a meta-data file @file{.guix-channel} that contains: | |||
| @cindex channel authorizations | ||||
| @subsection Specifying Channel Authorizations | ||||
| 
 | ||||
| @anchor{channel-authorizations} | ||||
| As we saw above, Guix ensures the source code it pulls from channels | ||||
| comes from authorized developers.  As a channel author, you need to | ||||
| specify the list of authorized developers in the | ||||
|  | @ -4259,6 +4261,18 @@ pair---i.e., the commit that introduced @file{.guix-authorizations}, and | |||
| the fingerprint of the OpenPGP used to sign it. | ||||
| @end enumerate | ||||
| 
 | ||||
| Before pushing to your public Git repository, you can run @command{guix | ||||
| git-authenticate} to verify that you did sign all the commits you are | ||||
| about to push with an authorized key: | ||||
| 
 | ||||
| @example | ||||
| guix git authenticate @var{commit} @var{signer} | ||||
| @end example | ||||
| 
 | ||||
| @noindent | ||||
| where @var{commit} and @var{signer} are your channel introduction. | ||||
| @xref{Invoking guix git authenticate}, for details. | ||||
| 
 | ||||
| Publishing a signed channel requires discipline: any mistake, such as an | ||||
| unsigned commit or a commit signed by an unauthorized key, will prevent | ||||
| users from pulling from your channel---well, that's the whole point of | ||||
|  | @ -4862,9 +4876,10 @@ pack} command allows you to create @dfn{application bundles} that can be | |||
| easily distributed to users who do not run Guix. | ||||
| 
 | ||||
| @menu | ||||
| * Invoking guix environment::  Setting up development environments. | ||||
| * Invoking guix pack::         Creating software bundles. | ||||
| * The GCC toolchain::          Working with languages supported by GCC. | ||||
| * Invoking guix environment::   Setting up development environments. | ||||
| * Invoking guix pack::          Creating software bundles. | ||||
| * The GCC toolchain::           Working with languages supported by GCC. | ||||
| * Invoking guix git authenticate:: Authenticating Git repositories. | ||||
| @end menu | ||||
| 
 | ||||
| @node Invoking guix environment | ||||
|  | @ -5602,6 +5617,68 @@ The package @code{gfortran-toolchain} provides a complete GCC toolchain | |||
| for Fortran development.  For other languages, please use | ||||
| @samp{guix search gcc toolchain} (@pxref{guix-search,, Invoking guix package}). | ||||
| 
 | ||||
| 
 | ||||
| @node Invoking guix git authenticate | ||||
| @section Invoking @command{guix git authenticate} | ||||
| 
 | ||||
| The @command{guix git authenticate} command authenticates a Git checkout | ||||
| following the same rule as for channels (@pxref{channel-authentication, | ||||
| channel authentication}).  That is, starting from a given commit, it | ||||
| ensures that all subsequent commits are signed by an OpenPGP key whose | ||||
| fingerprint appears in the @file{.guix-authorizations} file of its | ||||
| parent commit(s). | ||||
| 
 | ||||
| You will find this command useful if you maintain a channel.  But in | ||||
| fact, this authentication mechanism is useful in a broader context, so | ||||
| you might want to use it for Git repositories that have nothing to do | ||||
| with Guix. | ||||
| 
 | ||||
| The general syntax is: | ||||
| 
 | ||||
| @example | ||||
| guix git authenticate @var{commit} @var{signer} [@var{options}@dots{}] | ||||
| @end example | ||||
| 
 | ||||
| By default, this command authenticates the Git checkout in the current | ||||
| directory; it outputs nothing and exits with exit code zero on success | ||||
| and non-zero on failure.  @var{commit} above denotes the first commit | ||||
| where authentication takes place, and @var{signer} is the OpenPGP | ||||
| fingerprint of public key used to sign @var{commit}.  Together, they | ||||
| form a ``channel introduction'' (@pxref{channel-authentication, channel | ||||
| introduction}).  The options below allow you to fine-tune the process. | ||||
| 
 | ||||
| @table @code | ||||
| @item --repository=@var{directory} | ||||
| @itemx -r @var{directory} | ||||
| Open the Git repository in @var{directory} instead of the current | ||||
| directory. | ||||
| 
 | ||||
| @item --keyring=@var{reference} | ||||
| @itemx -k @var{reference} | ||||
| Load OpenPGP keyring from @var{reference}, the reference of a branch | ||||
| such as @code{origin/keyring} or @code{my-keyring}.  The branch must | ||||
| contain OpenPGP public keys in @file{.key} files, either in binary form | ||||
| or ``ASCII-armored''.  By default the keyring is loaded from the branch | ||||
| named @code{keyring}. | ||||
| 
 | ||||
| @item --stats | ||||
| Display commit signing statistics upon completion. | ||||
| 
 | ||||
| @item --cache-key=@var{key} | ||||
| Previously-authenticated commits are cached in a file under | ||||
| @file{~/.cache/guix/authentication}.  This option forces the cache to be | ||||
| stored in file @var{key} in that directory. | ||||
| 
 | ||||
| @item --historical-authorizations=@var{file} | ||||
| By default, any commit whose parent commit(s) lack the | ||||
| @file{.guix-authorizations} file is considered inauthentic.  In | ||||
| contrast, this option considers the authorizations in @var{file} for any | ||||
| commit that lacks @file{.guix-authorizations}.  The format of @var{file} | ||||
| is the same as that of @file{.guix-authorizations} | ||||
| (@pxref{channel-authorizations, @file{.guix-authorizations} format}). | ||||
| @end table | ||||
| 
 | ||||
| 
 | ||||
| @c ********************************************************************* | ||||
| @node Programming Interface | ||||
| @chapter Programming Interface | ||||
|  |  | |||
							
								
								
									
										63
									
								
								guix/scripts/git.scm
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								guix/scripts/git.scm
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,63 @@ | |||
| ;;; GNU Guix --- Functional package management for GNU | ||||
| ;;; Copyright © 2020 Ludovic Courtès <ludo@gnu.org> | ||||
| ;;; | ||||
| ;;; This file is part of GNU Guix. | ||||
| ;;; | ||||
| ;;; GNU Guix is free software; you can redistribute it and/or modify it | ||||
| ;;; under the terms of the GNU General Public License as published by | ||||
| ;;; the Free Software Foundation; either version 3 of the License, or (at | ||||
| ;;; your option) any later version. | ||||
| ;;; | ||||
| ;;; GNU Guix is distributed in the hope that it will be useful, but | ||||
| ;;; WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| ;;; GNU General Public License for more details. | ||||
| ;;; | ||||
| ;;; You should have received a copy of the GNU General Public License | ||||
| ;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| (define-module (guix scripts git) | ||||
|   #:use-module (ice-9 match) | ||||
|   #:use-module (guix ui) | ||||
|   #:export (guix-git)) | ||||
| 
 | ||||
| (define (show-help) | ||||
|   (display (G_ "Usage: guix git COMMAND ARGS... | ||||
| Operate on Git repositories.\n")) | ||||
|   (newline) | ||||
|   (display (G_ "The valid values for ACTION are:\n")) | ||||
|   (newline) | ||||
|   (display (G_ "\ | ||||
|    authenticate    verify commit signatures and authorizations\n")) | ||||
|   (newline) | ||||
|   (display (G_ " | ||||
|   -h, --help             display this help and exit")) | ||||
|   (display (G_ " | ||||
|   -V, --version          display version information and exit")) | ||||
|   (newline) | ||||
|   (show-bug-report-information)) | ||||
| 
 | ||||
| (define %sub-commands '("authenticate")) | ||||
| 
 | ||||
| (define (resolve-sub-command name) | ||||
|   (let ((module (resolve-interface | ||||
|                  `(guix scripts git ,(string->symbol name)))) | ||||
|         (proc (string->symbol (string-append "guix-git-" name)))) | ||||
|     (module-ref module proc))) | ||||
| 
 | ||||
| (define (guix-git . args) | ||||
|   (with-error-handling | ||||
|     (match args | ||||
|       (() | ||||
|        (format (current-error-port) | ||||
|                (G_ "guix git: missing sub-command~%"))) | ||||
|       ((or ("-h") ("--help")) | ||||
|        (show-help) | ||||
|        (exit 0)) | ||||
|       ((or ("-V") ("--version")) | ||||
|        (show-version-and-exit "guix git")) | ||||
|       ((sub-command args ...) | ||||
|        (if (member sub-command %sub-commands) | ||||
|            (apply (resolve-sub-command sub-command) args) | ||||
|            (format (current-error-port) | ||||
|                    (G_ "guix git: invalid sub-command~%"))))))) | ||||
							
								
								
									
										179
									
								
								guix/scripts/git/authenticate.scm
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										179
									
								
								guix/scripts/git/authenticate.scm
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,179 @@ | |||
| ;;; GNU Guix --- Functional package management for GNU | ||||
| ;;; Copyright © 2020 Ludovic Courtès <ludo@gnu.org> | ||||
| ;;; | ||||
| ;;; This file is part of GNU Guix. | ||||
| ;;; | ||||
| ;;; GNU Guix is free software; you can redistribute it and/or modify it | ||||
| ;;; under the terms of the GNU General Public License as published by | ||||
| ;;; the Free Software Foundation; either version 3 of the License, or (at | ||||
| ;;; your option) any later version. | ||||
| ;;; | ||||
| ;;; GNU Guix is distributed in the hope that it will be useful, but | ||||
| ;;; WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| ;;; GNU General Public License for more details. | ||||
| ;;; | ||||
| ;;; You should have received a copy of the GNU General Public License | ||||
| ;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| (define-module (guix scripts git authenticate) | ||||
|   #:use-module (git) | ||||
|   #:use-module (guix ui) | ||||
|   #:use-module (guix scripts) | ||||
|   #:use-module (guix git-authenticate) | ||||
|   #:autoload   (guix openpgp) (openpgp-format-fingerprint | ||||
|                                openpgp-public-key-fingerprint) | ||||
|   #:use-module ((guix channels) #:select (openpgp-fingerprint)) | ||||
|   #:use-module ((guix git) #:select (with-git-error-handling)) | ||||
|   #:use-module (guix progress) | ||||
|   #:use-module (guix base64) | ||||
|   #:use-module (srfi srfi-1) | ||||
|   #:use-module (srfi srfi-26) | ||||
|   #:use-module (srfi srfi-37) | ||||
|   #:use-module (ice-9 format) | ||||
|   #:use-module (ice-9 match) | ||||
|   #:export (guix-git-authenticate)) | ||||
| 
 | ||||
| ;;; Commentary: | ||||
| ;;; | ||||
| ;;; Authenticate a Git checkout by reading '.guix-authorizations' files and | ||||
| ;;; following the "authorizations invariant" also used by (guix channels). | ||||
| ;;; | ||||
| ;;; Code: | ||||
| 
 | ||||
| (define %options | ||||
|   ;; Specifications of the command-line options. | ||||
|   (list (option '(#\h "help") #f #f | ||||
|                 (lambda args | ||||
|                   (show-help) | ||||
|                   (exit 0))) | ||||
|         (option '(#\V "version") #f #f | ||||
|                 (lambda args | ||||
|                   (show-version-and-exit "guix git authenticate"))) | ||||
| 
 | ||||
|         (option '(#\r "repository") #t #f | ||||
|                 (lambda (opt name arg result) | ||||
|                   (alist-cons 'directory arg result))) | ||||
|         (option '(#\e "end") #t #f | ||||
|                 (lambda (opt name arg result) | ||||
|                   (alist-cons 'end-commit (string->oid arg) result))) | ||||
|         (option '(#\k "keyring") #t #f | ||||
|                 (lambda (opt name arg result) | ||||
|                   (alist-cons 'keyring-reference arg result))) | ||||
|         (option '("cache-key") #t #f | ||||
|                 (lambda (opt name arg result) | ||||
|                   (alist-cons 'cache-key arg result))) | ||||
|         (option '("historical-authorizations") #t #f | ||||
|                 (lambda (opt name arg result) | ||||
|                   (alist-cons 'historical-authorizations arg | ||||
|                               result))) | ||||
|         (option '("stats") #f #f | ||||
|                 (lambda (opt name arg result) | ||||
|                   (alist-cons 'show-stats? #t result))))) | ||||
| 
 | ||||
| (define %default-options | ||||
|   '((directory . ".") | ||||
|     (keyring-reference . "keyring"))) | ||||
| 
 | ||||
| (define (show-stats stats) | ||||
|   "Display STATS, an alist containing commit signing stats as returned by | ||||
| 'authenticate-repository'." | ||||
|   (format #t (G_ "Signing statistics:~%")) | ||||
|   (for-each (match-lambda | ||||
|               ((signer . count) | ||||
|                (format #t "  ~a ~10d~%" | ||||
|                        (openpgp-format-fingerprint | ||||
|                         (openpgp-public-key-fingerprint signer)) | ||||
|                        count))) | ||||
|             (sort stats | ||||
|                   (match-lambda* | ||||
|                     (((_ . count1) (_ . count2)) | ||||
|                      (> count1 count2)))))) | ||||
| 
 | ||||
| (define (show-help) | ||||
|   (display (G_ "Usage: guix git authenticate COMMIT SIGNER [OPTIONS...] | ||||
| Authenticate the given Git checkout using COMMIT/SIGNER as its introduction.\n")) | ||||
|   (display (G_ " | ||||
|   -r, --repository=DIRECTORY | ||||
|                          open the Git repository at DIRECTORY")) | ||||
|   (display (G_ " | ||||
|   -k, --keyring=REFERENCE | ||||
|                          load keyring from REFERENCE, a Git branch")) | ||||
|   (display (G_ " | ||||
|       --stats            display commit signing statistics upon completion")) | ||||
|   (display (G_ " | ||||
|       --cache-key=KEY    cache authenticated commits under KEY")) | ||||
|   (display (G_ " | ||||
|       --historical-authorizations=FILE | ||||
|                          read historical authorizations from FILE")) | ||||
|   (newline) | ||||
|   (display (G_ " | ||||
|   -h, --help             display this help and exit")) | ||||
|   (display (G_ " | ||||
|   -V, --version          display version information and exit")) | ||||
|   (newline) | ||||
|   (show-bug-report-information)) | ||||
| 
 | ||||
|  | ||||
| ;;; | ||||
| ;;; Entry point. | ||||
| ;;; | ||||
| 
 | ||||
| (define (guix-git-authenticate . args) | ||||
|   (define options | ||||
|     (parse-command-line args %options (list %default-options) | ||||
|                         #:build-options? #f)) | ||||
| 
 | ||||
|   (define (command-line-arguments lst) | ||||
|     (reverse (filter-map (match-lambda | ||||
|                            (('argument . arg) arg) | ||||
|                            (_ #f)) | ||||
|                          lst))) | ||||
| 
 | ||||
|   (define commit-short-id | ||||
|     (compose (cut string-take <> 7) oid->string commit-id)) | ||||
| 
 | ||||
|   (define (make-reporter start-commit end-commit commits) | ||||
|     (format (current-error-port) | ||||
|             (G_ "Authenticating commits ~a to ~a (~h new \ | ||||
| commits)...~%") | ||||
|             (commit-short-id start-commit) | ||||
|             (commit-short-id end-commit) | ||||
|             (length commits)) | ||||
| 
 | ||||
|     (if (isatty? (current-error-port)) | ||||
|         (progress-reporter/bar (length commits)) | ||||
|         progress-reporter/silent)) | ||||
| 
 | ||||
|   (with-error-handling | ||||
|     (with-git-error-handling | ||||
|      (match (command-line-arguments options) | ||||
|        ((commit signer) | ||||
|         (let* ((directory   (assoc-ref options 'directory)) | ||||
|                (show-stats? (assoc-ref options 'show-stats?)) | ||||
|                (keyring     (assoc-ref options 'keyring-reference)) | ||||
|                (repository  (repository-open directory)) | ||||
|                (end         (match (assoc-ref options 'end-commit) | ||||
|                               (#f  (reference-target | ||||
|                                     (repository-head repository))) | ||||
|                               (oid oid))) | ||||
|                (history     (match (assoc-ref options 'historical-authorizations) | ||||
|                               (#f '()) | ||||
|                               (file (call-with-input-file file | ||||
|                                       read-authorizations)))) | ||||
|                (cache-key   (or (assoc-ref options 'cache-key) | ||||
|                                 (repository-cache-key repository)))) | ||||
|           (define stats | ||||
|             (authenticate-repository repository (string->oid commit) | ||||
|                                      (openpgp-fingerprint signer) | ||||
|                                      #:end end | ||||
|                                      #:keyring-reference keyring | ||||
|                                      #:historical-authorizations history | ||||
|                                      #:cache-key cache-key | ||||
|                                      #:make-reporter make-reporter)) | ||||
| 
 | ||||
|           (when (and show-stats? (not (null? stats))) | ||||
|             (show-stats stats)))) | ||||
|        (_ | ||||
|         (leave (G_ "wrong number of arguments; \ | ||||
| expected COMMIT and SIGNER~%"))))))) | ||||
|  | @ -53,6 +53,8 @@ guix/scripts/upgrade.scm | |||
| guix/scripts/search.scm | ||||
| guix/scripts/show.scm | ||||
| guix/scripts/gc.scm | ||||
| guix/scripts/git.scm | ||||
| guix/scripts/git/authenticate.scm | ||||
| guix/scripts/hash.scm | ||||
| guix/scripts/import.scm | ||||
| guix/scripts/import/cran.scm | ||||
|  |  | |||
							
								
								
									
										56
									
								
								tests/guix-git-authenticate.sh
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								tests/guix-git-authenticate.sh
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,56 @@ | |||
| # GNU Guix --- Functional package management for GNU | ||||
| # Copyright © 2020 Ludovic Courtès <ludo@gnu.org> | ||||
| # | ||||
| # This file is part of GNU Guix. | ||||
| # | ||||
| # GNU Guix is free software; you can redistribute it and/or modify it | ||||
| # under the terms of the GNU General Public License as published by | ||||
| # the Free Software Foundation; either version 3 of the License, or (at | ||||
| # your option) any later version. | ||||
| # | ||||
| # GNU Guix is distributed in the hope that it will be useful, but | ||||
| # WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| # GNU General Public License for more details. | ||||
| # | ||||
| # You should have received a copy of the GNU General Public License | ||||
| # along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| # | ||||
| # Test the 'guix git authenticate' command-line utility. | ||||
| # | ||||
| 
 | ||||
| # Skip if we're not in a Git checkout. | ||||
| [ -d "$abs_top_srcdir/.git" ] || exit 77 | ||||
| 
 | ||||
| # Skip if there's no 'keyring' branch. | ||||
| guile -c '(use-modules (git)) | ||||
|   (member "refs/heads/keyring" (branch-list (repository-open ".")))' || \ | ||||
|     exit 77 | ||||
| 
 | ||||
| # Keep in sync with '%default-channels' in (guix channels)! | ||||
| intro_commit="9edb3f66fd807b096b48283debdcddccfea34bad" | ||||
| intro_signer="BBB0 2DDF 2CEA F6A8 0D1D  E643 A2A0 6DF2 A33A 54FA" | ||||
| 
 | ||||
| cache_key="test-$$" | ||||
| 
 | ||||
| guix git authenticate "$intro_commit" "$intro_signer"	\ | ||||
|      --cache-key="$cache_key" --stats			\ | ||||
|      --end=9549f0283a78fe36f2d4ff2a04ef8ad6b0c02604 | ||||
| 
 | ||||
| rm "$XDG_CACHE_HOME/guix/authentication/$cache_key" | ||||
| 
 | ||||
| # Commit and signer of the 'v1.0.0' tag. | ||||
| v1_0_0_commit="6298c3ffd9654d3231a6f25390b056483e8f407c" | ||||
| v1_0_0_signer="3CE4 6455 8A84 FDC6 9DB4  0CFB 090B 1199 3D9A EBB5" # civodul | ||||
| v1_0_1_commit="d68de958b60426798ed62797ff7c96c327a672ac" | ||||
| 
 | ||||
| # This should fail because these commits lack '.guix-authorizations'. | ||||
| if guix git authenticate "$v1_0_0_commit" "$v1_0_0_signer" \ | ||||
| 	--cache-key="$cache_key" --end="$v1_0_1_commit"; | ||||
| then false; else true; fi | ||||
| 
 | ||||
| # This should work thanks to '--historical-authorizations'. | ||||
| guix git authenticate "$v1_0_0_commit" "$v1_0_0_signer" 	\ | ||||
|      --cache-key="$cache_key" --end="$v1_0_1_commit" --stats	\ | ||||
|      --historical-authorizations="$abs_top_srcdir/etc/historical-authorizations" | ||||
		Reference in a new issue