diff mbox series

[bug#67072,4/4] weather: Report unauthorized substitute servers.

Message ID dc56e185b21eb0b3f4711e100d5e64c0aa2adc55.1699700050.git.ludo@gnu.org
State New
Headers show
Series Helping diagnose substitute setup issues | expand

Commit Message

Ludovic Courtès Nov. 11, 2023, 11:06 a.m. UTC
The goal is to make it easier to diagnose substitute
misconfiguration (where we’re passing a substitute URL whose
corresponding key is not authorized).

Suggested by Emmanuel Agullo.

* guix/scripts/weather.scm (check-narinfo-authorization): New procedure.
(report-server-coverage): Use it.
* doc/guix.texi (Invoking guix weather): Document it.
(Getting Substitutes from Other Servers): Add “Troubleshooting” frame.

Change-Id: I0a049c39eefb10d6a06634c8b16aa86902769791
---
 doc/guix.texi            | 21 ++++++++++++++++++++-
 guix/scripts/weather.scm | 34 ++++++++++++++++++++++++++++++++++
 2 files changed, 54 insertions(+), 1 deletion(-)

Comments

Simon Tournier Nov. 28, 2023, 1:14 p.m. UTC | #1
Hi,

On Sat, 11 Nov 2023 at 12:06, Ludovic Courtès <ludo@gnu.org> wrote:

> +  #:use-module (guix pki)

Looking at what it drags, I notice:

--8<---------------cut here---------------start------------->8---
(define* (authorized-key? key #:optional (acl (current-acl)))
  "Return #t if KEY (a canonical sexp) is an authorized public key for archive
imports according to ACL."
  ;; Note: ACL is kept in native sexp form to make 'authorized-key?' faster,
  ;; by not having to convert it with 'canonical-sexp->sexp' on each call.
  ;; TODO: We could use a better data type for ACLs.
  (let ((key (canonical-sexp->sexp key)))
    (match acl
      (('acl
        ('entry subject-keys
                ('tag ('guix 'import)))
        ...)
       (not (not (member key subject-keys))))
      (_
       (error "invalid access-control list" acl)))))
--8<---------------cut here---------------end--------------->8---

I know it is irrelevant with the patch at hand.  Maybe not. :-)

   1. Why this ’(not (not’ ?

   2. When testing the patch, I have not done --sysconfdir=/etc and it
      was not able to find the correct ACL.  Somehow…

> +(define (check-narinfo-authorization narinfo)
> +  "Print a warning when NARINFO is not signed by an authorized key."
> +  (unless (valid-narinfo? narinfo)

…I entered in this part – hence the look up (guix pki) ;-).  Well, my
mistake is hard to reproduce outside of Guix development tree but
’valid-narinfo?’ returns false for more cases than just
unauthorized-key.  Therefore, the hint could be misleading.

Since we are discussing about an helper, I would run ’signature-case’
here in check-narinfo.  For example, if the case is 'unauthorized-key,
then I would check is %acl-file exists.  Maybe display the full
%acl-file explaining that the key is not in, etc.

Moreover, running “guix challenge coreutils” does not warn about
anything; when I was expected the same warning as “guix weather”.


Last, once sysconfig fixed, I get:

--8<---------------cut here---------------start------------->8---
guix weather: warning: could not determine current substitute URLs; using defaults
computing 1 package derivations for x86_64-linux...
looking for 2 store items on https://ci.guix.gnu.org...
guix weather: error: open-file: Permission denied: "/etc/guix/acl"
--8<---------------cut here---------------end--------------->8---

Hum? Maybe I am doing something wrong…  The file /etc/guix/acl has the
permission:

    -rw-------   1 root root   528  acl

Is it incorrect?  Well, if all are allowed to read (chmod a+r) then
there is not error.  And it displays the warning:

--8<---------------cut here---------------start------------->8---
guix weather: warning: could not determine current substitute URLs; using defaults
--8<---------------cut here---------------end--------------->8---

And that’s because the daemon is not supporting the operation.  This
warning appears to me misleading: personally I think that I am
misconfigured something when that’s not the case.  Instead, I would
display:

    warning: using defaults substitute URLs


Cheers,
simon
Ludovic Courtès Dec. 2, 2023, 10:20 a.m. UTC | #2
Hi Simon,

Simon Tournier <zimon.toutoune@gmail.com> skribis:

> I know it is irrelevant with the patch at hand.  Maybe not. :-)
>
>    1. Why this ’(not (not’ ?

This ensures the result is a Boolean.

[...]

>> +(define (check-narinfo-authorization narinfo)
>> +  "Print a warning when NARINFO is not signed by an authorized key."
>> +  (unless (valid-narinfo? narinfo)
>
> …I entered in this part – hence the look up (guix pki) ;-).  Well, my
> mistake is hard to reproduce outside of Guix development tree but
> ’valid-narinfo?’ returns false for more cases than just
> unauthorized-key.  Therefore, the hint could be misleading.

It’s true that ‘valid-narinfo?’ catches more cases, but the other cases
where it returns #f are situations where the substitute server is bogus.
So I chose to favor conciseness here.

> Since we are discussing about an helper, I would run ’signature-case’
> here in check-narinfo.  For example, if the case is 'unauthorized-key,
> then I would check is %acl-file exists.  Maybe display the full
> %acl-file explaining that the key is not in, etc.

Right, checking for ‘%acl-file’ is a good idea; I wouldn’t display its
contents though because that’d be intimidating and unhelpful IMO.

> Moreover, running “guix challenge coreutils” does not warn about
> anything […]

That’s on purpose:

--8<---------------cut here---------------start------------->8---
(define (compare-contents items servers)
  "Challenge the substitute servers whose URLs are listed in SERVERS by
comparing the hash of the substitutes of ITEMS that they serve.  Return the
list of <comparison-report> objects.

This procedure does not authenticate narinfos from SERVERS, nor does it verify
that they are signed by an authorized public keys.  The reason is that, by
definition, we may want to target unknown servers.  Furthermore, no risk is
taken since we do not import the archives."
--8<---------------cut here---------------end--------------->8---

> guix weather: warning: could not determine current substitute URLs; using defaults
> computing 1 package derivations for x86_64-linux...
> looking for 2 store items on https://ci.guix.gnu.org...
> guix weather: error: open-file: Permission denied: "/etc/guix/acl"

Uh, it should be able to deal with it gracefully.

> Hum? Maybe I am doing something wrong…  The file /etc/guix/acl has the
> permission:
>
>     -rw-------   1 root root   528  acl

It’s normally world-readable.

> Is it incorrect?  Well, if all are allowed to read (chmod a+r) then
> there is not error.  And it displays the warning:
>
> guix weather: warning: could not determine current substitute URLs; using defaults
>
> And that’s because the daemon is not supporting the operation.  This
> warning appears to me misleading: personally I think that I am
> misconfigured something when that’s not the case.  Instead, I would
> display:
>
>     warning: using defaults substitute URLs

Yes, good idea.

I’ll send v2 soonish.

Thanks for your feedback!

Ludo’.
Simon Tournier Dec. 2, 2023, 1:31 p.m. UTC | #3
Hi Ludo,

On sam., 02 déc. 2023 at 11:20, Ludovic Courtès <ludo@gnu.org> wrote:

>> guix weather: warning: could not determine current substitute URLs; using defaults
>> computing 1 package derivations for x86_64-linux...
>> looking for 2 store items on https://ci.guix.gnu.org...
>> guix weather: error: open-file: Permission denied: "/etc/guix/acl"
>
> Uh, it should be able to deal with it gracefully.
>
>> Hum? Maybe I am doing something wrong…  The file /etc/guix/acl has the
>> permission:
>>
>>     -rw-------   1 root root   528  acl
>
> It’s normally world-readable.

On foreign distro, this %acl-file appears by default with ’rw’
permission for root only.  It is not word-readable.

When running guix-install.sh as root, if I read correctly, it runs:

	    local key=~root/.config/guix/current/share/guix/$host.pub
	    [ -f "$key" ] \
		&& guix archive --authorize < "$key" \
		&& _msg "${PAS}Authorized public key for $host"

Therefore, the file %acl-file is written as root by the procedure
’write-acl’.  Hence the permission ’rw’ for root only, no?

Somehow, ’write-acl’ should be tweaked or guix-install.sh, no?

Cheers,
simon
Ludovic Courtès Dec. 11, 2023, 10:52 p.m. UTC | #4
Hi,

Ludovic Courtès <ludo@gnu.org> skribis:

> Changes since v1:
>
>   • Gracefully handle unreadable /etc/guix/acl file in ‘guix weather’;
>
>   • Simplify ‘guix weather’ warning message when the daemon does not
>     support the ‘substitute-urls’ RPC.

I went ahead and pushed these changes:

  4348947c74 weather: Report unauthorized substitute servers.
  7e11369586 weather: Use the same substitute URLs as guix-daemon.
  f63a8c5ca2 challenge: Use the same substitute URLs as guix-daemon.
  1e47148f46 daemon: Implement ‘substitute-urls’ RPC.

Thanks again for your feedback!

Ludo’.
diff mbox series

Patch

diff --git a/doc/guix.texi b/doc/guix.texi
index 028c4f3357..45c3b7344f 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -4058,6 +4058,7 @@  Substitute Server Authorization
 
 @node Getting Substitutes from Other Servers
 @subsection Getting Substitutes from Other Servers
+@c Note: This section name appears in a hint printed by 'guix weather'.
 
 @cindex substitute servers, adding more
 Guix can look up and fetch substitutes from several servers.  This is
@@ -4157,6 +4158,21 @@  Getting Substitutes from Other Servers
 substitute lookup can be slowed down if too many servers need to be
 contacted.
 
+@quotation Troubleshooting
+To diagnose problems, you can run @command{guix weather}.  For example,
+running:
+
+@example
+guix weather coreutils
+@end example
+
+@noindent
+not only tells you which of the currently-configured servers has
+substitutes for the @code{coreutils} package, it also reports whether
+one of these servers is unauthorized.  @xref{Invoking guix weather}, for
+more information.
+@end quotation
+
 Note that there are also situations where one may want to add the URL of
 a substitute server @emph{without} authorizing its key.
 @xref{Substitute Authentication}, to understand this fine point.
@@ -16395,7 +16411,10 @@  Invoking guix weather
 specified servers so you can have an idea of whether you'll be grumpy
 today.  It can sometimes be useful info as a user, but it is primarily
 useful to people running @command{guix publish} (@pxref{Invoking guix
-publish}).
+publish}).  Sometimes substitutes @emph{are} available but they are not
+authorized on your system; @command{guix weather} reports it so you can
+authorize them if you want (@pxref{Getting Substitutes from Other
+Servers}).
 
 @cindex statistics, for substitutes
 @cindex availability of substitutes
diff --git a/guix/scripts/weather.scm b/guix/scripts/weather.scm
index 7e302fcea7..e7e5a75811 100644
--- a/guix/scripts/weather.scm
+++ b/guix/scripts/weather.scm
@@ -35,6 +35,8 @@  (define-module (guix scripts weather)
   #:use-module ((guix build utils) #:select (every*))
   #:use-module (guix substitutes)
   #:use-module (guix narinfo)
+  #:use-module (guix pki)
+  #:autoload   (gcrypt pk-crypto) (canonical-sexp->string)
   #:use-module (guix http-client)
   #:use-module (guix ci)
   #:use-module (guix sets)
@@ -185,6 +187,32 @@  (define (store-item-system store item)
     (()
      #f)))
 
+(define (check-narinfo-authorization narinfo)
+  "Print a warning when NARINFO is not signed by an authorized key."
+  (unless (valid-narinfo? narinfo)
+    (warning (G_ "substitutes from '~a' are unauthorized~%")
+             (narinfo-uri-base narinfo))
+    ;; The "all substitutes" below reflects the fact that, in reality, it *is*
+    ;; possible to download "unauthorized" substitutes, as long as they match
+    ;; authorized substitutes.
+    (display-hint (G_ "To authorize all substitutes from @uref{~a} to be
+downloaded, the following command needs to be run as root:
+
+@example
+guix archive --authorize <<EOF
+~a
+EOF
+@end example
+
+Alternatively, on Guix System, you can add the signing key above to the
+@code{authorized-keys} field of @code{guix-configuration}.
+
+See \"Getting Substitutes from Other Servers\" in the manual for more
+information.")
+                  (narinfo-uri-base narinfo)
+                  (canonical-sexp->string
+                   (signature-subject (narinfo-signature narinfo))))))
+
 (define* (report-server-coverage server items
                                  #:key display-missing?)
   "Report the subset of ITEMS available as substitutes on SERVER.
@@ -204,6 +232,12 @@  (define* (report-server-coverage server items
                     #:make-progress-reporter
                     (lambda* (total #:key url #:allow-other-keys)
                       (progress-reporter/bar total)))))
+    (match narinfos
+      (() #f)
+      ((narinfo . _)
+       ;; Help diagnose missing substitute authorizations.
+       (check-narinfo-authorization narinfo)))
+
     (let ((obtained  (length narinfos))
           (requested (length items))
           (missing   (lset-difference string=?