Skip to content

Commit

Permalink
🧹 hash github conn options
Browse files Browse the repository at this point in the history
Signed-off-by: Salim Afiune Maya <[email protected]>
  • Loading branch information
afiune committed Dec 16, 2024
1 parent 4eeebea commit 533e7f1
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 45 deletions.
118 changes: 74 additions & 44 deletions providers/github/connection/connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/cockroachdb/errors"
"github.com/google/go-github/v67/github"
"github.com/hashicorp/go-retryablehttp"
"github.com/mitchellh/hashstructure/v2"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"go.mondoo.com/cnquery/v11/logger/zerologadapter"
Expand All @@ -25,6 +26,7 @@ import (
)

const (
OPTION_TOKEN = "token"
OPTION_REPOS = "repos"
OPTION_REPOS_EXCLUDE = "repos-exclude"
OPTION_APP_ID = "app-id"
Expand All @@ -38,25 +40,75 @@ type GithubConnection struct {
asset *inventory.Asset
client *github.Client
ctx context.Context

// Used to avoid verifying a client with the same options more than once
Hash uint64
}

type githubConnectionOptions struct {
// Maps to OPTION_ENTERPRISE_URL
EnterpriseURL string

// Maps to OPTION_APP_ID
AppID string
// Maps to OPTION_APP_INSTALLATION_ID
AppInstallationID string
// Maps to OPTION_APP_PRIVATE_KEY
AppPrivateKeyFile string
AppPrivateKey []byte
// Maps to OPTION_TOKEN or the environment variable GITHUB_TOKEN
Token string
}

func connectionOptionsFromConfigOptions(conf *inventory.Config) (opts githubConnectionOptions) {
if conf == nil {
return
}

opts.AppID = conf.Options[OPTION_APP_ID]
opts.AppInstallationID = conf.Options[OPTION_APP_INSTALLATION_ID]
opts.AppPrivateKeyFile = conf.Options[OPTION_APP_PRIVATE_KEY]
opts.EnterpriseURL = conf.Options[OPTION_ENTERPRISE_URL]
opts.Token = conf.Options[OPTION_TOKEN]

if opts.Token == "" {
opts.Token = os.Getenv("GITHUB_TOKEN")
}

for _, cred := range conf.Credentials {
switch cred.Type {

case vault.CredentialType_private_key:
if opts.AppPrivateKeyFile != "" {
opts.AppPrivateKey = cred.Secret
}

case vault.CredentialType_password:
if opts.Token != "" {
opts.Token = string(cred.Secret)
}
}
}

return
}

func NewGithubConnection(id uint32, asset *inventory.Asset) (*GithubConnection, error) {
conf := asset.Connections[0]
connectionOpts := connectionOptionsFromConfigOptions(asset.Connections[0])

var client *github.Client
var err error
appIdStr := conf.Options[OPTION_APP_ID]
if appIdStr != "" {
client, err = newGithubAppClient(conf)
if connectionOpts.AppID != "" {
client, err = newGithubAppClient(connectionOpts)
} else {
client, err = newGithubTokenClient(conf)
client, err = newGithubTokenClient(connectionOpts)
}
if err != nil {
return nil, err
}

if enterpriseUrl := conf.Options[OPTION_ENTERPRISE_URL]; enterpriseUrl != "" {
parsedUrl, err := url.Parse(enterpriseUrl)
if connectionOpts.EnterpriseURL != "" {
parsedUrl, err := url.Parse(connectionOpts.EnterpriseURL)
if err != nil {
return nil, err
}
Expand All @@ -73,11 +125,15 @@ func NewGithubConnection(id uint32, asset *inventory.Asset) (*GithubConnection,
// (default behaviour is to send fake 403 response bypassing the retry logic)
ctx := context.WithValue(context.Background(), github.SleepUntilPrimaryRateLimitResetWhenRateLimited, true)

// store the hash of the config options used to generate this client
hash, err := hashstructure.Hash(connectionOpts, hashstructure.FormatV2, nil)

return &GithubConnection{
Connection: plugin.NewConnection(id, asset),
asset: asset,
client: client,
ctx: ctx,
Hash: hash,
}, nil
}

Expand Down Expand Up @@ -111,38 +167,28 @@ func (c *GithubConnection) Verify() error {
return nil
}

func newGithubAppClient(conf *inventory.Config) (*github.Client, error) {
appIdStr := conf.Options[OPTION_APP_ID]
if appIdStr == "" {
func newGithubAppClient(opts githubConnectionOptions) (*github.Client, error) {
if opts.AppID == "" {
return nil, errors.New("app-id is required for GitHub App authentication")
}
appId, err := strconv.ParseInt(appIdStr, 10, 32)
appId, err := strconv.ParseInt(opts.AppID, 10, 32)
if err != nil {
return nil, err
}

appInstallationIdStr := conf.Options[OPTION_APP_INSTALLATION_ID]
if appInstallationIdStr == "" {
if opts.AppInstallationID == "" {
return nil, errors.New("app-installation-id is required for GitHub App authentication")
}
appInstallationId, err := strconv.ParseInt(appInstallationIdStr, 10, 32)
appInstallationId, err := strconv.ParseInt(opts.AppInstallationID, 10, 32)
if err != nil {
return nil, err
}

var itr *ghinstallation.Transport
if pk := conf.Options[OPTION_APP_PRIVATE_KEY]; pk != "" {
itr, err = ghinstallation.NewKeyFromFile(http.DefaultTransport, appId, appInstallationId, pk)
if opts.AppPrivateKeyFile != "" {
itr, err = ghinstallation.NewKeyFromFile(http.DefaultTransport, appId, appInstallationId, opts.AppPrivateKeyFile)
} else {
for _, cred := range conf.Credentials {
switch cred.Type {
case vault.CredentialType_private_key:
itr, err = ghinstallation.New(http.DefaultTransport, appId, appInstallationId, cred.Secret)
if err != nil {
return nil, err
}
}
}
itr, err = ghinstallation.New(http.DefaultTransport, appId, appInstallationId, opts.AppPrivateKey)
}
if err != nil {
return nil, err
Expand All @@ -155,31 +201,15 @@ func newGithubAppClient(conf *inventory.Config) (*github.Client, error) {
return github.NewClient(newGithubRetryableClient(&http.Client{Transport: itr})), nil
}

func newGithubTokenClient(conf *inventory.Config) (*github.Client, error) {
token := ""
for i := range conf.Credentials {
cred := conf.Credentials[i]
switch cred.Type {
case vault.CredentialType_password:
token = string(cred.Secret)
}
}

if token == "" {
token = conf.Options["token"]
if token == "" {
token = os.Getenv("GITHUB_TOKEN")
}
}

func newGithubTokenClient(opts githubConnectionOptions) (*github.Client, error) {
// if we still have no token, error out
if token == "" {
if opts.Token == "" {
return nil, errors.New("a valid GitHub token is required, pass --token '<yourtoken>' or set GITHUB_TOKEN environment variable")
}

ctx := context.Background()
ts := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: token},
&oauth2.Token{AccessToken: opts.Token},
)
tc := oauth2.NewClient(ctx, ts)
return github.NewClient(newGithubRetryableClient(tc)), nil
Expand Down
3 changes: 2 additions & 1 deletion providers/github/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package provider
import (
"context"
"errors"
"fmt"
"os"
"strings"
"time"
Expand Down Expand Up @@ -173,7 +174,7 @@ func (s *Service) connect(req *plugin.ConnectReq, callback plugin.ProviderCallba
}

// verify the connection only once
_, err, _ = s.Memoize("conn_verify", func() (interface{}, error) {
_, err, _ = s.Memoize(fmt.Sprintf("conn_%d", conn.Hash), func() (interface{}, error) {
log.Trace().Msg("verifying github connection client")
err := conn.Verify()
return nil, err
Expand Down

0 comments on commit 533e7f1

Please sign in to comment.