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

[GitHub] Add a GraphQL client to the connector #3837

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -269,6 +269,8 @@ require (
github.com/sendgrid/rest v2.6.9+incompatible // indirect
github.com/shirou/gopsutil/v3 v3.23.12 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/shurcooL/githubv4 v0.0.0-20240727222349-48295856cce7 // indirect
github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/skeema/knownhosts v1.3.0 // indirect
github.com/sorairolake/lzip-go v0.3.5 // indirect
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -675,6 +675,10 @@ github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
github.com/shuheiktgw/go-travis v0.3.1 h1:SAT16mi77ccqogOslnXxBXzXbpeyChaIYUwi2aJpVZY=
github.com/shuheiktgw/go-travis v0.3.1/go.mod h1:avnFFDqJDdRHwlF9tgqvYi3asQCm/HGL8aLxYiKa4Yg=
github.com/shurcooL/githubv4 v0.0.0-20240727222349-48295856cce7 h1:cYCy18SHPKRkvclm+pWm1Lk4YrREb4IOIb/YdFO0p2M=
github.com/shurcooL/githubv4 v0.0.0-20240727222349-48295856cce7/go.mod h1:zqMwyHmnN/eDOZOdiTohqIUKUrTFX62PNlu7IJdu0q8=
github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 h1:17JxqqJY66GmZVHkmAsGEkcIu0oCe3AM420QDgGwZx0=
github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466/go.mod h1:9dIRpgIY7hVhoqfe0/FcYp0bpInZaT7dc3BYOprrIUE=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
76 changes: 75 additions & 1 deletion pkg/sources/github/connector.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,91 @@
package github

import (
"fmt"
"net/http"
"net/url"
"strings"

gogit "github.com/go-git/go-git/v5"
"github.com/google/go-github/v67/github"
"github.com/shurcooL/githubv4"

"github.com/trufflesecurity/trufflehog/v3/pkg/context"
"github.com/trufflesecurity/trufflehog/v3/pkg/log"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb"
)

const cloudEndpoint = "https://api.github.com"
const (
cloudV3Endpoint = "https://api.github.com"
cloudGraphqlEndpoint = "https://api.github.com/graphql" // https://docs.github.com/en/graphql/guides/forming-calls-with-graphql#the-graphql-endpoint
)

// Connector abstracts over the authenticated ways to interact with GitHub: cloning and API operations.
type Connector interface {
// APIClient returns a configured GitHub client that can be used for GitHub API operations.
APIClient() *github.Client
// GraphQLClient returns a client that can be used for GraphQL operations.
GraphQLClient() *githubv4.Client
// Clone clones a repository using the configured authentication information.
Clone(ctx context.Context, repoURL string, args ...string) (string, *gogit.Repository, error)
}

func newConnector(ctx context.Context, source *Source) (Connector, error) {
apiEndpoint := source.conn.Endpoint
if apiEndpoint == "" || endsWithGithub.MatchString(apiEndpoint) {
apiEndpoint = cloudV3Endpoint
}

switch cred := source.conn.GetCredential().(type) {
case *sourcespb.GitHub_GithubApp:
log.RedactGlobally(cred.GithubApp.GetPrivateKey())
return NewAppConnector(ctx, apiEndpoint, cred.GithubApp)
case *sourcespb.GitHub_BasicAuth:
log.RedactGlobally(cred.BasicAuth.GetPassword())
return NewBasicAuthConnector(ctx, apiEndpoint, cred.BasicAuth)
case *sourcespb.GitHub_Token:
log.RedactGlobally(cred.Token)
return NewTokenConnector(ctx, apiEndpoint, cred.Token, func(c context.Context, err error) bool {
return source.handleRateLimit(c, err)
})
case *sourcespb.GitHub_Unauthenticated:
return NewUnauthenticatedConnector(ctx, apiEndpoint)
default:
return nil, fmt.Errorf("unknown connection type %T", source.conn.GetCredential())
}
}

func createAPIClient(ctx context.Context, httpClient *http.Client, apiEndpoint string) (*github.Client, error) {
getLogger(ctx).V(2).Info("Creating API client", "url", apiEndpoint)

// If we're using public GitHub, make a regular client.
// Otherwise, make an enterprise client.
if strings.EqualFold(apiEndpoint, cloudV3Endpoint) {
return github.NewClient(httpClient), nil
}

return github.NewClient(httpClient).WithEnterpriseURLs(apiEndpoint, apiEndpoint)
}

func createGraphqlClient(ctx context.Context, client *http.Client, apiEndpoint string) (*githubv4.Client, error) {
var graphqlEndpoint string
if apiEndpoint == cloudV3Endpoint {
graphqlEndpoint = cloudGraphqlEndpoint
} else {
// Use the root endpoint for the host.
// https://docs.github.com/en/enterprise-server@3.11/graphql/guides/introduction-to-graphql
parsedURL, err := url.Parse(apiEndpoint)
if err != nil {
return nil, fmt.Errorf("could not create GraphQL client: %w", err)
}

// GitHub Enterprise uses `/api/v3` for the base. (https://github.com/google/go-github/issues/958)
// Swap it, and anything before `/api`, with GraphQL.
before, _ := strings.CutSuffix(parsedURL.Path, "/api/v3")
parsedURL.Path = before + "/api/graphql"
graphqlEndpoint = parsedURL.String()
}
getLogger(ctx).V(2).Info("Creating GraphQL client", "url", graphqlEndpoint)

return githubv4.NewEnterpriseClient(graphqlEndpoint, client), nil
}
15 changes: 14 additions & 1 deletion pkg/sources/github/connector_app.go
Original file line number Diff line number Diff line change
@@ -7,6 +7,8 @@ import (
"github.com/bradleyfalzon/ghinstallation/v2"
gogit "github.com/go-git/go-git/v5"
"github.com/google/go-github/v67/github"
"github.com/shurcooL/githubv4"

"github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/credentialspb"
@@ -15,13 +17,14 @@ import (

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

var _ Connector = (*appConnector)(nil)

func NewAppConnector(apiEndpoint string, app *credentialspb.GitHubApp) (Connector, error) {
func NewAppConnector(ctx context.Context, apiEndpoint string, app *credentialspb.GitHubApp) (Connector, 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)
@@ -67,8 +70,14 @@ func NewAppConnector(apiEndpoint string, app *credentialspb.GitHubApp) (Connecto
return nil, fmt.Errorf("could not create API client: %w", err)
}

graphqlClient, err := createGraphqlClient(ctx, httpClient, apiEndpoint)
if err != nil {
return nil, err
}

return &appConnector{
apiClient: apiClient,
graphqlClient: graphqlClient,
installationClient: installationClient,
installationID: installationID,
}, nil
@@ -91,6 +100,10 @@ func (c *appConnector) Clone(ctx context.Context, repoURL string, args ...string
return git.CloneRepoUsingToken(ctx, token.GetToken(), repoURL, "x-access-token", args...)
}

func (c *appConnector) GraphQLClient() *githubv4.Client {
return c.graphqlClient
}

func (c *appConnector) InstallationClient() *github.Client {
return c.installationClient
}
29 changes: 21 additions & 8 deletions pkg/sources/github/connector_basicauth.go
Original file line number Diff line number Diff line change
@@ -5,37 +5,46 @@ import (

gogit "github.com/go-git/go-git/v5"
"github.com/google/go-github/v67/github"
"github.com/shurcooL/githubv4"

"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
apiClient *github.Client
graphqlClient *githubv4.Client
username string
password string
}

var _ Connector = (*basicAuthConnector)(nil)

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

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

graphqlClient, err := createGraphqlClient(ctx, httpClient, apiEndpoint)
if err != nil {
return nil, err
}

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

@@ -46,3 +55,7 @@ func (c *basicAuthConnector) APIClient() *github.Client {
func (c *basicAuthConnector) Clone(ctx context.Context, repoURL string, args ...string) (string, *gogit.Repository, error) {
return git.CloneRepoUsingToken(ctx, c.password, repoURL, c.username, args...)
}

func (c *basicAuthConnector) GraphQLClient() *githubv4.Client {
return c.graphqlClient
}
26 changes: 20 additions & 6 deletions pkg/sources/github/connector_token.go
Original file line number Diff line number Diff line change
@@ -7,15 +7,19 @@ import (

gogit "github.com/go-git/go-git/v5"
"github.com/google/go-github/v67/github"
"github.com/shurcooL/githubv4"
"golang.org/x/oauth2"

"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
token string
apiClient *github.Client
graphqlClient *githubv4.Client

isGitHubEnterprise bool
handleRateLimit func(context.Context, error) bool
user string
@@ -24,7 +28,7 @@ type tokenConnector struct {

var _ Connector = (*tokenConnector)(nil)

func NewTokenConnector(apiEndpoint string, token string, handleRateLimit func(context.Context, error) bool) (Connector, error) {
func NewTokenConnector(ctx context.Context, apiEndpoint string, token string, handleRateLimit func(context.Context, error) bool) (Connector, error) {
const httpTimeoutSeconds = 60
httpClient := common.RetryableHTTPClientTimeout(int64(httpTimeoutSeconds))
tokenSource := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token})
@@ -33,15 +37,21 @@ func NewTokenConnector(apiEndpoint string, token string, handleRateLimit func(co
Source: tokenSource,
}

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

graphqlClient, err := createGraphqlClient(ctx, httpClient, apiEndpoint)
if err != nil {
return nil, err
}

return &tokenConnector{
apiClient: apiClient,
graphqlClient: graphqlClient,
token: token,
isGitHubEnterprise: !strings.EqualFold(apiEndpoint, cloudEndpoint),
isGitHubEnterprise: !strings.EqualFold(apiEndpoint, cloudV3Endpoint),
handleRateLimit: handleRateLimit,
}, nil
}
@@ -57,6 +67,10 @@ func (c *tokenConnector) Clone(ctx context.Context, repoURL string, args ...stri
return git.CloneRepoUsingToken(ctx, c.token, repoURL, c.user, args...)
}

func (c *tokenConnector) GraphQLClient() *githubv4.Client {
return c.graphqlClient
}

func (c *tokenConnector) IsGithubEnterprise() bool {
return c.isGitHubEnterprise
}
21 changes: 17 additions & 4 deletions pkg/sources/github/connector_unauthenticated.go
Original file line number Diff line number Diff line change
@@ -5,27 +5,36 @@ import (

gogit "github.com/go-git/go-git/v5"
"github.com/google/go-github/v67/github"
"github.com/shurcooL/githubv4"

"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
apiClient *github.Client
graphqlClient *githubv4.Client
}

var _ Connector = (*unauthenticatedConnector)(nil)

func NewUnauthenticatedConnector(apiEndpoint string) (Connector, error) {
func NewUnauthenticatedConnector(ctx context.Context, apiEndpoint string) (Connector, error) {
const httpTimeoutSeconds = 60
httpClient := common.RetryableHTTPClientTimeout(int64(httpTimeoutSeconds))
apiClient, err := createGitHubClient(httpClient, apiEndpoint)
apiClient, err := createAPIClient(ctx, httpClient, apiEndpoint)
if err != nil {
return nil, fmt.Errorf("could not create API client: %w", err)
}

graphqlClient, err := createGraphqlClient(ctx, httpClient, apiEndpoint)
if err != nil {
return nil, err
}

return &unauthenticatedConnector{
apiClient: apiClient,
apiClient: apiClient,
graphqlClient: graphqlClient,
}, nil
}

@@ -36,3 +45,7 @@ func (c *unauthenticatedConnector) APIClient() *github.Client {
func (c *unauthenticatedConnector) Clone(ctx context.Context, repoURL string, args ...string) (string, *gogit.Repository, error) {
return git.CloneRepoUsingUnauthenticated(ctx, repoURL, args...)
}

func (c *unauthenticatedConnector) GraphQLClient() *githubv4.Client {
return c.graphqlClient
}
Loading
Oops, something went wrong.