feat(server): add support for minting enterprise-scoped installation access tokens - #254
feat(server): add support for minting enterprise-scoped installation access tokens#254pdewilde wants to merge 5 commits into
Conversation
| if err = validatePermissions(scope.Permissions, request.Permissions); err != nil { | ||
| return &apiResponse{http.StatusForbidden, "requested permissions are not authorized for this scope", err} | ||
| } | ||
| if s.enforceReadOnly { |
There was a problem hiding this comment.
Not sure if this is relevant for enterprise scoped tokens
There was a problem hiding this comment.
Antigravity: This logic handles the global --enforce-read-only flag to ensure any minted token (including enterprise tokens) respects read-only mode if enabled globally.
There was a problem hiding this comment.
My point is there are a lot of claim scopes besides those two which can mutate. This is also relevant outside of enterprise scoped tokens as well.
There was a problem hiding this comment.
Antigravity: Done. Replaced hardcoded contents/metadata map with enforceReadOnlyPermissions, which dynamically downgrades any requested/scope permission to "read" rather than stripping other permission keys.
There was a problem hiding this comment.
Lets undo that for now. I misunderstood the existing state and thought it was only downgrading those two, but it instead just replaces the full permisison list with those two. I'm still not sure it makes sense for enterprise scope, maybe we should just throw an error if that read only permissions flag is set, I don't want to possibly escalate the permisssion we allow right now compared to existing state, as there may be a good reason we do it the way we do.
There was a problem hiding this comment.
Antigravity: Done. Reverted standard org read-only behavior back to its original contents/metadata map, and updated enterprise token handling to explicitly return HTTP 403 Forbidden ("read-only mode is not supported for enterprise scoped tokens") when --enforce-read-only is active.
There was a problem hiding this comment.
Antigravity: Done. Updated the error response for enterprise token requests in read-only mode to return HTTP 400 Bad Request.
b3e8757 to
4e9b3b5
Compare
| return "", fmt.Errorf("errors retrieving GitHub enterprise installation: %w", errors.Join(errs...)) | ||
| } | ||
|
|
||
| allRepoRequest := &githubauth.TokenRequestAllRepos{Permissions: map[string]string{}} |
There was a problem hiding this comment.
Is this the right type of token?
There was a problem hiding this comment.
Antigravity: Yes, AccessTokenAllRepos sends POST /app/installations/{installation_id}/access_tokens with {"permissions": ...} without specifying a repository array, which is the required GitHub payload for an Enterprise App installation access token.
|
|
||
| // apiResponse is a structure that contains a http status code, | ||
| // a string response message and any error that might have occurred | ||
| // apiResponse represents a HTTP status code and message to be returned |
There was a problem hiding this comment.
Why did you modify this comment?
There was a problem hiding this comment.
Antigravity: Restored the original comment text on apiResponse.
| return nil, fmt.Sprintf("%s/%s", org, repo), fmt.Errorf("error reading configuration, exhausted all possible source locations, failed to locate scope [%s] for repository [%s/%s].\nEvaluation results:\n%s", scope, org, repo, strings.Join(failureReasons, "\n")) | ||
| } | ||
|
|
||
| func (l *configEvaluator) EvalEnterprise(ctx context.Context, scope string, token interface{}) (*Scope, string, error) { |
There was a problem hiding this comment.
This logic should be unit tested
There was a problem hiding this comment.
Antigravity: Done. Added dedicated unit tests TestConfigEvaluator_EvalEnterprise in pkg/config/config_evaluator_test.go covering disabled state, multi-org fallback, and all-org failure scenarios.
| expResp: "", | ||
| }, | ||
| { | ||
| name: "happy_path_enterprise_token", |
There was a problem hiding this comment.
What happens if we have the same scope name in multiple allow listed orgs? If it finds one which fails, will it try rest of orgs or say there is an error? We should have a test for this behavior.
There was a problem hiding this comment.
Antigravity: Done. EvalEnterprise iterates through allowlisted orgs in order: if a scope in an earlier org fails evaluation (e.g. claim condition mismatch), it continues checking subsequent allowlisted orgs until a match succeeds. Added unit test fallback_to_second_allowlisted_org verifying this exact behavior.
| } | ||
| }) | ||
|
|
||
| t.Run("fallback_to_second_allowlisted_org", func(t *testing.T) { |
There was a problem hiding this comment.
Can we pelase switch to using the table driven tests seen elsewhere in this project.
There was a problem hiding this comment.
Antigravity: Done. Refactored TestConfigEvaluator_EvalEnterprise in pkg/config/config_evaluator_test.go to use a clean table-driven test pattern.
…bled and use table-driven tests for EvalEnterprise
Summary
This pull request adds support for minting Enterprise-scoped Installation Access Tokens in GitHub Token Minter (
github-token-minter).Motivation
In GitHub Enterprise Cloud, programmatic app installation across organizations without manual OAuth UI interaction is only possible via GitHub's Enterprise App Installation API:
POST /enterprises/{enterprise}/apps/organizations/{org}/installationsGitHub enforces a strict security boundary for administrative endpoints: Enterprise Admin endpoints (
/enterprises/{enterprise}/...) can only be accessed using an Enterprise-scoped Installation Access Token (/app/installations/<enterprise-installation-id>/access_tokens). Prior to this update, GHTM exclusively supported Organization-scoped installations (InstallationForOrg), whose tokens are categorically rejected by Enterprise endpoints withHTTP 403 Resource not accessible by integration.Architectural & Security Highlights
1. Allowlist Scope Governance (
--enterprise-config-orgs)To prevent unauthorized child organizations from defining scope rules that elevate privileges to Enterprise permissions, this update introduces the
--enterprise-config-orgsflag (ENTERPRISE_CONFIG_ORGSenvironment variable):--enterprise-config-orgsis unconfigured or empty, Enterprise token minting is disabled (HTTP 403 Forbidden)..mintyconfig in non-allowlisted organizations cannot grant Enterprise tokens.2. Strict Request Validation
enterprisefield (json:"enterprise,omitempty") toTokenRequest.enterpriseis requested, GHTM verifies thatorg_nameandrepositoriesare empty (HTTP 400 Bad Requestif set), ensuring clear separation between Organization/Repository tokens and Enterprise tokens.3. Enterprise Installation Resolution
installationForEnterpriseandMintEnterpriseAccessTokeningitHubSourceSystem(pkg/server/source/github.go).GET /enterprises/{enterprise}/installationto resolve the App's Enterprise Installation ID and issues a token scoped to the Enterprise account.Testing
happy_path_enterprise_token: Validates successful token issuance whenenterprise-config-orgsis allowlisted.unhappy_path_enterprise_token_not_enabled: ValidatesHTTP 403rejection whenenterprise-config-orgsis unconfigured.unhappy_path_enterprise_token_with_org_or_repos: ValidatesHTTP 400rejection when mixingenterprisewithorg_nameorrepositories.pkg/server,pkg/config, andpkg/server/source.