Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auth GitHub in Init #3131

Merged
merged 49 commits into from
Aug 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
9f1d2fa
use non-public repo in test
rosecodym Jul 26, 2024
4df0047
split out newBasicAuthClient
rosecodym Jul 29, 2024
b9e382e
create newUnauthenticatedClient
rosecodym Jul 29, 2024
72a1ed3
create newTokenClient
rosecodym Jul 29, 2024
3c42ce1
create (unused) newAppClient
rosecodym Jul 29, 2024
5890aa7
stash
rosecodym Jul 29, 2024
43bcedd
add unauthed new way
rosecodym Jul 29, 2024
1df7a14
add app connector
rosecodym Jul 29, 2024
c01bcda
stop checking for ghe in app connector
rosecodym Jul 29, 2024
2240bf7
split into separate files
rosecodym Jul 29, 2024
75a7aed
add token connector
rosecodym Jul 29, 2024
88b15fc
add basic auth connector
rosecodym Jul 29, 2024
2433227
update non-integration tests
rosecodym Jul 29, 2024
3f75432
extract enumerate()
rosecodym Jul 30, 2024
39a55b3
update enumerate
rosecodym Jul 30, 2024
e7e2afd
fix test setup
rosecodym Jul 30, 2024
122ffbf
init test correctly
rosecodym Jul 30, 2024
f53f5e7
stash
rosecodym Jul 30, 2024
13cd94e
add mock to support TestAddMembersByApp
rosecodym Jul 30, 2024
4d144e7
get integration tests compiling
rosecodym Jul 30, 2024
b48796a
update to github v63
rosecodym Jul 30, 2024
86fa529
un-extract enumeration
rosecodym Jul 31, 2024
0d2aea6
implement isGHE everywhere
rosecodym Jul 31, 2024
874fabf
remove old commented code
rosecodym Jul 31, 2024
52e0d41
wrap some errors
rosecodym Jul 31, 2024
ba14603
move cloudEndpoint
rosecodym Jul 31, 2024
909505d
add error check to test
rosecodym Jul 31, 2024
6d9ca1d
update+enable gist tests
rosecodym Jul 31, 2024
bf7edc8
stop pretending to auth in more places
rosecodym Jul 31, 2024
de3847b
update targeted scan test
rosecodym Aug 1, 2024
5ef11fe
update disabled tests
rosecodym Aug 1, 2024
585774e
enable another test
rosecodym Aug 1, 2024
ed047ee
enable another test
rosecodym Aug 1, 2024
64831d8
enable another test
rosecodym Aug 1, 2024
c9f26f0
update test
rosecodym Aug 1, 2024
dfa8a10
delete dead commented code
rosecodym Aug 1, 2024
7a344c7
delete obsolete test code
rosecodym Aug 1, 2024
366446a
self-review
rosecodym Aug 1, 2024
e019a1c
clean up regular tests file
rosecodym Aug 1, 2024
872b16c
remove HttpClient from connector
rosecodym Aug 1, 2024
0fe6175
add comments
rosecodym Aug 1, 2024
7b376bc
remove stuff from interface
rosecodym Aug 1, 2024
c7a14ac
remove some cruft
rosecodym Aug 1, 2024
778fa1a
remove more cruft
rosecodym Aug 1, 2024
33b982e
remove even more cruft
rosecodym Aug 1, 2024
2eda83e
review feedback
rosecodym Aug 2, 2024
5b8d602
use consts for http timeout
rosecodym Aug 2, 2024
fe0c961
Merge remote-tracking branch 'origin/main' into auth-github-targeted-…
rosecodym Aug 2, 2024
f0e37e3
typecheck in only one place
rosecodym Aug 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions pkg/sources/github/connector.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package github

import (
"fmt"

gogit "github.com/go-git/go-git/v5"
"github.com/google/go-github/v63/github"
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb"
)

const cloudEndpoint = "https://api.github.com"

type connector interface {
// APIClient returns a configured GitHub client that can be used for GitHub API operations.
APIClient() *github.Client
// Clone clones a repository using the configured authentication information.
Clone(ctx context.Context, repoURL string) (string, *gogit.Repository, error)
}

func newConnector(source *Source) (connector, error) {
apiEndpoint := source.conn.Endpoint
if apiEndpoint == "" || endsWithGithub.MatchString(apiEndpoint) {
apiEndpoint = cloudEndpoint
}

switch cred := source.conn.GetCredential().(type) {
case *sourcespb.GitHub_GithubApp:
return newAppConnector(apiEndpoint, cred.GithubApp)
case *sourcespb.GitHub_BasicAuth:
return newBasicAuthConnector(apiEndpoint, cred.BasicAuth)
case *sourcespb.GitHub_Token:
return newTokenConnector(apiEndpoint, cred.Token, source.handleRateLimit)
case *sourcespb.GitHub_Unauthenticated:
return newUnauthenticatedConnector(apiEndpoint)
default:
return nil, fmt.Errorf("unknown connection type")
}
}
96 changes: 96 additions & 0 deletions pkg/sources/github/connector_app.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package github

import (
"fmt"
"strconv"

"github.com/bradleyfalzon/ghinstallation/v2"
gogit "github.com/go-git/go-git/v5"
"github.com/google/go-github/v63/github"
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/credentialspb"
"github.com/trufflesecurity/trufflehog/v3/pkg/sources/git"
)

type appConnector struct {
apiClient *github.Client
installationClient *github.Client
installationID int64
}

var _ connector = (*appConnector)(nil)

func newAppConnector(apiEndpoint string, app *credentialspb.GitHubApp) (*appConnector, error) {
installationID, err := strconv.ParseInt(app.InstallationId, 10, 64)
if err != nil {
return nil, fmt.Errorf("could not parse app installation ID %q: %w", app.InstallationId, err)
}

appID, err := strconv.ParseInt(app.AppId, 10, 64)
if err != nil {
return nil, fmt.Errorf("could not parse app ID %q: %w", appID, err)
}

const httpTimeoutSeconds = 60
httpClient := common.RetryableHTTPClientTimeout(int64(httpTimeoutSeconds))

installationTransport, err := ghinstallation.NewAppsTransport(
httpClient.Transport,
appID,
[]byte(app.PrivateKey))
if err != nil {
return nil, fmt.Errorf("could not create installation client transport: %w", err)
}
installationTransport.BaseURL = apiEndpoint

installationHttpClient := common.RetryableHTTPClientTimeout(60)
installationHttpClient.Transport = installationTransport
installationClient, err := github.NewClient(installationHttpClient).WithEnterpriseURLs(apiEndpoint, apiEndpoint)
if err != nil {
return nil, fmt.Errorf("could not create installation client: %w", err)
}

apiTransport, err := ghinstallation.New(
httpClient.Transport,
appID,
installationID,
[]byte(app.PrivateKey))
if err != nil {
return nil, fmt.Errorf("could not create API client transport: %w", err)
}
apiTransport.BaseURL = apiEndpoint

httpClient.Transport = apiTransport
apiClient, err := github.NewClient(httpClient).WithEnterpriseURLs(apiEndpoint, apiEndpoint)
if err != nil {
return nil, fmt.Errorf("could not create API client: %w", err)
}

return &appConnector{
apiClient: apiClient,
installationClient: installationClient,
installationID: installationID,
}, nil
}

func (c *appConnector) APIClient() *github.Client {
return c.apiClient
}

func (c *appConnector) Clone(ctx context.Context, repoURL string) (string, *gogit.Repository, error) {
// TODO: Check rate limit for this call.
token, _, err := c.installationClient.Apps.CreateInstallationToken(
ctx,
c.installationID,
&github.InstallationTokenOptions{})
if err != nil {
return "", nil, fmt.Errorf("could not create installation token: %w", err)
}

return git.CloneRepoUsingToken(ctx, token.GetToken(), repoURL, "x-access-token")
}

func (c *appConnector) InstallationClient() *github.Client {
return c.installationClient
}
48 changes: 48 additions & 0 deletions pkg/sources/github/connector_basicauth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package github

import (
"fmt"

gogit "github.com/go-git/go-git/v5"
"github.com/google/go-github/v63/github"
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/credentialspb"
"github.com/trufflesecurity/trufflehog/v3/pkg/sources/git"
)

type basicAuthConnector struct {
apiClient *github.Client
username string
password string
}

var _ connector = (*basicAuthConnector)(nil)

func newBasicAuthConnector(apiEndpoint string, cred *credentialspb.BasicAuth) (*basicAuthConnector, error) {
const httpTimeoutSeconds = 60
httpClient := common.RetryableHTTPClientTimeout(int64(httpTimeoutSeconds))
httpClient.Transport = &github.BasicAuthTransport{
Username: cred.Username,
Password: cred.Password,
}

apiClient, err := createGitHubClient(httpClient, apiEndpoint)
if err != nil {
return nil, fmt.Errorf("could not create API client: %w", err)
}

return &basicAuthConnector{
apiClient: apiClient,
username: cred.Username,
password: cred.Password,
}, nil
}

func (c *basicAuthConnector) APIClient() *github.Client {
return c.apiClient
}

func (c *basicAuthConnector) Clone(ctx context.Context, repoURL string) (string, *gogit.Repository, error) {
return git.CloneRepoUsingToken(ctx, c.password, repoURL, c.username)
}
97 changes: 97 additions & 0 deletions pkg/sources/github/connector_token.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package github

import (
"fmt"
"strings"
"sync"

gogit "github.com/go-git/go-git/v5"
"github.com/google/go-github/v63/github"
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
"github.com/trufflesecurity/trufflehog/v3/pkg/sources/git"
"golang.org/x/oauth2"
)

type tokenConnector struct {
apiClient *github.Client
token string
isGitHubEnterprise bool
handleRateLimit func(error) bool
user string
userMu sync.Mutex
}

var _ connector = (*tokenConnector)(nil)

func newTokenConnector(apiEndpoint string, token string, handleRateLimit func(error) bool) (*tokenConnector, error) {
const httpTimeoutSeconds = 60
httpClient := common.RetryableHTTPClientTimeout(int64(httpTimeoutSeconds))
tokenSource := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token})
httpClient.Transport = &oauth2.Transport{
Base: httpClient.Transport,
Source: oauth2.ReuseTokenSource(nil, tokenSource),
}

apiClient, err := createGitHubClient(httpClient, apiEndpoint)
if err != nil {
return nil, fmt.Errorf("could not create API client: %w", err)
}

return &tokenConnector{
apiClient: apiClient,
token: token,
isGitHubEnterprise: !strings.EqualFold(apiEndpoint, cloudEndpoint),
handleRateLimit: handleRateLimit,
}, nil
}

func (c *tokenConnector) APIClient() *github.Client {
return c.apiClient
}

func (c *tokenConnector) Clone(ctx context.Context, repoURL string) (string, *gogit.Repository, error) {
if err := c.setUserIfUnset(ctx); err != nil {
return "", nil, err
}
return git.CloneRepoUsingToken(ctx, c.token, repoURL, c.user)
}

func (c *tokenConnector) IsGithubEnterprise() bool {
return c.isGitHubEnterprise
}

func (c *tokenConnector) getUser(ctx context.Context) (string, error) {
var (
user *github.User
err error
)
for {
user, _, err = c.apiClient.Users.Get(ctx, "")
if c.handleRateLimit(err) {
continue
}
if err != nil {
return "", fmt.Errorf("could not get GitHub user: %w", err)
}
break
}
return user.GetLogin(), nil
}

func (c *tokenConnector) setUserIfUnset(ctx context.Context) error {
c.userMu.Lock()
defer c.userMu.Unlock()

if c.user != "" {
return nil
}

user, err := c.getUser(ctx)
if err != nil {
return err
}

c.user = user
return nil
}
37 changes: 37 additions & 0 deletions pkg/sources/github/connector_unauthenticated.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package github

import (
"fmt"

gogit "github.com/go-git/go-git/v5"
"github.com/google/go-github/v63/github"
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
"github.com/trufflesecurity/trufflehog/v3/pkg/sources/git"
)

type unauthenticatedConnector struct {
apiClient *github.Client
}

var _ connector = (*unauthenticatedConnector)(nil)

func newUnauthenticatedConnector(apiEndpoint string) (*unauthenticatedConnector, error) {
const httpTimeoutSeconds = 60
httpClient := common.RetryableHTTPClientTimeout(int64(httpTimeoutSeconds))
apiClient, err := createGitHubClient(httpClient, apiEndpoint)
if err != nil {
return nil, fmt.Errorf("could not create API client: %w", err)
}
return &unauthenticatedConnector{
apiClient: apiClient,
}, nil
}

func (c *unauthenticatedConnector) APIClient() *github.Client {
return c.apiClient
}

func (c *unauthenticatedConnector) Clone(ctx context.Context, repoURL string) (string, *gogit.Repository, error) {
return git.CloneRepoUsingUnauthenticated(ctx, repoURL)
}
Loading
Loading