Leave no package-registry credential or clone userinfo in the workspace a check mounts (issue 462)
A build wrote .npmrc into its source tree and never removed it, and its clone recorded the URL's userinfo; a later check's container mounts the workspace as HOME. The .npmrc and the tree now go when the build ends, clones record their URL without userinfo, and a check first removes any .npmrc an older builder left.
This commit is contained in:
@@ -10,6 +10,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
@@ -169,6 +170,9 @@ func build(ctx context.Context, run Runner, publish Publisher,
|
||||
if err := os.RemoveAll(tree); err != nil {
|
||||
return Result{}, err
|
||||
}
|
||||
// Removed when the build ends, whatever happens: the tree holds the .npmrc a build may be handed, in the
|
||||
// workspace a later check's container mounts as its HOME (novox/hq issue 462).
|
||||
defer os.RemoveAll(tree)
|
||||
// A fresh clone every time rather than a fetch into a tree that is already there. A build
|
||||
// that reuses a working tree can succeed because of something a previous build left behind,
|
||||
// and that is a build nobody can reproduce.
|
||||
@@ -176,6 +180,9 @@ func build(ctx context.Context, run Runner, publish Publisher,
|
||||
say("clone", "FAILED: %v", err)
|
||||
return Result{}, fmt.Errorf("cannot clone %s: %w", repository, err)
|
||||
}
|
||||
if err := recordedWithoutUserinfo(ctx, run, tree, repository); err != nil {
|
||||
return Result{}, err
|
||||
}
|
||||
say("clone", "done")
|
||||
if ref != "" {
|
||||
if _, err := run(ctx, tree, "git", "checkout", "--quiet", ref); err != nil {
|
||||
@@ -247,6 +254,8 @@ func build(ctx context.Context, run Runner, publish Publisher,
|
||||
if err := os.WriteFile(npmrcPath, []byte(content), 0o600); err != nil {
|
||||
return Result{}, fmt.Errorf("cannot write the package-registry credential for the build: %w", err)
|
||||
}
|
||||
// Only for as long as the build: a later check's container mounts this workspace (novox/hq issue 462).
|
||||
defer os.Remove(npmrcPath)
|
||||
say("packages", "resolving %s from the mesh's package registry", npmrc.Scope)
|
||||
src.notPinned("it resolves packages from the mesh's registry at build time")
|
||||
}
|
||||
@@ -440,6 +449,9 @@ func contextFrom(ctx context.Context, run Runner, workspace, artifact, credentia
|
||||
if _, err := run(ctx, workspace, "git", cloneWith(credentials, "clone", "--quiet", url, dir)...); err != nil {
|
||||
return "", fmt.Errorf("cannot clone %s: %w", url, err)
|
||||
}
|
||||
if err := recordedWithoutUserinfo(ctx, run, dir, url); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if from.Ref != "" {
|
||||
if _, err := run(ctx, dir, "git", "checkout", "--quiet", from.Ref); err != nil {
|
||||
return "", fmt.Errorf("%s has no %s: %w", from.Repository, from.Ref, err)
|
||||
@@ -498,6 +510,47 @@ func storeCredential(workspace string, forge GitCredential) (string, func(), err
|
||||
return path, forget, nil
|
||||
}
|
||||
|
||||
// recordedWithoutUserinfo makes a clone record the URL it came from without userinfo: git keeps it as given in
|
||||
// the clone's .git/config, inside the workspace a check's container mounts (novox/hq issue 462).
|
||||
func recordedWithoutUserinfo(ctx context.Context, run Runner, clone, repository string) error {
|
||||
bare, carried := withoutUserinfo(repository)
|
||||
if !carried {
|
||||
return nil
|
||||
}
|
||||
if _, err := run(ctx, clone, "git", "remote", "set-url", "origin", bare); err != nil {
|
||||
return fmt.Errorf("the clone of %s keeps its credential: %w", bare, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// forgetLeftCredentials removes what an earlier build or an older builder left in the workspace that holds
|
||||
// a credential: the forge's git-credentials, and every .npmrc a build wrote into a tree it cloned. Run
|
||||
// before a check, whose container mounts the workspace as its HOME (novox/hq issue 462). The Go caches are
|
||||
// not walked: no build writes a credential there, and they hold more files than everything else.
|
||||
func forgetLeftCredentials(workspace string) error {
|
||||
if err := os.Remove(filepath.Join(workspace, "git-credentials")); err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return err
|
||||
}
|
||||
return filepath.WalkDir(workspace, func(p string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
if d.IsDir() && p != workspace && (d.Name() == "go-cache" || d.Name() == "go-modules") &&
|
||||
filepath.Dir(p) == workspace {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
if !d.IsDir() && d.Name() == ".npmrc" {
|
||||
if err := os.Remove(p); err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return fmt.Errorf("a package-registry credential left at %s cannot be removed: %w", p, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// withinDir is whether path is dir or under it, both made absolute and resolved.
|
||||
func withinDir(dir, path string) bool {
|
||||
d, err1 := filepath.Abs(dir)
|
||||
|
||||
Reference in New Issue
Block a user