Skip to content

Commit 92e9157

Browse files
Github and Gitlab Authentication via http.extraHeader for cloning Repositories (#4139)
* initial code changes * move args before clone command * add comments * added flag for github source to support backward compatibility * added flag for gitlab source to support backward compatibility * inverse the logic for enterprise * remove print statement * remove flag defaults * updated comments and removed CloneRepoUsingTokenInHeader function * false->true
1 parent acb9826 commit 92e9157

File tree

15 files changed

+610
-529
lines changed

15 files changed

+610
-529
lines changed

main.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ var (
119119
githubScanPRComments = githubScan.Flag("pr-comments", "Include pull request descriptions and comments in scan.").Bool()
120120
githubScanGistComments = githubScan.Flag("gist-comments", "Include gist comments in scan.").Bool()
121121
githubCommentsTimeframeDays = githubScan.Flag("comments-timeframe", "Number of days in the past to review when scanning issue, PR, and gist comments.").Uint32()
122+
githubAuthInUrl = githubScan.Flag("auth-in-url", "Embed authentication credentials in repository URLs instead of using secure HTTP headers").Bool()
122123

123124
// GitHub Cross Fork Object Reference Experimental Feature
124125
githubExperimentalScan = cli.Command("github-experimental", "Run an experimental GitHub scan. Must specify at least one experimental sub-module to run: object-discovery.")
@@ -139,6 +140,7 @@ var (
139140
gitlabScanExcludePaths = gitlabScan.Flag("exclude-paths", "Path to file with newline separated regexes for files to exclude in scan.").Short('x').String()
140141
gitlabScanIncludeRepos = gitlabScan.Flag("include-repos", `Repositories to include in an org scan. This can also be a glob pattern. You can repeat this flag. Must use Gitlab repo full name. Example: "trufflesecurity/trufflehog", "trufflesecurity/t*"`).Strings()
141142
gitlabScanExcludeRepos = gitlabScan.Flag("exclude-repos", `Repositories to exclude in an org scan. This can also be a glob pattern. You can repeat this flag. Must use Gitlab repo full name. Example: "trufflesecurity/driftwood", "trufflesecurity/d*"`).Strings()
143+
gitlabAuthInUrl = gitlabScan.Flag("auth-in-url", "Embed authentication credentials in repository URLs instead of using secure HTTP headers").Bool()
142144

143145
filesystemScan = cli.Command("filesystem", "Find credentials in a filesystem.")
144146
filesystemPaths = filesystemScan.Arg("path", "Path to file or directory to scan.").Strings()
@@ -741,6 +743,7 @@ func runSingleScan(ctx context.Context, cmd string, cfg engine.Config) (metrics,
741743
IncludeGistComments: *githubScanGistComments,
742744
CommentsTimeframeDays: *githubCommentsTimeframeDays,
743745
Filter: filter,
746+
AuthInUrl: *githubAuthInUrl,
744747
}
745748
if ref, err = eng.ScanGitHub(ctx, cfg); err != nil {
746749
return scanMetrics, fmt.Errorf("failed to scan Github: %v", err)
@@ -769,6 +772,7 @@ func runSingleScan(ctx context.Context, cmd string, cfg engine.Config) (metrics,
769772
IncludeRepos: *gitlabScanIncludeRepos,
770773
ExcludeRepos: *gitlabScanExcludeRepos,
771774
Filter: filter,
775+
AuthInUrl: *gitlabAuthInUrl,
772776
}
773777
if ref, err = eng.ScanGitLab(ctx, cfg); err != nil {
774778
return scanMetrics, fmt.Errorf("failed to scan GitLab: %v", err)

pkg/engine/github.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ func (e *Engine) ScanGitHub(ctx context.Context, c sources.GithubConfig) (source
2828
IncludeWikis: c.IncludeWikis,
2929
SkipBinaries: c.SkipBinaries,
3030
CommentsTimeframeDays: c.CommentsTimeframeDays,
31+
RemoveAuthInUrl: !c.AuthInUrl, // configuration uses the opposite field in proto to keep credentials in the URL by default.
3132
}
3233
if len(c.Token) > 0 {
3334
connection.Credential = &sourcespb.GitHub_Token{

pkg/engine/gitlab.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ func (e *Engine) ScanGitLab(ctx context.Context, c sources.GitlabConfig) (source
2424
}
2525
scanOptions := git.NewScanOptions(opts...)
2626

27-
connection := &sourcespb.GitLab{SkipBinaries: c.SkipBinaries}
27+
connection := &sourcespb.GitLab{
28+
SkipBinaries: c.SkipBinaries,
29+
RemoveAuthInUrl: !c.AuthInUrl, // configuration uses the opposite field in proto to keep credentials in the URL by default.
30+
}
2831

2932
switch {
3033
case len(c.Token) > 0:

pkg/pb/sourcespb/sources.pb.go

Lines changed: 523 additions & 501 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/pb/sourcespb/sources.pb.validate.go

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/sources/git/git.go

Lines changed: 42 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package git
33
import (
44
"bufio"
55
"bytes"
6+
"encoding/base64"
67
"errors"
78
"fmt"
89
"io"
@@ -92,6 +93,8 @@ type Config struct {
9293
// When set to true, the parser will use a custom contentWriter provided through the WithContentWriter option.
9394
// When false, the parser will use the default buffer (in-memory) contentWriter.
9495
UseCustomContentWriter bool
96+
// pass authentication embedded in the repository urls
97+
AuthInUrl bool
9598
}
9699

97100
// NewGit creates a new Git instance with the provided configuration. The Git instance is used to interact with
@@ -276,7 +279,7 @@ func (s *Source) scanRepo(ctx context.Context, repoURI string, reporter sources.
276279
cloneFunc = func() (string, *git.Repository, error) {
277280
user := cred.BasicAuth.Username
278281
token := cred.BasicAuth.Password
279-
return CloneRepoUsingToken(ctx, token, repoURI, user)
282+
return CloneRepoUsingToken(ctx, token, repoURI, user, true)
280283
}
281284
case *sourcespb.Git_Unauthenticated:
282285
cloneFunc = func() (string, *git.Repository, error) {
@@ -373,6 +376,7 @@ func GitURLParse(gitURL string) (*url.URL, error) {
373376
return nil, originalError
374377
}
375378
}
379+
376380
return parsedURL, nil
377381
}
378382

@@ -381,20 +385,21 @@ type cloneParams struct {
381385
gitURL string
382386
args []string
383387
clonePath string
388+
authInUrl bool
384389
}
385390

386391
// CloneRepo orchestrates the cloning of a given Git repository, returning its local path
387392
// and a git.Repository object for further operations. The function sets up error handling
388393
// infrastructure, ensuring that any encountered errors trigger a cleanup of resources.
389394
// The core cloning logic is delegated to a nested function, which returns errors to the
390395
// outer function for centralized error handling and cleanup.
391-
func CloneRepo(ctx context.Context, userInfo *url.Userinfo, gitURL string, args ...string) (string, *git.Repository, error) {
396+
func CloneRepo(ctx context.Context, userInfo *url.Userinfo, gitURL string, authInUrl bool, args ...string) (string, *git.Repository, error) {
392397
clonePath, err := cleantemp.MkdirTemp()
393398
if err != nil {
394399
return "", nil, err
395400
}
396401

397-
repo, err := executeClone(ctx, cloneParams{userInfo, gitURL, args, clonePath})
402+
repo, err := executeClone(ctx, cloneParams{userInfo, gitURL, args, clonePath, authInUrl})
398403
if err != nil {
399404
// DO NOT FORGET TO CLEAN UP THE CLONE PATH HERE!!
400405
// If we don't, we'll end up with a bunch of orphaned directories in the temp dir.
@@ -412,21 +417,40 @@ func executeClone(ctx context.Context, params cloneParams) (*git.Repository, err
412417
if err != nil {
413418
return nil, err
414419
}
415-
if cloneURL.User == nil {
416-
cloneURL.User = params.userInfo
417-
}
418420

419-
gitArgs := []string{
420-
"clone",
421-
cloneURL.String(),
422-
params.clonePath,
423-
"--quiet", // https://git-scm.com/docs/git-clone#Documentation/git-clone.txt-code--quietcode
421+
var gitArgs []string
422+
423+
if params.authInUrl {
424+
if cloneURL.User == nil {
425+
cloneURL.User = params.userInfo
426+
}
427+
} else { // default
428+
cloneURL.User = nil // remove user information from the url
429+
430+
pass, ok := params.userInfo.Password()
431+
if ok {
432+
/*
433+
Sources:
434+
- https://medium.com/%40szpytfire/authenticating-with-github-via-a-personal-access-token-7c639a979eb3
435+
- https://trinhngocthuyen.com/posts/tech/50-shades-of-git-remotes-and-authentication/#using-httpextraheader-config
436+
*/
437+
authHeader := base64.StdEncoding.EncodeToString(fmt.Appendf([]byte(""), "%s:%s", params.userInfo.Username(), pass))
438+
gitArgs = append(gitArgs, "-c", fmt.Sprintf("http.extraHeader=Authorization: Basic %s", authHeader))
439+
}
424440
}
441+
425442
if !feature.SkipAdditionalRefs.Load() {
426443
gitArgs = append(gitArgs,
427444
"-c",
428445
"remote.origin.fetch=+refs/*:refs/remotes/origin/*")
429446
}
447+
448+
gitArgs = append(gitArgs, "clone",
449+
cloneURL.String(),
450+
params.clonePath,
451+
"--quiet", // https://git-scm.com/docs/git-clone#Documentation/git-clone.txt-code--quietcode
452+
)
453+
430454
gitArgs = append(gitArgs, params.args...)
431455
cloneCmd := exec.Command("git", gitArgs...)
432456

@@ -499,23 +523,23 @@ func PingRepoUsingToken(ctx context.Context, token, gitUrl, user string) error {
499523
}
500524

501525
// CloneRepoUsingToken clones a repo using a provided token.
502-
func CloneRepoUsingToken(ctx context.Context, token, gitUrl, user string, args ...string) (string, *git.Repository, error) {
526+
func CloneRepoUsingToken(ctx context.Context, token, gitUrl, user string, authInUrl bool, args ...string) (string, *git.Repository, error) {
503527
userInfo := url.UserPassword(user, token)
504-
return CloneRepo(ctx, userInfo, gitUrl, args...)
528+
return CloneRepo(ctx, userInfo, gitUrl, authInUrl, args...)
505529
}
506530

507531
// CloneRepoUsingUnauthenticated clones a repo with no authentication required.
508532
func CloneRepoUsingUnauthenticated(ctx context.Context, url string, args ...string) (string, *git.Repository, error) {
509-
return CloneRepo(ctx, nil, url, args...)
533+
return CloneRepo(ctx, nil, url, false, args...)
510534
}
511535

512536
// CloneRepoUsingSSH clones a repo using SSH.
513537
func CloneRepoUsingSSH(ctx context.Context, gitURL string, args ...string) (string, *git.Repository, error) {
514538
if isCodeCommitURL(gitURL) {
515-
return CloneRepo(ctx, nil, gitURL, args...)
539+
return CloneRepo(ctx, nil, gitURL, false, args...)
516540
}
517541
userInfo := url.User("git")
518-
return CloneRepo(ctx, userInfo, gitURL, args...)
542+
return CloneRepo(ctx, userInfo, gitURL, false, args...)
519543
}
520544

521545
var codeCommitRE = regexp.MustCompile(`ssh://git-codecommit\.[\w-]+\.amazonaws\.com`)
@@ -1142,7 +1166,7 @@ func prepareRepoSinceCommit(ctx context.Context, uriString, commitHash string) (
11421166
if !ok {
11431167
return "", true, fmt.Errorf("password must be included in Git repo URL when username is provided")
11441168
}
1145-
path, _, err = CloneRepoUsingToken(ctx, password, remotePath, uri.User.Username(), "--shallow-since", timestamp)
1169+
path, _, err = CloneRepoUsingToken(ctx, password, remotePath, uri.User.Username(), true, "--shallow-since", timestamp)
11461170
if err != nil {
11471171
return path, true, fmt.Errorf("failed to clone authenticated Git repo (%s): %s", uri.Redacted(), err)
11481172
}
@@ -1180,7 +1204,7 @@ func PrepareRepo(ctx context.Context, uriString string) (string, bool, error) {
11801204
if !ok {
11811205
return "", remote, fmt.Errorf("password must be included in Git repo URL when username is provided")
11821206
}
1183-
path, _, err = CloneRepoUsingToken(ctx, password, remotePath, uri.User.Username())
1207+
path, _, err = CloneRepoUsingToken(ctx, password, remotePath, uri.User.Username(), true)
11841208
if err != nil {
11851209
return path, remote, fmt.Errorf("failed to clone authenticated Git repo (%s): %s", uri.Redacted(), err)
11861210
}

pkg/sources/github/connector_app.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ func (c *appConnector) Clone(ctx context.Context, repoURL string, args ...string
8888
return "", nil, fmt.Errorf("could not create installation token: %w", err)
8989
}
9090

91-
return git.CloneRepoUsingToken(ctx, token.GetToken(), repoURL, "x-access-token", args...)
91+
return git.CloneRepoUsingToken(ctx, token.GetToken(), repoURL, "x-access-token", true, args...)
9292
}
9393

9494
func (c *appConnector) InstallationClient() *github.Client {

pkg/sources/github/connector_basicauth.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,5 +44,5 @@ func (c *basicAuthConnector) APIClient() *github.Client {
4444
}
4545

4646
func (c *basicAuthConnector) Clone(ctx context.Context, repoURL string, args ...string) (string, *gogit.Repository, error) {
47-
return git.CloneRepoUsingToken(ctx, c.password, repoURL, c.username, args...)
47+
return git.CloneRepoUsingToken(ctx, c.password, repoURL, c.username, true, args...)
4848
}

pkg/sources/github/connector_token.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,12 @@ type tokenConnector struct {
2020
handleRateLimit func(context.Context, error) bool
2121
user string
2222
userMu sync.Mutex
23+
authInUrl bool
2324
}
2425

2526
var _ Connector = (*tokenConnector)(nil)
2627

27-
func NewTokenConnector(apiEndpoint string, token string, handleRateLimit func(context.Context, error) bool) (Connector, error) {
28+
func NewTokenConnector(apiEndpoint string, token string, authInUrl bool, handleRateLimit func(context.Context, error) bool) (Connector, error) {
2829
const httpTimeoutSeconds = 60
2930
httpClient := common.RetryableHTTPClientTimeout(int64(httpTimeoutSeconds))
3031
tokenSource := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token})
@@ -43,6 +44,7 @@ func NewTokenConnector(apiEndpoint string, token string, handleRateLimit func(co
4344
token: token,
4445
isGitHubEnterprise: !strings.EqualFold(apiEndpoint, cloudEndpoint),
4546
handleRateLimit: handleRateLimit,
47+
authInUrl: authInUrl,
4648
}, nil
4749
}
4850

@@ -54,7 +56,8 @@ func (c *tokenConnector) Clone(ctx context.Context, repoURL string, args ...stri
5456
if err := c.setUserIfUnset(ctx); err != nil {
5557
return "", nil, err
5658
}
57-
return git.CloneRepoUsingToken(ctx, c.token, repoURL, c.user, args...)
59+
60+
return git.CloneRepoUsingToken(ctx, c.token, repoURL, c.user, c.authInUrl, args...)
5861
}
5962

6063
func (c *tokenConnector) IsGithubEnterprise() bool {

pkg/sources/github/github.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ type Source struct {
7474

7575
sources.Progress
7676
sources.CommonSourceUnitUnmarshaller
77+
78+
useAuthInUrl bool // pass credentials in the repository urls for cloning
7779
}
7880

7981
// --------------------------------------------------------------------------------
@@ -226,6 +228,9 @@ func (s *Source) Init(aCtx context.Context, name string, jobID sources.JobID, so
226228
}
227229
s.conn = &conn
228230

231+
// configuration uses the inverse logic of the `useAuthInUrl` flag.
232+
s.useAuthInUrl = !s.conn.RemoveAuthInUrl
233+
229234
connector, err := newConnector(s)
230235
if err != nil {
231236
return fmt.Errorf("could not create connector: %w", err)
@@ -290,6 +295,7 @@ func (s *Source) Init(aCtx context.Context, name string, jobID sources.JobID, so
290295
}
291296
},
292297
UseCustomContentWriter: s.useCustomContentWriter,
298+
AuthInUrl: s.useAuthInUrl,
293299
}
294300
s.git = git.NewGit(cfg)
295301

@@ -1597,7 +1603,7 @@ func newConnector(source *Source) (Connector, error) {
15971603
return NewBasicAuthConnector(apiEndpoint, cred.BasicAuth)
15981604
case *sourcespb.GitHub_Token:
15991605
log.RedactGlobally(cred.Token)
1600-
return NewTokenConnector(apiEndpoint, cred.Token, func(c context.Context, err error) bool {
1606+
return NewTokenConnector(apiEndpoint, cred.Token, source.useAuthInUrl, func(c context.Context, err error) bool {
16011607
return source.handleRateLimit(c, err)
16021608
})
16031609
case *sourcespb.GitHub_Unauthenticated:

0 commit comments

Comments
 (0)