package remote import ( "context" "io/ioutil" "net/url" "os" git "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" "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/types" ) type Artifact struct { url string local artifact.Artifact } func NewArtifact(rawurl string, c cache.ArtifactCache, artifactOpt artifact.Option) ( artifact.Artifact, func(), error) { cleanup := func() {} u, err := newURL(rawurl) if err != nil { return nil, cleanup, err } tmpDir, err := ioutil.TempDir("", "fanal-remote") if err != nil { return nil, cleanup, err } cloneOptions := git.CloneOptions{ URL: u.String(), Auth: gitAuth(), Progress: os.Stdout, InsecureSkipTLS: artifactOpt.InsecureSkipTLS, } // 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 nil, cleanup, xerrors.Errorf("git clone error: %w", err) } if artifactOpt.RepoCommit != "" { w, err := r.Worktree() if err != nil { return nil, cleanup, xerrors.Errorf("git worktree error: %w", err) } err = w.Checkout(&git.CheckoutOptions{ Hash: plumbing.NewHash(artifactOpt.RepoCommit), }) if err != nil { return nil, cleanup, xerrors.Errorf("git checkout error: %w", err) } } cleanup = func() { _ = os.RemoveAll(tmpDir) } art, err := local.NewArtifact(tmpDir, c, artifactOpt) if err != nil { return nil, cleanup, xerrors.Errorf("fs artifact: %w", err) } return Artifact{ url: rawurl, local: art, }, cleanup, nil } func (a Artifact) Inspect(ctx context.Context) (types.ArtifactReference, error) { ref, err := a.local.Inspect(ctx) if err != nil { return types.ArtifactReference{}, xerrors.Errorf("remote repository error: %w", err) } ref.Name = a.url ref.Type = types.ArtifactRemoteRepository return ref, nil } func (Artifact) Clean(_ types.ArtifactReference) error { return 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 }