feat: Add GitHub App authentication for git cloning and releases#83
feat: Add GitHub App authentication for git cloning and releases#83
Conversation
757ae3c to
6daa5c0
Compare
6daa5c0 to
7542a5a
Compare
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.
7542a5a to
f624f8a
Compare
| 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) | ||
| } |
There was a problem hiding this comment.
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...) |
There was a problem hiding this comment.
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"` |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
This should be a constructor for the above suggested struct, like:
func NewInstallations(config Config) *Installations { ... }| return nil | ||
| } | ||
|
|
||
| func (c *Config) IsConfigured() bool { |
There was a problem hiding this comment.
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"` |
There was a problem hiding this comment.
I think we got rid of this...
| } | ||
|
|
||
| // Use shared GitHub App token manager if configured | ||
| tokenManager := githubapp.GetShared() |
There was a problem hiding this comment.
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() |
| 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 | ||
| } |
There was a problem hiding this comment.
None of this should be here - instead we construct it once in main and inject it through constructors wherever required.
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-blox1time 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-orcproxied git request to cachew and cachew in staging was able to clone repo