diff mbox series

[bug#50814] guix: git-authenticate: Also authenticate the channel intro commit.

Message ID 20210926101928.3877-1-attila@lendvai.name
State New
Headers show
Series [bug#50814] guix: git-authenticate: Also authenticate the channel intro commit. | expand

Checks

Context Check Description
cbaines/comparison success View comparision
cbaines/git branch success View Git branch
cbaines/applying patch success View Laminar job
cbaines/issue success View issue
cbaines/comparison success View comparision
cbaines/git branch success View Git branch
cbaines/applying patch success View Laminar job
cbaines/issue success View issue
cbaines/comparison success View comparision
cbaines/git branch success View Git branch
cbaines/applying patch success View Laminar job
cbaines/issue success View issue

Commit Message

Attila Lendvai Sept. 26, 2021, 10:19 a.m. UTC
* guix/git-authenticate.scm (authenticate-commit): Reword and extend the error
message to point to the relevant part of the manual.
(authenticate-repository): Explicitly authenticate the channel introduction
commit, so that it's also rejected unless it is signed by an authorized
key. Otherwise only the second commit would yield an error, which
is confusing.
---

here's how i tested this:

i set up pulling from a local checkout of guix.
in that branch i created a signed dummy commit, and added it as a channel
introduction, replacing guix in my /etc/guix/channels.scm. then tried to
guix pull, which worked.

then i added another dummy commit, which resulted in an error when pulling.

then i reset the branch back to only contain the first commit, and added
this code that then resulted in an error even with a single commit.

i have encountered it while i was trying to set up my local checkout to
test my patches on my live guix, and i was utterly confused why my commit
was rejected as unauthenticated (i misunderstood how git-authenticate
works).

 guix/git-authenticate.scm | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

Comments

Leo Famulari Sept. 26, 2021, 6:02 p.m. UTC | #1
On Sun, Sep 26, 2021 at 12:19:29PM +0200, Attila Lendvai wrote:
> * guix/git-authenticate.scm (authenticate-commit): Reword and extend the error
> message to point to the relevant part of the manual.
> (authenticate-repository): Explicitly authenticate the channel introduction
> commit, so that it's also rejected unless it is signed by an authorized
> key. Otherwise only the second commit would yield an error, which
> is confusing.
> ---
> 
> here's how i tested this:
> 
> i set up pulling from a local checkout of guix.
> in that branch i created a signed dummy commit, and added it as a channel
> introduction, replacing guix in my /etc/guix/channels.scm. then tried to
> guix pull, which worked.
> 
> then i added another dummy commit, which resulted in an error when pulling.
> 
> then i reset the branch back to only contain the first commit, and added
> this code that then resulted in an error even with a single commit.
> 
> i have encountered it while i was trying to set up my local checkout to
> test my patches on my live guix, and i was utterly confused why my commit
> was rejected as unauthenticated (i misunderstood how git-authenticate
> works).

Thanks for your report.

I've marked the severity as "grave", which in Debbugs parlance means
"makes the package in question unusable or mostly so, or causes data
loss, or introduces a security hole allowing access to the accounts of
users who use the package."

https://debbugs.gnu.org/Developer.html#severities

I'm not sure if that's justified or not but this patch should be
prioritized.
M Sept. 26, 2021, 6:14 p.m. UTC | #2
Attila Lendvai schreef op zo 26-09-2021 om 12:19 [+0200]:
> * guix/git-authenticate.scm (authenticate-commit): Reword and extend the error
> message to point to the relevant part of the manual.
> (authenticate-repository): Explicitly authenticate the channel introduction
> commit, so that it's also rejected unless it is signed by an authorized
> key. Otherwise only the second commit would yield an error, which
> is confusing.
> ---
> 
> here's how i tested this:
> 
> i set up pulling from a local checkout of guix.
> in that branch i created a signed dummy commit, and added it as a channel
> introduction, replacing guix in my /etc/guix/channels.scm. then tried to
> guix pull, which worked.
> 
> then i added another dummy commit, which resulted in an error when pulling.
> 
> then i reset the branch back to only contain the first commit, and added
> this code that then resulted in an error even with a single commit.
> 
> i have encountered it while i was trying to set up my local checkout to
> test my patches on my live guix, and i was utterly confused why my commit
> was rejected as unauthenticated (i misunderstood how git-authenticate
> works).
> 
>  guix/git-authenticate.scm | 11 ++++++++---
>  1 file changed, 8 insertions(+), 3 deletions(-)
> 
> diff --git a/guix/git-authenticate.scm b/guix/git-authenticate.scm
> index ab3fcd8b2f..7d66bf0754 100644
> --- a/guix/git-authenticate.scm
> +++ b/guix/git-authenticate.scm
> @@ -236,8 +236,8 @@ not specify anything, fall back to DEFAULT-AUTHORIZATIONS."
>              (condition
>               (&unauthorized-commit-error (commit id)
>                                           (signing-key signing-key)))
> -            (formatted-message (G_ "commit ~a not signed by an authorized \
> -key: ~a")
> +            (formatted-message (G_ "commit ~a is signed by an unauthorized \
> +key: ~a\nSee info guix \"Specifying Channel Authorizations\".")
>                                 (oid->string id)
>                                 (openpgp-format-fingerprint
>                                  (openpgp-public-key-fingerprint
> @@ -424,7 +424,12 @@ denoting the authorized keys for commits whose parent lack the
>          ;; If it's our first time, verify START-COMMIT's signature.
>          (when (null? authenticated-commits)
>            (verify-introductory-commit repository keyring
> -                                      start-commit signer))
> +                                      start-commit signer)
> +          ;; Explicitly authenticate the channel introduction commit, so that
> +          ;; it's also rejected unless it's signed by an authorized
> +          ;; key. Otherwise only the second commit would yield an error, which
> +          ;; is confusing.
> +          (authenticate-commits repository (list start-commit)))

Could you add a test to tests/git-authenticate.scm, verifying the right comit
is reported?  (Maybe use unauthorized-commit-error?, guard and
authenticate-repository.)

I'm not sure explicitely validating the start commit is sufficient.  What happens
in the following scenario:

(Order of commits)
  0. start commit
  1. valid (already authenticated?) commit
  2. invalid commit
  3. invalid commit

Is commit 2 reported, or commit 3 reported?  I think commit 2 should be reported,
but from your messages on IRC, I think you saw commit 3 being reported?

Greetings,
Maxime.
Attila Lendvai Sept. 27, 2021, 6:01 p.m. UTC | #3
just a quick update that i'm working on putting together an extensive test for this, and a fix.

- attila
PGP: 5D5F 45C7 DFCD 0A39
Attila Lendvai Sept. 27, 2021, 6:45 p.m. UTC | #4
what should happen if a channel-introduction commit does not update the .guix-authorizations file?

this means that this single commit will get through the authentication (channel intro commits are always trusted), but any later commits signed with the same key will be rejected.

this is the situation that got me confused, and thrust me into attempting to fix this (trying to `guix pull` from a local git checkout of guix containing my patches).

i wrote the code for this, but i don't know what should be its UI. how should this be reported to the user?

using `(warning ...)` will just print something to stderr.

i was hoping to raise a continuable condition of type &warning, that i can even check for in the tests, but i have failed to put that together. the scheme/guile condition system is a bit messy/convoluted.

can someone help me out with a hint/outline about how to report this that best fits the rest of guix?

note that it's not really an error, because until another commit is added with the new key, this channel is valid.

- attila
PGP: 5D5F 45C7 DFCD 0A39
M Sept. 28, 2021, 10:02 a.m. UTC | #5
Attila Lendvai schreef op ma 27-09-2021 om 18:45 [+0000]:
> 
[...]
> using `(warning ...)` will just print something to stderr.
> 
> i was hoping to raise a continuable condition of type &warning, that i can even check for in the tests,
> but i have failed to put that together. the scheme/guile condition system is a bit messy/convoluted.

Technically, Scheme supports continuable exceptions with 'raise-continuable'
and with-exception-hander.  E.g., the following Racket example:

 (use-modules (rnrs exceptions) (rnrs conditions))
 (with-exception-handler
  (lambda (con)
    (cond
      ((not (warning? con))
       (raise con))
      ((message-condition? con)
       (display (condition-message con)))
      (else
       (display "a warning has been issued")))
    42)
  (lambda ()
    (+ (raise-continuable
         (condition
           (make-warning)
           (make-message-condition
             "should be a number")))
       23)))
    prints: should be a number
           ⇒ 65

(from https://docs.racket-lang.org/r6rs/r6rs-lib-std/r6rs-lib-Z-H-8.html#node_idx_378)
works in Guile

You might need to modify 'call-with-error-handling' in (guix ui) to recognise
&warning though, such that the &warning exception will be properly handled.

Alternatively, you can use the procedure 'warning' from (guix diagnostics).
To detect the error in this case, you can use parameterise, guix-warning-port
and procedures like call-with-output-string.  You may need to reset the locale
first (with dynamic-wind?).

Greetings,
Maxime.
Ludovic Courtès Oct. 9, 2021, 1:44 p.m. UTC | #6
Hi!

Leo Famulari <leo@famulari.name> skribis:

> I've marked the severity as "grave", which in Debbugs parlance means
> "makes the package in question unusable or mostly so, or causes data
> loss, or introduces a security hole allowing access to the accounts of
> users who use the package."

This had the unfortunate effect that this patch would not show up in
Emacs debbugs.el, which, for some reason, doesn’t know about “grave”.

I’ve changed to “important” and I’d suggest not going beyond “serious”,
which is already grave enough.  :-)

Ludo’.
Ludovic Courtès Oct. 9, 2021, 1:53 p.m. UTC | #7
Hi Attila,

Attila Lendvai <attila@lendvai.name> skribis:

> * guix/git-authenticate.scm (authenticate-commit): Reword and extend the error
> message to point to the relevant part of the manual.
> (authenticate-repository): Explicitly authenticate the channel introduction
> commit, so that it's also rejected unless it is signed by an authorized
> key. Otherwise only the second commit would yield an error, which
> is confusing.

This behavior is intentional and documented (info "(guix) Specifying
Channel Authorizations"):

     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 ‘guix pull’ or ‘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.

  […]

     The channel introduction, as we saw above, is the commit/key
     pair—i.e., the commit that introduced ‘.guix-authorizations’, and
     the fingerprint of the OpenPGP used to sign it.

By definition, parent commits of the introduction do not (not
necessarily) provide ‘.guix-authorizations’.  So there’s nothing to be
done here, other than checking that the introductory commit is indeed
signed by the key specified in the introduction.

Does that make sense?

(Other patches you posted in this thread might be useful though, but we
can discuss them independently.)

Thanks,
Ludo’.

PS: If you haven’t already, you can take a look at the following pages
    for more on the design rationale:

      https://guix.gnu.org/en/blog/2020/securing-updates/
      https://issues.guix.gnu.org/22883#69
Attila Lendvai Oct. 9, 2021, 3:31 p.m. UTC | #8
> Does that make sense?

there are three main topics of this patchset:

1) adding a (hopefully helpful) warning. the primary goal.
2) general cleanups
3) IIRC, fixing some actual bugs in the process

as for 1):

what i did was fork guix master, and now i'm pulling my own
authenticated branch from my own local git checkout, where every once
in a while i merge my various topic branches into my branch, and guix
pull it.

when i added my second commit i have spent a disproportionate amount
of time trying to figure out what was happening: the first commit was
accepted, and i thought it's set up all fine. then who knows how much
later, when i added my second commit, i was staring at the screen
without a clue why pulling doesn't work anymore.

then i ventured into quickly adding warning, so that others won't
waste their time on this, and went down the rabbit hole, which
resulted in fixing actual bugs, i believe. IIRC, they are exposed by
the test that i have added when run on the current codebase.

as for 3), any actual bugs:

i'll investigate again later by running the test without the fix, and write
up my results here, or better yet, in a better commit message.

--
• attila lendvai
• PGP: 963F 5D5F 45C7 DFCD 0A39
--
It should be a grammatical if not legal offense to ascribe thoughts, opinions and decisions to "we" without a signed power of attorney.
Ludovic Courtès Oct. 12, 2021, 9:39 a.m. UTC | #9
Hi,

Attila Lendvai <attila@lendvai.name> skribis:

> there are three main topics of this patchset:
>
> 1) adding a (hopefully helpful) warning. the primary goal.
> 2) general cleanups
> 3) IIRC, fixing some actual bugs in the process

Alright.  Please next time open one issue per topic: that’s a good way
to maximize the chances that review happens in a timely fashion.  :-)

> as for 1):
>
> what i did was fork guix master, and now i'm pulling my own
> authenticated branch from my own local git checkout, where every once
> in a while i merge my various topic branches into my branch, and guix
> pull it.
>
> when i added my second commit i have spent a disproportionate amount
> of time trying to figure out what was happening: the first commit was
> accepted, and i thought it's set up all fine. then who knows how much
> later, when i added my second commit, i was staring at the screen
> without a clue why pulling doesn't work anymore.
>
> then i ventured into quickly adding warning, so that others won't
> waste their time on this, and went down the rabbit hole, which
> resulted in fixing actual bugs, i believe. IIRC, they are exposed by
> the test that i have added when run on the current codebase.

I understand the behavior was surprising to you, but I’d like to see if
we can pinpoint why.  Can you think of anything that could be added to
the documentation?

  https://guix.gnu.org/manual/en/html_node/Specifying-Channel-Authorizations.html

> as for 3), any actual bugs:
>
> i'll investigate again later by running the test without the fix, and write
> up my results here, or better yet, in a better commit message.

Yes please.  In general, please start by reporting the bug: what you
get, what you expected, and how to reproduce.  That makes it easier to
understand and evaluate proposed fixes.

Thanks!

Ludo’.
Leo Famulari Oct. 12, 2021, 3:17 p.m. UTC | #10
On Sat, Oct 09, 2021 at 03:44:40PM +0200, Ludovic Courtès wrote:
> This had the unfortunate effect that this patch would not show up in
> Emacs debbugs.el, which, for some reason, doesn’t know about “grave”.
> 
> I’ve changed to “important” and I’d suggest not going beyond “serious”,
> which is already grave enough.  :-)

Noted!
Attila Lendvai Oct. 17, 2021, 10:09 a.m. UTC | #11
> i'll investigate again later by running the test without the fix, and write
> up my results here, or better yet, in a better commit message.

i ran the test without my fix, and indeed it fails at two points:

1)

;; Should fail because it is signed with key2, not key1
(check-from "commit 3" #:should-fail? #true)

2)

;; It is not very intuitive why commit 1 and 2 should be trusted
;; at this point: commit 4 has previously been used as a channel
;; intro, thus it got marked as trusted in the ~/.cache/.
;; Because commit 1 and 2 are among its parents, it should also
;; be trusted at this point because of the cache.  Note that
;; it's debatable whether this semantics is a good idea, but
;; this is how git-authenticate is and has been implemented for
;; a while (modulo failing to update the cache in the past when
;; taking certain code paths).
(check-from "commit 1")
(check-from "commit 2")

note that i have extended the above comments compared to what's in the
commits that i have sent previously (and i also fixed the check for
the warning). i suspect there are still things to discuss, so i'll
wait for any feedback before i resend the patches. i did not touch the
test code itself, so you can easily find these points in it.


> Yes please.  In general, please start by reporting the bug: what you
> get, what you expected, and how to reproduce.  That makes it easier
> to understand and evaluate proposed fixes.


understood. the problem is that it all started out as adding a
warning, and the rest were just side-quests... :)


> Alright.  Please next time open one issue per topic: that’s a good
> way to maximize the chances that review happens in a timely fashion.
> :-)


can i mark dependencies between issues/patchsets?

because all that i could do here is split this into two sets of
commits (because of the dependencies between the commits):

1) the 3 test commits, and
2) the 2 guix commits.

i thought that separating the test that is exhibiting the bug, from
the fix that fixes it, would only hinder the process.


> I understand the behavior was surprising to you, but I’d like to see
> if we can pinpoint why.  Can you think of anything that could be
> added to the documentation?


if we assume that everyone reads and internalizes every page of the
documentation of every software that they use, then i guess nothing
needs to be added.

but if our goal is to maximize the effectiveness of the users, then no
amount of static, free-flowing text can compete with a warning that is
signalled in close context to the issue.

i think the right question to ask here is how often would this warning
be superfluous. my assumption is that very rarely, if ever, but i may
not be aware of some use-cases.

looking forward to any feedback on how to improve this.

--
• attila lendvai
• PGP: 963F 5D5F 45C7 DFCD 0A39
--
If the source of fear is the unknown, and fear is the only way to be controlled, then knowledge is the only way to be free.
Ludovic Courtès Oct. 18, 2021, 9:10 a.m. UTC | #12
Hi Attila,

Attila Lendvai <attila@lendvai.name> skribis:

>> i'll investigate again later by running the test without the fix, and write
>> up my results here, or better yet, in a better commit message.
>
> i ran the test without my fix, and indeed it fails at two points:

Sorry, which test is failing?  Is that part of the patches you sent?
I need more context.  :-)

[...]

>> Alright.  Please next time open one issue per topic: that’s a good
>> way to maximize the chances that review happens in a timely fashion.
>> :-)
>
>
> can i mark dependencies between issues/patchsets?
>
> because all that i could do here is split this into two sets of
> commits (because of the dependencies between the commits):
>
> 1) the 3 test commits, and
> 2) the 2 guix commits.
>
> i thought that separating the test that is exhibiting the bug, from
> the fix that fixes it, would only hinder the process.

Yes, in general it’s best to have the test and the fix in the same
commit.

However, at this point, I’m not sure which “bug” we’re talking about.
What you described in your initial message is not a bug in my view:

  https://issues.guix.gnu.org/50814#28

>> I understand the behavior was surprising to you, but I’d like to see
>> if we can pinpoint why.  Can you think of anything that could be
>> added to the documentation?
>
>
> if we assume that everyone reads and internalizes every page of the
> documentation of every software that they use, then i guess nothing
> needs to be added.
>
> but if our goal is to maximize the effectiveness of the users, then no
> amount of static, free-flowing text can compete with a warning that is
> signalled in close context to the issue.

Sure, I agree.

However, you’re clearly a power user at this point :-), and we’re
talking about one of the most sensitive pieces of code in Guix.  I think
it’s important to make sure we’re on the same level of understanding of
the design and current implementation; I also think it’s not
unreasonable to expect channel writers to pay attention to documentation
on these matters.

I’m not saying that we should not change anything, but rather that it’s
not like a simple usability/UX issue.

I hope this makes sense!

Ludo’.
Attila Lendvai Oct. 18, 2021, 3:27 p.m. UTC | #13
hi Ludo,


> > i ran the test without my fix, and indeed it fails at two points:
>
> Sorry, which test is failing? Is that part of the patches you sent?
>
> I need more context. :-)


i have sent 5 patches. three of them are prefixed with 'test:', and
two of those are test idempotent test infrastructure changes. the
third of them adds a new test that tests git-authenticate. this is the
test that i'm talking about.

if you apply only these 3 test related commits, and run the new test
on the unpatched codebase, then you'll see the two failures that i'm
talking about in my previous mail.

search the test log for 'FAILURE' (the test runs fully, but fails in
case any of the tests fail).

one of the two failures is a more serious issue because a channel
intro commit is accepted while it shouldn't be.


> > > Alright. Please next time open one issue per topic: that’s a good
> > > way to maximize the chances that review happens in a timely fashion.
> > >
> > > :-)
> >
> > can i mark dependencies between issues/patchsets?
> > because all that i could do here is split this into two sets of
> > commits (because of the dependencies between the commits):
> >
> > 1.  the 3 test commits, and
> > 2.  the 2 guix commits.
> >
> > i thought that separating the test that is exhibiting the bug, from
> > the fix that fixes it, would only hinder the process.
>
> Yes, in general it’s best to have the test and the fix in the same
> commit.


i cut the fix and the test in separate commits (but sent them in the
same patchset/issue), so that it's possible to partially apply only
the test commits, and study its behavior on the current codebase.


> However, at this point, I’m not sure which “bug” we’re talking about.
>
> What you described in your initial message is not a bug in my view:
>
> https://issues.guix.gnu.org/50814#28


the bug is described, formally, by the test that i have added (unless
the test itself is wrong, that is). IIRC, i started putting together
this new test to expose the bugs that i have suspected while reviewing
the implementation of git-authenticate, and then to support my effort
to fix them afterwards.

i think the best next-action is for someone qualified to take a look
at the test that i have added, and see if any of the assumptions
encoded in it is wrong. i think i understand this part of the codebase
pretty well now, but i may have erred.

if the test seems to be valid, then proceed to review the rest of the
commits.


> I’m not saying that we should not change anything, but rather that it’s
> not like a simple usability/UX issue.
>
> I hope this makes sense!


yes, it does! actually, i welcome the reluctance to haphazardly apply
patches to this part of the codebase. i was kinda expecting this, and
that's why i have prepared the commits so that the test can be applied
and tried separately.

hope this clarifies the situation,

--
• attila lendvai
• PGP: 963F 5D5F 45C7 DFCD 0A39
--
“Everything can be taken from a man but one thing: the last of the human freedoms—to choose one’s attitude in any given set of circumstances, to choose one’s own way.”
	— Viktor E. Frankl (1905–1997), 'Man's Search for Meaning' (1946)
Attila Lendvai April 4, 2022, 6:47 a.m. UTC | #14
FTR, apparently, i have abandoned the pursuit of this issue.

to sum up my perspective:

guix pull's behavior has greatly confused me once by accepting a commit that it (much) later rejected (when it wasn't the tip of the history anymore). i have wasted quite some time debugging/understanding this, which included reading the code. due to my annoyance i got into the presented work/patch while in the process.

if this is not considered an issue, then i don't have a dog in this fight anymore. i have learned this quirk by now, so it won't bite me again, and the rest is up to the maintainers to decide.

feel free to take from this patch whatever you find useful, or close it if you find no more value in it.

happy hacking,

--
• attila lendvai
• PGP: 963F 5D5F 45C7 DFCD 0A39
--
“Nothing is so permanent as a temporary government program.”
	— Milton Friedman (1912–2006)
diff mbox series

Patch

diff --git a/guix/git-authenticate.scm b/guix/git-authenticate.scm
index ab3fcd8b2f..7d66bf0754 100644
--- a/guix/git-authenticate.scm
+++ b/guix/git-authenticate.scm
@@ -236,8 +236,8 @@  not specify anything, fall back to DEFAULT-AUTHORIZATIONS."
             (condition
              (&unauthorized-commit-error (commit id)
                                          (signing-key signing-key)))
-            (formatted-message (G_ "commit ~a not signed by an authorized \
-key: ~a")
+            (formatted-message (G_ "commit ~a is signed by an unauthorized \
+key: ~a\nSee info guix \"Specifying Channel Authorizations\".")
                                (oid->string id)
                                (openpgp-format-fingerprint
                                 (openpgp-public-key-fingerprint
@@ -424,7 +424,12 @@  denoting the authorized keys for commits whose parent lack the
         ;; If it's our first time, verify START-COMMIT's signature.
         (when (null? authenticated-commits)
           (verify-introductory-commit repository keyring
-                                      start-commit signer))
+                                      start-commit signer)
+          ;; Explicitly authenticate the channel introduction commit, so that
+          ;; it's also rejected unless it's signed by an authorized
+          ;; key. Otherwise only the second commit would yield an error, which
+          ;; is confusing.
+          (authenticate-commits repository (list start-commit)))
 
         (let ((stats (call-with-progress-reporter reporter
                        (lambda (report)