Skip to content

Commit

Permalink
⭐️ github app authentication (#4041)
Browse files Browse the repository at this point in the history
* ⭐️ github app authentication

Signed-off-by: Ivan Milchev <[email protected]>

* add support for enterprise url

Signed-off-by: Ivan Milchev <[email protected]>

---------

Signed-off-by: Ivan Milchev <[email protected]>
  • Loading branch information
imilchev authored May 21, 2024
1 parent 271d3fb commit d3bd637
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 33 deletions.
24 changes: 24 additions & 0 deletions providers/github/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,30 @@ var Config = plugin.Provider{
Default: "",
Desc: "Only include repositories with matching names.",
},
{
Long: "app-id",
Type: plugin.FlagType_String,
Default: "",
Desc: "GitHub Application ID.",
},
{
Long: "app-installation-id",
Type: plugin.FlagType_String,
Default: "",
Desc: "GitHub Application installation ID.",
},
{
Long: "app-private-key",
Type: plugin.FlagType_String,
Default: "",
Desc: "GitHub Application private key.",
},
{
Long: "enterprise-url",
Type: plugin.FlagType_String,
Default: "",
Desc: "GitHub Enterprise URL.",
},
},
},
},
Expand Down
134 changes: 104 additions & 30 deletions providers/github/connection/connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,26 @@ package connection
import (
"context"
"net/http"
"net/url"
"os"
"strconv"

"github.com/bradleyfalzon/ghinstallation/v2"
"github.com/cockroachdb/errors"
"github.com/google/go-github/v61/github"
"github.com/rs/zerolog/log"
"go.mondoo.com/cnquery/v11/providers-sdk/v1/inventory"
"go.mondoo.com/cnquery/v11/providers-sdk/v1/plugin"
"go.mondoo.com/cnquery/v11/providers-sdk/v1/vault"
"golang.org/x/oauth2"
)

const (
OPTION_REPOS = "repos"
OPTION_REPOS_EXCLUDE = "repos-exclude"
OPTION_REPOS = "repos"
OPTION_REPOS_EXCLUDE = "repos-exclude"
OPTION_APP_ID = "app-id"
OPTION_APP_INSTALLATION_ID = "app-installation-id"
OPTION_APP_PRIVATE_KEY = "app-private-key"
OPTION_ENTERPRISE_URL = "enterprise-url"
)

type GithubConnection struct {
Expand All @@ -30,41 +36,35 @@ type GithubConnection struct {

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

// if no token was provided, lets read the env variable
if token == "" {
token = os.Getenv("GITHUB_TOKEN")
var client *github.Client
var err error
appIdStr := conf.Options[OPTION_APP_ID]
if appIdStr != "" {
client, err = newGithubAppClient(conf)
} else {
client, err = newGithubTokenClient(conf)
}

// if a secret was provided, it always overrides the env variable since it has precedence
if len(conf.Credentials) > 0 {
for i := range conf.Credentials {
cred := conf.Credentials[i]
if cred.Type == vault.CredentialType_password {
token = string(cred.Secret)
} else {
log.Warn().Str("credential-type", cred.Type.String()).Msg("unsupported credential type for GitHub provider")
}
}
if err != nil {
return nil, err
}

if token == "" {
return nil, errors.New("a valid GitHub token is required, pass --token '<yourtoken>' or set GITHUB_TOKEN environment variable")
}
if enterpriseUrl := conf.Options[OPTION_ENTERPRISE_URL]; enterpriseUrl != "" {
parsedUrl, err := url.Parse(enterpriseUrl)
if err != nil {
return nil, err
}

var oauthClient *http.Client
if token != "" {
ts := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: token},
)
ctx := context.Background()
oauthClient = oauth2.NewClient(ctx, ts)
baseUrl := parsedUrl.JoinPath("api/v3/")
uploadUrl := parsedUrl.JoinPath("api/uploads/")
client, err = client.WithEnterpriseURLs(baseUrl.String(), uploadUrl.String())
if err != nil {
return nil, err
}
}

client := github.NewClient(oauthClient)
// perform a quick call to verify the token's validity.
_, resp, err := client.Zen(context.Background())
_, resp, err := client.Meta.Zen(context.Background())
if err != nil {
if resp != nil && resp.StatusCode == 401 {
return nil, errors.New("invalid GitHub token provided. check the value passed with the --token flag or the GITHUB_TOKEN environment variable")
Expand All @@ -89,3 +89,77 @@ func (c *GithubConnection) Asset() *inventory.Asset {
func (c *GithubConnection) Client() *github.Client {
return c.client
}

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

appInstallationIdStr := conf.Options[OPTION_APP_INSTALLATION_ID]
if appInstallationIdStr == "" {
return nil, errors.New("app-installation-id is required for GitHub App authentication")
}
appInstallationId, err := strconv.ParseInt(appInstallationIdStr, 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)
} 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
}
}
}
}
if err != nil {
return nil, err
}

if itr == nil {
return nil, errors.New("app-private-key is required for GitHub App authentication")
}

return github.NewClient(&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")
}
}

// if we still have no token, error out
if 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},
)
tc := oauth2.NewClient(ctx, ts)
return github.NewClient(tc), nil
}
3 changes: 3 additions & 0 deletions providers/github/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ go 1.22
toolchain go1.22.0

require (
github.com/bradleyfalzon/ghinstallation/v2 v2.10.0
github.com/cockroachdb/errors v1.11.1
github.com/gobwas/glob v0.2.3
github.com/google/go-github/v61 v61.0.0
Expand Down Expand Up @@ -153,6 +154,7 @@ require (
github.com/gofrs/flock v0.8.1 // indirect
github.com/gofrs/uuid v4.4.0+incompatible // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.4 // indirect
Expand All @@ -166,6 +168,7 @@ require (
github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/go-containerregistry v0.19.1 // indirect
github.com/google/go-github/v60 v60.0.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/s2a-go v0.1.7 // indirect
Expand Down
6 changes: 6 additions & 0 deletions providers/github/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,8 @@ github.com/bodgit/windows v1.0.1 h1:tF7K6KOluPYygXa3Z2594zxlkbKPAOvqr97etrGNIz4=
github.com/bodgit/windows v1.0.1/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM=
github.com/bombsimon/wsl/v4 v4.2.1 h1:Cxg6u+XDWff75SIFFmNsqnIOgob+Q9hG6y/ioKbRFiM=
github.com/bombsimon/wsl/v4 v4.2.1/go.mod h1:Xu/kDxGZTofQcDGCtQe9KCzhHphIe0fDuyWTxER9Feo=
github.com/bradleyfalzon/ghinstallation/v2 v2.10.0 h1:XWuWBRFEpqVrHepQob9yPS3Xg4K3Wr9QCx4fu8HbUNg=
github.com/bradleyfalzon/ghinstallation/v2 v2.10.0/go.mod h1:qoGA4DxWPaYTgVCrmEspVSjlTu4WYAiSxMIhorMRXXc=
github.com/breml/bidichk v0.2.7 h1:dAkKQPLl/Qrk7hnP6P+E0xOodrq8Us7+U0o4UBOAlQY=
github.com/breml/bidichk v0.2.7/go.mod h1:YodjipAGI9fGcYM7II6wFvGhdMYsC5pHDlGzqvEW3tQ=
github.com/breml/errchkjson v0.3.6 h1:VLhVkqSBH96AvXEyclMR37rZslRrY2kcyq+31HCsVrA=
Expand Down Expand Up @@ -429,6 +431,8 @@ github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRx
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
Expand Down Expand Up @@ -502,6 +506,8 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-containerregistry v0.19.1 h1:yMQ62Al6/V0Z7CqIrrS1iYoA5/oQCm88DeNujc7C1KY=
github.com/google/go-containerregistry v0.19.1/go.mod h1:YCMFNQeeXeLF+dnhhWkqDItx/JSkH01j1Kis4PsjzFI=
github.com/google/go-github/v60 v60.0.0 h1:oLG98PsLauFvvu4D/YPxq374jhSxFYdzQGNCyONLfn8=
github.com/google/go-github/v60 v60.0.0/go.mod h1:ByhX2dP9XT9o/ll2yXAu2VD8l5eNVg8hD4Cr0S/LmQk=
github.com/google/go-github/v61 v61.0.0 h1:VwQCBwhyE9JclCI+22/7mLB1PuU9eowCXKY5pNlu1go=
github.com/google/go-github/v61 v61.0.0/go.mod h1:0WR+KmsWX75G2EbpyGsGmradjo3IiciuI4BmdVCobQY=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
Expand Down
24 changes: 21 additions & 3 deletions providers/github/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,35 @@ func (s *Service) ParseCLI(req *plugin.ParseCLIReq) (*plugin.ParseCLIRes, error)
Discover: &inventory.Discovery{},
}

if x, ok := flags["enterprise-url"]; ok && len(x.Value) != 0 {
conf.Options[connection.OPTION_ENTERPRISE_URL] = string(x.Value)
}

isAppAuth := false
if appId, ok := req.Flags[connection.OPTION_APP_ID]; ok && len(appId.Value) > 0 {
conf.Options[connection.OPTION_APP_ID] = string(appId.Value)

installId := req.Flags[connection.OPTION_APP_INSTALLATION_ID]
conf.Options[connection.OPTION_APP_INSTALLATION_ID] = string(installId.Value)

pk := req.Flags[connection.OPTION_APP_PRIVATE_KEY]
conf.Options[connection.OPTION_APP_PRIVATE_KEY] = string(pk.Value)
isAppAuth = true
}

token := ""
if x, ok := flags["token"]; ok && len(x.Value) != 0 {
token = string(x.Value)
}
if token == "" {
token = os.Getenv("GITHUB_TOKEN")
}
if token == "" {
return nil, errors.New("a valid GitHub token is required, pass --token '<yourtoken>' or set GITHUB_TOKEN environment variable")
if token == "" && !isAppAuth {
return nil, errors.New("a valid GitHub authentication is required, pass --token '<yourtoken>', set GITHUB_TOKEN environment variable or provider GitHub App credentials")
}
if token != "" {
conf.Credentials = append(conf.Credentials, vault.NewPasswordCredential("", token))
}
conf.Credentials = append(conf.Credentials, vault.NewPasswordCredential("", token))

// discovery flags
discoverTargets := []string{}
Expand Down

0 comments on commit d3bd637

Please sign in to comment.