github.com/devseccon/trivy@v0.47.1-0.20231123133102-bd902a0bd996/pkg/fanal/artifact/repo/git.go (about) 1 package repo 2 3 import ( 4 "context" 5 "net/url" 6 "os" 7 8 "github.com/go-git/go-git/v5" 9 "github.com/go-git/go-git/v5/plumbing" 10 "github.com/go-git/go-git/v5/plumbing/transport/http" 11 "github.com/hashicorp/go-multierror" 12 "golang.org/x/xerrors" 13 14 "github.com/devseccon/trivy/pkg/fanal/artifact" 15 "github.com/devseccon/trivy/pkg/fanal/artifact/local" 16 "github.com/devseccon/trivy/pkg/fanal/cache" 17 "github.com/devseccon/trivy/pkg/fanal/types" 18 ) 19 20 type Artifact struct { 21 url string 22 local artifact.Artifact 23 } 24 25 func NewArtifact(target string, c cache.ArtifactCache, artifactOpt artifact.Option) ( 26 artifact.Artifact, func(), error) { 27 28 var cleanup func() 29 var errs error 30 31 // Try the local repository 32 art, err := tryLocalRepo(target, c, artifactOpt) 33 if err == nil { 34 return art, func() {}, nil 35 } 36 errs = multierror.Append(errs, err) 37 38 // Try the remote git repository 39 art, cleanup, err = tryRemoteRepo(target, c, artifactOpt) 40 if err == nil { 41 return art, cleanup, nil 42 } 43 errs = multierror.Append(errs, err) 44 45 // Return errors 46 return nil, cleanup, errs 47 } 48 49 func (a Artifact) Inspect(ctx context.Context) (types.ArtifactReference, error) { 50 ref, err := a.local.Inspect(ctx) 51 if err != nil { 52 return types.ArtifactReference{}, xerrors.Errorf("remote repository error: %w", err) 53 } 54 55 if a.url != "" { 56 ref.Name = a.url 57 } 58 ref.Type = types.ArtifactRepository 59 60 return ref, nil 61 } 62 63 func (Artifact) Clean(_ types.ArtifactReference) error { 64 return nil 65 } 66 67 func tryLocalRepo(target string, c cache.ArtifactCache, artifactOpt artifact.Option) (artifact.Artifact, error) { 68 if _, err := os.Stat(target); err != nil { 69 return nil, xerrors.Errorf("no such path: %w", err) 70 } 71 72 art, err := local.NewArtifact(target, c, artifactOpt) 73 if err != nil { 74 return nil, xerrors.Errorf("local repo artifact error: %w", err) 75 } 76 return Artifact{ 77 local: art, 78 }, nil 79 } 80 81 func tryRemoteRepo(target string, c cache.ArtifactCache, artifactOpt artifact.Option) (artifact.Artifact, func(), error) { 82 cleanup := func() {} 83 u, err := newURL(target) 84 if err != nil { 85 return nil, cleanup, err 86 } 87 88 tmpDir, err := cloneRepo(u, artifactOpt) 89 if err != nil { 90 return nil, cleanup, xerrors.Errorf("repository clone error: %w", err) 91 } 92 93 cleanup = func() { _ = os.RemoveAll(tmpDir) } 94 95 art, err := local.NewArtifact(tmpDir, c, artifactOpt) 96 if err != nil { 97 return nil, cleanup, xerrors.Errorf("fs artifact: %w", err) 98 } 99 100 return Artifact{ 101 url: target, 102 local: art, 103 }, cleanup, nil 104 105 } 106 107 func cloneRepo(u *url.URL, artifactOpt artifact.Option) (string, error) { 108 tmpDir, err := os.MkdirTemp("", "trivy-remote-repo") 109 if err != nil { 110 return "", xerrors.Errorf("failed to create a temp dir: %w", err) 111 } 112 113 cloneOptions := git.CloneOptions{ 114 URL: u.String(), 115 Auth: gitAuth(), 116 Progress: os.Stdout, 117 InsecureSkipTLS: artifactOpt.Insecure, 118 } 119 120 // suppress clone output if noProgress 121 if artifactOpt.NoProgress { 122 cloneOptions.Progress = nil 123 } 124 125 if artifactOpt.RepoCommit == "" { 126 cloneOptions.Depth = 1 127 } 128 129 if artifactOpt.RepoBranch != "" { 130 cloneOptions.ReferenceName = plumbing.NewBranchReferenceName(artifactOpt.RepoBranch) 131 cloneOptions.SingleBranch = true 132 } 133 134 if artifactOpt.RepoTag != "" { 135 cloneOptions.ReferenceName = plumbing.NewTagReferenceName(artifactOpt.RepoTag) 136 cloneOptions.SingleBranch = true 137 } 138 139 r, err := git.PlainClone(tmpDir, false, &cloneOptions) 140 if err != nil { 141 return "", xerrors.Errorf("git clone error: %w", err) 142 } 143 144 if artifactOpt.RepoCommit != "" { 145 w, err := r.Worktree() 146 if err != nil { 147 return "", xerrors.Errorf("git worktree error: %w", err) 148 } 149 150 err = w.Checkout(&git.CheckoutOptions{ 151 Hash: plumbing.NewHash(artifactOpt.RepoCommit), 152 }) 153 if err != nil { 154 return "", xerrors.Errorf("git checkout error: %w", err) 155 } 156 } 157 158 return tmpDir, nil 159 } 160 161 func newURL(rawurl string) (*url.URL, error) { 162 u, err := url.Parse(rawurl) 163 if err != nil { 164 return nil, xerrors.Errorf("url parse error: %w", err) 165 } 166 // "https://" can be omitted 167 // e.g. github.com/devseccon/trivy 168 if u.Scheme == "" { 169 u.Scheme = "https" 170 } 171 172 return u, nil 173 } 174 175 // Helper function to check for a GitHub/GitLab token from env vars in order to 176 // make authenticated requests to access private repos 177 func gitAuth() *http.BasicAuth { 178 var auth *http.BasicAuth 179 180 // The username can be anything for HTTPS Git operations 181 gitUsername := "fanal-aquasecurity-scan" 182 183 // We first check if a GitHub token was provided 184 githubToken := os.Getenv("GITHUB_TOKEN") 185 if githubToken != "" { 186 auth = &http.BasicAuth{ 187 Username: gitUsername, 188 Password: githubToken, 189 } 190 return auth 191 } 192 193 // Otherwise we check if a GitLab token was provided 194 gitlabToken := os.Getenv("GITLAB_TOKEN") 195 if gitlabToken != "" { 196 auth = &http.BasicAuth{ 197 Username: gitUsername, 198 Password: gitlabToken, 199 } 200 return auth 201 } 202 203 // If no token was provided, we simply return a nil, 204 // which will make the request to be unauthenticated 205 return nil 206 207 }