mirror of
https://git.in.rschanz.org/ryan77627/guix.git
synced 2024-12-24 21:38:07 -05:00
channels: Make channel introductions public.
* guix/channels.scm (<channel-introduction>): Rename constructor to '%make-channel-introduction'. (make-channel-introduction): New procedure. * tests/channels.scm ("authenticate-channel, wrong first commit signer") ("authenticate-channel, .guix-authorizations"): Use 'make-channel-introduction' without '@@' and without third argument. * doc/guix.texi (Channels)[Channel Authentication, Specifying Channel Authorizations]: New subsections.
This commit is contained in:
parent
6577682a6c
commit
8b7d982e6a
3 changed files with 125 additions and 11 deletions
112
doc/guix.texi
112
doc/guix.texi
|
@ -3975,8 +3975,47 @@ deploys Guix itself from the official GNU@tie{}Guix repository. This can be
|
||||||
customized by defining @dfn{channels} in the
|
customized by defining @dfn{channels} in the
|
||||||
@file{~/.config/guix/channels.scm} file. A channel specifies a URL and branch
|
@file{~/.config/guix/channels.scm} file. A channel specifies a URL and branch
|
||||||
of a Git repository to be deployed, and @command{guix pull} can be instructed
|
of a Git repository to be deployed, and @command{guix pull} can be instructed
|
||||||
to pull from one or more channels. In other words, channels can be used to
|
to pull from one or more channels. In other words, channels can be used
|
||||||
@emph{customize} and to @emph{extend} Guix, as we will see below.
|
to @emph{customize} and to @emph{extend} Guix, as we will see below.
|
||||||
|
Before that, some security considerations.
|
||||||
|
|
||||||
|
@subsection 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
|
||||||
|
commit that is fetched is signed by an authorized developer. The goal
|
||||||
|
is to protect from unauthorized modifications to the channel that would
|
||||||
|
lead users to run malicious code.
|
||||||
|
|
||||||
|
As a user, you must provide a @dfn{channel introduction} in your
|
||||||
|
channels file so that Guix knows how to authenticate its first commit.
|
||||||
|
A channel specification, including its introduction, looks something
|
||||||
|
along these lines:
|
||||||
|
|
||||||
|
@lisp
|
||||||
|
(channel
|
||||||
|
(name 'my-channel)
|
||||||
|
(url "https://example.org/my-channel.git")
|
||||||
|
(introduction
|
||||||
|
(make-channel-introduction
|
||||||
|
"6f0d8cc0d88abb59c324b2990bfee2876016bb86"
|
||||||
|
(openpgp-fingerprint
|
||||||
|
"CABB A931 C0FF EEC6 900D 0CFB 090B 1199 3D9A EBB5"))))
|
||||||
|
@end lisp
|
||||||
|
|
||||||
|
The specification above shows the name and URL of the channel. The call
|
||||||
|
to @code{make-channel-introduction} above specifies that authentication
|
||||||
|
of this channel starts at commit @code{6f0d8cc@dots{}}, which is signed
|
||||||
|
by the OpenPGP key with fingerprint @code{CABB A931@dots{}}.
|
||||||
|
|
||||||
|
For the main channel, called @code{guix}, you automatically get that
|
||||||
|
information from your Guix installation. For other channels, include
|
||||||
|
the channel introduction provided by the channel authors in your
|
||||||
|
@file{channels.scm} file. Make sure you retrieve the channel
|
||||||
|
introduction from a trusted source since that is the root of your trust.
|
||||||
|
|
||||||
|
If you're curious about the authentication mechanics, read on!
|
||||||
|
|
||||||
@subsection Using a Custom Guix Channel
|
@subsection Using a Custom Guix Channel
|
||||||
|
|
||||||
|
@ -4150,6 +4189,75 @@ add a meta-data file @file{.guix-channel} that contains:
|
||||||
(directory "guix"))
|
(directory "guix"))
|
||||||
@end lisp
|
@end lisp
|
||||||
|
|
||||||
|
@cindex channel authorizations
|
||||||
|
@subsection Specifying 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
|
||||||
|
@file{.guix-authorizations} file in the channel's Git repository. The
|
||||||
|
authentication rule is simple: each commit must be signed by a key
|
||||||
|
listed in the @file{.guix-authorizations} file of its parent
|
||||||
|
commit(s)@footnote{Git commits form a @dfn{directed acyclic graph}
|
||||||
|
(DAG). Each commit can have zero or more parents; ``regular'' commits
|
||||||
|
have one parent and merge commits have two parent commits. Read
|
||||||
|
@uref{https://eagain.net/articles/git-for-computer-scientists/, @i{Git
|
||||||
|
for Computer Scientists}} for a great overview.} The
|
||||||
|
@file{.guix-authorizations} file looks like this:
|
||||||
|
|
||||||
|
@lisp
|
||||||
|
;; Example '.guix-authorizations' file.
|
||||||
|
|
||||||
|
(authorizations
|
||||||
|
(version 0) ;current file format version
|
||||||
|
|
||||||
|
(("AD17 A21E F8AE D8F1 CC02 DBD9 F8AE D8F1 765C 61E3"
|
||||||
|
(name "alice"))
|
||||||
|
("2A39 3FFF 68F4 EF7A 3D29 12AF 68F4 EF7A 22FB B2D5"
|
||||||
|
(name "bob"))
|
||||||
|
("CABB A931 C0FF EEC6 900D 0CFB 090B 1199 3D9A EBB5"
|
||||||
|
(name "charlie"))))
|
||||||
|
@end lisp
|
||||||
|
|
||||||
|
Each fingerprint is followed by optional key/value pairs, as in the
|
||||||
|
example above. Currently these key/value pairs are ignored.
|
||||||
|
|
||||||
|
This authentication rule creates a chicken-and-egg issue: how do we
|
||||||
|
authenticate the first commit? Related to that: how do we deal with
|
||||||
|
channels whose repository history contains unsigned commits and lack
|
||||||
|
@file{.guix-authorizations}? And how do we fork existing channels?
|
||||||
|
|
||||||
|
@cindex channel introduction
|
||||||
|
Channel introductions answer these questions by describing the first
|
||||||
|
commit of a channel that should be authenticated. The first time a
|
||||||
|
channel is fetched with @command{guix pull} or @command{guix
|
||||||
|
time-machine}, the command looks up the introductory commit and verifies
|
||||||
|
that it is signed by the specified OpenPGP key. From then on, it
|
||||||
|
authenticates commits according to the rule above.
|
||||||
|
|
||||||
|
To summarize, as the author of a channel, there are two things you have
|
||||||
|
to do to allow users to authenticate your code:
|
||||||
|
|
||||||
|
@enumerate
|
||||||
|
@item
|
||||||
|
Introduce an initial @file{.guix-authorizations} in the channel's
|
||||||
|
repository. Do that in a signed commit (@pxref{Commit Access}, for
|
||||||
|
information on how to sign Git commits.)
|
||||||
|
|
||||||
|
@item
|
||||||
|
Advertise the channel introduction, for instance on your channel's web
|
||||||
|
page. The channel introduction, as we saw above, is the commit/key
|
||||||
|
pair---i.e., the commit that introduced @file{.guix-authorizations}, and
|
||||||
|
the fingerprint of the OpenPGP used to sign it.
|
||||||
|
@end enumerate
|
||||||
|
|
||||||
|
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
|
||||||
|
authentication! Pay attention to merges in particular: merge commits
|
||||||
|
are considered authentic if and only if they are signed by a key present
|
||||||
|
in the @file{.guix-authorizations} file of @emph{both} branches.
|
||||||
|
|
||||||
@cindex primary URL, channels
|
@cindex primary URL, channels
|
||||||
@subsection Primary URL
|
@subsection Primary URL
|
||||||
|
|
||||||
|
|
|
@ -69,7 +69,9 @@ (define-module (guix channels)
|
||||||
channel-location
|
channel-location
|
||||||
|
|
||||||
channel-introduction?
|
channel-introduction?
|
||||||
;; <channel-introduction> accessors purposefully omitted for now.
|
make-channel-introduction
|
||||||
|
channel-introduction-first-signed-commit
|
||||||
|
channel-introduction-first-commit-signer
|
||||||
|
|
||||||
openpgp-fingerprint->bytevector
|
openpgp-fingerprint->bytevector
|
||||||
openpgp-fingerprint
|
openpgp-fingerprint
|
||||||
|
@ -130,13 +132,19 @@ (define-record-type* <channel> channel make-channel
|
||||||
;; commit so that only them may emit this introduction. Introductions are
|
;; commit so that only them may emit this introduction. Introductions are
|
||||||
;; used to bootstrap trust in a channel.
|
;; used to bootstrap trust in a channel.
|
||||||
(define-record-type <channel-introduction>
|
(define-record-type <channel-introduction>
|
||||||
(make-channel-introduction first-signed-commit first-commit-signer
|
(%make-channel-introduction first-signed-commit first-commit-signer
|
||||||
signature)
|
signature)
|
||||||
channel-introduction?
|
channel-introduction?
|
||||||
(first-signed-commit channel-introduction-first-signed-commit) ;hex string
|
(first-signed-commit channel-introduction-first-signed-commit) ;hex string
|
||||||
(first-commit-signer channel-introduction-first-commit-signer) ;bytevector
|
(first-commit-signer channel-introduction-first-commit-signer) ;bytevector
|
||||||
(signature channel-introduction-signature)) ;string
|
(signature channel-introduction-signature)) ;string
|
||||||
|
|
||||||
|
(define (make-channel-introduction commit signer)
|
||||||
|
"Return a new channel introduction: COMMIT is the introductory where
|
||||||
|
authentication starts, and SIGNER is the OpenPGP fingerprint (a bytevector) of
|
||||||
|
the signer of that commit."
|
||||||
|
(%make-channel-introduction commit signer #f))
|
||||||
|
|
||||||
(define (openpgp-fingerprint->bytevector str)
|
(define (openpgp-fingerprint->bytevector str)
|
||||||
"Convert STR, an OpenPGP fingerprint (hexadecimal string with whitespace),
|
"Convert STR, an OpenPGP fingerprint (hexadecimal string with whitespace),
|
||||||
to the corresponding bytevector."
|
to the corresponding bytevector."
|
||||||
|
|
|
@ -451,12 +451,11 @@ (define (find-commit* message)
|
||||||
(with-repository directory repository
|
(with-repository directory repository
|
||||||
(let* ((commit1 (find-commit repository "first"))
|
(let* ((commit1 (find-commit repository "first"))
|
||||||
(commit2 (find-commit repository "second"))
|
(commit2 (find-commit repository "second"))
|
||||||
(intro ((@@ (guix channels) make-channel-introduction)
|
(intro (make-channel-introduction
|
||||||
(commit-id-string commit1)
|
(commit-id-string commit1)
|
||||||
(openpgp-public-key-fingerprint
|
(openpgp-public-key-fingerprint
|
||||||
(read-openpgp-packet
|
(read-openpgp-packet
|
||||||
%ed25519bis-public-key-file)) ;different key
|
%ed25519bis-public-key-file)))) ;different key
|
||||||
#f)) ;no signature
|
|
||||||
(channel (channel (name 'example)
|
(channel (channel (name 'example)
|
||||||
(url (string-append "file://" directory))
|
(url (string-append "file://" directory))
|
||||||
(introduction intro))))
|
(introduction intro))))
|
||||||
|
@ -507,12 +506,11 @@ (define (find-commit* message)
|
||||||
(let* ((commit1 (find-commit repository "first"))
|
(let* ((commit1 (find-commit repository "first"))
|
||||||
(commit2 (find-commit repository "second"))
|
(commit2 (find-commit repository "second"))
|
||||||
(commit3 (find-commit repository "third"))
|
(commit3 (find-commit repository "third"))
|
||||||
(intro ((@@ (guix channels) make-channel-introduction)
|
(intro (make-channel-introduction
|
||||||
(commit-id-string commit1)
|
(commit-id-string commit1)
|
||||||
(openpgp-public-key-fingerprint
|
(openpgp-public-key-fingerprint
|
||||||
(read-openpgp-packet
|
(read-openpgp-packet
|
||||||
%ed25519-public-key-file))
|
%ed25519-public-key-file))))
|
||||||
#f)) ;no signature
|
|
||||||
(channel (channel (name 'example)
|
(channel (channel (name 'example)
|
||||||
(url (string-append "file://" directory))
|
(url (string-append "file://" directory))
|
||||||
(introduction intro))))
|
(introduction intro))))
|
||||||
|
|
Loading…
Reference in a new issue