Skip to content

feat: Add GitHub App authentication for git cloning and releases#83

Open
nssherpa wants to merge 1 commit intomainfrom
github-app-authentication
Open

feat: Add GitHub App authentication for git cloning and releases#83
nssherpa wants to merge 1 commit intomainfrom
github-app-authentication

Conversation

@nssherpa
Copy link
Collaborator

@nssherpa nssherpa commented Feb 4, 2026

Summary

Guide followed

Local Testing

Both github releases and git clones are able to use the github app

git clone http://localhost:8080/git/github.com/squareup/blox.git /tmp/test-blox1

Screenshot 2026-02-06 at 5 56 09 PM

time curl -v http://localhost:8080/github.com/squareup/orc/releases/download/v1.115.3/orc-1.115.3-darwin-arm64.tar.gz -o /tmp/test-download-orc
Screenshot 2026-02-06 at 5 55 06 PM

proxied git request to cachew and cachew in staging was able to clone repo

Screenshot 2026-02-04 at 10 16 20 PM Screenshot 2026-02-04 at 10 18 27 PM

@nssherpa nssherpa force-pushed the github-app-authentication branch 19 times, most recently from 757ae3c to 6daa5c0 Compare February 7, 2026 01:52
@nssherpa nssherpa marked this pull request as ready for review February 7, 2026 01:56
@nssherpa nssherpa requested a review from a team as a code owner February 7, 2026 01:56
@nssherpa nssherpa requested review from alecthomas and removed request for a team February 7, 2026 01:56
@nssherpa nssherpa force-pushed the github-app-authentication branch from 6daa5c0 to 7542a5a Compare February 7, 2026 01:58
Implements GitHub App authentication to enable authenticated access to
private repositories and releases. This provides better security and
higher rate limits compared to personal access tokens.

Key features:
- JWT generation with RSA signing for GitHub App authentication
- Installation token management with 1-hour expiration and auto-refresh
- Token caching to minimize GitHub API calls
- Credential injection into git clone/fetch operations
- Support for both git strategy and github-releases strategy
- Falls back to system credentials if GitHub App not configured

Implementation details:
- New internal/githubapp package for authentication logic
- Updated gitclone package to support credential providers
- Modified git commands to inject tokens into GitHub URLs
- Added GitHub App configuration blocks to both strategies
- Updated configuration examples in cachew.hcl

Usage:
Configure GitHub App credentials (app ID, private key path, and
installation IDs per org) in the git and github-releases strategy
blocks to enable authenticated access.
@nssherpa nssherpa force-pushed the github-app-authentication branch from 7542a5a to f624f8a Compare February 7, 2026 02:06
Comment on lines +54 to +61
if strings.HasPrefix(url, "https://github.com/") {
return strings.Replace(url, "https://github.com/", fmt.Sprintf("https://x-access-token:%s@github.com/", token), 1)
}

// Upgrade http to https when adding token for security
if strings.HasPrefix(url, "http://github.com/") {
return strings.Replace(url, "http://github.com/", fmt.Sprintf("https://x-access-token:%s@github.com/", token), 1)
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would very much suggest parsing the URL:

	u, _ := url.Parse("https://github.com/foo/bar")
	u.User = url.UserPassword("x-access-token", "XYZ")

}

cmd, err := gitCommand(ctx, r.upstreamURL, args...)
cmd, err := gitCommand(ctx, r.upstreamURL, r.credentialProvider, args...)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should make gitCommand a method on Repository, then we can get rid of these two arguments.

type Config struct {
AppID string `hcl:"app-id" help:"GitHub App ID"`
PrivateKeyPath string `hcl:"private-key-path" help:"Path to GitHub App private key (PEM format)"`
InstallationsJSON string `hcl:"installations-json" help:"JSON string mapping org names to installation IDs"`
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this JSON rather than HCL?

If this is because it's not supported by Kong, I think it'd be preferable to pull some of the config out of flags parsing and just be for config.

InstallationsJSON string `hcl:"installations-json" help:"JSON string mapping org names to installation IDs"`

// Populated from InstallationsJSON during Initialize(). Not exposed in HCL.
Installations map[string]string
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be in a dedicated struct, and the methods below should be a constructor, and methods on the new dedicated struct.

}

// Initialize must be called after loading config to parse InstallationsJSON into Installations map.
func (c *Config) Initialize(logger *slog.Logger) error {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be a constructor for the above suggested struct, like:

func NewInstallations(config Config) *Installations { ... }

return nil
}

func (c *Config) IsConfigured() bool {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then these become methods on the struct.

RefCheckInterval time.Duration `hcl:"ref-check-interval,optional" help:"How long to cache ref checks." default:"10s"`
BundleInterval time.Duration `hcl:"bundle-interval,optional" help:"How often to generate bundles. 0 disables bundling." default:"0"`
SnapshotInterval time.Duration `hcl:"snapshot-interval,optional" help:"How often to generate tar.zstd snapshots. 0 disables snapshots." default:"0"`
CloneDepth int `hcl:"clone-depth,optional" help:"Depth for shallow clones. 0 means full clone." default:"0"`
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we got rid of this...

}

// Use shared GitHub App token manager if configured
tokenManager := githubapp.GetShared()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't be using globals. Construct in main and inject into the registration function.

logger.WarnContext(ctx, "No token configured for github-releases strategy")

// Use shared GitHub App token manager if configured
tokenManager := githubapp.GetShared()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above.

Comment on lines +18 to +33
var (
sharedTokenManager *TokenManager
sharedTokenManagerMu sync.RWMutex
)

func SetShared(tm *TokenManager) {
sharedTokenManagerMu.Lock()
defer sharedTokenManagerMu.Unlock()
sharedTokenManager = tm
}

func GetShared() *TokenManager {
sharedTokenManagerMu.RLock()
defer sharedTokenManagerMu.RUnlock()
return sharedTokenManager
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

None of this should be here - instead we construct it once in main and inject it through constructors wherever required.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants