summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLudovic Courtès <ludo@gnu.org>2020-07-05 23:40:29 +0200
committerLudovic Courtès <ludo@gnu.org>2020-07-11 12:33:01 +0200
commita98712785e0b042a290420fd74e5a4a5da4fc68f (patch)
treee2bf598486e5847c7c8c36c1ef711a9410cd587e
parent69db2993b58579e7b0990f5c35f9a289844f22e5 (diff)
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'.
-rw-r--r--Makefile.am3
-rw-r--r--doc/guix.texi83
-rw-r--r--guix/scripts/git.scm63
-rw-r--r--guix/scripts/git/authenticate.scm179
-rw-r--r--po/guix/POTFILES.in2
-rw-r--r--tests/guix-git-authenticate.sh56
6 files changed, 383 insertions, 3 deletions
diff --git a/Makefile.am b/Makefile.am
index 47699351b91..20bfaba88bc 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -281,6 +281,8 @@ MODULES = \
281 guix/scripts/publish.scm \ 281 guix/scripts/publish.scm \
282 guix/scripts/edit.scm \ 282 guix/scripts/edit.scm \
283 guix/scripts/size.scm \ 283 guix/scripts/size.scm \
284 guix/scripts/git.scm \
285 guix/scripts/git/authenticate.scm \
284 guix/scripts/graph.scm \ 286 guix/scripts/graph.scm \
285 guix/scripts/weather.scm \ 287 guix/scripts/weather.scm \
286 guix/scripts/container.scm \ 288 guix/scripts/container.scm \
@@ -463,6 +465,7 @@ SH_TESTS = \
463 tests/guix-build-branch.sh \ 465 tests/guix-build-branch.sh \
464 tests/guix-download.sh \ 466 tests/guix-download.sh \
465 tests/guix-gc.sh \ 467 tests/guix-gc.sh \
468 tests/guix-git-authenticate.sh \
466 tests/guix-hash.sh \ 469 tests/guix-hash.sh \
467 tests/guix-pack.sh \ 470 tests/guix-pack.sh \
468 tests/guix-pack-localstatedir.sh \ 471 tests/guix-pack-localstatedir.sh \
diff --git a/doc/guix.texi b/doc/guix.texi
index 992bc303bbc..17338ed764b 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -3981,6 +3981,7 @@ Before that, some security considerations.
3981 3981
3982@subsection Channel Authentication 3982@subsection Channel Authentication
3983 3983
3984@anchor{channel-authentication}
3984@cindex authentication, of channel code 3985@cindex authentication, of channel code
3985The @command{guix pull} and @command{guix time-machine} commands 3986The @command{guix pull} and @command{guix time-machine} commands
3986@dfn{authenticate} the code retrieved from channels: they make sure each 3987@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:
4200@cindex channel authorizations 4201@cindex channel authorizations
4201@subsection Specifying Channel Authorizations 4202@subsection Specifying Channel Authorizations
4202 4203
4204@anchor{channel-authorizations}
4203As we saw above, Guix ensures the source code it pulls from channels 4205As we saw above, Guix ensures the source code it pulls from channels
4204comes from authorized developers. As a channel author, you need to 4206comes from authorized developers. As a channel author, you need to
4205specify the list of authorized developers in the 4207specify the list of authorized developers in the
@@ -4259,6 +4261,18 @@ pair---i.e., the commit that introduced @file{.guix-authorizations}, and
4259the fingerprint of the OpenPGP used to sign it. 4261the fingerprint of the OpenPGP used to sign it.
4260@end enumerate 4262@end enumerate
4261 4263
4264Before pushing to your public Git repository, you can run @command{guix
4265git-authenticate} to verify that you did sign all the commits you are
4266about to push with an authorized key:
4267
4268@example
4269guix git authenticate @var{commit} @var{signer}
4270@end example
4271
4272@noindent
4273where @var{commit} and @var{signer} are your channel introduction.
4274@xref{Invoking guix git authenticate}, for details.
4275
4262Publishing a signed channel requires discipline: any mistake, such as an 4276Publishing a signed channel requires discipline: any mistake, such as an
4263unsigned commit or a commit signed by an unauthorized key, will prevent 4277unsigned commit or a commit signed by an unauthorized key, will prevent
4264users from pulling from your channel---well, that's the whole point of 4278users 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
4862easily distributed to users who do not run Guix. 4876easily distributed to users who do not run Guix.
4863 4877
4864@menu 4878@menu
4865* Invoking guix environment:: Setting up development environments. 4879* Invoking guix environment:: Setting up development environments.
4866* Invoking guix pack:: Creating software bundles. 4880* Invoking guix pack:: Creating software bundles.
4867* The GCC toolchain:: Working with languages supported by GCC. 4881* The GCC toolchain:: Working with languages supported by GCC.
4882* Invoking guix git authenticate:: Authenticating Git repositories.
4868@end menu 4883@end menu
4869 4884
4870@node Invoking guix environment 4885@node Invoking guix environment
@@ -5602,6 +5617,68 @@ The package @code{gfortran-toolchain} provides a complete GCC toolchain
5602for Fortran development. For other languages, please use 5617for Fortran development. For other languages, please use
5603@samp{guix search gcc toolchain} (@pxref{guix-search,, Invoking guix package}). 5618@samp{guix search gcc toolchain} (@pxref{guix-search,, Invoking guix package}).
5604 5619
5620
5621@node Invoking guix git authenticate
5622@section Invoking @command{guix git authenticate}
5623
5624The @command{guix git authenticate} command authenticates a Git checkout
5625following the same rule as for channels (@pxref{channel-authentication,
5626channel authentication}). That is, starting from a given commit, it
5627ensures that all subsequent commits are signed by an OpenPGP key whose
5628fingerprint appears in the @file{.guix-authorizations} file of its
5629parent commit(s).
5630
5631You will find this command useful if you maintain a channel. But in
5632fact, this authentication mechanism is useful in a broader context, so
5633you might want to use it for Git repositories that have nothing to do
5634with Guix.
5635
5636The general syntax is:
5637
5638@example
5639guix git authenticate @var{commit} @var{signer} [@var{options}@dots{}]
5640@end example
5641
5642By default, this command authenticates the Git checkout in the current
5643directory; it outputs nothing and exits with exit code zero on success
5644and non-zero on failure. @var{commit} above denotes the first commit
5645where authentication takes place, and @var{signer} is the OpenPGP
5646fingerprint of public key used to sign @var{commit}. Together, they
5647form a ``channel introduction'' (@pxref{channel-authentication, channel
5648introduction}). The options below allow you to fine-tune the process.
5649
5650@table @code
5651@item --repository=@var{directory}
5652@itemx -r @var{directory}
5653Open the Git repository in @var{directory} instead of the current
5654directory.
5655
5656@item --keyring=@var{reference}
5657@itemx -k @var{reference}
5658Load OpenPGP keyring from @var{reference}, the reference of a branch
5659such as @code{origin/keyring} or @code{my-keyring}. The branch must
5660contain OpenPGP public keys in @file{.key} files, either in binary form
5661or ``ASCII-armored''. By default the keyring is loaded from the branch
5662named @code{keyring}.
5663
5664@item --stats
5665Display commit signing statistics upon completion.
5666
5667@item --cache-key=@var{key}
5668Previously-authenticated commits are cached in a file under
5669@file{~/.cache/guix/authentication}. This option forces the cache to be
5670stored in file @var{key} in that directory.
5671
5672@item --historical-authorizations=@var{file}
5673By default, any commit whose parent commit(s) lack the
5674@file{.guix-authorizations} file is considered inauthentic. In
5675contrast, this option considers the authorizations in @var{file} for any
5676commit that lacks @file{.guix-authorizations}. The format of @var{file}
5677is the same as that of @file{.guix-authorizations}
5678(@pxref{channel-authorizations, @file{.guix-authorizations} format}).
5679@end table
5680
5681
5605@c ********************************************************************* 5682@c *********************************************************************
5606@node Programming Interface 5683@node Programming Interface
5607@chapter Programming Interface 5684@chapter Programming Interface
diff --git a/guix/scripts/git.scm b/guix/scripts/git.scm
new file mode 100644
index 00000000000..bc829cbe998
--- /dev/null
+++ b/guix/scripts/git.scm
@@ -0,0 +1,63 @@
1;;; GNU Guix --- Functional package management for GNU
2;;; Copyright © 2020 Ludovic Courtès <ludo@gnu.org>
3;;;
4;;; This file is part of GNU Guix.
5;;;
6;;; GNU Guix is free software; you can redistribute it and/or modify it
7;;; under the terms of the GNU General Public License as published by
8;;; the Free Software Foundation; either version 3 of the License, or (at
9;;; your option) any later version.
10;;;
11;;; GNU Guix is distributed in the hope that it will be useful, but
12;;; WITHOUT ANY WARRANTY; without even the implied warranty of
13;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14;;; GNU General Public License for more details.
15;;;
16;;; You should have received a copy of the GNU General Public License
17;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
18
19(define-module (guix scripts git)
20 #:use-module (ice-9 match)
21 #:use-module (guix ui)
22 #:export (guix-git))
23
24(define (show-help)
25 (display (G_ "Usage: guix git COMMAND ARGS...
26Operate on Git repositories.\n"))
27 (newline)
28 (display (G_ "The valid values for ACTION are:\n"))
29 (newline)
30 (display (G_ "\
31 authenticate verify commit signatures and authorizations\n"))
32 (newline)
33 (display (G_ "
34 -h, --help display this help and exit"))
35 (display (G_ "
36 -V, --version display version information and exit"))
37 (newline)
38 (show-bug-report-information))
39
40(define %sub-commands '("authenticate"))
41
42(define (resolve-sub-command name)
43 (let ((module (resolve-interface
44 `(guix scripts git ,(string->symbol name))))
45 (proc (string->symbol (string-append "guix-git-" name))))
46 (module-ref module proc)))
47
48(define (guix-git . args)
49 (with-error-handling
50 (match args
51 (()
52 (format (current-error-port)
53 (G_ "guix git: missing sub-command~%")))
54 ((or ("-h") ("--help"))
55 (show-help)
56 (exit 0))
57 ((or ("-V") ("--version"))
58 (show-version-and-exit "guix git"))
59 ((sub-command args ...)
60 (if (member sub-command %sub-commands)
61 (apply (resolve-sub-command sub-command) args)
62 (format (current-error-port)
63 (G_ "guix git: invalid sub-command~%")))))))
diff --git a/guix/scripts/git/authenticate.scm b/guix/scripts/git/authenticate.scm
new file mode 100644
index 00000000000..5f5d423f281
--- /dev/null
+++ b/guix/scripts/git/authenticate.scm
@@ -0,0 +1,179 @@
1;;; GNU Guix --- Functional package management for GNU
2;;; Copyright © 2020 Ludovic Courtès <ludo@gnu.org>
3;;;
4;;; This file is part of GNU Guix.
5;;;
6;;; GNU Guix is free software; you can redistribute it and/or modify it
7;;; under the terms of the GNU General Public License as published by
8;;; the Free Software Foundation; either version 3 of the License, or (at
9;;; your option) any later version.
10;;;
11;;; GNU Guix is distributed in the hope that it will be useful, but
12;;; WITHOUT ANY WARRANTY; without even the implied warranty of
13;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14;;; GNU General Public License for more details.
15;;;
16;;; You should have received a copy of the GNU General Public License
17;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
18
19(define-module (guix scripts git authenticate)
20 #:use-module (git)
21 #:use-module (guix ui)
22 #:use-module (guix scripts)
23 #:use-module (guix git-authenticate)
24 #:autoload (guix openpgp) (openpgp-format-fingerprint
25 openpgp-public-key-fingerprint)
26 #:use-module ((guix channels) #:select (openpgp-fingerprint))
27 #:use-module ((guix git) #:select (with-git-error-handling))
28 #:use-module (guix progress)
29 #:use-module (guix base64)
30 #:use-module (srfi srfi-1)
31 #:use-module (srfi srfi-26)
32 #:use-module (srfi srfi-37)
33 #:use-module (ice-9 format)
34 #:use-module (ice-9 match)
35 #:export (guix-git-authenticate))
36
37;;; Commentary:
38;;;
39;;; Authenticate a Git checkout by reading '.guix-authorizations' files and
40;;; following the "authorizations invariant" also used by (guix channels).
41;;;
42;;; Code:
43
44(define %options
45 ;; Specifications of the command-line options.
46 (list (option '(#\h "help") #f #f
47 (lambda args
48 (show-help)
49 (exit 0)))
50 (option '(#\V "version") #f #f
51 (lambda args
52 (show-version-and-exit "guix git authenticate")))
53
54 (option '(#\r "repository") #t #f
55 (lambda (opt name arg result)
56 (alist-cons 'directory arg result)))
57 (option '(#\e "end") #t #f
58 (lambda (opt name arg result)
59 (alist-cons 'end-commit (string->oid arg) result)))
60 (option '(#\k "keyring") #t #f
61 (lambda (opt name arg result)
62 (alist-cons 'keyring-reference arg result)))
63 (option '("cache-key") #t #f
64 (lambda (opt name arg result)
65 (alist-cons 'cache-key arg result)))
66 (option '("historical-authorizations") #t #f
67 (lambda (opt name arg result)
68 (alist-cons 'historical-authorizations arg
69 result)))
70 (option '("stats") #f #f
71 (lambda (opt name arg result)
72 (alist-cons 'show-stats? #t result)))))
73
74(define %default-options
75 '((directory . ".")
76 (keyring-reference . "keyring")))
77
78(define (show-stats stats)
79 "Display STATS, an alist containing commit signing stats as returned by
80'authenticate-repository'."
81 (format #t (G_ "Signing statistics:~%"))
82 (for-each (match-lambda
83 ((signer . count)
84 (format #t " ~a ~10d~%"
85 (openpgp-format-fingerprint
86 (openpgp-public-key-fingerprint signer))
87 count)))
88 (sort stats
89 (match-lambda*
90 (((_ . count1) (_ . count2))
91 (> count1 count2))))))
92
93(define (show-help)
94 (display (G_ "Usage: guix git authenticate COMMIT SIGNER [OPTIONS...]
95Authenticate the given Git checkout using COMMIT/SIGNER as its introduction.\n"))
96 (display (G_ "
97 -r, --repository=DIRECTORY
98 open the Git repository at DIRECTORY"))
99 (display (G_ "
100 -k, --keyring=REFERENCE
101 load keyring from REFERENCE, a Git branch"))
102 (display (G_ "
103 --stats display commit signing statistics upon completion"))
104 (display (G_ "
105 --cache-key=KEY cache authenticated commits under KEY"))
106 (display (G_ "
107 --historical-authorizations=FILE
108 read historical authorizations from FILE"))
109 (newline)
110 (display (G_ "
111 -h, --help display this help and exit"))
112 (display (G_ "
113 -V, --version display version information and exit"))
114 (newline)
115 (show-bug-report-information))
116
117
118;;;
119;;; Entry point.
120;;;
121
122(define (guix-git-authenticate . args)
123 (define options
124 (parse-command-line args %options (list %default-options)
125 #:build-options? #f))
126
127 (define (command-line-arguments lst)
128 (reverse (filter-map (match-lambda
129 (('argument . arg) arg)
130 (_ #f))
131 lst)))
132
133 (define commit-short-id
134 (compose (cut string-take <> 7) oid->string commit-id))
135
136 (define (make-reporter start-commit end-commit commits)
137 (format (current-error-port)
138 (G_ "Authenticating commits ~a to ~a (~h new \
139commits)...~%")
140 (commit-short-id start-commit)
141 (commit-short-id end-commit)
142 (length commits))
143
144 (if (isatty? (current-error-port))
145 (progress-reporter/bar (length commits))
146 progress-reporter/silent))
147
148 (with-error-handling
149 (with-git-error-handling
150 (match (command-line-arguments options)
151 ((commit signer)
152 (let* ((directory (assoc-ref options 'directory))
153 (show-stats? (assoc-ref options 'show-stats?))
154 (keyring (assoc-ref options 'keyring-reference))
155 (repository (repository-open directory))
156 (end (match (assoc-ref options 'end-commit)
157 (#f (reference-target
158 (repository-head repository)))
159 (oid oid)))
160 (history (match (assoc-ref options 'historical-authorizations)
161 (#f '())
162 (file (call-with-input-file file
163 read-authorizations))))
164 (cache-key (or (assoc-ref options 'cache-key)
165 (repository-cache-key repository))))
166 (define stats
167 (authenticate-repository repository (string->oid commit)
168 (openpgp-fingerprint signer)
169 #:end end
170 #:keyring-reference keyring
171 #:historical-authorizations history
172 #:cache-key cache-key
173 #:make-reporter make-reporter))
174
175 (when (and show-stats? (not (null? stats)))
176 (show-stats stats))))
177 (_
178 (leave (G_ "wrong number of arguments; \
179expected COMMIT and SIGNER~%")))))))
diff --git a/po/guix/POTFILES.in b/po/guix/POTFILES.in
index 62b3cbf4e42..f4d020782c6 100644
--- a/po/guix/POTFILES.in
+++ b/po/guix/POTFILES.in
@@ -53,6 +53,8 @@ guix/scripts/upgrade.scm
53guix/scripts/search.scm 53guix/scripts/search.scm
54guix/scripts/show.scm 54guix/scripts/show.scm
55guix/scripts/gc.scm 55guix/scripts/gc.scm
56guix/scripts/git.scm
57guix/scripts/git/authenticate.scm
56guix/scripts/hash.scm 58guix/scripts/hash.scm
57guix/scripts/import.scm 59guix/scripts/import.scm
58guix/scripts/import/cran.scm 60guix/scripts/import/cran.scm
diff --git a/tests/guix-git-authenticate.sh b/tests/guix-git-authenticate.sh
new file mode 100644
index 00000000000..1c76e240b54
--- /dev/null
+++ b/tests/guix-git-authenticate.sh
@@ -0,0 +1,56 @@
1# GNU Guix --- Functional package management for GNU
2# Copyright © 2020 Ludovic Courtès <ludo@gnu.org>
3#
4# This file is part of GNU Guix.
5#
6# GNU Guix is free software; you can redistribute it and/or modify it
7# under the terms of the GNU General Public License as published by
8# the Free Software Foundation; either version 3 of the License, or (at
9# your option) any later version.
10#
11# GNU Guix is distributed in the hope that it will be useful, but
12# WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
18
19#
20# Test the 'guix git authenticate' command-line utility.
21#
22
23# Skip if we're not in a Git checkout.
24[ -d "$abs_top_srcdir/.git" ] || exit 77
25
26# Skip if there's no 'keyring' branch.
27guile -c '(use-modules (git))
28 (member "refs/heads/keyring" (branch-list (repository-open ".")))' || \
29 exit 77
30
31# Keep in sync with '%default-channels' in (guix channels)!
32intro_commit="9edb3f66fd807b096b48283debdcddccfea34bad"
33intro_signer="BBB0 2DDF 2CEA F6A8 0D1D E643 A2A0 6DF2 A33A 54FA"
34
35cache_key="test-$$"
36
37guix git authenticate "$intro_commit" "$intro_signer" \
38 --cache-key="$cache_key" --stats \
39 --end=9549f0283a78fe36f2d4ff2a04ef8ad6b0c02604
40
41rm "$XDG_CACHE_HOME/guix/authentication/$cache_key"
42
43# Commit and signer of the 'v1.0.0' tag.
44v1_0_0_commit="6298c3ffd9654d3231a6f25390b056483e8f407c"
45v1_0_0_signer="3CE4 6455 8A84 FDC6 9DB4 0CFB 090B 1199 3D9A EBB5" # civodul
46v1_0_1_commit="d68de958b60426798ed62797ff7c96c327a672ac"
47
48# This should fail because these commits lack '.guix-authorizations'.
49if guix git authenticate "$v1_0_0_commit" "$v1_0_0_signer" \
50 --cache-key="$cache_key" --end="$v1_0_1_commit";
51then false; else true; fi
52
53# This should work thanks to '--historical-authorizations'.
54guix git authenticate "$v1_0_0_commit" "$v1_0_0_signer" \
55 --cache-key="$cache_key" --end="$v1_0_1_commit" --stats \
56 --historical-authorizations="$abs_top_srcdir/etc/historical-authorizations"