From de1f48111acde716bea832eb843d2c42afb170fb Mon Sep 17 00:00:00 2001 From: Felipe Galindo Sanchez Date: Wed, 22 Dec 2021 16:24:10 -0800 Subject: [PATCH] feat: support routing GitHub API calls to custom proxy API (#1017) GitHub currently has some limitations w.r.t permissions management on runner groups as they all require org admin, however at our company we're using runner groups to serve different internal teams (with different permissions), thus we needed to deploy a custom proxy API with our internal authentication to provide who has access to certain APIs depending on the repository/runner group on a given org/enterprise This change just allows to optionally send the GitHub API calls to an alternate custom proxy URL instead of cloud github (github.com) or an enterprise URL with basic authentication Co-authored-by: Yusuke Kuoka --- charts/actions-runner-controller/README.md | 5 ++ .../templates/deployment.yaml | 22 ++++++++ .../templates/githubwebhook.deployment.yaml | 26 ++++++++++ .../templates/manager_secrets.yaml | 3 ++ charts/actions-runner-controller/values.yaml | 8 +++ cmd/githubwebhookserver/main.go | 7 ++- github/github.go | 50 ++++++++++++++++++- main.go | 5 ++ 8 files changed, 124 insertions(+), 2 deletions(-) diff --git a/charts/actions-runner-controller/README.md b/charts/actions-runner-controller/README.md index 2beb1242..12b52d52 100644 --- a/charts/actions-runner-controller/README.md +++ b/charts/actions-runner-controller/README.md @@ -17,6 +17,9 @@ All additional docs are kept in the `docs/` folder, this README is solely for do | `leaderElectionId` | Set the election ID for the controller group | | | `githubAPICacheDuration` | Set the cache period for API calls | | | `githubEnterpriseServerURL` | Set the URL for a self-hosted GitHub Enterprise Server | | +| `githubURL` | Override GitHub URL to be used for GitHub API calls | | +| `githubUploadURL` | Override GitHub Upload URL to be used for GitHub API calls | | +| `runnerGithubURL` | Override GitHub URL to be used by runners during registration | | | `logLevel` | Set the log level of the controller container | | | `additionalVolumes` | Set additional volumes to add to the manager container | | | `additionalVolumeMounts` | Set additional volume mounts to add to the manager container | | @@ -27,6 +30,8 @@ All additional docs are kept in the `docs/` folder, this README is solely for do | `authSecret.github_app_installation_id` | The ID of your GitHub App installation. **This can't be set at the same time as `authSecret.github_token`** | | | `authSecret.github_app_private_key` | The multiline string of your GitHub App's private key. **This can't be set at the same time as `authSecret.github_token`** | | | `authSecret.github_token` | Your chosen GitHub PAT token. **This can't be set at the same time as the `authSecret.github_app_*`** | | +| `authSecret.github_basicauth_username` | Username for GitHub basic auth to use instead of PAT or GitHub APP in case it's running behind a proxy API | | +| `authSecret.github_basicauth_password` | Password for GitHub basic auth to use instead of PAT or GitHub APP in case it's running behind a proxy API | | | `dockerRegistryMirror` | The default Docker Registry Mirror used by runners. | | | `image.repository` | The "repository/image" of the controller container | summerwind/actions-runner-controller | | `image.tag` | The tag of the controller container | | diff --git a/charts/actions-runner-controller/templates/deployment.yaml b/charts/actions-runner-controller/templates/deployment.yaml index 594aaba7..8885a6a5 100644 --- a/charts/actions-runner-controller/templates/deployment.yaml +++ b/charts/actions-runner-controller/templates/deployment.yaml @@ -61,6 +61,9 @@ spec: {{- if .Values.logLevel }} - "--log-level={{ .Values.logLevel }}" {{- end }} + {{- if .Values.runnerGithubURL }} + - "--runner-github-url={{ .Values.runnerGithubURL }}" + {{- end }} command: - "/manager" env: @@ -68,6 +71,14 @@ spec: - name: GITHUB_ENTERPRISE_URL value: {{ .Values.githubEnterpriseServerURL }} {{- end }} + {{- if .Values.githubURL }} + - name: GITHUB_URL + value: {{ .Values.githubURL }} + {{- end }} + {{- if .Values.githubUploadURL }} + - name: GITHUB_UPLOAD_URL + value: {{ .Values.githubUploadURL }} + {{- end }} {{- if .Values.authSecret.enabled }} - name: GITHUB_TOKEN valueFrom: @@ -93,6 +104,17 @@ spec: key: github_app_private_key name: {{ include "actions-runner-controller.secretName" . }} optional: true + {{- if .Values.authSecret.github_basicauth_username }} + - name: GITHUB_BASICAUTH_USERNAME + value: {{ .Values.authSecret.github_basicauth_username }} + {{- end }} + {{- if .Values.authSecret.github_basicauth_password }} + - name: GITHUB_BASICAUTH_PASSWORD + valueFrom: + secretKeyRef: + key: github_basicauth_password + name: {{ include "actions-runner-controller.secretName" . }} + {{- end }} {{- end }} {{- range $key, $val := .Values.env }} - name: {{ $key }} diff --git a/charts/actions-runner-controller/templates/githubwebhook.deployment.yaml b/charts/actions-runner-controller/templates/githubwebhook.deployment.yaml index 5cc350e0..624a98c2 100644 --- a/charts/actions-runner-controller/templates/githubwebhook.deployment.yaml +++ b/charts/actions-runner-controller/templates/githubwebhook.deployment.yaml @@ -45,6 +45,9 @@ spec: {{- if .Values.scope.singleNamespace }} - "--watch-namespace={{ default .Release.Namespace .Values.scope.watchNamespace }}" {{- end }} + {{- if .Values.runnerGithubURL }} + - "--runner-github-url={{ .Values.runnerGithubURL }}" + {{- end }} command: - "/github-webhook-server" env: @@ -54,6 +57,18 @@ spec: key: github_webhook_secret_token name: {{ include "actions-runner-controller-github-webhook-server.secretName" . }} optional: true + {{- if .Values.githubEnterpriseServerURL }} + - name: GITHUB_ENTERPRISE_URL + value: {{ .Values.githubEnterpriseServerURL }} + {{- end }} + {{- if .Values.githubURL }} + - name: GITHUB_URL + value: {{ .Values.githubURL }} + {{- end }} + {{- if .Values.githubUploadURL }} + - name: GITHUB_UPLOAD_URL + value: {{ .Values.githubUploadURL }} + {{- end }} {{- if .Values.authSecret.enabled }} - name: GITHUB_TOKEN valueFrom: @@ -79,6 +94,17 @@ spec: key: github_app_private_key name: {{ include "actions-runner-controller.secretName" . }} optional: true + {{- if .Values.authSecret.github_basicauth_username }} + - name: GITHUB_BASICAUTH_USERNAME + value: {{ .Values.authSecret.github_basicauth_username }} + {{- end }} + {{- if .Values.authSecret.github_basicauth_password }} + - name: GITHUB_BASICAUTH_PASSWORD + valueFrom: + secretKeyRef: + key: github_basicauth_password + name: {{ include "actions-runner-controller.secretName" . }} + {{- end }} {{- end }} {{- range $key, $val := .Values.githubWebhookServer.env }} - name: {{ $key }} diff --git a/charts/actions-runner-controller/templates/manager_secrets.yaml b/charts/actions-runner-controller/templates/manager_secrets.yaml index dc3d259c..7d95c5cf 100644 --- a/charts/actions-runner-controller/templates/manager_secrets.yaml +++ b/charts/actions-runner-controller/templates/manager_secrets.yaml @@ -26,4 +26,7 @@ data: {{- if .Values.authSecret.github_token }} github_token: {{ .Values.authSecret.github_token | toString | b64enc }} {{- end }} +{{- if .Values.authSecret.github_basicauth_password }} + github_basicauth_password: {{ .Values.authSecret.github_basicauth_password | toString | b64enc }} +{{- end }} {{- end }} diff --git a/charts/actions-runner-controller/values.yaml b/charts/actions-runner-controller/values.yaml index d63fffb2..0dbbd246 100644 --- a/charts/actions-runner-controller/values.yaml +++ b/charts/actions-runner-controller/values.yaml @@ -21,6 +21,11 @@ enableLeaderElection: true # The URL of your GitHub Enterprise server, if you're using one. #githubEnterpriseServerURL: https://github.example.com +# Override GitHub URLs in case of using proxy APIs +#githubURL: "" +#githubUploadURL: "" +#runnerGithubURL: "" + # Only 1 authentication method can be deployed at a time # Uncomment the configuration you are applying and fill in the details # @@ -41,6 +46,9 @@ authSecret: #github_app_private_key: | ### GitHub PAT Configuration #github_token: "" + ### Basic auth for github API proxy + #github_basicauth_username: "" + #github_basicauth_password: "" dockerRegistryMirror: "" image: diff --git a/cmd/githubwebhookserver/main.go b/cmd/githubwebhookserver/main.go index b440fd54..2c410a52 100644 --- a/cmd/githubwebhookserver/main.go +++ b/cmd/githubwebhookserver/main.go @@ -103,6 +103,11 @@ func main() { flag.Int64Var(&c.AppID, "github-app-id", c.AppID, "The application ID of GitHub App.") flag.Int64Var(&c.AppInstallationID, "github-app-installation-id", c.AppInstallationID, "The installation ID of GitHub App.") flag.StringVar(&c.AppPrivateKey, "github-app-private-key", c.AppPrivateKey, "The path of a private key file to authenticate as a GitHub App") + flag.StringVar(&c.URL, "github-url", c.URL, "GitHub URL to be used for GitHub API calls") + flag.StringVar(&c.UploadURL, "github-upload-url", c.UploadURL, "GitHub Upload URL to be used for GitHub API calls") + flag.StringVar(&c.BasicauthUsername, "github-basicauth-username", c.BasicauthUsername, "Username for GitHub basic auth to use instead of PAT or GitHub APP in case it's running behind a proxy API") + flag.StringVar(&c.BasicauthPassword, "github-basicauth-password", c.BasicauthPassword, "Password for GitHub basic auth to use instead of PAT or GitHub APP in case it's running behind a proxy API") + flag.StringVar(&c.RunnerGitHubURL, "runner-github-url", c.RunnerGitHubURL, "GitHub URL to be used by runners during registration") flag.Parse() @@ -137,7 +142,7 @@ func main() { } }) - if len(c.Token) > 0 || (c.AppID > 0 && c.AppInstallationID > 0 && c.AppPrivateKey != "") { + if len(c.Token) > 0 || (c.AppID > 0 && c.AppInstallationID > 0 && c.AppPrivateKey != "") || (len(c.BasicauthUsername) > 0 && len(c.BasicauthPassword) > 0) { ghClient, err = c.NewClient() if err != nil { fmt.Fprintln(os.Stderr, "Error: Client creation failed.", err) diff --git a/github/github.go b/github/github.go index 3ad428f6..76ed7e08 100644 --- a/github/github.go +++ b/github/github.go @@ -23,6 +23,11 @@ type Config struct { AppInstallationID int64 `split_words:"true"` AppPrivateKey string `split_words:"true"` Token string + URL string `split_words:"true"` + UploadURL string `split_words:"true"` + BasicauthUsername string `split_words:"true"` + BasicauthPassword string `split_words:"true"` + RunnerGitHubURL string `split_words:"true"` } // Client wraps GitHub client with some additional @@ -34,10 +39,23 @@ type Client struct { GithubBaseURL string } +type BasicAuthTransport struct { + Username string + Password string +} + +func (p BasicAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) { + req.SetBasicAuth(p.Username, p.Password) + req.Header.Set("User-Agent", "actions-runner-controller") + return http.DefaultTransport.RoundTrip(req) +} + // NewClient creates a Github Client func (c *Config) NewClient() (*Client, error) { var transport http.RoundTripper - if len(c.Token) > 0 { + if len(c.BasicauthUsername) > 0 && len(c.BasicauthPassword) > 0 { + transport = BasicAuthTransport{Username: c.BasicauthUsername, Password: c.BasicauthPassword} + } else if len(c.Token) > 0 { transport = oauth2.NewClient(context.Background(), oauth2.StaticTokenSource(&oauth2.Token{AccessToken: c.Token})).Transport } else { var tr *ghinstallation.Transport @@ -63,6 +81,7 @@ func (c *Config) NewClient() (*Client, error) { } transport = tr } + transport = metrics.Transport{Transport: transport} httpClient := &http.Client{Transport: transport} @@ -78,6 +97,35 @@ func (c *Config) NewClient() (*Client, error) { } else { client = github.NewClient(httpClient) githubBaseURL = "https://github.com/" + + if len(c.URL) > 0 { + baseUrl, err := url.Parse(c.URL) + if err != nil { + return nil, fmt.Errorf("github client creation failed: %v", err) + } + if !strings.HasSuffix(baseUrl.Path, "/") { + baseUrl.Path += "/" + } + client.BaseURL = baseUrl + } + + if len(c.UploadURL) > 0 { + uploadUrl, err := url.Parse(c.UploadURL) + if err != nil { + return nil, fmt.Errorf("github client creation failed: %v", err) + } + if !strings.HasSuffix(uploadUrl.Path, "/") { + uploadUrl.Path += "/" + } + client.UploadURL = uploadUrl + } + + if len(c.RunnerGitHubURL) > 0 { + githubBaseURL = c.RunnerGitHubURL + if !strings.HasSuffix(githubBaseURL, "/") { + githubBaseURL += "/" + } + } } return &Client{ diff --git a/main.go b/main.go index 439555a1..43c55961 100644 --- a/main.go +++ b/main.go @@ -111,6 +111,11 @@ func main() { flag.Int64Var(&c.AppID, "github-app-id", c.AppID, "The application ID of GitHub App.") flag.Int64Var(&c.AppInstallationID, "github-app-installation-id", c.AppInstallationID, "The installation ID of GitHub App.") flag.StringVar(&c.AppPrivateKey, "github-app-private-key", c.AppPrivateKey, "The path of a private key file to authenticate as a GitHub App") + flag.StringVar(&c.URL, "github-url", c.URL, "GitHub URL to be used for GitHub API calls") + flag.StringVar(&c.UploadURL, "github-upload-url", c.UploadURL, "GitHub Upload URL to be used for GitHub API calls") + flag.StringVar(&c.BasicauthUsername, "github-basicauth-username", c.BasicauthUsername, "Username for GitHub basic auth to use instead of PAT or GitHub APP in case it's running behind a proxy API") + flag.StringVar(&c.BasicauthPassword, "github-basicauth-password", c.BasicauthPassword, "Password for GitHub basic auth to use instead of PAT or GitHub APP in case it's running behind a proxy API") + flag.StringVar(&c.RunnerGitHubURL, "runner-github-url", c.RunnerGitHubURL, "GitHub URL to be used by runners during registration") flag.DurationVar(&gitHubAPICacheDuration, "github-api-cache-duration", 0, "The duration until the GitHub API cache expires. Setting this to e.g. 10m results in the controller tries its best not to make the same API call within 10m to reduce the chance of being rate-limited. Defaults to mostly the same value as sync-period. If you're tweaking this in order to make autoscaling more responsive, you'll probably want to tweak sync-period, too") flag.DurationVar(&syncPeriod, "sync-period", 10*time.Minute, "Determines the minimum frequency at which K8s resources managed by this controller are reconciled. When you use autoscaling, set to a lower value like 10 minute, because this corresponds to the minimum time to react on demand change. . If you're tweaking this in order to make autoscaling more responsive, you'll probably want to tweak github-api-cache-duration, too") flag.Var(&commonRunnerLabels, "common-runner-labels", "Runner labels in the K1=V1,K2=V2,... format that are inherited all the runners created by the controller. See https://github.com/actions-runner-controller/actions-runner-controller/issues/321 for more information")