Skip to content

Commit

Permalink
Introduce GitHub App authentication for GitHub Scaler (#4708)
Browse files Browse the repository at this point in the history
Signed-off-by: Eldarrin <mortxbox@live.com>
Signed-off-by: Andy Ward <mortx@toothless.eldarrin.io>
Signed-off-by: Eldarrin <32762846+Eldarrin@users.noreply.github.com>
Co-authored-by: Andy Ward <mortx@toothless.eldarrin.io>
  • Loading branch information
Eldarrin and Andy Ward authored Jun 21, 2023
1 parent 33f9f06 commit d5ef935
Show file tree
Hide file tree
Showing 156 changed files with 52,989 additions and 13 deletions.
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,9 @@ To learn more about active deprecations, we recommend checking [GitHub Discussio
- **Azure Data Explorer Scaler**: Use azidentity SDK ([#4489](https://github.com/kedacore/keda/issues/4489))
- **External Scaler**: Add tls options in TriggerAuth metadata. ([#3565](https://github.com/kedacore/keda/issues/3565))
- **GCP PubSub Scaler**: Make it more flexible for metrics ([#4243](https://github.com/kedacore/keda/issues/4243))
- **Kafka Scaler**: Add support for OAuth extensions ([#4544](https://github.com/kedacore/keda/issues/4544))
- **NATS JetStream Scaler**: Add support for pulling AccountID from TriggerAuthentication ([#4586]https://github.com/kedacore/keda/issues/4586)
- **GitHub Runner Scaler**: Added support for GitHub App authentication ([#4651](https://github.com/kedacore/keda/issues/4651))
- **Kafka Scaler:** Add support for OAuth extensions ([#4544](https://github.com/kedacore/keda/issues/4544))
- **NATS JetStream Scaler:** Add support for pulling AccountID from TriggerAuthentication ([#4586]https://github.com/kedacore/keda/issues/4586)
- **Pulsar Scaler**: Improve error messages for unsuccessful connections ([#4563](https://github.com/kedacore/keda/issues/4563))
- **Security**: Enable secret scanning in GitHub repo
- **RabbitMQ Scaler**: Add support for `unsafeSsl` in trigger metadata ([#4448](https://github.com/kedacore/keda/issues/4448))
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ require (
github.com/Huawei/gophercloud v1.0.21
github.com/Shopify/sarama v1.38.1
github.com/arangodb/go-driver v1.6.0
github.com/bradleyfalzon/ghinstallation/v2 v2.5.0
github.com/aws/aws-sdk-go v1.44.286
github.com/denisenkom/go-mssqldb v0.12.3
github.com/dysnix/predictkube-libs v0.0.4-0.20230109175007-5a82fccd31c7
Expand Down Expand Up @@ -188,6 +189,7 @@ require (
github.com/golang/snappy v0.0.4 // indirect
github.com/google/cel-go v0.13.0 // indirect
github.com/google/gnostic v0.6.9 // indirect
github.com/google/go-github/v53 v53.0.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10 // indirect
Expand Down
9 changes: 9 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4g
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/compute v1.19.3 h1:DcTwsFgGev/wV5+q8o2fzgcHOaac+DKGC91ZlvpsQds=
cloud.google.com/go/compute v1.19.3/go.mod h1:qxvISKp/gYnXkSAD1ppcSOveRAmzxicEv/JlizULFrI=
cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
Expand Down Expand Up @@ -154,10 +155,13 @@ github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2y
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/bradleyfalzon/ghinstallation/v2 v2.5.0 h1:yaYcGQ7yEIGbsJfW/9z7v1sLiZg/5rSNNXwmMct5XaE=
github.com/bradleyfalzon/ghinstallation/v2 v2.5.0/go.mod h1:amcvPQMrRkWNdueWOjPytGL25xQGzox7425qMgzo+Vo=
github.com/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao=
github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M=
github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs=
github.com/cenkalti/backoff/v4 v4.2.0 h1:HN5dHm3WBOgndBH6E8V0q2jIYIR3s9yglV8k/+MN3u4=
Expand Down Expand Up @@ -375,10 +379,13 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-github/v50 v50.2.0 h1:j2FyongEHlO9nxXLc+LP3wuBSVU9mVxfpdYUexMpIfk=
github.com/google/go-github/v50 v50.2.0/go.mod h1:VBY8FB6yPIjrtKhozXv4FQupxKLS6H4m6xFZlT43q8Q=
github.com/google/go-github/v53 v53.0.0 h1:T1RyHbSnpHYnoF0ZYKiIPSgPtuJ8G6vgc0MKodXsQDQ=
github.com/google/go-github/v53 v53.0.0/go.mod h1:XhFRObz+m/l+UCm9b7KSIC3lT3NWSXGt7mOsAWEloao=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
Expand Down Expand Up @@ -918,6 +925,7 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
Expand Down Expand Up @@ -1088,6 +1096,7 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
65 changes: 58 additions & 7 deletions pkg/scalers/github_runner_scaler.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"strings"
"time"

gha "github.com/bradleyfalzon/ghinstallation/v2"
"github.com/go-logr/logr"
v2 "k8s.io/api/autoscaling/v2"
"k8s.io/metrics/pkg/apis/external_metrics"
Expand Down Expand Up @@ -38,11 +39,14 @@ type githubRunnerMetadata struct {
githubAPIURL string
owner string
runnerScope string
personalAccessToken string
personalAccessToken *string
repos []string
labels []string
targetWorkflowQueueLength int64
scalerIndex int
applicationID *int64
installationID *int64
applicationKey *string
}

type WorkflowRuns struct {
Expand Down Expand Up @@ -334,6 +338,15 @@ func NewGitHubRunnerScaler(config *ScalerConfig) (Scaler, error) {
return nil, fmt.Errorf("error parsing GitHub Runner metadata: %w", err)
}

if meta.applicationID != nil && meta.installationID != nil && meta.applicationKey != nil {
httpTrans := kedautil.CreateHTTPTransport(false)
hc, err := gha.New(httpTrans, *meta.applicationID, *meta.installationID, []byte(*meta.applicationKey))
if err != nil {
return nil, fmt.Errorf("error creating GitHub App client: %w, \n appID: %d, instID: %d", err, meta.applicationID, meta.installationID)
}
httpClient = &http.Client{Transport: hc}
}

return &githubRunnerScaler{
metricType: metricType,
metadata: meta,
Expand Down Expand Up @@ -367,7 +380,7 @@ func getInt64ValueFromMetaOrEnv(key string, config *ScalerConfig) (int64, error)
}

func parseGitHubRunnerMetadata(config *ScalerConfig) (*githubRunnerMetadata, error) {
meta := githubRunnerMetadata{}
meta := &githubRunnerMetadata{}
meta.targetWorkflowQueueLength = defaultTargetWorkflowQueueLength

if val, err := getValueFromMetaOrEnv("runnerScope", config.TriggerMetadata, config.ResolvedEnv); err == nil && val != "" {
Expand Down Expand Up @@ -403,15 +416,50 @@ func parseGitHubRunnerMetadata(config *ScalerConfig) (*githubRunnerMetadata, err
}

if val, ok := config.AuthParams["personalAccessToken"]; ok && val != "" {
// Found the organizationURL in a parameter from TriggerAuthentication
meta.personalAccessToken = val
// Found the pat token in a parameter from TriggerAuthentication
meta.personalAccessToken = &val
}

if appID, instID, key, err := setupGitHubApp(config); err == nil {
meta.applicationID = appID
meta.installationID = instID
meta.applicationKey = key
} else {
return nil, fmt.Errorf("no personalAccessToken given")
return nil, err
}

if meta.applicationKey == nil && meta.personalAccessToken == nil {
return nil, fmt.Errorf("no personalAccessToken or appKey given")
}

meta.scalerIndex = config.ScalerIndex

return &meta, nil
return meta, nil
}

func setupGitHubApp(config *ScalerConfig) (*int64, *int64, *string, error) {
var appID *int64
var instID *int64
var appKey *string

if val, err := getInt64ValueFromMetaOrEnv("applicationID", config); err == nil && val != -1 {
appID = &val
}

if val, err := getInt64ValueFromMetaOrEnv("installationID", config); err == nil && val != -1 {
instID = &val
}

if val, ok := config.AuthParams["appKey"]; ok && val != "" {
appKey = &val
}

if (appID != nil || instID != nil || appKey != nil) &&
(appID == nil || instID == nil || appKey == nil) {
return nil, nil, nil, fmt.Errorf("applicationID, installationID and applicationKey must be given")
}

return appID, instID, appKey, nil
}

// getRepositories returns a list of repositories for a given organization, user or enterprise
Expand Down Expand Up @@ -457,9 +505,12 @@ func getGithubRequest(ctx context.Context, url string, metadata *githubRunnerMet
}

req.Header.Set("Accept", "application/vnd.github.v3+json")
req.Header.Set("Authorization", "Bearer "+metadata.personalAccessToken)
req.Header.Set("X-GitHub-Api-Version", "2022-11-28")

if metadata.applicationID == nil && metadata.personalAccessToken != nil {
req.Header.Set("Authorization", "Bearer "+*metadata.personalAccessToken)
}

r, err := httpClient.Do(req)
if err != nil {
return []byte{}, err
Expand Down
16 changes: 15 additions & 1 deletion pkg/scalers/github_runner_scaler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ var testGitHubRunnerResolvedEnv = map[string]string{
"OWNER": "ownername",
"LABELS": "foo,bar",
"REPOS": "reponame,otherrepo",
"APP_ID": "1",
"INST_ID": "2",
}

var testGitHubRunnerTokenEnv = map[string]string{
Expand Down Expand Up @@ -64,6 +66,10 @@ var testGitHubRunnerMetadata = []parseGitHubRunnerMetadataTestData{
{"empty owner", map[string]string{"githubApiURL": "https://api.github.com", "runnerScope": "repo", "owner": "", "repos": "reponame", "targetWorkflowQueueLength": "1"}, true, true, "no owner given"},
// empty token
{"empty targetWorkflowQueueLength", map[string]string{"githubApiURL": "https://api.github.com", "runnerScope": "repo", "owner": "ownername", "repos": "reponame"}, true, false, ""},
// missing installationID From Env
{"missing installationID Env", map[string]string{"githubApiURL": "https://api.github.com", "runnerScope": "org", "owner": "ownername", "repos": "reponame,otherrepo", "labels": "golang", "targetWorkflowQueueLength": "1", "applicationIDFromEnv": "APP_ID"}, true, true, "applicationID, installationID and applicationKey must be given"},
// missing applicationID From Env
{"missing applicationId Env", map[string]string{"githubApiURL": "https://api.github.com", "runnerScope": "org", "owner": "ownername", "repos": "reponame,otherrepo", "labels": "golang", "targetWorkflowQueueLength": "1", "installationIDFromEnv": "INST_ID"}, true, true, "applicationID, installationID and applicationKey must be given"},
// nothing passed
{"empty, no envs", map[string]string{}, false, true, "no runnerScope given"},
// empty githubApiURL
Expand Down Expand Up @@ -92,6 +98,12 @@ var testGitHubRunnerMetadata = []parseGitHubRunnerMetadataTestData{
{"missing repos, no envs", map[string]string{"githubApiURL": "https://api.github.com", "runnerScope": "org", "owner": "ownername", "labels": "golang", "targetWorkflowQueueLength": "1"}, false, false, ""},
// empty repos, no envs
{"empty repos, no envs", map[string]string{"githubApiURL": "https://api.github.com", "runnerScope": "org", "owner": "ownername", "labels": "golang", "repos": "", "targetWorkflowQueueLength": "1"}, false, false, ""},
// missing installationID
{"missing installationID", map[string]string{"githubApiURL": "https://api.github.com", "runnerScope": "org", "owner": "ownername", "repos": "reponame,otherrepo", "labels": "golang", "targetWorkflowQueueLength": "1", "applicationID": "1"}, true, true, "applicationID, installationID and applicationKey must be given"},
// missing applicationID
{"missing applicationID", map[string]string{"githubApiURL": "https://api.github.com", "runnerScope": "org", "owner": "ownername", "repos": "reponame,otherrepo", "labels": "golang", "targetWorkflowQueueLength": "1", "installationID": "1"}, true, true, "applicationID, installationID and applicationKey must be given"},
// all good
{"missing applicationKey", map[string]string{"githubApiURL": "https://api.github.com", "runnerScope": "org", "owner": "ownername", "repos": "reponame,otherrepo", "labels": "golang", "targetWorkflowQueueLength": "1", "applicationID": "1", "installationID": "1"}, true, true, "applicationID, installationID and applicationKey must be given"},
}

func TestGitHubRunnerParseMetadata(t *testing.T) {
Expand All @@ -117,11 +129,13 @@ func TestGitHubRunnerParseMetadata(t *testing.T) {
}

func getGitHubTestMetaData(url string) *githubRunnerMetadata {
testpat := "testpat"

meta := githubRunnerMetadata{
githubAPIURL: url,
runnerScope: "repo",
owner: "testOwner",
personalAccessToken: "testPAT",
personalAccessToken: &testpat,
targetWorkflowQueueLength: 1,
}

Expand Down
4 changes: 4 additions & 0 deletions tests/.env
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,8 @@ GH_SCOPE=
GH_REPOS=
GH_WORKFLOW_ID=
GH_SO_WORKFLOW_ID=
GH_GHA_WORKFLOW_ID=
GH_AUTOMATION_PAT=
GH_APP_ID=
GH_INST_ID=
GH_APP_KEY=
Loading

0 comments on commit d5ef935

Please sign in to comment.