package repo import ( "context" "net/url" "os" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/transport/http" "github.com/google/wire" "github.com/hashicorp/go-multierror" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/fanal/artifact" "github.com/aquasecurity/trivy/pkg/fanal/artifact/local" "github.com/aquasecurity/trivy/pkg/fanal/cache" "github.com/aquasecurity/trivy/pkg/fanal/walker" ) var ( ArtifactSet = wire.NewSet( walker.NewFS, wire.Bind(new(Walker), new(*walker.FS)), NewArtifact, ) _ Walker = (*walker.FS)(nil) ) type Walker interface { Walk(root string, opt walker.Option, fn walker.WalkFunc) error } type Artifact struct { url string local artifact.Artifact } func NewArtifact(target string, c cache.ArtifactCache, w Walker, artifactOpt artifact.Option) ( artifact.Artifact, func(), error) { var cleanup func() var errs error // Try the local repository art, err := tryLocalRepo(target, c, w, artifactOpt) if err == nil { return art, func() {}, nil } errs = multierror.Append(errs, err) // Try the remote git repository art, cleanup, err = tryRemoteRepo(target, c, w, artifactOpt) if err == nil { return art, cleanup, nil } errs = multierror.Append(errs, err) // Return errors return nil, cleanup, errs } func (a Artifact) Inspect(ctx context.Context) (artifact.Reference, error) { ref, err := a.local.Inspect(ctx) if err != nil { return artifact.Reference{}, xerrors.Errorf("remote repository error: %w", err) } if a.url != "" { ref.Name = a.url } ref.Type = artifact.TypeRepository return ref, nil } func (Artifact) Clean(_ artifact.Reference) error { return nil } func tryLocalRepo(target string, c cache.ArtifactCache, w Walker, artifactOpt artifact.Option) (artifact.Artifact, error) { if _, err := os.Stat(target); err != nil { return nil, xerrors.Errorf("no such path: %w", err) } art, err := local.NewArtifact(target, c, w, artifactOpt) if err != nil { return nil, xerrors.Errorf("local repo artifact error: %w", err) } return Artifact{ local: art, }, nil } func tryRemoteRepo(target string, c cache.ArtifactCache, w Walker, artifactOpt artifact.Option) (artifact.Artifact, func(), error) { cleanup := func() {} u, err := newURL(target) if err != nil { return nil, cleanup, err } tmpDir, err := cloneRepo(u, artifactOpt) if err != nil { return nil, cleanup, xerrors.Errorf("repository clone error: %w", err) } cleanup = func() { _ = os.RemoveAll(tmpDir) } art, err := local.NewArtifact(tmpDir, c, w, artifactOpt) if err != nil { return nil, cleanup, xerrors.Errorf("fs artifact: %w", err) } return Artifact{ url: target, local: art, }, cleanup, nil } func cloneRepo(u *url.URL, artifactOpt artifact.Option) (string, error) { tmpDir, err := os.MkdirTemp("", "trivy-remote-repo") if err != nil { return "", xerrors.Errorf("failed to create a temp dir: %w", err) } cloneOptions := git.CloneOptions{ URL: u.String(), Auth: gitAuth(), Progress: os.Stdout, InsecureSkipTLS: artifactOpt.Insecure, } // suppress clone output if noProgress if artifactOpt.NoProgress { cloneOptions.Progress = nil } if artifactOpt.RepoCommit == "" { cloneOptions.Depth = 1 } if artifactOpt.RepoBranch != "" { cloneOptions.ReferenceName = plumbing.NewBranchReferenceName(artifactOpt.RepoBranch) cloneOptions.SingleBranch = true } if artifactOpt.RepoTag != "" { cloneOptions.ReferenceName = plumbing.NewTagReferenceName(artifactOpt.RepoTag) cloneOptions.SingleBranch = true } r, err := git.PlainClone(tmpDir, false, &cloneOptions) if err != nil { return "", xerrors.Errorf("git clone error: %w", err) } if artifactOpt.RepoCommit != "" { w, err := r.Worktree() if err != nil { return "", xerrors.Errorf("git worktree error: %w", err) } err = w.Checkout(&git.CheckoutOptions{ Hash: plumbing.NewHash(artifactOpt.RepoCommit), }) if err != nil { return "", xerrors.Errorf("git checkout error: %w", err) } } return tmpDir, nil } func newURL(rawurl string) (*url.URL, error) { u, err := url.Parse(rawurl) if err != nil { return nil, xerrors.Errorf("url parse error: %w", err) } // "https://" can be omitted // e.g. github.com/aquasecurity/trivy if u.Scheme == "" { u.Scheme = "https" } return u, nil } // Helper function to check for a GitHub/GitLab token from env vars in order to // make authenticated requests to access private repos func gitAuth() *http.BasicAuth { var auth *http.BasicAuth // The username can be anything for HTTPS Git operations gitUsername := "fanal-aquasecurity-scan" // We first check if a GitHub token was provided githubToken := os.Getenv("GITHUB_TOKEN") if githubToken != "" { auth = &http.BasicAuth{ Username: gitUsername, Password: githubToken, } return auth } // Otherwise we check if a GitLab token was provided gitlabToken := os.Getenv("GITLAB_TOKEN") if gitlabToken != "" { auth = &http.BasicAuth{ Username: gitUsername, Password: gitlabToken, } return auth } // If no token was provided, we simply return a nil, // which will make the request to be unauthenticated return nil }