ci: get token from github app (#42674)

* create github-app token

* use flags

* gomod

* return error

* test gentoken

* testing wip

* add tests

* addressing PR feedback

* lint

* go mod tidy

* ineff

* fix flags

* Update enterprise/dev/ci/scripts/app-token/main_test.go

Co-authored-by: Jean-Hadrien Chabran <jh@chabran.fr>

* fix exit code

Co-authored-by: Jean-Hadrien Chabran <jh@chabran.fr>
This commit is contained in:
Dave Try 2022-11-09 12:33:03 -05:00 committed by GitHub
parent 4345944f0a
commit 9a98849cb9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 286 additions and 0 deletions

View File

@ -0,0 +1,98 @@
package main
import (
"context"
"crypto/x509"
"encoding/pem"
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"time"
"github.com/golang-jwt/jwt"
"github.com/google/go-github/v47/github"
"golang.org/x/oauth2"
"github.com/sourcegraph/sourcegraph/lib/errors"
)
func main() {
appID := flag.String("appid", os.Getenv("GITHUB_APP_ID"), "(required) github application id.")
keyPath := flag.String("keypath", os.Getenv("KEY_PATH"), "(required) path to private key file for github app.")
flag.Parse()
if len(*appID) == 0 || len(*keyPath) == 0 {
flag.PrintDefaults()
os.Exit(1)
}
jwt, err := genJwtToken(*appID, *keyPath)
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
ts := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: jwt},
)
tc := oauth2.NewClient(ctx, ts)
ghc := github.NewClient(tc)
appToken, err := getInstallAccessToken(ctx, ghc)
if err != nil {
log.Fatal(err)
}
fmt.Println(*appToken)
}
func genJwtToken(appID string, keyPath string) (string, error) {
rawPem, err := ioutil.ReadFile(keyPath)
if err != nil {
return "", errors.Wrap(err, "Failed to read key file.")
}
privPem, _ := pem.Decode(rawPem)
if privPem == nil {
return "", errors.Wrap(nil, "failed to decode PEM block containing public key")
}
priv, err := x509.ParsePKCS1PrivateKey(privPem.Bytes)
if err != nil {
return "", errors.Wrap(err, "Failed to parse key.")
}
// Create new JWT token with 10 minute (max duration) expiry
token := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims{
"iat": time.Now().Unix() - 60,
"exp": time.Now().Unix() + (10 * 60),
"iss": appID,
})
jwtString, err := token.SignedString(priv)
if err != nil {
return "", errors.Wrap(err, "Failed to create token.")
}
return jwtString, nil
}
func getInstallAccessToken(ctx context.Context, ghc *github.Client) (*string, error) {
// Get organation installation ID
orgInstallation, _, err := ghc.Apps.FindOrganizationInstallation(ctx, "sourcegraph")
if err != nil {
log.Fatal(err)
}
orgID := orgInstallation.ID
// Create new installation token with 60 minute duraction with default read repo contents permissions
token, _, err := ghc.Apps.CreateInstallationToken(ctx, *orgID, &github.InstallationTokenOptions{
Repositories: []string{"sourcegraph"},
})
if err != nil {
log.Fatal(err)
}
return token.Token, nil
}

View File

@ -0,0 +1,70 @@
package main
import (
"context"
"flag"
"net/http"
"os"
"path/filepath"
"strings"
"testing"
"github.com/dnaeon/go-vcr/cassette"
"github.com/google/go-github/v47/github"
"github.com/stretchr/testify/require"
"golang.org/x/oauth2"
"github.com/sourcegraph/sourcegraph/internal/httptestutil"
)
var updateRecordings = flag.Bool("update-integration", false, "refresh integration test recordings")
func TestGenJwtToken(t *testing.T) {
if os.Getenv("BUILDKITE") == "true" {
t.Skip("Skipping testing in CI environment")
} else {
appID := os.Getenv("GITHUB_APP_ID")
require.NotEmpty(t, appID, "GITHUB_APP_ID must be set.")
keyPath := os.Getenv("KEY_PATH")
require.NotEmpty(t, keyPath, "KEY_PATH must be set.")
_, err := genJwtToken(appID, keyPath)
require.NoError(t, err)
}
}
func newTestGitHubClient(ctx context.Context, t *testing.T) (ghc *github.Client, stop func() error) {
recording := filepath.Join("tests/testdata", strings.ReplaceAll(t.Name(), " ", "-"))
recorder, err := httptestutil.NewRecorder(recording, *updateRecordings, func(i *cassette.Interaction) error {
return nil
})
if err != nil {
t.Fatal(err)
}
if *updateRecordings {
appID := os.Getenv("GITHUB_APP_ID")
require.NotEmpty(t, appID, "GITHUB_APP_ID must be set.")
keyPath := os.Getenv("KEY_PATH")
require.NotEmpty(t, keyPath, "KEY_PATH must be set.")
jwt, err := genJwtToken(appID, keyPath)
if err != nil {
t.Fatal(err)
httpClient := oauth2.NewClient(ctx, oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: jwt},
))
recorder.SetTransport(httpClient.Transport)
}
}
return github.NewClient(&http.Client{Transport: recorder}), recorder.Stop
}
func TestGetInstallAccessToken(t *testing.T) {
ctx := context.Background()
ghc, stop := newTestGitHubClient(ctx, t)
defer stop()
_, err := getInstallAccessToken(ctx, ghc)
require.NoError(t, err)
}

View File

@ -0,0 +1,113 @@
---
version: 1
interactions:
- request:
body: ""
form: {}
headers:
Accept:
- application/vnd.github.v3+json
User-Agent:
- go-github/v47.0.0
url: https://api.github.com/orgs/sourcegraph/installation
method: GET
response:
body: '{"id":30579796,"account":{"login":"sourcegraph","id":3979584,"node_id":"MDEyOk9yZ2FuaXphdGlvbjM5Nzk1ODQ=","avatar_url":"https://avatars.githubusercontent.com/u/3979584?v=4","gravatar_id":"","url":"https://api.github.com/users/sourcegraph","html_url":"https://github.com/sourcegraph","followers_url":"https://api.github.com/users/sourcegraph/followers","following_url":"https://api.github.com/users/sourcegraph/following{/other_user}","gists_url":"https://api.github.com/users/sourcegraph/gists{/gist_id}","starred_url":"https://api.github.com/users/sourcegraph/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sourcegraph/subscriptions","organizations_url":"https://api.github.com/users/sourcegraph/orgs","repos_url":"https://api.github.com/users/sourcegraph/repos","events_url":"https://api.github.com/users/sourcegraph/events{/privacy}","received_events_url":"https://api.github.com/users/sourcegraph/received_events","type":"Organization","site_admin":false},"repository_selection":"selected","access_tokens_url":"https://api.github.com/app/installations/30579796/access_tokens","repositories_url":"https://api.github.com/installation/repositories","html_url":"https://github.com/organizations/sourcegraph/settings/installations/30579796","app_id":244693,"app_slug":"buildkite-token-gen","target_id":3979584,"target_type":"Organization","permissions":{"contents":"read","metadata":"read"},"events":[],"created_at":"2022-10-25T12:35:08.000Z","updated_at":"2022-10-25T12:35:09.000Z","single_file_name":null,"has_multiple_single_files":false,"single_file_paths":[],"suspended_by":null,"suspended_at":null}'
headers:
Access-Control-Allow-Origin:
- '*'
Access-Control-Expose-Headers:
- ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining,
X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes,
X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO,
X-GitHub-Request-Id, Deprecation, Sunset
Cache-Control:
- public, max-age=60, s-maxage=60
Content-Security-Policy:
- default-src 'none'
Content-Type:
- application/json; charset=utf-8
Date:
- Tue, 25 Oct 2022 14:27:36 GMT
Etag:
- W/"5fd5963d4e6164d8a0976397a1e24af2454fea5d3f31a9c100f8d6d04a8219ee"
Referrer-Policy:
- origin-when-cross-origin, strict-origin-when-cross-origin
Server:
- GitHub.com
Strict-Transport-Security:
- max-age=31536000; includeSubdomains; preload
Vary:
- Accept
- Accept-Encoding, Accept, X-Requested-With
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- deny
X-Github-Media-Type:
- github.v3; format=json
X-Github-Request-Id:
- E647:08F0:C5A40A:194F1A7:6357F258
X-Xss-Protection:
- "0"
status: 200 OK
code: 200
duration: ""
- request:
body: |
{"repositories":["sourcegraph"]}
form: {}
headers:
Accept:
- application/vnd.github.v3+json
Content-Type:
- application/json
User-Agent:
- go-github/v47.0.0
url: https://api.github.com/app/installations/30579796/access_tokens
method: POST
response:
body: '{"token":"dummy","expires_at":"2022-10-25T15:27:36Z","permissions":{"contents":"read","metadata":"read"},"repository_selection":"selected","repositories":[{"id":41288708,"node_id":"MDEwOlJlcG9zaXRvcnk0MTI4ODcwOA==","name":"sourcegraph","full_name":"sourcegraph/sourcegraph","private":false,"owner":{"login":"sourcegraph","id":3979584,"node_id":"MDEyOk9yZ2FuaXphdGlvbjM5Nzk1ODQ=","avatar_url":"https://avatars.githubusercontent.com/u/3979584?v=4","gravatar_id":"","url":"https://api.github.com/users/sourcegraph","html_url":"https://github.com/sourcegraph","followers_url":"https://api.github.com/users/sourcegraph/followers","following_url":"https://api.github.com/users/sourcegraph/following{/other_user}","gists_url":"https://api.github.com/users/sourcegraph/gists{/gist_id}","starred_url":"https://api.github.com/users/sourcegraph/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sourcegraph/subscriptions","organizations_url":"https://api.github.com/users/sourcegraph/orgs","repos_url":"https://api.github.com/users/sourcegraph/repos","events_url":"https://api.github.com/users/sourcegraph/events{/privacy}","received_events_url":"https://api.github.com/users/sourcegraph/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/sourcegraph/sourcegraph","description":"Universal
code search (self-hosted)","fork":false,"url":"https://api.github.com/repos/sourcegraph/sourcegraph","forks_url":"https://api.github.com/repos/sourcegraph/sourcegraph/forks","keys_url":"https://api.github.com/repos/sourcegraph/sourcegraph/keys{/key_id}","collaborators_url":"https://api.github.com/repos/sourcegraph/sourcegraph/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/sourcegraph/sourcegraph/teams","hooks_url":"https://api.github.com/repos/sourcegraph/sourcegraph/hooks","issue_events_url":"https://api.github.com/repos/sourcegraph/sourcegraph/issues/events{/number}","events_url":"https://api.github.com/repos/sourcegraph/sourcegraph/events","assignees_url":"https://api.github.com/repos/sourcegraph/sourcegraph/assignees{/user}","branches_url":"https://api.github.com/repos/sourcegraph/sourcegraph/branches{/branch}","tags_url":"https://api.github.com/repos/sourcegraph/sourcegraph/tags","blobs_url":"https://api.github.com/repos/sourcegraph/sourcegraph/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/sourcegraph/sourcegraph/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/sourcegraph/sourcegraph/git/refs{/sha}","trees_url":"https://api.github.com/repos/sourcegraph/sourcegraph/git/trees{/sha}","statuses_url":"https://api.github.com/repos/sourcegraph/sourcegraph/statuses/{sha}","languages_url":"https://api.github.com/repos/sourcegraph/sourcegraph/languages","stargazers_url":"https://api.github.com/repos/sourcegraph/sourcegraph/stargazers","contributors_url":"https://api.github.com/repos/sourcegraph/sourcegraph/contributors","subscribers_url":"https://api.github.com/repos/sourcegraph/sourcegraph/subscribers","subscription_url":"https://api.github.com/repos/sourcegraph/sourcegraph/subscription","commits_url":"https://api.github.com/repos/sourcegraph/sourcegraph/commits{/sha}","git_commits_url":"https://api.github.com/repos/sourcegraph/sourcegraph/git/commits{/sha}","comments_url":"https://api.github.com/repos/sourcegraph/sourcegraph/comments{/number}","issue_comment_url":"https://api.github.com/repos/sourcegraph/sourcegraph/issues/comments{/number}","contents_url":"https://api.github.com/repos/sourcegraph/sourcegraph/contents/{+path}","compare_url":"https://api.github.com/repos/sourcegraph/sourcegraph/compare/{base}...{head}","merges_url":"https://api.github.com/repos/sourcegraph/sourcegraph/merges","archive_url":"https://api.github.com/repos/sourcegraph/sourcegraph/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/sourcegraph/sourcegraph/downloads","issues_url":"https://api.github.com/repos/sourcegraph/sourcegraph/issues{/number}","pulls_url":"https://api.github.com/repos/sourcegraph/sourcegraph/pulls{/number}","milestones_url":"https://api.github.com/repos/sourcegraph/sourcegraph/milestones{/number}","notifications_url":"https://api.github.com/repos/sourcegraph/sourcegraph/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/sourcegraph/sourcegraph/labels{/name}","releases_url":"https://api.github.com/repos/sourcegraph/sourcegraph/releases{/id}","deployments_url":"https://api.github.com/repos/sourcegraph/sourcegraph/deployments","created_at":"2015-08-24T07:27:28Z","updated_at":"2022-10-25T08:43:48Z","pushed_at":"2022-10-25T14:27:19Z","git_url":"git://github.com/sourcegraph/sourcegraph.git","ssh_url":"git@github.com:sourcegraph/sourcegraph.git","clone_url":"https://github.com/sourcegraph/sourcegraph.git","svn_url":"https://github.com/sourcegraph/sourcegraph","homepage":"https://sourcegraph.com","size":797242,"stargazers_count":7045,"watchers_count":7045,"language":"Go","has_issues":true,"has_projects":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":850,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":4673,"license":{"key":"other","name":"Other","spdx_id":"NOASSERTION","url":null,"node_id":"MDc6TGljZW5zZTA="},"allow_forking":true,"is_template":false,"web_commit_signoff_required":false,"topics":["code-intelligence","code-search","lsif-enabled","open-source","repo-type-main","sourcegraph"],"visibility":"public","forks":850,"open_issues":4673,"watchers":7045,"default_branch":"main"}]}'
headers:
Access-Control-Allow-Origin:
- '*'
Access-Control-Expose-Headers:
- ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining,
X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes,
X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO,
X-GitHub-Request-Id, Deprecation, Sunset
Cache-Control:
- public, max-age=60, s-maxage=60
Content-Length:
- "5577"
Content-Security-Policy:
- default-src 'none'
Content-Type:
- application/json; charset=utf-8
Date:
- Tue, 25 Oct 2022 14:27:36 GMT
Etag:
- '"75eee91f9a00496ee85b18131e18dbcb9e867504cbb64204b4dff8107989190f"'
Referrer-Policy:
- origin-when-cross-origin, strict-origin-when-cross-origin
Server:
- GitHub.com
Strict-Transport-Security:
- max-age=31536000; includeSubdomains; preload
Vary:
- Accept
- Accept-Encoding, Accept, X-Requested-With
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- deny
X-Github-Media-Type:
- github.v3; format=json
X-Github-Request-Id:
- E647:08F0:C5A443:194F21D:6357F258
X-Xss-Protection:
- "0"
status: 201 Created
code: 201
duration: ""

2
go.mod
View File

@ -219,6 +219,8 @@ require (
github.com/coreos/go-iptables v0.6.0
github.com/dcadenas/pagerank v0.0.0-20171013173705-af922e3ceea8
github.com/frankban/quicktest v1.14.3
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/google/go-github/v47 v47.1.0
github.com/hashicorp/hcl v1.0.0
github.com/opsgenie/opsgenie-go-sdk-v2 v1.2.13
github.com/prometheus/prometheus v0.37.1

3
go.sum
View File

@ -1035,6 +1035,7 @@ github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXP
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/gogo/status v1.1.0/go.mod h1:BFv9nrluPLmrS0EmGVvLaPNmRosr9KapBYd5/hpY1WM=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
@ -1154,6 +1155,8 @@ github.com/google/go-github/v41 v41.0.0 h1:HseJrM2JFf2vfiZJ8anY2hqBjdfY1Vlj/K27u
github.com/google/go-github/v41 v41.0.0/go.mod h1:XgmCA5H323A9rtgExdTcnDkcqp6S30AVACCBDOonIxg=
github.com/google/go-github/v43 v43.0.0 h1:y+GL7LIsAIF2NZlJ46ZoC/D1W1ivZasT0lnWHMYPZ+U=
github.com/google/go-github/v43 v43.0.0/go.mod h1:ZkTvvmCXBvsfPpTHXnH/d2hP9Y0cTbvN9kr5xqyXOIc=
github.com/google/go-github/v47 v47.1.0 h1:Cacm/WxQBOa9lF0FT0EMjZ2BWMetQ1TQfyurn4yF1z8=
github.com/google/go-github/v47 v47.1.0/go.mod h1:VPZBXNbFSJGjyjFRUKo9vZGawTajnWzC/YjGw/oFKi0=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=