diff --git a/internal/tufclient/client.go b/internal/tufclient/client.go index cdf68b4..0b51e29 100644 --- a/internal/tufclient/client.go +++ b/internal/tufclient/client.go @@ -89,7 +89,14 @@ func RepoClient(ctx context.Context, cacheDir *pathrs.Root, repoLike tufext.Repo // Fallback to fetch from the trusted root source. rootData, err := repo.RootTrust.FetchRoot(ctx, repo) if err != nil { - return nil, skippableErr(fmt.Errorf("fetch trusted root.json: %w", err)) + err = fmt.Errorf("fetch trusted root.json: %w", err) + // Only a missing root.json means the repository may not exist + // yet. Any other failure (auth, network, server error) must not + // look like an empty repository list. + if errors.Is(err, fs.ErrNotExist) { + err = skippableErr(err) + } + return nil, err } rootFile, err = repoCacheDir.Create(".", unix.O_TMPFILE|unix.O_RDWR|unix.O_NOFOLLOW, 0o644) if err != nil { diff --git a/internal/tufclient/client_repos_test.go b/internal/tufclient/client_repos_test.go index 3a4bce8..bc7cecd 100644 --- a/internal/tufclient/client_repos_test.go +++ b/internal/tufclient/client_repos_test.go @@ -184,6 +184,25 @@ func TestNewClient_SkipsInvalidRemoteRoot(t *testing.T) { require.ErrorContains(t, err, "root_trust root.json is invalid JSON") } +// A remote root_trust fetch that fails for any reason but a 404 (here, the +// server refusing the client) is fatal for NewClient: only a missing +// repository may be skipped. +func TestNewClient_RemoteRootFetchErrorFatal(t *testing.T) { + valid, denied := testrepo.New(t), testrepo.NewBroken(t) + denied.Handle("/1.root.json", http.HandlerFunc(func(wtr http.ResponseWriter, _ *http.Request) { + wtr.WriteHeader(http.StatusUnauthorized) + })) + cfg := testrepo.Config(t, + valid.ConfigBlock("valid-repo"), + denied.ConfigBlock("denied-repo"), + ) + + _, err := tufclient.NewClient(t.Context(), cfg) + require.NotErrorIs(t, err, tufclient.ErrSkippableRepo) + require.ErrorContains(t, err, "bad repo denied-repo") + require.ErrorContains(t, err, "status code 401") +} + // A repository with a non-remote root_trust source that fails to load is a // fatal error for NewClient, never a skippable one -- local sources are meant // to always exist and be valid.