From a7c510615c3f3f94cdcc75eb5ff5e78250e35e8f Mon Sep 17 00:00:00 2001 From: subencheng Date: Fri, 30 Jan 2026 15:25:08 -0800 Subject: [PATCH 1/4] baton-github: refersh appjwttoken once it expires --- pkg/connector/connector.go | 68 +++++++++++++++++++++++++++++--------- 1 file changed, 52 insertions(+), 16 deletions(-) diff --git a/pkg/connector/connector.go b/pkg/connector/connector.go index d4c9808a..ff361844 100644 --- a/pkg/connector/connector.go +++ b/pkg/connector/connector.go @@ -30,6 +30,9 @@ import ( const githubDotCom = "https://github.com" +// The JWT token expires in 10 minutes, so we set it to 9 minutes to leave some buffer. +const jwtExpiryTime = 9 * time.Minute + var ( ValidAssetDomains = []string{"avatars.githubusercontent.com"} maxPageSize int = 100 // maximum page size github supported. @@ -219,9 +222,9 @@ func (gh *GitHub) validateAppCredentials(ctx context.Context) (annotations.Annot return nil, fmt.Errorf("github-connector: only one org is allowed when using github app") } - _, resp, err := findInstallation(ctx, gh.appClient, orgLogins[0]) + _, err := findInstallation(ctx, gh.appClient, orgLogins[0]) if err != nil { - return nil, wrapGitHubError(err, resp, "github-connector: failed to retrieve org installation") + return nil, err } return nil, nil } @@ -273,9 +276,9 @@ func New(ctx context.Context, ghc *cfg.Github, appKey string) (*GitHub, error) { if err != nil { return nil, err } - installation, resp, err := findInstallation(ctx, appClient, ghc.Orgs[0]) + installation, err := findInstallation(ctx, appClient, ghc.Orgs[0]) if err != nil { - return nil, wrapGitHubError(err, resp, "github-connector: failed to find app installation") + return nil, err } token, err := getInstallationToken(ctx, appClient, installation.GetID()) @@ -292,7 +295,16 @@ func New(ctx context.Context, ghc *cfg.Github, appKey string) (*GitHub, error) { ctx: ctx, instanceURL: ghc.InstanceUrl, installationID: installation.GetID(), - jwttoken: jwttoken, + jwtTokenSource: oauth2.ReuseTokenSource( + &oauth2.Token{ + AccessToken: jwttoken, + Expiry: time.Now().Add(jwtExpiryTime), + }, + &appJWTTokenRefresher{ + appID: ghc.AppId, + privateKey: appKey, + }, + ), }, ) } @@ -377,28 +389,36 @@ func getClientToken(ghc *cfg.Github, privateKey string) (string, string, error) return "", ghc.Token, nil } - key, err := loadPrivateKeyFromString(privateKey) + token, err := getJWTToken(ghc.AppId, privateKey) if err != nil { return "", "", err } + return token, "", nil +} + +func getJWTToken(appID string, privateKey string) (string, error) { + key, err := loadPrivateKeyFromString(privateKey) + if err != nil { + return "", err + } now := time.Now() token, err := jwtv5.NewWithClaims(jwtv5.SigningMethodRS256, jwtv5.MapClaims{ "iat": now.Unix() - 60, // issued at "exp": now.Add(time.Minute * 10).Unix(), // expires - "iss": ghc.AppId, // GitHub App ID + "iss": appID, // GitHub App ID }).SignedString(key) if err != nil { - return "", "", err + return "", err } - return token, "", nil + return token, nil } -func findInstallation(ctx context.Context, c *github.Client, orgName string) (*github.Installation, *github.Response, error) { +func findInstallation(ctx context.Context, c *github.Client, orgName string) (*github.Installation, error) { installation, resp, err := c.Apps.FindOrganizationInstallation(ctx, orgName) if err != nil { - return nil, nil, err + return nil, wrapGitHubError(err, resp, "github-connector: failed to find installation") } - return installation, resp, nil + return installation, nil } func getInstallationToken(ctx context.Context, c *github.Client, id int64) (*github.InstallationToken, error) { @@ -415,9 +435,27 @@ func getInstallationToken(ctx context.Context, c *github.Client, id int64) (*git return token, nil } +// appJWTTokenRefresher is used to refresh the app jwt token when it expires. +type appJWTTokenRefresher struct { + appID string + privateKey string +} + +func (r *appJWTTokenRefresher) Token() (*oauth2.Token, error) { + token, err := getJWTToken(r.appID, r.privateKey) + if err != nil { + return nil, err + } + + return &oauth2.Token{ + AccessToken: token, + Expiry: time.Now().Add(jwtExpiryTime), + }, nil +} + type appTokenRefresher struct { ctx context.Context - jwttoken string + jwtTokenSource oauth2.TokenSource instanceURL string installationID int64 } @@ -425,9 +463,7 @@ type appTokenRefresher struct { func (r *appTokenRefresher) Token() (*oauth2.Token, error) { appClient, err := newGitHubClient(r.ctx, r.instanceURL, - oauth2.StaticTokenSource( - &oauth2.Token{AccessToken: r.jwttoken}, - ), + r.jwtTokenSource, ) if err != nil { return nil, err From d81f5510ff60014dabcec8b3879de16921b01a0f Mon Sep 17 00:00:00 2001 From: subencheng Date: Mon, 2 Feb 2026 13:03:36 -0800 Subject: [PATCH 2/4] update the comments --- pkg/connector/connector.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/connector/connector.go b/pkg/connector/connector.go index ff361844..45679dc0 100644 --- a/pkg/connector/connector.go +++ b/pkg/connector/connector.go @@ -30,7 +30,7 @@ import ( const githubDotCom = "https://github.com" -// The JWT token expires in 10 minutes, so we set it to 9 minutes to leave some buffer. +// JWT token expires in 10 minutes, so we set it to 9 minutes to leave some buffer. const jwtExpiryTime = 9 * time.Minute var ( From 7073cc74eeeeb7df0a7c0188409773c618c5299c Mon Sep 17 00:00:00 2001 From: subencheng Date: Mon, 2 Feb 2026 15:09:57 -0800 Subject: [PATCH 3/4] update sleep time --- .github/workflows/ci.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 1e57e426..636274c8 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -40,6 +40,7 @@ jobs: env: BATON_TOKEN: ${{ secrets.BATON_TOKEN }} BATON_ORGS: ConductorOne + SLEEP: ${{ vars.SLEEP }} steps: - name: Checkout code uses: actions/checkout@v4 From 1e1775964951a91d5f9e9b502e213e35abdc2a49 Mon Sep 17 00:00:00 2001 From: subencheng Date: Mon, 2 Feb 2026 16:42:35 -0800 Subject: [PATCH 4/4] update workflow --- .github/workflows/ci.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 636274c8..656c01e0 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -40,7 +40,6 @@ jobs: env: BATON_TOKEN: ${{ secrets.BATON_TOKEN }} BATON_ORGS: ConductorOne - SLEEP: ${{ vars.SLEEP }} steps: - name: Checkout code uses: actions/checkout@v4 @@ -54,6 +53,6 @@ jobs: uses: ConductorOne/github-workflows/actions/sync-test@v2 with: connector: ./baton-github - baton-entitlement: 'repository:642588514:triage' + baton-entitlement: 'repository:642588514:admin' baton-principal: '166871869' baton-principal-type: 'user' \ No newline at end of file